fix(payment): critical audit fixes for security, idempotency and correctness

Backend fixes:
- #1: doSub subscription idempotency via audit log check
- #2: markFailed only when status=RECHARGING (prevents overwriting COMPLETED)
- #3: ExpireTimedOutOrders checks upstream payment before expiring
- #4: Public verify endpoint for payment result page (no auth required)
- #5: EasyPay QueryOrder returns amount, confirmPayment handles zero amount
- #6: WxPay notifyUrl priority: request-first, config-fallback
- #7: EasyPay remove double URL decode in VerifyNotification
- #8: checkPaid/cancelUpstreamPayment use order's provider instance
- #9: Amount NaN/Inf/negative validation in order creation and refund
- #10: Refund amount comparison uses tolerance instead of float64 ==
- #11: Skip balance deduction on retry when previous rollback failed
- #12: checkPaid logs fulfillment errors instead of silently ignoring
- #13: WxPay certSerial added to required config fields

Frontend fixes:
- Payment result page no longer requires authentication
- Public verify API fallback for expired sessions
This commit is contained in:
erio
2026-04-10 02:23:19 +08:00
parent 56e4a9a914
commit c738cfec93
7 changed files with 152 additions and 54 deletions

View File

@@ -136,14 +136,17 @@ onMounted(async () => {
}
}
// If we have an out_trade_no from a provider return URL, actively verify
// the payment with the upstream provider (handles missed notify callbacks)
// Verify payment via public endpoint (works without login)
if (outTradeNo) {
try {
const result = await paymentAPI.verifyOrder(outTradeNo)
const result = await paymentAPI.verifyOrderPublic(outTradeNo)
order.value = result.data
} catch (_err: unknown) {
// Verification failed, fall through to normal order lookup
// Public verify failed, try authenticated endpoint if logged in
try {
const result = await paymentAPI.verifyOrder(outTradeNo)
order.value = result.data
} catch (_e: unknown) { /* fall through */ }
}
}