优化支付退款钱包链路
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ChatDotRound } from '@element-plus/icons-vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { ChatDotRound, Loading } from '@element-plus/icons-vue'
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import QRCode from 'qrcode'
|
||||
|
||||
import { fetchOrderChat } from '@/api/chats'
|
||||
import { createDispute } from '@/api/disputes'
|
||||
@@ -15,11 +16,13 @@ import {
|
||||
counterCheckout,
|
||||
fetchHandoffRecords,
|
||||
fetchOrder,
|
||||
payOrder,
|
||||
queryOrderPayment,
|
||||
startOrderPayment,
|
||||
submitCheckout,
|
||||
submitHandoff,
|
||||
type HandoffRecord,
|
||||
type Order,
|
||||
type PaymentOrder,
|
||||
} from '@/api/orders'
|
||||
import { useSessionStore } from '@/stores/session'
|
||||
import { handoffStatusLabel, orderStatusLabel } from '@/utils/statusLabels'
|
||||
@@ -30,7 +33,7 @@ const router = useRouter()
|
||||
const session = useSessionStore()
|
||||
const loading = ref(false)
|
||||
const cancelling = ref(false)
|
||||
const paying = ref(false)
|
||||
const startingPayment = ref(false)
|
||||
const handoffing = ref(false)
|
||||
const confirming = ref(false)
|
||||
const returning = ref(false)
|
||||
@@ -43,6 +46,12 @@ const uploadingEvidence = ref(false)
|
||||
const order = ref<Order | null>(null)
|
||||
const handoffRecords = ref<HandoffRecord[]>([])
|
||||
const handoffContent = ref('')
|
||||
const paymentDialogVisible = ref(false)
|
||||
const activePayment = ref<PaymentOrder | null>(null)
|
||||
const paymentQRCodeURL = ref('')
|
||||
const qrGenerating = ref(false)
|
||||
const checkingPayment = ref(false)
|
||||
let paymentPollingTimer: number | undefined
|
||||
const checkoutForm = ref({
|
||||
content: '',
|
||||
consumable_amount: 0,
|
||||
@@ -64,6 +73,7 @@ const disputeType = ref('cannot_login')
|
||||
const disputeDescription = ref('')
|
||||
const disputeEvidenceText = ref('')
|
||||
const openingChat = ref(false)
|
||||
let autoPayHandled = false
|
||||
|
||||
const isOwner = computed(() => order.value?.owner_id === session.userId)
|
||||
const isRenter = computed(() => order.value?.renter_id === session.userId)
|
||||
@@ -97,6 +107,7 @@ const remainingHafCoinM = computed(() => {
|
||||
})
|
||||
|
||||
onMounted(loadOrder)
|
||||
onBeforeUnmount(stopPaymentPolling)
|
||||
|
||||
async function loadOrder() {
|
||||
loading.value = true
|
||||
@@ -105,6 +116,16 @@ async function loadOrder() {
|
||||
handoffRecords.value = await fetchHandoffRecords(String(route.params.id))
|
||||
hydrateResourceUsage()
|
||||
hydrateCounterForm()
|
||||
if (
|
||||
!autoPayHandled &&
|
||||
route.query.pay === '1' &&
|
||||
order.value?.status === 'pending_payment' &&
|
||||
order.value?.renter_id === session.userId
|
||||
) {
|
||||
autoPayHandled = true
|
||||
void router.replace({ path: route.path })
|
||||
void handlePay()
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -126,18 +147,106 @@ async function handleCancel() {
|
||||
|
||||
async function handlePay() {
|
||||
if (!order.value) return
|
||||
paying.value = true
|
||||
startingPayment.value = true
|
||||
try {
|
||||
await payOrder(order.value.id)
|
||||
ElMessage.success('支付成功,等待号主交接')
|
||||
await loadOrder()
|
||||
const payment = await startOrderPayment(order.value.id)
|
||||
if (payment.paid) {
|
||||
ElMessage.success('支付成功,等待号主交接')
|
||||
await loadOrder()
|
||||
} else {
|
||||
showPaymentDialog(payment)
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(readError(error, '支付失败'))
|
||||
} finally {
|
||||
paying.value = false
|
||||
startingPayment.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function showPaymentDialog(payment: PaymentOrder) {
|
||||
activePayment.value = payment
|
||||
paymentDialogVisible.value = true
|
||||
void renderPaymentQRCode(payment)
|
||||
startPaymentPolling()
|
||||
}
|
||||
|
||||
function paymentPayURL(payment = activePayment.value) {
|
||||
return payment?.jspay_url || payment?.td_code || payment?.jspay_info || ''
|
||||
}
|
||||
|
||||
async function renderPaymentQRCode(payment = activePayment.value) {
|
||||
const payURL = paymentPayURL(payment)
|
||||
paymentQRCodeURL.value = ''
|
||||
if (!payURL) {
|
||||
return
|
||||
}
|
||||
qrGenerating.value = true
|
||||
try {
|
||||
paymentQRCodeURL.value = await QRCode.toDataURL(payURL, {
|
||||
width: 240,
|
||||
margin: 1,
|
||||
errorCorrectionLevel: 'M',
|
||||
color: {
|
||||
dark: '#111827',
|
||||
light: '#ffffff',
|
||||
},
|
||||
})
|
||||
} catch {
|
||||
ElMessage.error('二维码生成失败')
|
||||
} finally {
|
||||
qrGenerating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function startPaymentPolling() {
|
||||
stopPaymentPolling()
|
||||
paymentPollingTimer = window.setInterval(() => {
|
||||
void refreshPaymentStatus(true)
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
function stopPaymentPolling() {
|
||||
if (paymentPollingTimer !== undefined) {
|
||||
window.clearInterval(paymentPollingTimer)
|
||||
paymentPollingTimer = undefined
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshPaymentStatus(silent = false) {
|
||||
if (!order.value || checkingPayment.value) {
|
||||
return
|
||||
}
|
||||
checkingPayment.value = true
|
||||
const currentPayURL = paymentPayURL()
|
||||
try {
|
||||
const payment = await queryOrderPayment(order.value.id)
|
||||
if (payment.paid) {
|
||||
stopPaymentPolling()
|
||||
ElMessage.success('支付成功')
|
||||
paymentDialogVisible.value = false
|
||||
await loadOrder()
|
||||
} else {
|
||||
activePayment.value = payment
|
||||
if (paymentPayURL(payment) !== currentPayURL) {
|
||||
await renderPaymentQRCode(payment)
|
||||
}
|
||||
if (!silent) {
|
||||
ElMessage.info('支付未完成')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (!silent) {
|
||||
ElMessage.error(readError(error, '刷新支付状态失败'))
|
||||
}
|
||||
} finally {
|
||||
checkingPayment.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRefreshPayment() {
|
||||
await refreshPaymentStatus(false)
|
||||
}
|
||||
|
||||
async function handleSubmitHandoff() {
|
||||
if (!order.value) return
|
||||
handoffing.value = true
|
||||
@@ -511,7 +620,7 @@ function linesToList(value: string) {
|
||||
<p>开始:{{ formatDateTime(orderRentedAt(), '未开始') }}</p>
|
||||
<p>预计截止:{{ formatDateTime(orderEstimatedEndAt(), '未设置') }}</p>
|
||||
<p v-if="order.status === 'pending_payment'">订单待支付,支付后账号进入交接流程。</p>
|
||||
<el-button v-if="order.status === 'pending_payment' && isRenter" type="primary" :loading="paying" @click="handlePay">
|
||||
<el-button v-if="order.status === 'pending_payment' && isRenter" type="primary" :loading="startingPayment" @click="handlePay">
|
||||
支付订单
|
||||
</el-button>
|
||||
<el-button v-if="['pending_payment', 'pending_handoff'].includes(order.status)" type="danger" :loading="cancelling" @click="handleCancel">
|
||||
@@ -662,6 +771,39 @@ function linesToList(value: string) {
|
||||
{{ isCheckoutDisputeStage ? '提交结账争议' : '提交申诉' }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="paymentDialogVisible"
|
||||
title="订单支付"
|
||||
width="520px"
|
||||
append-to-body
|
||||
:z-index="4000"
|
||||
class="order-pay-dialog"
|
||||
@closed="stopPaymentPolling"
|
||||
>
|
||||
<div v-if="activePayment" class="pay-dialog-body">
|
||||
<div class="pay-summary">
|
||||
<span>支付金额</span>
|
||||
<strong>¥{{ (activePayment.amount_cent / 100).toFixed(2) }}</strong>
|
||||
</div>
|
||||
<div v-if="paymentPayURL()" class="pay-qr-panel">
|
||||
<div class="pay-qr-box">
|
||||
<el-icon v-if="qrGenerating" class="is-loading" :size="32"><Loading /></el-icon>
|
||||
<img v-else-if="paymentQRCodeURL" :src="paymentQRCodeURL" alt="支付二维码" />
|
||||
</div>
|
||||
<div class="pay-scan-copy">
|
||||
<strong>请使用微信或支付宝扫码支付</strong>
|
||||
<span>扫码完成后将自动刷新,也可手动点击下方按钮确认。</span>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else class="pay-hint">支付单已创建,请完成付款后刷新状态。</p>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="pay-dialog-footer">
|
||||
<el-button type="primary" :loading="checkingPayment" @click="handleRefreshPayment">我已支付,刷新状态</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -722,4 +864,91 @@ function linesToList(value: string) {
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.pay-dialog-body {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.pay-summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 8px;
|
||||
background: #f7f9fc;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.pay-summary strong {
|
||||
color: #111a44;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.pay-qr-panel {
|
||||
display: grid;
|
||||
grid-template-columns: 240px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
padding: 18px;
|
||||
border-radius: 8px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.pay-qr-box {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.pay-qr-box img {
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.pay-scan-copy {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.pay-scan-copy strong {
|
||||
color: #111a44;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.pay-scan-copy span {
|
||||
color: #64748b;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.pay-hint {
|
||||
margin: 0;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.pay-dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
:global(.order-pay-dialog) {
|
||||
position: relative;
|
||||
z-index: 4001;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.pay-qr-panel {
|
||||
grid-template-columns: 1fr;
|
||||
justify-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user