From 5e36f243efe3fbb7062f5a3895a81f1961eb4009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Wed, 4 Feb 2026 15:32:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(order):=20=E6=B7=BB=E5=8A=A0=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E9=87=8D=E6=96=B0=E5=8F=91=E8=B5=B7=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=B9=B6=E4=BC=98=E5=8C=96=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 prepayShopOrder 接口用于对未支付订单生成新预支付参数 - 实现多路径兼容探测机制,支持不同后端版本的支付接口 - 优化订单支付逻辑,优先使用服务端最新状态避免重复支付 - 添加 fallback 机制,在重新支付失败时降级为重新创建订单 - 实现支付成功后自动取消旧待支付订单,避免列表堆积 - 修复订单列表中key值重复的问题 - 在商品列表中添加数量标识符x提升UI显示效果 --- src/api/shop/shopOrder/index.ts | 40 ++++++++- src/user/order/components/OrderList.tsx | 109 ++++++++++++++++-------- 2 files changed, 111 insertions(+), 38 deletions(-) diff --git a/src/api/shop/shopOrder/index.ts b/src/api/shop/shopOrder/index.ts index ca1e805..2d64084 100644 --- a/src/api/shop/shopOrder/index.ts +++ b/src/api/shop/shopOrder/index.ts @@ -1,4 +1,4 @@ -import request from '@/utils/request'; +import request, { ErrorType, RequestError } from '@/utils/request'; import type { ApiResult, PageResult } from '@/api'; import type { ShopOrder, ShopOrderParam, OrderCreateRequest } from './model'; @@ -113,6 +113,44 @@ export interface WxPayResult { paySign: string; } +/** + * 订单重新发起支付(对“已创建但未支付”的订单生成新的预支付参数,不应重复创建订单) + * + * 说明:不同后端版本可能暴露不同路径,这里做兼容探测;若全部失败,调用方可自行降级处理。 + */ +export interface OrderPrepayRequest { + orderId: number; + payType: number; +} + +export async function prepayShopOrder(data: OrderPrepayRequest) { + const urls = [ + '/shop/shop-order/pay', + '/shop/shop-order/prepay', + '/shop/shop-order/repay' + ]; + + let lastError: unknown; + let businessError: unknown; + for (const url of urls) { + try { + const res = await request.post>(url, data, { showError: false }); + // request.ts 在 code!=0 时会直接 throw;走到这里通常都是 code===0 + if (res.code === 0) return res.data; + } catch (e) { + // 若已命中“业务错误”(例如订单已取消/已支付),优先保留该错误用于向上提示; + // 不要被后续的 404/网络错误覆盖掉,避免调用方误判为“不支持该接口”而降级走创建订单。 + if (!businessError && e instanceof RequestError && e.type === ErrorType.BUSINESS_ERROR) { + businessError = e; + } else { + lastError = e; + } + } + } + + return Promise.reject(businessError || lastError || new Error('发起支付失败')); +} + /** * 创建订单 */ diff --git a/src/user/order/components/OrderList.tsx b/src/user/order/components/OrderList.tsx index b721513..3c37ca4 100644 --- a/src/user/order/components/OrderList.tsx +++ b/src/user/order/components/OrderList.tsx @@ -4,13 +4,14 @@ import {View, Text} from '@tarojs/components' import Taro from '@tarojs/taro'; import {InfiniteLoading} from '@nutui/nutui-react-taro' import dayjs from "dayjs"; -import {pageShopOrder, updateShopOrder, createOrder} from "@/api/shop/shopOrder"; -import {ShopOrder, ShopOrderParam} from "@/api/shop/shopOrder/model"; +import {pageShopOrder, updateShopOrder, createOrder, getShopOrder, prepayShopOrder} from "@/api/shop/shopOrder"; +import {OrderCreateRequest, ShopOrder, ShopOrderParam} from "@/api/shop/shopOrder/model"; import {listShopOrderGoods} from "@/api/shop/shopOrderGoods"; import {copyText} from "@/utils/common"; import PaymentCountdown from "@/components/PaymentCountdown"; import {PaymentType} from "@/utils/payment"; import {goTo} from "@/utils/navigation"; +import {ErrorType, RequestError} from "@/utils/request"; // 判断订单是否支付已过期 const isPaymentExpired = (createTime: string, timeoutHours: number = 24): boolean => { @@ -406,7 +407,7 @@ function OrderList(props: OrderListProps) { // 立即支付 const payOrder = async (order: ShopOrder) => { try { - if (!order.orderId || !order.orderNo) { + if (!order.orderId) { Taro.showToast({ title: '订单信息错误', icon: 'error' @@ -419,27 +420,37 @@ function OrderList(props: OrderListProps) { } setPayingOrderId(order.orderId); - // 检查订单是否已过期 - if (order.createTime && isPaymentExpired(order.createTime)) { - Taro.showToast({ - title: '订单已过期,无法支付', - icon: 'error' - }); - return; + // 尽量以服务端最新状态为准,避免“已取消/已支付”但列表未刷新导致误发起支付 + let latestOrder: ShopOrder | null = null; + try { + latestOrder = await getShopOrder(order.orderId); + } catch (_e) { + // 忽略:网络波动时继续使用列表数据兜底 } + const effectiveOrder = latestOrder ? { ...order, ...latestOrder } : order; - // 检查订单状态 - if (order.payStatus) { + if (effectiveOrder.payStatus) { Taro.showToast({ title: '订单已支付', icon: 'none' }); + // 同步刷新一次,避免列表显示旧状态 + void reload(true); + return; + } + if (effectiveOrder.orderStatus === 2) { + Taro.showToast({ + title: '订单已取消,无法支付', + icon: 'error' + }); + void reload(true); return; } - if (order.orderStatus === 2) { + // 检查订单是否已过期(以最新 createTime 为准) + if (effectiveOrder.createTime && isPaymentExpired(effectiveOrder.createTime)) { Taro.showToast({ - title: '订单已取消,无法支付', + title: '订单已过期,无法支付', icon: 'error' }); return; @@ -448,10 +459,10 @@ function OrderList(props: OrderListProps) { Taro.showLoading({title: '发起支付...'}); // 构建商品数据:优先使用订单分页接口返回的 orderGoods;缺失时再补拉一次,避免goodsItems为空导致后端拒绝/再次支付失败 - let orderGoods = order.orderGoods || []; + let orderGoods = effectiveOrder.orderGoods || []; if (!orderGoods.length) { try { - orderGoods = (await listShopOrderGoods({orderId: order.orderId})) || []; + orderGoods = (await listShopOrderGoods({orderId: effectiveOrder.orderId})) || []; } catch (e) { // 继续走下面的校验提示 console.error('补拉订单商品失败:', e); @@ -476,26 +487,37 @@ function OrderList(props: OrderListProps) { return; } - // 对于已存在的订单,我们需要重新发起支付 - // 构建支付请求数据,包含完整的商品信息 - const paymentData = { - orderId: order.orderId, - orderNo: order.orderNo, - goodsItems: goodsItems, - addressId: order.addressId, - payType: PaymentType.WECHAT, - // 尽量携带原订单信息,避免后端重新计算/校验不一致(如使用了优惠券/自提等) - couponId: order.couponId, - deliveryType: order.deliveryType, - selfTakeMerchantId: order.selfTakeMerchantId, - comments: order.comments, - title: order.title - }; - - console.log('重新支付数据:', paymentData); - - // 直接调用createOrder API进行重新支付 - const result = await createOrder(paymentData as any); + // 优先:对“已创建但未支付”的订单走“重新发起支付”接口(不应重复创建订单) + // 若后端未提供该接口,则降级为重新创建订单(此时不传 orderNo,避免出现“相同订单号重复订单”) + let result: any; + let usedFallbackCreate = false; + try { + result = await prepayShopOrder({ + orderId: effectiveOrder.orderId!, + payType: PaymentType.WECHAT + }); + } catch (e) { + // 订单状态等业务错误:直接提示,不要降级“重新创建订单”导致产生多笔订单 + if (e instanceof RequestError && e.type === ErrorType.BUSINESS_ERROR) { + throw e; + } + usedFallbackCreate = true; + const orderData: OrderCreateRequest = { + goodsItems, + addressId: effectiveOrder.addressId, + payType: PaymentType.WECHAT, + couponId: effectiveOrder.couponId, + deliveryType: effectiveOrder.deliveryType, + selfTakeMerchantId: effectiveOrder.selfTakeMerchantId, + comments: effectiveOrder.comments, + title: effectiveOrder.title, + storeId: effectiveOrder.storeId, + storeName: effectiveOrder.storeName, + riderId: effectiveOrder.riderId, + warehouseId: effectiveOrder.warehouseId + }; + result = await createOrder(orderData); + } if (!result) { throw new Error('支付发起失败'); @@ -534,6 +556,18 @@ function OrderList(props: OrderListProps) { icon: 'success' }); + // 若因后端不支持“重新发起支付”而降级“重新创建订单”,则原订单会遗留为待支付,支付成功后自动将其标记为已取消,避免列表堆积 + if (usedFallbackCreate && effectiveOrder.orderId && !effectiveOrder.payStatus && effectiveOrder.orderStatus !== 2) { + try { + await updateShopOrder({ + orderId: effectiveOrder.orderId, + orderStatus: 2 + }); + } catch (e) { + console.warn('自动取消旧待支付订单失败:', e); + } + } + // 重新加载订单列表 void reload(true); props.onReload?.(); @@ -689,7 +723,7 @@ function OrderList(props: OrderListProps) { }) ?.map((item, index) => { return ( - Taro.navigateTo({url: `/shop/orderDetail/index?orderId=${item.orderId}`})}> @@ -739,6 +773,7 @@ function OrderList(props: OrderListProps) { )} 数量:{(goods as any).quantity ?? goods.totalNum} + x ¥{goods.price || (goods as any).payPrice} ))