feat: 支付可能重复的问题修复

This commit is contained in:
2026-07-20 12:45:52 +08:00
parent 2407a9d941
commit 54e6d423ae
3 changed files with 179 additions and 89 deletions

View File

@@ -210,8 +210,8 @@ const validateUserForms = () => {
return true;
};
// 处理支付点击事件
const handlePayClick = ThrottleUtils.createThrottle(async (goodsData) => {
// 处理支付点击事件 - 使用执行锁防止重复支付
const handlePayClick = ThrottleUtils.createExecutionLock(async (goodsData) => {
console.log("处理支付点击事件", userFormList.value);
// 预约日期,酒店类型不需要
if (orderData.value.reservationEnabled) {
@@ -267,72 +267,82 @@ const handlePayClick = ThrottleUtils.createThrottle(async (goodsData) => {
// 点击后立即展示 loading
uni.showLoading({ title: "正在提交订单..." });
const res = await orderPay(params);
console.log("确认订单---2:", res);
uni.hideLoading();
try {
const res = await orderPay(params);
console.log("确认订单---2:", res);
// 检查接口返回数据
if (!res || !res.data) {
// 检查接口返回数据
if (!res || !res.data) {
setTimeout(() => {
uni.showToast({
title: res.msg || "订单创建失败,请重试",
icon: "none",
});
}, 100);
return;
}
const { data } = res;
const { nonceStr, packageVal, paySign, signType, timeStamp } = data;
// 验证支付参数是否完整
if (!nonceStr || !packageVal || !paySign || !signType || !timeStamp) {
setTimeout(() => {
uni.showToast({ title: "支付参数错误,请重试", icon: "none" });
}, 100);
return;
}
// 关闭 loading
uni.hideLoading();
setTimeout(() => {
uni.showToast({
title: res.msg || "订单创建失败,请重试",
icon: "none",
});
}, 100);
return;
}
const { data } = res;
const { nonceStr, packageVal, paySign, signType, timeStamp } = data;
// 验证支付参数是否完整
if (!nonceStr || !packageVal || !paySign || !signType || !timeStamp) {
uni.hideLoading();
setTimeout(() => {
uni.showToast({ title: "支付参数错误,请重试", icon: "none" });
}, 100);
return;
}
// 在发起微信支付前关闭 loading避免与原生支付 UI 冲突)
uni.hideLoading();
// #ifdef MP-WEIXIN
// 调用微信支付
uni.requestPayment({
provider: "wxpay",
timeStamp: String(timeStamp), // 确保为字符串类型
nonceStr: String(nonceStr),
package: String(packageVal), // 确保为字符串类型
signType: String(signType),
paySign: String(paySign),
success: () => {
uni.showToast({
title: "支付成功",
icon: "success",
// #ifdef MP-WEIXIN
// 调用微信支付
await new Promise((resolve) => {
uni.requestPayment({
provider: "wxpay",
timeStamp: String(timeStamp), // 确保为字符串类型
nonceStr: String(nonceStr),
package: String(packageVal), // 确保为字符串类型
signType: String(signType),
paySign: String(paySign),
success: () => {
uni.navigateTo({
url: "/pages-order/order/list",
uni.showToast({
title: "支付成功",
icon: "success",
success: () => {
uni.navigateTo({
url: "/pages-order/order/list",
});
},
});
resolve();
},
fail: (e) => {
console.error("支付失败:", e);
uni.showToast({ title: "支付失败,请重试", icon: "none" });
resolve();
},
});
},
fail: (e) => {
console.error("支付失败:", e);
uni.showToast({ title: "支付失败,请重试", icon: "none" });
},
});
// #endif
});
// #endif
// #ifdef APP-PLUS
uni.showModal({
title: "提示",
content: "支付功能开发中",
showCancel: false,
});
// #endif
}, 1000);
// #ifdef APP-PLUS
uni.showModal({
title: "提示",
content: "支付功能开发中",
showCancel: false,
});
// #endif
} catch (error) {
console.error("支付流程出错:", error);
uni.showToast({ title: "支付失败,请重试", icon: "none" });
} finally {
// 确保 loading 被关闭
uni.hideLoading();
}
});
</script>
<style scoped lang="scss">

View File

@@ -27,7 +27,7 @@
<script setup>
import { defineProps, defineEmits, computed } from "vue";
import { orderPayNow } from "@/request/api/OrderApi";
import { DebounceUtils } from "@/utils";
import { ThrottleUtils } from "@/utils";
const props = defineProps({
orderData: {
@@ -64,8 +64,8 @@ const buttonText = computed(() => {
// 定义事件发射器
const emit = defineEmits(["refund", "refresh"]);
// 处理按钮点击事件
const handleButtonClick = DebounceUtils.createDebounce(async (orderData) => {
// 处理按钮点击事件 - 使用执行锁防止重复支付
const handleButtonClick = ThrottleUtils.createExecutionLock(async (orderData) => {
try {
// 再次预定跳转商品详情
if (["1", "2", "3", "4", "5", "6"].includes(statusCode.value)) {
@@ -89,7 +89,6 @@ const handleButtonClick = DebounceUtils.createDebounce(async (orderData) => {
// 检查接口返回数据
if (!res || !res.data) {
uni.hideLoading();
setTimeout(() => {
uni.showToast({ title: res.msg || "订单创建失败,请重试", icon: "none" });
}, 100);
@@ -101,38 +100,41 @@ const handleButtonClick = DebounceUtils.createDebounce(async (orderData) => {
// 验证支付参数是否完整
if (!nonceStr || !packageVal || !paySign || !signType || !timeStamp) {
uni.hideLoading();
setTimeout(() => {
uni.showToast({ title: "支付参数错误,请重试", icon: "none" });
}, 100);
return;
}
// 在发起微信支付前关闭 loading(避免与原生支付 UI 冲突)
// 关闭 loading
uni.hideLoading();
// #ifdef MP-WEIXIN
// 调用微信支付
uni.requestPayment({
provider: "wxpay",
timeStamp: String(timeStamp), // 确保为字符串类型
nonceStr: String(nonceStr),
package: String(packageVal), // 确保为字符串类型
signType: String(signType),
paySign: String(paySign),
success: () => {
uni.showToast({
title: "支付成功",
icon: "success",
success: () => {
console.log("支付成功,刷新订单详情");
emit("refresh", { orderId: orderId });
},
});
},
fail: () => {
uni.showToast({ title: "支付失败,请重试", icon: "none" });
},
// 调用微信支付 - 包装成 Promise 以便锁住执行状态
await new Promise((resolve) => {
uni.requestPayment({
provider: "wxpay",
timeStamp: String(timeStamp), // 确保为字符串类型
nonceStr: String(nonceStr),
package: String(packageVal), // 确保为字符串类型
signType: String(signType),
paySign: String(paySign),
success: () => {
uni.showToast({
title: "支付成功",
icon: "success",
success: () => {
console.log("支付成功,刷新订单详情");
emit("refresh", { orderId: orderId });
},
});
resolve();
},
fail: () => {
uni.showToast({ title: "支付失败,请重试", icon: "none" });
resolve();
},
});
});
// #endif
@@ -146,9 +148,12 @@ const handleButtonClick = DebounceUtils.createDebounce(async (orderData) => {
}
} catch (error) {
console.error("操作失败:", error);
uni.showToast({ title: "操作失败,请重试", icon: "none" });
} finally {
// 确保 loading 被关闭
uni.hideLoading();
}
}, 1000);
});
</script>
<style lang="scss" scoped>

View File

@@ -280,7 +280,7 @@ export class DebounceUtils {
*/
export class ThrottleUtils {
/**
* 创建节流函数
* 创建节流函数(传统时间戳方式)
* @param {Function} func - 要节流的函数
* @param {number} delay - 节流延迟时间
* @returns {Function} 节流后的函数
@@ -297,6 +297,81 @@ export class ThrottleUtils {
}
};
}
/**
* 创建异步安全的节流函数(带执行状态锁)
* 适用于支付等需要等待完成的异步操作
* @param {Function} func - 要节流的异步函数
* @param {number} delay - 节流延迟时间(毫秒)
* @returns {Function} 节流后的函数
*/
static createAsyncThrottle(func, delay = 1000) {
let isExecuting = false; // 执行状态锁
let lastExecTime = 0; // 上次执行时间
return async function (...args) {
const now = Date.now();
// 如果正在执行中,直接返回
if (isExecuting) {
console.warn('函数正在执行中,跳过此次调用');
return;
}
// 如果还在节流时间内,直接返回
if (now - lastExecTime < delay) {
console.warn('节流时间内,跳过此次调用');
return;
}
// 加锁
isExecuting = true;
lastExecTime = now;
try {
// 执行异步函数
return await func.apply(this, args);
} catch (error) {
console.error('异步节流函数执行出错:', error);
throw error;
} finally {
// 无论成功失败都解锁
isExecuting = false;
}
};
}
/**
* 创建执行锁函数(最简单的防重复方式,只锁执行状态)
* 适用于支付等场景,上一次没完成前绝对不执行第二次
* @param {Function} func - 要加锁的函数
* @returns {Function} 加锁后的函数
*/
static createExecutionLock(func) {
let isExecuting = false;
return async function (...args) {
// 如果正在执行中,直接返回
if (isExecuting) {
console.warn('函数正在执行中,跳过此次调用');
return;
}
// 加锁
isExecuting = true;
try {
// 执行函数
return await func.apply(this, args);
} catch (error) {
console.error('执行锁函数执行出错:', error);
throw error;
} finally {
// 无论成功失败都解锁
isExecuting = false;
}
};
}
}
/**