feat(payment): show order status page after EasyPay redirect payment

- After opening pay_url in new window, navigate to order status page
  with countdown timer and order polling (reuses QRCode view)
- QRCode view now shows "pay in new window" UI when no QR code present
- Add verbose logging for webhook signature verification failures
This commit is contained in:
erio
2026-04-07 16:03:02 +08:00
parent 18cd8bd63b
commit 5a2d6dd839
6 changed files with 21 additions and 3 deletions
+1 -1
View File
@@ -1 +1 @@
0.1.108.77
0.1.108.78
@@ -83,7 +83,7 @@ func (h *PaymentWebhookHandler) handleNotify(c *gin.Context, providerKey string)
notification, err := provider.VerifyNotification(c.Request.Context(), rawBody, headers)
if err != nil {
slog.Error("[Payment Webhook] verify failed", "provider", providerKey, "error", err)
slog.Error("[Payment Webhook] verify failed", "provider", providerKey, "error", err, "method", c.Request.Method, "rawBody", rawBody)
c.String(http.StatusBadRequest, "verify failed")
return
}
+3
View File
@@ -5095,6 +5095,9 @@ export default {
},
qr: {
scanToPay: 'Scan to Pay',
payInNewWindow: 'Complete Payment in New Window',
payInNewWindowHint: 'The payment page has opened in a new window. Please complete the payment there and return to this page.',
openPayWindow: 'Reopen Payment Page',
expiresIn: 'Expires in',
expired: 'Order Expired',
waitingPayment: 'Waiting for payment...',
+3
View File
@@ -5292,6 +5292,9 @@ export default {
},
qr: {
scanToPay: '请扫码支付',
payInNewWindow: '请在新窗口中完成支付',
payInNewWindowHint: '支付页面已在新窗口打开,请在新窗口中完成支付后返回此页面',
openPayWindow: '重新打开支付页面',
expiresIn: '剩余支付时间',
expired: '订单已过期',
waitingPayment: '等待支付...',
+12 -1
View File
@@ -1,10 +1,19 @@
<template>
<AppLayout>
<div class="mx-auto flex max-w-md flex-col items-center space-y-6 py-8">
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">{{ t('payment.qr.scanToPay') }}</h2>
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
{{ qrUrl ? t('payment.qr.scanToPay') : t('payment.qr.payInNewWindow') }}
</h2>
<div v-if="qrUrl" class="rounded-2xl bg-white p-6 shadow-lg dark:bg-dark-800">
<canvas ref="qrCanvas" class="mx-auto"></canvas>
</div>
<div v-else-if="payUrl && !expired" class="w-full space-y-3 text-center">
<p class="text-sm text-gray-500 dark:text-gray-400">{{ t('payment.qr.payInNewWindowHint') }}</p>
<a :href="payUrl" target="_blank" rel="noopener noreferrer"
class="btn btn-primary inline-flex w-full items-center justify-center gap-2 py-3">
{{ t('payment.qr.openPayWindow') }}
</a>
</div>
<div v-if="expired" class="text-center">
<p class="text-lg font-medium text-red-500">{{ t('payment.qr.expired') }}</p>
<button class="btn btn-primary mt-4" @click="router.push('/purchase')">{{ t('payment.result.backToRecharge') }}</button>
@@ -41,6 +50,7 @@ const appStore = useAppStore()
const qrCanvas = ref<HTMLCanvasElement | null>(null)
const qrUrl = ref('')
const payUrl = ref('')
const orderId = ref(0)
const remainingSeconds = ref(0)
const expired = ref(false)
@@ -114,6 +124,7 @@ watch(qrUrl, () => renderQR())
onMounted(() => {
orderId.value = Number(route.query.order_id) || 0
qrUrl.value = (route.query.qr as string) || ''
payUrl.value = (route.query.pay_url as string) || ''
// Calculate countdown from expiresAt
const expiresAtStr = route.query.expires_at as string
+1
View File
@@ -236,6 +236,7 @@ async function createOrder(orderAmount: number, orderType: string, planId?: numb
router.push({ path: '/payment/qrcode', query: { order_id: String(result.order_id), qr: result.qr_code || '', pay_url: result.pay_url || '', expires_at: result.expires_at || '' } })
} else if (result.pay_url) {
window.open(result.pay_url, '_blank')
router.push({ path: '/payment/qrcode', query: { order_id: String(result.order_id), pay_url: result.pay_url, expires_at: result.expires_at || '' } })
} else {
errorMessage.value = t('payment.result.failed')
appStore.showError(errorMessage.value)