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:
@@ -1,95 +1,56 @@
|
||||
<template>
|
||||
<view class="booking h-screen flex flex-col">
|
||||
<TopNavBar
|
||||
titleAlign="center"
|
||||
:backgroundColor="$theme-color-100"
|
||||
backIconColor="#000"
|
||||
:shadow="false"
|
||||
>
|
||||
<TopNavBar titleAlign="center" :backgroundColor="$theme - color - 100" backIconColor="#000" :shadow="false">
|
||||
<template #title>
|
||||
{{ GOODS_TYPE[orderData.orderType] }}
|
||||
</template>
|
||||
</TopNavBar>
|
||||
|
||||
<view
|
||||
class="booking-content flex-full border-box p-12 overflow-hidden scroll-y"
|
||||
>
|
||||
<view class="booking-content flex-full border-box p-12 overflow-hidden scroll-y">
|
||||
<!-- 预约内容 -->
|
||||
<view class="border-box bg-white p-12 rounded-12 mb-12">
|
||||
<!-- 酒店类型入住离店日期部分 -->
|
||||
<DateRangeSection
|
||||
v-if="orderData.orderType == 0"
|
||||
:selectedDate="selectedDate"
|
||||
:showBtn="true"
|
||||
@click="navigateToDetail(orderData)"
|
||||
/>
|
||||
<DateRangeSection v-if="orderData.orderType == 0" :selectedDate="selectedDate" :showBtn="true"
|
||||
@click="navigateToDetail(orderData)" />
|
||||
|
||||
<view class="font-size-16 font-500 color-000 line-height-24 ellipsis-1">
|
||||
{{ orderData.commodityName }}
|
||||
</view>
|
||||
|
||||
<view class="border-box border-bottom">
|
||||
<view
|
||||
class="font-size-12 color-99A0AE line-height-16 pb-12 break-all"
|
||||
>
|
||||
<view class="font-size-12 color-99A0AE line-height-16 pb-12 break-all">
|
||||
{{ orderData.commodityDescription }}
|
||||
</view>
|
||||
|
||||
<!-- 权益部分 -->
|
||||
<view class="flex flex-items-center mb-8">
|
||||
<text
|
||||
class="bg-F7F7F7 border-box rounded-4 font-size-11 color-525866 mr-4 pt-4 pb-4 pl-6 pr-6"
|
||||
v-for="(item, index) in orderData.commodityFacilityList"
|
||||
:key="index"
|
||||
>
|
||||
<text class="bg-F7F7F7 border-box rounded-4 font-size-11 color-525866 mr-4 pt-4 pb-4 pl-6 pr-6"
|
||||
v-for="(item, index) in orderData.commodityFacilityList" :key="index">
|
||||
{{ item }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="border-box flex flex-items-center flex-justify-between pt-12"
|
||||
>
|
||||
<text class="font-size-12 color-525866 line-height-18"
|
||||
>取消政策及说明</text
|
||||
>
|
||||
<view class="border-box flex flex-items-center flex-justify-between pt-12">
|
||||
<text class="font-size-12 color-525866 line-height-18">取消政策及说明</text>
|
||||
<view class="flex flex-items-center">
|
||||
<text
|
||||
class="font-size-12 theme-color-500 line-height-16"
|
||||
@click="refundVisible = true"
|
||||
>取消政策</text
|
||||
>
|
||||
<text class="font-size-12 theme-color-500 line-height-16" @click="refundVisible = true">取消政策</text>
|
||||
<uni-icons type="right" size="15" color="#99A0AE" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 非酒店类型 -->
|
||||
<ContactSection
|
||||
v-if="orderData.orderType != 0"
|
||||
v-model="quantity"
|
||||
:userFormList="userFormList"
|
||||
v-model:reservationDate="selectedReservationDate"
|
||||
:orderData="orderData"
|
||||
/>
|
||||
<ContactSection v-if="orderData.orderType != 0" v-model="quantity" :userFormList="userFormList"
|
||||
v-model:reservationDate="selectedReservationDate" :orderData="orderData" />
|
||||
|
||||
<!-- 酒店类型 -->
|
||||
<UserSection
|
||||
v-if="orderData.orderType == 0"
|
||||
v-model="quantity"
|
||||
:userFormList="userFormList"
|
||||
/>
|
||||
<UserSection v-if="orderData.orderType == 0" v-model="quantity" :userFormList="userFormList" />
|
||||
</view>
|
||||
|
||||
<!-- 底部 -->
|
||||
<FooterSection
|
||||
v-if="Object.keys(orderData).length"
|
||||
v-model="quantity"
|
||||
:selectedDate="selectedDate"
|
||||
:orderData="orderData"
|
||||
@detailClick="detailVisible = true"
|
||||
@payClick="handlePayClick"
|
||||
/>
|
||||
<FooterSection v-if="Object.keys(orderData).length" v-model="quantity" :selectedDate="selectedDate"
|
||||
:orderData="orderData" @detailClick="detailVisible = true" @payClick="handlePayClick" />
|
||||
|
||||
<!-- 取消政策弹窗 -->
|
||||
<RefundPopup v-model="refundVisible" :orderData="orderData" />
|
||||
@@ -210,8 +171,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 +228,82 @@ const handlePayClick = ThrottleUtils.createThrottle(async (goodsData) => {
|
||||
|
||||
// 点击后立即展示 loading
|
||||
uni.showLoading({ title: "正在提交订单..." });
|
||||
const res = await orderPay(params);
|
||||
console.log("确认订单---2:", res);
|
||||
uni.hideLoading();
|
||||
|
||||
// 检查接口返回数据
|
||||
if (!res || !res.data) {
|
||||
try {
|
||||
const res = await orderPay(params);
|
||||
console.log("确认订单---2:", res);
|
||||
|
||||
// 检查接口返回数据
|
||||
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">
|
||||
|
||||
Reference in New Issue
Block a user