Lufaneng 10 months ago
parent 2a0fa0836d
commit af9c4a4e5b

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