Lufaneng 10 months ago
parent 2a0fa0836d
commit af9c4a4e5b

@ -107,6 +107,13 @@
{ {
"navigationBarTitleText" : "确认支付" "navigationBarTitleText" : "确认支付"
} }
},
{
"path" : "payment-result/payment-result",
"style" :
{
"navigationBarTitleText" : "支付结果"
}
} }
] ]
} }

@ -0,0 +1,393 @@
<template>
<view class="container">
<!-- 支付结果显示 -->
<view class="result-section">
<!-- 加载中状态 -->
<view v-if="paymentStatus === 'loading'" class="status-loading">
<view class="loading-icon">
<uv-loading-icon mode="circle" color="#4285f4" size="60"></uv-loading-icon>
</view>
<view class="status-title">支付处理中...</view>
<view class="status-desc">请稍候正在确认支付结果</view>
<view class="countdown-text">{{ countdownText }}</view>
</view>
<!-- 支付成功 -->
<view v-else-if="paymentStatus === 'success'" class="status-success">
<view class="success-icon">
<uv-icon name="checkmark-circle-fill" color="#52c41a" size="80"></uv-icon>
</view>
<view class="status-title">支付成功</view>
<view class="status-desc">恭喜您会员服务已开通</view>
</view>
<!-- 支付失败 -->
<view v-else-if="paymentStatus === 'failed'" class="status-failed">
<view class="failed-icon">
<uv-icon name="close-circle-fill" color="#ff4d4f" size="80"></uv-icon>
</view>
<view class="status-title">支付失败</view>
<view class="status-desc">{{ failedReason || '支付过程中出现问题,请重试' }}</view>
</view>
<!-- 支付取消 -->
<view v-else-if="paymentStatus === 'cancelled'" class="status-cancelled">
<view class="cancelled-icon">
<uv-icon name="info-circle-fill" color="#faad14" size="80"></uv-icon>
</view>
<view class="status-title">支付已取消</view>
<view class="status-desc">您已取消本次支付</view>
</view>
<!-- 超时状态 -->
<view v-else-if="paymentStatus === 'timeout'" class="status-timeout">
<view class="timeout-icon">
<uv-icon name="clock-fill" color="#faad14" size="80"></uv-icon>
</view>
<view class="status-title">查询超时</view>
<view class="status-desc">支付结果查询超时请手动查看订单状态</view>
</view>
</view>
<!-- 订单信息 -->
<view class="order-info" v-if="orderInfo">
<view class="info-title">订单信息</view>
<view class="info-item">
<text class="label">订单号</text>
<text class="value">{{ orderNo }}</text>
</view>
<view class="info-item" v-if="orderInfo.plan_name">
<text class="label">商品</text>
<text class="value">{{ orderInfo.plan_name }}</text>
</view>
<view class="info-item" v-if="orderInfo.amount">
<text class="label">金额</text>
<text class="value price">¥{{ orderInfo.amount }}</text>
</view>
<view class="info-item" v-if="orderInfo.pay_time">
<text class="label">支付时间</text>
<text class="value">{{ formatTime(orderInfo.pay_time) }}</text>
</view>
</view>
<!-- 底部按钮 -->
<view class="bottom-section">
<view class="button-group">
<!-- 支付成功时的按钮 -->
<template v-if="paymentStatus === 'success'">
<uv-button type="primary" @click="goToMine">
返回会员中心
</uv-button>
</template>
<!-- 支付失败时的按钮 -->
<template v-else-if="paymentStatus === 'failed'">
<uv-button type="primary" @click="goToMine" customStyle="margin-right: 12px; flex: 1;">
返回会员中心
</uv-button>
<!-- <uv-button type="primary" @click="retryPayment" customStyle="flex: 1;">
重新支付
</uv-button> -->
</template>
<!-- 其他状态的按钮 -->
<template v-else>
<uv-button type="primary" plain @click="goToMine">
返回会员中心
</uv-button>
<uv-button type="primary" @click="checkOrderStatus" >
查看订单
</uv-button>
</template>
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted,
onUnmounted
} from "vue";
import {
onLoad
} from "@dcloudio/uni-app";
import {
useUserStore
} from "@/store";
import RPC from "@/utils/request";
import dayjs from "dayjs";
const userStore = useUserStore();
//
const orderNo = ref('');
//
const paymentStatus = ref('loading'); // loading, success, failed, cancelled, timeout
const failedReason = ref('');
const orderInfo = ref(null);
const countdown = ref(60); // 60
const countdownText = ref('');
//
let queryTimer = null;
let countdownTimer = null;
//
onLoad((options) => {
orderNo.value = options.order_no || '';
if (orderNo.value) {
startQueryPaymentStatus();
} else {
paymentStatus.value = 'failed';
failedReason.value = '订单号参数缺失';
}
});
//
const startQueryPaymentStatus = () => {
//
startCountdown();
//
queryPaymentStatus();
// 3
queryTimer = setInterval(() => {
queryPaymentStatus();
}, 3000);
};
//
const queryPaymentStatus = async () => {
try {
const res = await RPC.post('/membership/order/status', {
order_no: orderNo.value
});
orderInfo.value = res;
//
if (res.status === 'paid') {
paymentStatus.value = 'success';
clearTimers();
} else if (res.status === 'failed') {
paymentStatus.value = 'failed';
failedReason.value = res.fail_reason || '支付失败';
clearTimers();
} else if (res.status === 'canceled' || res.status === 'cancelled') {
paymentStatus.value = 'cancelled';
clearTimers();
} else if (res.status === 'created' || res.status === 'pending') {
//
paymentStatus.value = 'loading';
}
} catch (error) {
console.error('查询支付状态失败:', error);
//
if (countdown.value <= 0) {
paymentStatus.value = 'timeout';
clearTimers();
}
}
};
//
const startCountdown = () => {
updateCountdownText();
countdownTimer = setInterval(() => {
countdown.value--;
updateCountdownText();
if (countdown.value <= 0) {
paymentStatus.value = 'timeout';
clearTimers();
}
}, 1000);
};
//
const updateCountdownText = () => {
countdownText.value = `查询超时倒计时:${countdown.value}`;
};
//
const clearTimers = () => {
if (queryTimer) {
clearInterval(queryTimer);
queryTimer = null;
}
if (countdownTimer) {
clearInterval(countdownTimer);
countdownTimer = null;
}
};
//
const formatTime = (timestamp) => {
if (!timestamp) return '';
return dayjs(timestamp * 1000).format('YYYY-MM-DD HH:mm:ss');
};
//
const goToMine = () => {
uni.switchTab({
url: '/pages/mine/mine'
});
};
//
const retryPayment = () => {
//
uni.navigateBack({
delta: 1
});
};
//
const checkOrderStatus = () => {
uni.switchTab({
url: '/pages/order/order'
});
};
//
onUnmounted(() => {
clearTimers();
});
</script>
<style lang="scss" scoped>
.container {
font-size: 14px;
background-color: #f8f8f8;
min-height: 100vh;
padding: 20px 16px;
box-sizing: border-box;
}
.result-section {
background: white;
border-radius: 12px;
padding: 40px 20px;
text-align: center;
margin-bottom: 20px;
.loading-icon,
.success-icon,
.failed-icon,
.cancelled-icon,
.timeout-icon {
margin-bottom: 20px;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
.status-title {
font-size: 20px;
font-weight: bold;
color: #333;
margin-bottom: 8px;
}
.status-desc {
font-size: 14px;
color: #666;
margin-bottom: 16px;
}
.countdown-text {
font-size: 12px;
color: #999;
}
.status-success {
.status-title {
color: #52c41a;
}
}
.status-failed {
.status-title {
color: #ff4d4f;
}
}
.status-cancelled {
.status-title {
color: #faad14;
}
}
.status-timeout {
.status-title {
color: #faad14;
}
}
}
.order-info {
background: white;
border-radius: 12px;
padding: 16px;
margin-bottom: 20px;
.info-title {
font-size: 16px;
font-weight: 500;
color: #333;
margin-bottom: 16px;
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.label {
font-size: 14px;
color: #666;
}
.value {
font-size: 14px;
color: #333;
&.price {
color: #ff3333;
font-weight: 500;
}
}
}
}
.bottom-section {
// position: fixed;
// bottom: 0;
// left: 0;
// right: 0;
background: white;
padding: 16px;
border-radius: 10px;
// border-top: 1px solid #e5e5e5;
/* #ifdef H5 */
// bottom: 50px;
/* H5环境下给tabBar留出空间 */
/* #endif */
.button-group {
display: flex;
gap: 30px;
}
}
</style>

@ -23,8 +23,11 @@
<view class="section-title">选择支付方式</view> <view class="section-title">选择支付方式</view>
<!-- 钱包支付 --> <!-- 钱包支付 -->
<view class="payment-item" :class="{ active: selectedPayment === 'wallet' }" <view
@click="selectPayment('wallet')"> class="payment-item"
:class="{ active: selectedPayment === 'wallet' }"
@click="selectPayment('wallet')"
>
<view class="payment-info"> <view class="payment-info">
<uv-icon name="empty-coupon" color="#07c160" size="24"></uv-icon> <uv-icon name="empty-coupon" color="#07c160" size="24"></uv-icon>
<view class="payment-details"> <view class="payment-details">
@ -32,12 +35,19 @@
<text class="payment-desc">余额¥{{ walletBalance }}</text> <text class="payment-desc">余额¥{{ walletBalance }}</text>
</view> </view>
</view> </view>
<uv-icon name="checkmark-circle-fill" :color="selectedPayment === 'wallet' ? '#ff3333' : '#ccc'" <uv-icon
size="20"></uv-icon> name="checkmark-circle-fill"
:color="selectedPayment === 'wallet' ? '#ff3333' : '#ccc'"
size="20"
></uv-icon>
</view> </view>
<!-- 微信支付 --> <!-- 微信支付 -->
<view class="payment-item" :class="{ active: selectedPayment === 'wxpay' }" @click="selectPayment('wxpay')"> <view
class="payment-item"
:class="{ active: selectedPayment === 'wxpay' }"
@click="selectPayment('wxpay')"
>
<view class="payment-info"> <view class="payment-info">
<uv-icon name="weixin-fill" color="#07c160" size="24"></uv-icon> <uv-icon name="weixin-fill" color="#07c160" size="24"></uv-icon>
<view class="payment-details"> <view class="payment-details">
@ -45,28 +55,47 @@
<text class="payment-desc">推荐使用微信支付</text> <text class="payment-desc">推荐使用微信支付</text>
</view> </view>
</view> </view>
<uv-icon name="checkmark-circle-fill" :color="selectedPayment === 'wxpay' ? '#ff3333' : '#ccc'" <uv-icon
size="20"></uv-icon> name="checkmark-circle-fill"
:color="selectedPayment === 'wxpay' ? '#ff3333' : '#ccc'"
size="20"
></uv-icon>
</view> </view>
<!-- 支付宝支付 - 仅在H5环境显示 --> <!-- 支付宝支付 - 仅在H5环境显示 -->
<view v-if="showAlipay" class="payment-item" :class="{ active: selectedPayment === 'alipay' }" <view
@click="selectPayment('alipay')"> v-if="showAlipay"
class="payment-item"
:class="{ active: selectedPayment === 'alipay' }"
@click="selectPayment('alipay')"
>
<view class="payment-info"> <view class="payment-info">
<uv-icon name="/static/imgs/alipay.png" color="#1677ff" size="24"></uv-icon> <uv-icon
name="/static/imgs/alipay.png"
color="#1677ff"
size="24"
></uv-icon>
<view class="payment-details"> <view class="payment-details">
<text class="payment-name">支付宝</text> <text class="payment-name">支付宝</text>
<text class="payment-desc">安全便捷的支付方式</text> <text class="payment-desc">安全便捷的支付方式</text>
</view> </view>
</view> </view>
<uv-icon name="checkmark-circle-fill" :color="selectedPayment === 'alipay' ? '#ff3333' : '#ccc'" <uv-icon
size="20"></uv-icon> name="checkmark-circle-fill"
:color="selectedPayment === 'alipay' ? '#ff3333' : '#ccc'"
size="20"
></uv-icon>
</view> </view>
</view> </view>
<!-- 底部支付按钮 --> <!-- 底部支付按钮 -->
<view class="bottom-section"> <view class="bottom-section">
<uv-button type="primary" size="large" @click="confirmPayment" :loading="paying"> <uv-button
type="primary"
size="large"
@click="confirmPayment"
:loading="paying"
>
立即支付 ¥{{ planPrice }} 立即支付 ¥{{ planPrice }}
</uv-button> </uv-button>
<view class="agreement"> <view class="agreement">
@ -78,64 +107,55 @@
</template> </template>
<script setup> <script setup>
import { import { ref, onMounted } from "vue";
ref, import { onLoad, onShow } from "@dcloudio/uni-app";
onMounted import { useAuthStore, useUserStore } from "@/store";
} from "vue"; import RPC from "@/utils/request";
import {
onLoad, const authStore = useAuthStore();
onShow const userStore = useUserStore();
} from "@dcloudio/uni-app";
import { //
useAuthStore, const planId = ref("");
useUserStore const planName = ref("");
} from "@/store"; const planPrice = ref(0);
import RPC from "@/utils/request";
//
const authStore = useAuthStore(); const selectedPayment = ref("wxpay"); //
const userStore = useUserStore(); const paying = ref(false);
const showAlipay = ref(true); //
// const walletBalance = ref(0);
const planId = ref('');
const planName = ref(''); //
const planPrice = ref(0); const getWalletBalance = async () => {
//
const selectedPayment = ref("wxpay"); //
const paying = ref(false);
const showAlipay = ref(true); //
const walletBalance = ref(0);
//
const getWalletBalance = async () => {
try { try {
const res = await RPC.post('/membership/user/info', { const res = await RPC.post("/membership/user/info", {
user_id: userStore.userInfo.id user_id: userStore.userInfo.id,
}); });
walletBalance.value = res.wallet_balance || 0; walletBalance.value = res.wallet_balance || 0;
} catch (error) { } catch (error) {
console.error('获取钱包余额失败:', error); console.error("获取钱包余额失败:", error);
} }
}; };
// //
const selectPayment = (payment) => { const selectPayment = (payment) => {
selectedPayment.value = payment; selectedPayment.value = payment;
}; };
// //
onLoad((options) => { onLoad((options) => {
planId.value = Number(options.planId) || ''; planId.value = Number(options.planId) || "";
planName.value = decodeURIComponent(options.planName || ''); planName.value = decodeURIComponent(options.planName || "");
planPrice.value = parseFloat(options.planPrice || 0); planPrice.value = parseFloat(options.planPrice || 0);
}); });
onShow(() => { onShow(() => {
getWalletBalance(); getWalletBalance();
}); });
// //
const checkEnvironment = () => { const checkEnvironment = () => {
// #ifdef MP-WEIXIN // #ifdef MP-WEIXIN
// //
showAlipay.value = false; showAlipay.value = false;
@ -145,10 +165,10 @@
// H5 // H5
showAlipay.value = true; showAlipay.value = true;
// #endif // #endif
}; };
// //
const wechatPay = async (orderInfo) => { const wechatPay = async (orderInfo) => {
// #ifdef MP-WEIXIN // #ifdef MP-WEIXIN
// //
try { try {
@ -164,7 +184,7 @@
console.log("微信支付成功:", paymentResult); console.log("微信支付成功:", paymentResult);
return { return {
success: true, success: true,
data: paymentResult data: paymentResult,
}; };
} catch (error) { } catch (error) {
console.error("微信支付失败:", error); console.error("微信支付失败:", error);
@ -181,25 +201,25 @@
window.location.href = payUrl; window.location.href = payUrl;
return { return {
success: true, success: true,
redirect: true redirect: true,
}; };
// #endif // #endif
}; };
// H5 // H5
const alipayPay = async (orderInfo) => { const alipayPay = async (orderInfo) => {
// #ifdef H5 // #ifdef H5
// H5 // H5
const payUrl = orderInfo.pay_url; // H5 const payUrl = orderInfo.pay_url; // H5
window.location.href = payUrl; window.location.href = payUrl;
return { return {
success: true, success: true,
redirect: true redirect: true,
}; };
// #endif // #endif
}; };
function showModalAsync(options) { function showModalAsync(options) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
uni.showModal({ uni.showModal({
...options, ...options,
@ -208,13 +228,13 @@
}, },
fail: (err) => { fail: (err) => {
reject(err); reject(err);
} },
}); });
}); });
} }
// //
const confirmPayment = async () => { const confirmPayment = async () => {
if (!selectedPayment.value) { if (!selectedPayment.value) {
uni.showToast({ uni.showToast({
title: "请选择支付方式", title: "请选择支付方式",
@ -229,7 +249,7 @@
const orderParams = { const orderParams = {
user_id: userStore.userInfo?.id, user_id: userStore.userInfo?.id,
plan_id: planId.value, plan_id: planId.value,
pay_channel: selectedPayment.value pay_channel: selectedPayment.value,
}; };
uni.showLoading({ uni.showLoading({
@ -237,88 +257,97 @@
}); });
// 2. // 2.
const orderNum = await RPC.post('/membership/order/create', orderParams); const orderNum = await RPC.post("/membership/order/create", orderParams);
uni.hideLoading(); uni.hideLoading();
let orderResult = {} let orderResult = {};
// 3. // 3.
let paymentResult; let paymentResult;
if (selectedPayment.value === "wxpay") { if (selectedPayment.value === "wxpay") {
// #ifdef MP-WEIXIN // #ifdef MP-WEIXIN
const res = await uni.login({ const res = await uni.login({
provider: 'weixin' provider: "weixin",
}) });
orderResult = await RPC.post('/membership/order/pay_by_wechat_mini', { orderResult = await RPC.post("/membership/order/pay_by_wechat_mini", {
code: res.code, code: res.code,
"order_no": orderNum.order_no order_no: orderNum.order_no,
}) });
// #endif // #endif
// #ifdef H5 // #ifdef H5
orderResult = await RPC.post('/membership/order/pay_by_wechat_h5', { orderResult = await RPC.post("/membership/order/pay_by_wechat_h5", {
"order_no": orderNum.order_no order_no: orderNum.order_no,
}) });
// #endif // #endif
uni.showLoading({ uni.showLoading({
title: "正在跳转微信支付...", title: "正在跳转微信支付...",
}); });
console.log('orderResult', orderResult) console.log("orderResult", orderResult);
paymentResult = await wechatPay(orderResult); paymentResult = await wechatPay(orderResult);
} else if (selectedPayment.value === "alipay") { } else if (selectedPayment.value === "alipay") {
orderResult = await RPC.post('/membership/order/pay_by_alipay', { orderResult = await RPC.post("/membership/order/pay_by_alipay", {
"order_no": orderNum.order_no order_no: orderNum.order_no,
}) });
uni.showLoading({ uni.showLoading({
title: "正在跳转支付宝...", title: "正在跳转支付宝...",
}); });
console.log('orderResult', orderResult) console.log("orderResult", orderResult);
// if (1 == 1) {
// uni.redirectTo({
// url: `/pagesA/payment-result/payment-result?order_no=${orderNum.order_no}`,
// });
// uni.hideLoading();
// return;
// }
paymentResult = await alipayPay(orderResult); paymentResult = await alipayPay(orderResult);
} else if (selectedPayment.value === 'wallet') { } else if (selectedPayment.value === "wallet") {
const res = await showModalAsync({ const res = await showModalAsync({
title: '确认支付', title: "确认支付",
content: '确认使用钱包余额支付?' content: "确认使用钱包余额支付?",
}); });
if (res.confirm) { if (res.confirm) {
paymentResult = await RPC.post('/membership/order/pay_by_wallet', { paymentResult = await RPC.post("/membership/order/pay_by_wallet", {
order_no: orderNum.order_no order_no: orderNum.order_no,
}) });
console.log('paymentResult', paymentResult) console.log("paymentResult", paymentResult);
} else if (res.cancel) { } else if (res.cancel) {
console.log('用户点击了取消'); console.log("用户点击了取消");
paymentResult = { success: false } paymentResult = { success: false };
} }
} }
uni.hideLoading(); uni.hideLoading();
console.log('paymentResult', paymentResult) console.log("paymentResult", paymentResult);
// 4. // 4.
if (paymentResult?.success) { if (paymentResult?.success) {
if (paymentResult.redirect) { if (paymentResult.redirect) {
// H5 // H5
uni.showToast({ uni.redirectTo({
title: "正在跳转支付页面...", url: `/pagesA/payment-result/payment-result?order_no=${orderNum.order_no}`,
icon: "loading",
duration: 2000,
}); });
} else { } else {
//
//
// //
setTimeout(() => { setTimeout(() => {
uni.showToast({ uni.showToast({
title: "支付成功", title: "支付成功",
icon: "success", icon: "success",
}); });
//
setTimeout(() => {
uni.navigateBack({
delta: 2 //
}); });
}, 1500); setTimeout(() => {
}, 1000) uni.navigateBack();
}, 1000);
} }
} else {
//
uni.redirectTo({
url: `/pagesA/payment-result/payment-result?order_no=${orderNum.order_no}`,
});
} }
} catch (error) { } catch (error) {
uni.hideLoading(); uni.hideLoading();
@ -329,7 +358,7 @@
} else if (error.message) { } else if (error.message) {
errorMessage = error.message; errorMessage = error.message;
} else if (error) { } else if (error) {
errorMessage = error errorMessage = error;
} }
uni.showToast({ uni.showToast({
@ -339,29 +368,29 @@
} finally { } finally {
paying.value = false; paying.value = false;
} }
}; };
// //
const showAgreement = () => { const showAgreement = () => {
uni.navigateTo({ uni.navigateTo({
url: "/pagesA/user-agreement/user-agreement", url: "/pagesA/user-agreement/user-agreement",
}); });
}; };
onMounted(() => { onMounted(() => {
checkEnvironment(); checkEnvironment();
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container { .container {
font-size: 14px; font-size: 14px;
background-color: #f8f8f8; background-color: #f8f8f8;
min-height: 100vh; min-height: 100vh;
padding-bottom: 120px; padding-bottom: 120px;
} }
.header { .header {
background: white; background: white;
padding: 20px 16px; padding: 20px 16px;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
@ -372,9 +401,9 @@
color: #333; color: #333;
text-align: center; text-align: center;
} }
} }
.order-info { .order-info {
background: white; background: white;
margin: 16px; margin: 16px;
border-radius: 12px; border-radius: 12px;
@ -424,9 +453,9 @@
color: #ff3333; color: #ff3333;
} }
} }
} }
.payment-section { .payment-section {
background: white; background: white;
margin: 0 16px 16px; margin: 0 16px 16px;
border-radius: 12px; border-radius: 12px;
@ -480,9 +509,9 @@
} }
} }
} }
} }
.bottom-section { .bottom-section {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
left: 0; left: 0;
@ -506,5 +535,5 @@
color: #4285f4; color: #4285f4;
} }
} }
} }
</style> </style>
Loading…
Cancel
Save