forked from gxwebsoft/mp-10550
feat(order): 添加订单重新发起支付功能并优化支付流程
- 新增 prepayShopOrder 接口用于对未支付订单生成新预支付参数 - 实现多路径兼容探测机制,支持不同后端版本的支付接口 - 优化订单支付逻辑,优先使用服务端最新状态避免重复支付 - 添加 fallback 机制,在重新支付失败时降级为重新创建订单 - 实现支付成功后自动取消旧待支付订单,避免列表堆积 - 修复订单列表中key值重复的问题 - 在商品列表中添加数量标识符x提升UI显示效果
This commit is contained in:
@@ -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<ApiResult<WxPayResult>>(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('发起支付失败'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建订单
|
||||
*/
|
||||
|
||||
@@ -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 (
|
||||
<Cell key={index} style={{padding: '16px'}}
|
||||
<Cell key={item.orderId ?? item.orderNo ?? index} style={{padding: '16px'}}
|
||||
onClick={() => Taro.navigateTo({url: `/shop/orderDetail/index?orderId=${item.orderId}`})}>
|
||||
<Space direction={'vertical'} className={'w-full flex flex-col'}>
|
||||
<View className={'order-no flex justify-between'}>
|
||||
@@ -739,6 +773,7 @@ function OrderList(props: OrderListProps) {
|
||||
)}
|
||||
<Text className={'text-gray-500 text-xs'}>数量:{(goods as any).quantity ?? goods.totalNum}</Text>
|
||||
</View>
|
||||
<Text className={'text-gray-400 text-xs'}>x</Text>
|
||||
<Text className={'text-sm'}>¥{goods.price || (goods as any).payPrice}</Text>
|
||||
</View>
|
||||
))
|
||||
|
||||
Reference in New Issue
Block a user