From ddec22be490da5a50e0feed32030fad99c211bfb Mon Sep 17 00:00:00 2001 From: ggyy <34892002@qq.com> Date: Sat, 25 Apr 2026 15:44:01 +0800 Subject: [PATCH] fix: payment --- README.md | 5 ++- components/SecretInput.vue | 34 ++++++++++++++++++++ docs/components.md | 19 +++++++++++ lib/utils/order-status.ts | 2 +- modules/payment/alipay.ts | 6 ++-- modules/payment/bepusdt.ts | 37 ++++++++++++++-------- modules/payment/service.ts | 4 +-- pages/admin/email/+Page.vue | 7 ++-- pages/admin/orders/+Page.vue | 2 +- pages/admin/payments/+Page.vue | 10 +++--- pages/admin/payments/PaymentConfigCard.vue | 6 ++-- pages/admin/payments/forms/AlipayForm.vue | 5 +-- pages/admin/payments/forms/BEpusdtForm.vue | 3 +- pages/admin/payments/forms/EpayForm.vue | 3 +- 14 files changed, 106 insertions(+), 37 deletions(-) create mode 100644 components/SecretInput.vue diff --git a/README.md b/README.md index 908195d..60ec31a 100644 --- a/README.md +++ b/README.md @@ -300,5 +300,8 @@ bun dev 4. 在搜索框输入关键词过滤日志,例如: - `email.notify_order_paid.config_failed` — 支付后邮件配置获取失败 - `email.send.failed` — 邮件发送失败 + - `email.order_paid.failed` — 支付成功后发送邮件通知失败 - `payment.notify.route_exception` — 支付回调路由异常 - - `payment.notify.context_missing` — 支付回调缺少数据库上下文 \ No newline at end of file + - `payment.notify.context_missing` — 支付回调缺少数据库上下文 + - `payment.notify.diagnostic` — 支付回调校验异常诊断(签名错误、金额不匹配等) + - `bepusdt.verify_notify` — BEpusdt 回调原始 payload(debug 级别) \ No newline at end of file diff --git a/components/SecretInput.vue b/components/SecretInput.vue new file mode 100644 index 0000000..b07c0ae --- /dev/null +++ b/components/SecretInput.vue @@ -0,0 +1,34 @@ + + + \ No newline at end of file diff --git a/docs/components.md b/docs/components.md index 5c544a1..b52ac2c 100644 --- a/docs/components.md +++ b/docs/components.md @@ -1,5 +1,24 @@ # 公共组件文档 +## SecretInput + +带显示/隐藏切换的密钥输入框,用于密码、API Secret 等敏感字段。 + +### Props + +| 属性 | 类型 | 说明 | +|------|------|------| +| `modelValue` | `string` | 输入值(v-model) | + +支持透传所有原生 `input` 属性(如 `placeholder`、`disabled` 等)。 + +### 基本用法 + +```components/SecretInput.vue#L1-3 + +``` + + ## DataTable 通用带翻页的表格组件,基于 daisyUI `table` 样式。 diff --git a/lib/utils/order-status.ts b/lib/utils/order-status.ts index 3eb84ff..2d47a6a 100644 --- a/lib/utils/order-status.ts +++ b/lib/utils/order-status.ts @@ -46,7 +46,7 @@ export function getPaymentProviderLabel(provider: string) { case "EPAY": return "易支付"; case "BEPUSDT": - return "USDT"; + return "BEpusdt"; default: return provider; } diff --git a/modules/payment/alipay.ts b/modules/payment/alipay.ts index c111237..d6273af 100644 --- a/modules/payment/alipay.ts +++ b/modules/payment/alipay.ts @@ -16,7 +16,7 @@ export interface AlipayConfig { returnUrl?: string; } -const DEFAULT_GATEWAY = "https://openapi.alipay.com/gateway.do"; + function pemToBase64(pem: string) { return pem.replace(/-----[^-]+-----/g, "").replace(/\s+/g, ""); @@ -65,7 +65,7 @@ function buildSignString(params: Record) { export function createAlipayAdapter(config: AlipayConfig): PaymentProviderAdapter { return { async createPayment(input) { - const gateway = config.baseUrl?.trim().replace(/\/+$/, "") || DEFAULT_GATEWAY; + const gateway = `${config.baseUrl?.trim().replace(/\/+$/, "")}/gateway.do`; if (!config.alipayAppId || !config.alipayPrivateKey) { throw badRequestError("支付宝配置不完整", "ALIPAY_CONFIG_INCOMPLETE"); @@ -136,7 +136,7 @@ export async function queryAlipayTrade(config: AlipayConfig, outTradeNo: string) throw badRequestError("支付宝配置不完整", "ALIPAY_CONFIG_INCOMPLETE"); } - const gateway = config.baseUrl?.trim().replace(/\/+$/, "") || DEFAULT_GATEWAY; + const gateway = `${config.baseUrl?.trim().replace(/\/+$/, "")}/gateway.do`; const timestamp = new Date().toISOString().replace("T", " ").slice(0, 19); const params: Record = { app_id: config.alipayAppId, diff --git a/modules/payment/bepusdt.ts b/modules/payment/bepusdt.ts index f2850d0..9205063 100644 --- a/modules/payment/bepusdt.ts +++ b/modules/payment/bepusdt.ts @@ -1,5 +1,6 @@ import { createHash } from "node:crypto"; import { badRequestError, externalServiceError } from "../../lib/app-error"; +import { logger } from "../../lib/logger"; import type { PaymentProviderAdapter } from "./provider"; interface BepusdtConfig { @@ -40,18 +41,8 @@ export function createBepusdtAdapter(config: BepusdtConfig): PaymentProviderAdap }; const signature = signBepusdt(payload, config.appSecret); - const response = await fetch(`${normalizeBaseUrl(config.baseUrl)}/api/v1/order/create-transaction`, { - method: "POST", - headers: { - "content-type": "application/json", - }, - body: JSON.stringify({ - ...payload, - signature, - }), - }); - const json = (await response.json()) as { + type BepusdtResponse = { status_code?: number; message?: string; data?: { @@ -60,7 +51,23 @@ export function createBepusdtAdapter(config: BepusdtConfig): PaymentProviderAdap }; }; - if (!response.ok || json.status_code !== 200 || !json.data?.payment_url) { + let json: BepusdtResponse; + try { + const response = await fetch(`${normalizeBaseUrl(config.baseUrl)}/api/v1/order/create-order`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ...payload, signature }), + }); + const text = (await response.text()).replace(/^\uFEFF/, ""); + json = JSON.parse(text) as BepusdtResponse; + } catch (err) { + throw externalServiceError( + `BEpusdt 请求失败: ${err instanceof Error ? err.message : String(err)}`, + "BEPUSDT_INVALID_RESPONSE" + ); + } + + if (json.status_code !== 200 || !json.data?.payment_url) { throw externalServiceError(json.message || "BEpusdt 创建支付失败", "BEPUSDT_CREATE_PAYMENT_FAILED"); } @@ -80,11 +87,13 @@ export function createBepusdtAdapter(config: BepusdtConfig): PaymentProviderAdap }; } + logger.debug("bepusdt.verify_notify", { payload }); const signature = payload.signature || ""; const unsignedPayload = { ...payload }; delete unsignedPayload.signature; const expected = signBepusdt(unsignedPayload, config.appSecret); - const status = payload.status === "2" ? "PAID" : payload.status === "3" ? "FAILED" : "PENDING"; + const statusVal = String(payload.status); + const status = statusVal === "2" ? "PAID" : statusVal === "3" ? "FAILED" : "PENDING"; return { isValid: signature === expected, @@ -97,4 +106,4 @@ export function createBepusdtAdapter(config: BepusdtConfig): PaymentProviderAdap }; }, }; -} +} \ No newline at end of file diff --git a/modules/payment/service.ts b/modules/payment/service.ts index c7cc972..861931e 100644 --- a/modules/payment/service.ts +++ b/modules/payment/service.ts @@ -19,7 +19,7 @@ import { notifyOrderPaid as _notifyOrderPaid } from "../email/service"; const defaultPaymentConfigs: Record = { BEPUSDT: { provider: "BEPUSDT", - name: "USDT", + name: "BEpusdt", isEnabled: false, baseUrl: "", appId: "", @@ -41,7 +41,7 @@ const defaultPaymentConfigs: Record = { provider: "ALIPAY", name: "支付宝", isEnabled: false, - baseUrl: "", + baseUrl: "https://openapi.alipay.com", alipayAppId: "", alipayPrivateKey: "", alipayPublicKey: "", diff --git a/pages/admin/email/+Page.vue b/pages/admin/email/+Page.vue index 4011773..6a30daa 100644 --- a/pages/admin/email/+Page.vue +++ b/pages/admin/email/+Page.vue @@ -212,11 +212,11 @@ \ No newline at end of file diff --git a/pages/admin/payments/forms/EpayForm.vue b/pages/admin/payments/forms/EpayForm.vue index af7dbcf..728f76d 100644 --- a/pages/admin/payments/forms/EpayForm.vue +++ b/pages/admin/payments/forms/EpayForm.vue @@ -6,7 +6,7 @@

@@ -15,5 +15,6 @@ \ No newline at end of file