|
|
|
@ -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>
|