434 lines
13 KiB
Vue
434 lines
13 KiB
Vue
<template>
|
||
<div v-if="show" class="fixed inset-0 bg-black bg-opacity-60 z-[9999] flex items-center justify-center overflow-auto p-4">
|
||
<div class="bg-white rounded-xl shadow-2xl w-full max-w-xl mx-auto relative animate-fadeIn">
|
||
<button @click="onClose" class="absolute top-4 right-4 p-2 rounded-full hover:bg-gray-100 transition-colors z-10">
|
||
<svg class="w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||
</svg>
|
||
</button>
|
||
|
||
<div class="px-8 py-10">
|
||
<div v-if="$resources.processBalancePayment.loading || $resources.processAlipayPayment.loading || $resources.processWechatPayment.loading" class="text-center py-8">
|
||
<div class="h-12 w-12 mx-auto animate-spin text-blue-600 mb-4 flex items-center justify-center">
|
||
<i class="fe fe-loader text-3xl"></i>
|
||
</div>
|
||
<p class="text-gray-700 text-lg">正在处理支付,请稍候...</p>
|
||
</div>
|
||
|
||
<div v-else-if="paymentMethod === 'wechatpay' && paymentQrCode" class="text-center py-6">
|
||
<div class="relative mb-6">
|
||
<div class="flex items-center justify-center">
|
||
<WeChatPayLogo class="h-10" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="text-center mb-6">
|
||
<div class="text-sm text-gray-600 mb-2">扫一扫付款(元)</div>
|
||
<div class="text-3xl font-bold text-[#FF0036]">{{ order?.total_amount }} 元</div>
|
||
</div>
|
||
|
||
<div class="flex justify-center">
|
||
<div class="qrcode-container bg-white p-4 border border-gray-100 shadow-sm rounded-lg">
|
||
<img
|
||
:src="paymentQrCodeImage"
|
||
alt="微信支付二维码"
|
||
class="qrcode-image"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-6 text-center">
|
||
<p class="text-gray-600">
|
||
请使用微信扫描二维码完成支付
|
||
</p>
|
||
<p class="text-gray-500 text-sm mt-2">二维码有效期 15 分钟</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-else-if="paymentMethod === 'alipay' && paymentUrl" class="text-center py-6">
|
||
<div class="mb-6">
|
||
<AlipayLogo class="h-10 mx-auto" />
|
||
</div>
|
||
|
||
<h3 class="text-lg font-medium text-gray-900 mb-4">请在新页面完成支付宝支付</h3>
|
||
<p class="text-gray-600 mb-6">
|
||
如果没有自动跳转,请点击下方按钮打开支付页面
|
||
</p>
|
||
<div class="space-y-4">
|
||
<button
|
||
class="w-full px-6 py-3 bg-[#1677FF] text-white rounded-lg hover:bg-[#0E5FD8] transition-colors shadow-sm"
|
||
@click="window.open(paymentUrl, '_blank')"
|
||
>
|
||
打开支付页面
|
||
</button>
|
||
<p class="text-gray-600 text-sm mt-4">
|
||
支付完成后,请稍等片刻,系统会自动继续创建站点
|
||
</p>
|
||
<button
|
||
class="w-full mt-4 px-6 py-3 bg-gray-100 rounded-lg hover:bg-gray-200 text-gray-700 transition-colors"
|
||
@click="paymentMethod = null"
|
||
>
|
||
返回选择其他支付方式
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-else class="space-y-6">
|
||
<div class="bg-gray-50 p-4 rounded-lg mb-6">
|
||
<p class="text-gray-700 mb-2 font-medium">
|
||
站点URL:{{ order?.title }}
|
||
</p>
|
||
<p class="text-xl font-bold text-gray-900">
|
||
订单金额: {{ $format.userCurrency(order?.total_amount) }}
|
||
</p>
|
||
</div>
|
||
|
||
<ErrorMessage
|
||
class="mt-3"
|
||
:message="$resources.processBalancePayment.error || $resources.processAlipayPayment.error || $resources.processWechatPayment.error"
|
||
/>
|
||
|
||
<p class="text-gray-700 font-medium">
|
||
选择支付方式:
|
||
</p>
|
||
|
||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-4">
|
||
<button
|
||
class="p-6 border border-gray-200 rounded-lg flex flex-col items-center justify-center hover:bg-gray-50 hover:border-gray-300 transition-all transform hover:-translate-y-1"
|
||
@click="processPayment('balance')"
|
||
:disabled="$resources.processBalancePayment.loading || $resources.processAlipayPayment.loading || $resources.processWechatPayment.loading"
|
||
>
|
||
<span class="text-gray-800 font-medium">余额支付</span>
|
||
</button>
|
||
|
||
<button
|
||
@click="processPayment('alipay')"
|
||
:disabled="$resources.processBalancePayment.loading || $resources.processAlipayPayment.loading || $resources.processWechatPayment.loading"
|
||
class="p-6 border border-gray-200 rounded-lg hover:bg-gray-50 hover:border-gray-300 transition-all transform hover:-translate-y-1"
|
||
>
|
||
<div class="flex flex-col items-center">
|
||
<div class="mb-3">
|
||
<AlipayLogo class="h-10" />
|
||
</div>
|
||
</div>
|
||
</button>
|
||
|
||
<button
|
||
@click="processPayment('wechatpay')"
|
||
:disabled="$resources.processBalancePayment.loading || $resources.processAlipayPayment.loading || $resources.processWechatPayment.loading"
|
||
class="p-6 border border-gray-200 rounded-lg hover:bg-gray-50 hover:border-gray-300 transition-all transform hover:-translate-y-1"
|
||
>
|
||
<div class="flex flex-col items-center">
|
||
<div class="mb-3">
|
||
<WeChatPayLogo class="h-10" />
|
||
</div>
|
||
</div>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { toast } from 'vue-sonner';
|
||
import { Teleport } from 'vue';
|
||
import { DashboardError } from '../utils/error';
|
||
import AlipayLogo from '../logo/AlipayLogo.vue';
|
||
import WeChatPayLogo from '../logo/WeChatPayLogo.vue';
|
||
|
||
export default {
|
||
name: 'OrderCheckout',
|
||
props: {
|
||
value: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
order: {
|
||
type: Object,
|
||
required: true
|
||
},
|
||
orderType: {
|
||
type: String,
|
||
default: '订单'
|
||
}
|
||
},
|
||
components: {
|
||
AlipayLogo,
|
||
WeChatPayLogo
|
||
},
|
||
data() {
|
||
return {
|
||
paymentMethod: null,
|
||
paymentUrl: null,
|
||
paymentQrCode: null,
|
||
paymentQrCodeImage: null,
|
||
orderId: null,
|
||
checkInterval: null,
|
||
localShow: false
|
||
};
|
||
},
|
||
computed: {
|
||
show: {
|
||
get() {
|
||
return this.value || this.localShow;
|
||
},
|
||
set(val) {
|
||
this.$emit('input', val);
|
||
this.localShow = val;
|
||
}
|
||
}
|
||
},
|
||
resources: {
|
||
processBalancePayment() {
|
||
return {
|
||
url: 'jcloud.api.billing.process_balance_payment_for_order',
|
||
params: {
|
||
order_id: this.order.order_id || this.order.name
|
||
},
|
||
validate() {
|
||
if (!this.order) {
|
||
throw new DashboardError('缺少订单信息');
|
||
}
|
||
},
|
||
onSuccess(response) {
|
||
// 检查响应中的success字段
|
||
if (response.status === "Error" || response.success === false) {
|
||
// 显示错误消息
|
||
toast.error(response.message || '支付失败,请确保余额充足');
|
||
this.$emit('payment-error', { message: response.message || '余额不足' });
|
||
return;
|
||
}
|
||
|
||
// 支付成功,通知父组件
|
||
toast.success('支付成功!');
|
||
// 确保传递完整的订单信息,并更新订单状态为已支付
|
||
const orderData = {
|
||
...this.order,
|
||
...(response.order || {}),
|
||
status: '已支付' // 明确设置订单状态为已支付
|
||
};
|
||
this.$emit('payment-success', orderData);
|
||
this.onClose();
|
||
}
|
||
};
|
||
},
|
||
processAlipayPayment() {
|
||
return {
|
||
url: 'jcloud.api.billing.process_alipay_order',
|
||
params: {
|
||
order_id: this.order.order_id || this.order.name,
|
||
},
|
||
validate() {
|
||
if (!this.order) {
|
||
throw new DashboardError('缺少订单信息');
|
||
}
|
||
},
|
||
onSuccess(response) {
|
||
// 保存订单ID用于后续状态检查
|
||
this.orderId = this.order.order_id || this.order.name;
|
||
this.paymentUrl = response.payment_url;
|
||
// 支付宝则打开新窗口
|
||
window.open(response.payment_url, '_blank');
|
||
toast.success('支付页面已在新窗口打开');
|
||
|
||
// 开始轮询订单状态
|
||
this.startPaymentCheck();
|
||
}
|
||
};
|
||
},
|
||
processWechatPayment() {
|
||
return {
|
||
url: 'jcloud.api.billing.process_wechatpay_order',
|
||
params: {
|
||
order_id: this.order.order_id || this.order.name,
|
||
},
|
||
validate() {
|
||
if (!this.order) {
|
||
throw new DashboardError('缺少订单信息');
|
||
}
|
||
},
|
||
onSuccess(response) {
|
||
|
||
// 保存订单ID用于后续状态检查
|
||
this.orderId = this.order.order_id || this.order.name;
|
||
|
||
// 确保设置正确的数据
|
||
this.paymentQrCode = response.payment_url;
|
||
this.paymentQrCodeImage = response.qr_code_image || null;
|
||
|
||
// 开始轮询订单状态
|
||
this.startPaymentCheck();
|
||
}
|
||
};
|
||
},
|
||
checkPaymentStatus() {
|
||
return {
|
||
url: 'jcloud.api.billing.check_site_order_payment_status',
|
||
params: {
|
||
order_id: this.order.order_id || this.order.name
|
||
},
|
||
onSuccess(data) {
|
||
if (data && data.status === '已支付') {
|
||
// 支付成功,停止轮询并通知父组件
|
||
this.stopPaymentCheck();
|
||
toast.success('支付成功!');
|
||
// 确保传递完整的订单信息,包括原始订单和服务器返回的数据
|
||
const orderData = { ...this.order, ...(data.order || {}) };
|
||
this.$emit('payment-success', orderData);
|
||
this.onClose();
|
||
}
|
||
}
|
||
};
|
||
}
|
||
},
|
||
watch: {
|
||
value: {
|
||
immediate: true,
|
||
handler(newVal) {
|
||
this.localShow = newVal;
|
||
}
|
||
}
|
||
},
|
||
methods: {
|
||
onClose() {
|
||
this.stopPaymentCheck();
|
||
this.paymentMethod = null;
|
||
this.paymentUrl = null;
|
||
this.paymentQrCode = null;
|
||
this.paymentQrCodeImage = null;
|
||
this.show = false;
|
||
|
||
// 向父组件发出关闭信号,确保父组件也知道弹窗已关闭
|
||
this.$emit('close');
|
||
},
|
||
|
||
processPayment(method) {
|
||
this.paymentMethod = method;
|
||
|
||
if (method === 'balance') {
|
||
// 处理余额支付
|
||
this.$resources.processBalancePayment.submit();
|
||
} else if (method === 'alipay') {
|
||
// 处理支付宝支付
|
||
this.$resources.processAlipayPayment.submit();
|
||
} else if (method === 'wechatpay') {
|
||
// 处理微信支付
|
||
this.$resources.processWechatPayment.submit();
|
||
}
|
||
},
|
||
|
||
startPaymentCheck() {
|
||
this.checkInterval = setInterval(() => {
|
||
this.$resources.checkPaymentStatus.submit();
|
||
}, 3000);
|
||
|
||
// 15分钟后停止检查
|
||
setTimeout(() => {
|
||
this.stopPaymentCheck();
|
||
}, 900000);
|
||
},
|
||
|
||
stopPaymentCheck() {
|
||
if (this.checkInterval) {
|
||
clearInterval(this.checkInterval);
|
||
this.checkInterval = null;
|
||
}
|
||
}
|
||
},
|
||
mounted() {
|
||
this.localShow = true;
|
||
},
|
||
beforeUnmount() {
|
||
this.stopPaymentCheck();
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.payment-icon {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: auto;
|
||
width: 100px;
|
||
max-width: 100px;
|
||
min-width: 70px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.alipay-svg, .wechat-svg {
|
||
width: 100%;
|
||
height: auto;
|
||
max-height: 36px;
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.payment-icon {
|
||
width: 80px;
|
||
min-width: 60px;
|
||
}
|
||
|
||
.alipay-svg, .wechat-svg {
|
||
max-height: 30px;
|
||
}
|
||
}
|
||
|
||
/* 添加微信支付二维码相关样式 */
|
||
.qrcode-container {
|
||
height: 250px;
|
||
width: 250px;
|
||
margin: 0 auto;
|
||
padding: 5px;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 12px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.qrcode-image {
|
||
height: 100%;
|
||
width: 100%;
|
||
object-fit: contain;
|
||
}
|
||
|
||
.close-button {
|
||
position: absolute;
|
||
top: -12px;
|
||
right: -12px;
|
||
background: #f3f4f6;
|
||
border-radius: 50%;
|
||
width: 32px;
|
||
height: 32px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border: none;
|
||
cursor: pointer;
|
||
color: #6b7280;
|
||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.close-button:hover {
|
||
background: #e5e7eb;
|
||
color: #374151;
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
/* 添加渐入动画 */
|
||
@keyframes fadeIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(10px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.animate-fadeIn {
|
||
animation: fadeIn 0.3s ease-out;
|
||
}
|
||
</style> |