update:对接虚拟支付

This commit is contained in:
2026-07-03 13:55:24 +08:00
parent 3e99bb8ad9
commit 8c6374b2c5
5 changed files with 102 additions and 79 deletions
+85 -71
View File
@@ -1,10 +1,10 @@
<script setup>
import { computed, ref, onBeforeUnmount } from "vue";
import { computed, ref } from "vue";
import { onShow } from "@dcloudio/uni-app";
import Container from "@/components/Container.vue";
import Signin from "@/components/Signin.vue";
import { createOrderAPI, getAppConfig, getHomeData } from "@/apis";
import { capsuleHeight } from "@/util";
import { virtualPayOrderAPI, getAppConfig, getHomeData, getOrderDetailAPI } from "@/apis";
import { capsuleHeight, wxLogin } from "@/util";
import useStore from "@/store";
import { storeToRefs } from "pinia";
@@ -18,9 +18,6 @@ const showModal = ref(false);
const loadingConfig = ref(false);
const paying = ref(false);
const refreshing = ref(false);
const timer = ref(null);
const lastDate = ref(user.value.expiredAt || 0);
const maxRefreshTimes = 12;
// 会员页核心展示数据:视觉、权益、套餐均按蓝湖当前两张设计稿拆分。
const memberTypes = [
@@ -100,16 +97,6 @@ const getVipExpiredValue = (type, source = user.value) => {
return type.key === "super" ? source.superVipExpiredAt : source.normalVipExpiredAt;
};
// 支付后轮询需要比较最新会员到期时间,兼容旧字段 expiredAt。
const getLatestVipExpiredTime = (source = user.value) => {
if (!source) return 0;
return Math.max(
toTimestamp(source.normalVipExpiredAt),
toTimestamp(source.superVipExpiredAt),
toTimestamp(source.expiredAt)
);
};
// 未过期才展示“会员生效中”样式,已过期或无值继续展示未开通样式。
const isVipActive = (type) => {
return toTimestamp(getVipExpiredValue(type)) > Date.now();
@@ -263,41 +250,38 @@ const loadVipConfig = async () => {
}
};
const clearRefreshTimer = () => {
if (timer.value) {
clearInterval(timer.value);
timer.value = null;
const refreshUserAfterPay = async () => {
refreshing.value = true;
try {
const result = await getHomeData();
if (result.user) {
updateUser(result.user);
}
} catch (error) {
console.log("refresh user after pay error", error);
} finally {
refreshing.value = false;
}
};
const refreshUserAfterPay = () => {
clearRefreshTimer();
let refreshTimes = 0;
// 先记录支付前的最大到期时间,轮询到更大的值就认为会员状态已刷新。
lastDate.value = getLatestVipExpiredTime();
refreshing.value = true;
timer.value = setInterval(async () => {
refreshTimes += 1;
try {
const result = await getHomeData();
const latestExpiredAt = getLatestVipExpiredTime(result.user);
if (result.user && latestExpiredAt > lastDate.value) {
lastDate.value = latestExpiredAt;
updateUser(result.user);
clearRefreshTimer();
refreshing.value = false;
} else if (refreshTimes >= maxRefreshTimes) {
clearRefreshTimer();
refreshing.value = false;
}
} catch (error) {
console.log("refresh user after pay error", error);
if (refreshTimes >= maxRefreshTimes) {
clearRefreshTimer();
refreshing.value = false;
}
}
}, 1000);
const getVirtualPaySignData = (result) => {
// 后端可能返回字符串或对象,这里统一转成微信虚拟支付需要的 JSON 字符串。
const signDataObj = typeof result?.signData === "string" ? JSON.parse(result.signData) : result?.signData;
if (!signDataObj) return "";
// 微信虚拟支付短剧/虚拟商品模式需要购买数量;后端未返回时使用订单数量兜底。
if (!signDataObj.buyQuantity) {
signDataObj.buyQuantity = result.quantity || 1;
}
return JSON.stringify(signDataObj);
};
const isVirtualPayCancel = (res = {}) => {
const message = String(res.errMsg || res.message || "").toLowerCase();
const errCode = Number(res.errCode);
const errno = Number(res.errno);
return message.includes("cancel") || errCode === -23 || errno === -2;
};
const onPay = async () => {
@@ -317,12 +301,22 @@ const onPay = async () => {
});
return;
}
if (typeof wx === "undefined" || !wx.requestVirtualPayment) {
uni.showToast({
title: "当前环境不支持微信虚拟支付",
icon: "none",
});
return;
}
paying.value = true;
let waitingPayment = false;
let payToast = null;
try {
const result = await createOrderAPI(vipId);
const params = result?.pay?.order?.jsApi?.params;
if (!params?.timeStamp || !params?.nonceStr || !params?.package || !params?.paySign) {
// 微信虚拟支付创建订单前需要登录 code,服务端用它换取本次支付签名参数。
const wxResult = await wxLogin();
const result = await virtualPayOrderAPI(vipId, wxResult.code);
const finalSignData = getVirtualPaySignData(result);
if (!finalSignData || !result?.paySig || !result?.signature) {
uni.showToast({
title: "支付参数生成失败",
icon: "none",
@@ -330,34 +324,57 @@ const onPay = async () => {
refreshing.value = false;
return;
}
waitingPayment = true;
wx.requestPayment({
timeStamp: params.timeStamp, // 微信支付时间戳
nonceStr: params.nonceStr, // 微信支付随机串
package: params.package, // 预支付交易会话标识
paySign: params.paySign, // 微信支付签名
signType: params.signType || "RSA",
success() {
uni.showToast({
wx.requestVirtualPayment({
signData: finalSignData,
paySig: result.paySig,
signature: result.signature,
mode: "short_series_goods",
async success() {
payToast = {
title: "支付成功",
icon: "none",
});
icon: "success",
};
if (result?.outTradeNo) {
try {
const orderDetail = await getOrderDetailAPI(result.outTradeNo);
console.log("virtual pay order detail", orderDetail);
} catch (error) {
console.log("virtual pay order detail error", error);
}
}
// 客户端支付成功后,会员是否真正生效仍以服务端用户信息刷新结果为准。
refreshUserAfterPay();
},
fail(res) {
console.log("pay error", res);
if (res.errMsg && res.errMsg.indexOf("cancel") !== -1) return;
uni.showToast({
title: "支付失败,请稍后重试",
console.log("virtual pay error", res);
if (isVirtualPayCancel(res)) {
payToast = {
title: "支付已取消",
icon: "none",
};
return;
}
payToast = {
title: res.message || "支付失败,请稍后重试",
icon: "none",
});
};
},
complete() {
paying.value = false;
if (payToast) {
setTimeout(() => {
uni.showToast(payToast);
}, 200);
}
},
});
waitingPayment = true;
} catch (error) {
console.log("create vip order error", error);
console.log("create virtual pay order error", error);
uni.showToast({
title: error.message || "下单失败",
icon: "none",
});
} finally {
if (!waitingPayment) paying.value = false;
}
@@ -365,9 +382,6 @@ const onPay = async () => {
onShow(loadVipConfig);
onBeforeUnmount(() => {
clearRefreshTimer();
});
</script>
<template>
+3 -5
View File
@@ -97,12 +97,10 @@ const cancelOrder = async () => {
>复制</text
>
</view>
<text>下单时间{{ data.vipCreateAt }}</text>
<text
>支付时间{{
<text>创建时间{{ data.orderCreateAt }}</text>
<text v-if="data.orderStatus === 4">支付时间{{
data.orderStatus === 4 ? data.paymentTime : ""
}}</text
>
}}</text>
<text>金额{{ data.total }} </text>
<text>支付方式微信</text>
</view>