feat(utils, payment): add execution lock utils and prevent duplicate payment requests

Add two new throttle utility methods: createAsyncThrottle and createExecutionLock for safe async operation locking. Update the existing throttle method's comment to clarify traditional timestamp usage. Replace DebounceUtils usage with ThrottleUtils.createExecutionLock in order footer and booking page payment handlers to prevent duplicate payments. Rewrite uni.requestPayment calls to use Promise wrapping for proper async handling, and add try/catch/finally blocks to ensure loading state is always cleaned up correctly. Fix template formatting and trailing commas across modified files.
This commit is contained in:
duanshuwen
2026-07-23 21:10:34 +08:00
parent f71f28fe99
commit 34711330bd
3 changed files with 206 additions and 162 deletions

View File

@@ -1,24 +1,17 @@
<template>
<view
class="footer bg-white border-box flex flex-items-center flex-justify-between p-12"
>
<button
v-if="['1', '2'].includes(statusCode)"
<view class="footer bg-white border-box flex flex-items-center flex-justify-between p-12">
<button v-if="['1', '2'].includes(statusCode)"
class="left border-none border-box bg-white rounded-10 flex flex-items-center flex-justify-center font-size-14 font-500 color-525866 mr-12"
@click="emit('refund', orderData)"
>
@click="emit('refund', orderData)">
申请退款
</button>
<button
:class="[
'right border-none rounded-10 flex flex-full flex-items-center flex-justify-center font-size-14 font-500 bg-theme-color-500',
{
'bg-FF3D60': statusCode === '0',
'color-white': ['1', '2', '3', '4', '5', '6'].includes(statusCode),
},
]"
@click="handleButtonClick(orderData)"
>
<button :class="[
'right border-none rounded-10 flex flex-full flex-items-center flex-justify-center font-size-14 font-500 bg-theme-color-500',
{
'bg-FF3D60': statusCode === '0',
'color-white': ['1', '2', '3', '4', '5', '6'].includes(statusCode),
},
]" @click="handleButtonClick(orderData)">
{{ buttonText }}
</button>
</view>
@@ -27,7 +20,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 +57,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 +82,6 @@ const handleButtonClick = DebounceUtils.createDebounce(async (orderData) => {
// 检查接口返回数据
if (!res || !res.data) {
uni.hideLoading();
setTimeout(() => {
uni.showToast({ title: res.msg || "订单创建失败,请重试", icon: "none" });
}, 100);
@@ -101,38 +93,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 +141,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>