update:对接虚拟支付
This commit is contained in:
+11
@@ -352,6 +352,17 @@ export const createOrderAPI = (vipId) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const virtualPayOrderAPI = (vipId = 0, code = "") => {
|
||||
return request("POST", "/user/virtualPay/createOrder", {
|
||||
vipId,
|
||||
code,
|
||||
});
|
||||
};
|
||||
|
||||
export const getOrderDetailAPI = (orderId) => {
|
||||
return request("GET", `/user/order/detail?orderId=${encodeURIComponent(orderId)}`);
|
||||
};
|
||||
|
||||
export const payOrderAPI = (id) => {
|
||||
return request("POST", "/user/order/pay", {
|
||||
id,
|
||||
|
||||
@@ -143,7 +143,7 @@ const openCoachComment = () => {
|
||||
}}</text
|
||||
>环的成绩,所有箭支上靶后的平均点间距为<text
|
||||
:style="{ color: '#fed847' }"
|
||||
>{{ Number((result.average_distance || 0).toFixed(2)) }}</text
|
||||
>{{ Number((result?.interpretation?.spreadStability || 0).toFixed(2)) }}</text
|
||||
>,{{
|
||||
result.spreadEvaluation === "Dispersed"
|
||||
? "还需要持续改进哦~"
|
||||
|
||||
+78
-64
@@ -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 = () => {
|
||||
clearRefreshTimer();
|
||||
let refreshTimes = 0;
|
||||
// 先记录支付前的最大到期时间,轮询到更大的值就认为会员状态已刷新。
|
||||
lastDate.value = getLatestVipExpiredTime();
|
||||
const refreshUserAfterPay = async () => {
|
||||
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;
|
||||
if (result.user) {
|
||||
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();
|
||||
} finally {
|
||||
refreshing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}, 1000);
|
||||
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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -13,7 +13,7 @@ import Container from "@/components/Container.vue";
|
||||
<view class="section">
|
||||
<view class="title">一、段位体系概述</view>
|
||||
<view class="text">
|
||||
我们的段位体系分为多个等级,从低到高依次为:倔强青铜、秩序白银、黄金王者、永恒钻石、最强王者、非凡王者、无双王者、绝世王者、至圣王者、荣耀王者和传奇王者。每个大段位下又分为若干小段位,玩家需要通过积累积分来提升段位。
|
||||
我们的段位体系分为多个等级,从低到高依次为:倔强青铜、秩序白银、荣耀黄金、永恒钻石、最强王者、非凡王者、无双王者、绝世王者、至圣王者、荣耀王者和传奇王者。每个大段位下又分为若干小段位,玩家需要通过积累积分来提升段位。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -79,7 +79,7 @@ import Container from "@/components/Container.vue";
|
||||
<text>每个小段位需要满 3颗星才能晋升到下一个段位,共9颗星。</text>
|
||||
</view>
|
||||
<view class="table-row">
|
||||
<text>黄金王者</text>
|
||||
<text>荣耀黄金</text>
|
||||
<view>
|
||||
<text>黄金1*</text>
|
||||
<text>黄金2*</text>
|
||||
|
||||
Reference in New Issue
Block a user