{"_id":"@daviekong/payment-core","_rev":"4-2f25d4d2331d212034183e13c1737051","name":"@daviekong/payment-core","dist-tags":{"latest":"2.0.3"},"versions":{"2.0.0":{"name":"@daviekong/payment-core","version":"2.0.0","keywords":["payment","alipay","wechat","wechat-pay","sdk"],"author":"","license":"MIT","_id":"@daviekong/payment-core@2.0.0","maintainers":[{"name":"daviekong","email":"iscooleye@163.com"}],"dist":{"shasum":"126477fdbce6307d537fddaf72ab2dc75de8c04f","tarball":"https://registry.npmjs.org/@daviekong/payment-core/-/payment-core-2.0.0.tgz","fileCount":8,"integrity":"sha512-Ni/6LXlT7V67Q0Kg3hh8CJsA5rsfC8+N9cKARSdblFvbJRCCQwbrwz/2KQDtCd6MzxeCTIxRiIJx5rQykkLiww==","signatures":[{"sig":"MEUCIGJV/2cbsKAo+cT4snQltcJlOOQIU0ukQqeaUw1kQPrAAiEAnut66BuUnZyCG9kmRvSVuO9FHkzBpAmjQhG+OwXj1Bw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":181471},"main":"./dist/index.cjs","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.0.0"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"gitHead":"22cb3899ff11240cc5d86e5f08bf6893e19c8f1f","scripts":{"dev":"tsup --watch","lint":"tsc --noEmit","build":"tsup","prepublishOnly":"npm run build"},"_npmUser":{"name":"daviekong","email":"iscooleye@163.com"},"_npmVersion":"11.8.0","description":"支付中台核心 SDK，支持支付宝、微信支付，可扩展接入其他支付渠道","directories":{},"_nodeVersion":"25.6.0","dependencies":{"alipay-sdk":"^4.2.0"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","typescript":"^5","@types/node":"^20"},"_npmOperationalInternal":{"tmp":"tmp/payment-core_2.0.0_1776321765622_0.36030096193380245","host":"s3://npm-registry-packages-npm-production"}},"2.0.1":{"name":"@daviekong/payment-core","version":"2.0.1","keywords":["payment","alipay","wechat","wechat-pay","sdk","payment-gateway","payment-integration","支付宝","微信支付","nodejs","typescript"],"author":{"name":"daviekong"},"license":"MIT","_id":"@daviekong/payment-core@2.0.1","maintainers":[{"name":"daviekong","email":"iscooleye@163.com"}],"homepage":"https://github.com/cooleye/test-my-iq/tree/main/packages/payment-core#readme","bugs":{"url":"https://github.com/cooleye/test-my-iq/issues"},"dist":{"shasum":"2b0a4c5eb9f27006091b210831b0c4fa50aaabee","tarball":"https://registry.npmjs.org/@daviekong/payment-core/-/payment-core-2.0.1.tgz","fileCount":9,"integrity":"sha512-fvdEZZb4XizcqTUmTO9ZEiiw8g6PtjdRImvy0RyFcFRgn48j+FztxwwAJvICGxn8QcAs+ITr1SXdmgI4YBg8Dg==","signatures":[{"sig":"MEQCIBIU8aG6sgjnCfaHc5PvAJwdxaCP584qlOLKi4Ki1oxrAiB9rpbNP9fxgZVQCcm+EbrtyGVuwEpjrEUhhuIOqCR6iQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":183588},"main":"./dist/index.cjs","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.0.0"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"gitHead":"456c22cd4bd081e76d05535420bbf0e85eeb0def","scripts":{"dev":"tsup --watch","lint":"tsc --noEmit","build":"tsup","prepublishOnly":"npm run build"},"_npmUser":{"name":"daviekong","email":"iscooleye@163.com"},"repository":{"url":"git+https://github.com/cooleye/test-my-iq.git","type":"git","directory":"packages/payment-core"},"_npmVersion":"11.8.0","description":"支付中台核心 SDK，支持支付宝、微信支付，可扩展接入其他支付渠道","directories":{},"_nodeVersion":"25.6.0","dependencies":{"alipay-sdk":"^4.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","typescript":"^5","@types/node":"^20"},"_npmOperationalInternal":{"tmp":"tmp/payment-core_2.0.1_1776916636558_0.023784224124179687","host":"s3://npm-registry-packages-npm-production"}},"2.0.2":{"name":"@daviekong/payment-core","version":"2.0.2","keywords":["payment","alipay","wechat","wechat-pay","sdk","payment-gateway","payment-integration","支付宝","微信支付","nodejs","typescript"],"author":{"name":"daviekong"},"license":"MIT","_id":"@daviekong/payment-core@2.0.2","maintainers":[{"name":"daviekong","email":"iscooleye@163.com"}],"homepage":"https://github.com/cooleye/test-my-iq/tree/main/packages/payment-core#readme","bugs":{"url":"https://github.com/cooleye/test-my-iq/issues"},"dist":{"shasum":"c5c99d827a497cda8c4cc2060bafd44d4dab9f31","tarball":"https://registry.npmjs.org/@daviekong/payment-core/-/payment-core-2.0.2.tgz","fileCount":9,"integrity":"sha512-V4v4sqgHwOvCJjK0ShdVJgckA5MiAiHXhlHSYohmK8rsaFDlrkOcNvSDFq/9TiC691LL6XzPqhBBvLDDlQwEPQ==","signatures":[{"sig":"MEUCIQCPwCooIObOijmwdeC9L1JUt/YyLddtuWuUbEQb3nf2SwIgPyOhCem4a9cZnOk3q9UDbksJdG//bFye4oKt/pIh1V4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":183587},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.0.0"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.ts","default":"./dist/index.js"}}},"gitHead":"456c22cd4bd081e76d05535420bbf0e85eeb0def","scripts":{"dev":"tsup --watch","lint":"tsc --noEmit","build":"tsup","prepublishOnly":"npm run build"},"_npmUser":{"name":"daviekong","email":"iscooleye@163.com"},"repository":{"url":"git+https://github.com/cooleye/test-my-iq.git","type":"git","directory":"packages/payment-core"},"_npmVersion":"11.8.0","description":"支付中台核心 SDK，支持支付宝、微信支付，可扩展接入其他支付渠道","directories":{},"_nodeVersion":"25.6.0","dependencies":{"alipay-sdk":"^4.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","typescript":"^5","@types/node":"^20"},"_npmOperationalInternal":{"tmp":"tmp/payment-core_2.0.2_1776919544183_0.39551869493078207","host":"s3://npm-registry-packages-npm-production"}},"2.0.3":{"name":"@daviekong/payment-core","version":"2.0.3","description":"支付中台核心 SDK，支持支付宝、微信支付，可扩展接入其他支付渠道","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.ts","default":"./dist/index.js"}}},"scripts":{"build":"tsup","dev":"tsup --watch","lint":"tsc --noEmit","prepublishOnly":"npm run build"},"keywords":["payment","alipay","wechat","wechat-pay","sdk","payment-gateway","payment-integration","支付宝","微信支付","nodejs","typescript"],"author":{"name":"daviekong"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/cooleye/test-my-iq.git","directory":"packages/payment-core"},"bugs":{"url":"https://github.com/cooleye/test-my-iq/issues"},"homepage":"https://github.com/cooleye/test-my-iq/tree/main/packages/payment-core#readme","publishConfig":{"access":"public"},"devDependencies":{"@types/node":"^20","tsup":"^8.0.0","typescript":"^5"},"dependencies":{"alipay-sdk":"^4.2.0"},"engines":{"node":">=18.0.0"},"gitHead":"456c22cd4bd081e76d05535420bbf0e85eeb0def","_id":"@daviekong/payment-core@2.0.3","_nodeVersion":"25.6.0","_npmVersion":"11.8.0","dist":{"integrity":"sha512-78Bwp4dJM2NH7jGmVtM2qGc0WVrh7HLQi+UvnLEI5AgkyDGO8HS1nX8qjft4rIkT21phNEimprBVLlMd8bEqiQ==","shasum":"97e47d60c60668eb8771b4f9898343d6802158fd","tarball":"https://registry.npmjs.org/@daviekong/payment-core/-/payment-core-2.0.3.tgz","fileCount":9,"unpackedSize":203439,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDKrQ2w3vfha4PXzph2AcygAbd1nv5fdPWsUY/8b8A0VQIgVEnDjhpK9sFrUe/m/DTxuk8D+y3ZUIzSxJ5ixeaOFkw="}]},"_npmUser":{"name":"daviekong","email":"iscooleye@163.com"},"directories":{},"maintainers":[{"name":"daviekong","email":"iscooleye@163.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/payment-core_2.0.3_1777039631646_0.5012537624339575"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-16T06:42:45.517Z","modified":"2026-04-24T14:07:11.962Z","2.0.0":"2026-04-16T06:42:45.759Z","2.0.1":"2026-04-23T03:57:16.700Z","2.0.2":"2026-04-23T04:45:44.332Z","2.0.3":"2026-04-24T14:07:11.861Z"},"bugs":{"url":"https://github.com/cooleye/test-my-iq/issues"},"author":{"name":"daviekong"},"license":"MIT","homepage":"https://github.com/cooleye/test-my-iq/tree/main/packages/payment-core#readme","keywords":["payment","alipay","wechat","wechat-pay","sdk","payment-gateway","payment-integration","支付宝","微信支付","nodejs","typescript"],"repository":{"type":"git","url":"git+https://github.com/cooleye/test-my-iq.git","directory":"packages/payment-core"},"description":"支付中台核心 SDK，支持支付宝、微信支付，可扩展接入其他支付渠道","maintainers":[{"name":"daviekong","email":"iscooleye@163.com"}],"readme":"# @daviekong/payment-core\n\n[![npm version](https://img.shields.io/npm/v/@daviekong/payment-core.svg)](https://www.npmjs.com/package/@daviekong/payment-core)\n[![npm downloads](https://img.shields.io/npm/dm/@daviekong/payment-core.svg)](https://www.npmjs.com/package/@daviekong/payment-core)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n统一支付 SDK，支持支付宝、微信支付，可扩展接入其他支付渠道。\n\n> **English**: A unified payment SDK for Chinese payment channels (Alipay & WeChat Pay). Supports multiple payment scenes and is extensible for custom channels.\n\n## 特性\n\n- 🎯 **统一 API** — 支付宝、微信支付使用相同的接口调用方式\n- 🔌 **可扩展** — 通过 `BaseChannel` 基类轻松接入新支付渠道\n- 🔐 **安全** — 内置签名验证、回调验签、AES-256-GCM 解密\n- 📦 **零框架依赖** — 可在 Next.js / Express / NestJS / 任意 Node.js 项目中使用\n- 💪 **TypeScript** — 完整类型定义，开发体验友好\n- 🔄 **多项目回调中转** — 内置 CallbackRelay，一次配置多项目复用\n\n## 安装\n\n```bash\nnpm install @daviekong/payment-core\n# 或\npnpm add @daviekong/payment-core\n```\n\n> **依赖说明**：支付宝渠道依赖 `alipay-sdk`，已作为 peer dependency 自动安装；微信支付渠道基于 V3 API 自行实现，无额外依赖。\n\n## 快速接入（3 步完成）\n\n### 第 1 步：配置环境变量\n\n在项目根目录 `.env` 文件中添加支付配置：\n\n```bash\n# ========== 支付宝配置 ==========\nALIPAY_APP_ID=                  # 支付宝应用 AppID\nALIPAY_PRIVATE_KEY=             # 应用私钥（RSA2）\nALIPAY_PUBLIC_KEY=              # 支付宝公钥（注意：不是应用公钥！）\n\n# ========== 微信支付配置 ==========\nWECHAT_APP_ID=                  # 微信应用 AppID（公众号/小程序/APP）\nWECHAT_MCH_ID=                  # 微信支付商户号\nWECHAT_API_KEY_V3=              # APIv3 密钥（32位字符串）\nWECHAT_PRIVATE_KEY=             # 商户API证书私钥（PEM格式）\nWECHAT_SERIAL_NO=               # 商户API证书序列号\nWECHAT_PAY_PUBLIC_KEY=          # 微信支付平台公钥（PEM格式，用于回调验签）\n\n# ========== 通用配置 ==========\nPAYMENT_NOTIFY_URL=https://your-domain.com/api/payment/notify\nPAYMENT_SANDBOX=false\n```\n\n> ⚠️ **PEM 密钥格式重要说明**：`.env` 文件不支持多行值，PEM 格式的私钥/公钥需要将换行替换为 `\\n`：\n> ```bash\n> WECHAT_PRIVATE_KEY=\"-----BEGIN PRIVATE KEY-----\\nMIIEvgIBADA...\\n-----END PRIVATE KEY-----\"\n> ```\n> 代码中读取后需替换回真实换行符：`process.env.WECHAT_PRIVATE_KEY?.replace(/\\\\n/g, \"\\n\")`\n>\n> 详细的密钥获取指南请参考 [CREDENTIALS.md](./CREDENTIALS.md)。\n\n### 第 2 步：初始化 PaymentManager\n\n```typescript\nimport { PaymentManager } from \"@daviekong/payment-core\"\n\nconst manager = new PaymentManager({\n  config: {\n    alipay: {\n      appId: process.env.ALIPAY_APP_ID!,\n      privateKey: process.env.ALIPAY_PRIVATE_KEY!,\n      alipayPublicKey: process.env.ALIPAY_PUBLIC_KEY!,\n    },\n    wechat: {\n      appId: process.env.WECHAT_APP_ID!,\n      mchId: process.env.WECHAT_MCH_ID!,\n      apiKeyV3: process.env.WECHAT_API_KEY_V3!,\n      privateKey: process.env.WECHAT_PRIVATE_KEY!.replace(/\\\\n/g, \"\\n\"),\n      serialNo: process.env.WECHAT_SERIAL_NO!,\n      wechatPayPublicKey: process.env.WECHAT_PAY_PUBLIC_KEY!.replace(/\\\\n/g, \"\\n\"),\n    },\n    notifyUrl: process.env.PAYMENT_NOTIFY_URL!,\n    sandbox: process.env.PAYMENT_SANDBOX === \"true\",\n  },\n})\n```\n\n> 💡 **懒加载建议**：如果支付配置可能不存在（如开发环境），可以延迟初始化：\n> ```typescript\n> let manager: PaymentManager | null = null\n>\n> function getPaymentManager(): PaymentManager {\n>   if (!manager) {\n>     manager = new PaymentManager({ config: { ... } })\n>   }\n>   return manager\n> }\n> ```\n\n### 第 3 步：创建支付 + 处理回调\n\n#### 创建支付\n\n```typescript\n// 支付宝 PC 网页支付\nconst result = await manager.createPayment({\n  orderId: \"ORDER_20240101_001\",\n  amount: 990,                    // 金额，单位：分（9.9元）\n  subject: \"IQ测试报告解锁\",\n  channel: \"alipay\",\n  scene: \"pc_web\",                // pc_web | h5 | native | app | jsapi\n})\n// result.htmlForm → 返回 HTML 表单，前端直接提交即可跳转支付宝\n\n// 微信扫码支付（Native）\nconst result = await manager.createPayment({\n  orderId: \"ORDER_20240101_002\",\n  amount: 2990,                   // 29.9元\n  subject: \"专业版IQ测试\",\n  channel: \"wechat\",\n  scene: \"native\",                // native | jsapi | h5 | app\n})\n// result.qrCode → 二维码内容，前端生成二维码展示\n\n// 微信 JSAPI 支付（小程序/公众号内支付）\nconst result = await manager.createPayment({\n  orderId: \"ORDER_20240101_003\",\n  amount: 990,\n  subject: \"IQ测试报告解锁\",\n  channel: \"wechat\",\n  scene: \"jsapi\",\n  openid: \"用户的openid\",         // JSAPI 支付必须传 openid\n})\n// result.jsapiParams → 前端调用 WeixinJSBridge 的参数\n```\n\n#### 处理支付回调（Next.js 示例）\n\n```typescript\n// app/api/payment/notify/[channel]/route.ts\nimport { PaymentManager } from \"@daviekong/payment-core\"\n\nexport async function POST(\n  req: Request,\n  { params }: { params: { channel: string } }\n) {\n  const channel = params.channel as \"alipay\" | \"wechat\"\n\n  try {\n    const rawBody = await req.text()\n    const headers: Record<string, string> = {}\n    req.headers.forEach((value, key) => {\n      headers[key] = value\n    })\n\n    // 验签 + 解析回调数据\n    const callbackData = await manager.handleCallback(channel, rawBody, headers)\n\n    if (callbackData.status === \"SUCCESS\") {\n      // ✅ 支付成功，处理业务逻辑\n      // 注意：必须做幂等处理，回调可能重复推送！\n      await updateOrderStatus(callbackData.orderId, {\n        status: \"PAID\",\n        tradeNo: callbackData.tradeNo,\n        paidAt: callbackData.paidAt,\n      })\n    }\n\n    // 返回成功响应\n    const successResponse = manager.buildSuccessResponse(channel)\n    if (channel === \"alipay\") {\n      return new Response(successResponse as string, {\n        headers: { \"Content-Type\": \"text/plain\" },\n      })\n    }\n    return Response.json(successResponse)\n  } catch (error) {\n    console.error(\"Payment callback error:\", error)\n    const failResponse = manager.buildFailResponse(channel, \"处理失败\")\n    if (channel === \"alipay\") {\n      return new Response(failResponse as string, { status: 500 })\n    }\n    return Response.json(failResponse, { status: 500 })\n  }\n}\n```\n\n#### 处理支付回调（Express 示例）\n\n```typescript\nimport express from \"express\"\nimport { PaymentManager } from \"@daviekong/payment-core\"\n\nconst app = express()\n\n// 支付宝回调\napp.post(\"/api/payment/notify/alipay\", express.urlencoded({ extended: false }), async (req, res) => {\n  try {\n    const rawBody = req.rawBody || new URLSearchParams(req.body).toString()\n    const callbackData = await manager.handleCallback(\"alipay\", rawBody)\n\n    if (callbackData.status === \"SUCCESS\") {\n      await updateOrderStatus(callbackData.orderId, callbackData)\n    }\n\n    res.send(\"success\")\n  } catch {\n    res.status(500).send(\"fail\")\n  }\n})\n\n// 微信支付回调\napp.post(\"/api/payment/notify/wechat\", express.raw({ type: \"*/*\" }), async (req, res) => {\n  try {\n    const rawBody = req.body.toString(\"utf8\")\n    const headers = req.headers as Record<string, string>\n    const callbackData = await manager.handleCallback(\"wechat\", rawBody, headers)\n\n    if (callbackData.status === \"SUCCESS\") {\n      await updateOrderStatus(callbackData.orderId, callbackData)\n    }\n\n    res.json({ code: \"SUCCESS\", message: \"\" })\n  } catch {\n    res.json({ code: \"FAIL\", message: \"ERROR\" })\n  }\n})\n```\n\n---\n\n## 完整 API 参考\n\n### PaymentManager\n\n| 方法 | 说明 |\n|------|------|\n| `createPayment(params)` | 创建支付，返回支付结果 |\n| `handleCallback(channel, rawBody, headers?)` | 处理支付回调（验签 + 解析） |\n| `queryPayment(channel, orderId)` | 主动查询支付状态 |\n| `refund(channel, params)` | 申请退款 |\n| `closePayment(channel, orderId)` | 关闭订单 |\n| `verifyCallbackSign(channel, rawBody, headers?)` | 仅验签（不解析回调数据） |\n| `buildSuccessResponse(channel)` | 构建回调成功响应（支付宝返回 \"success\"，微信返回 `{code:\"SUCCESS\"}`） |\n| `buildFailResponse(channel, message?)` | 构建回调失败响应 |\n| `registerChannel(channel)` | 注册自定义渠道 |\n| `getChannel(name)` | 获取渠道实例 |\n| `getAvailableChannels()` | 获取已注册渠道列表 |\n\n### CreatePaymentParams\n\n| 参数 | 类型 | 必填 | 说明 |\n|------|------|------|------|\n| `orderId` | `string` | ✅ | 商户订单号 |\n| `amount` | `number` | ✅ | 金额，单位：分 |\n| `subject` | `string` | ✅ | 商品标题 |\n| `channel` | `\"alipay\" \\| \"wechat\"` | ✅ | 支付渠道 |\n| `scene` | `PaymentScene` | ❌ | 支付场景，默认 alipay=`pc_web`，wechat=`native` |\n| `openid` | `string` | ❌ | JSAPI 支付必填 |\n| `body` | `string` | ❌ | 商品描述 |\n| `timeExpire` | `Date` | ❌ | 过期时间 |\n| `notifyUrl` | `string` | ❌ | 自定义回调地址（覆盖全局配置） |\n| `returnUrl` | `string` | ❌ | 支付完成后的同步跳转地址（仅支付宝有效） |\n| `extra` | `Record<string, unknown>` | ❌ | 额外参数（如微信 H5 的 clientIp） |\n\n### PaymentResult\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| `orderId` | `string` | 商户订单号 |\n| `channel` | `PaymentChannel` | 支付渠道 |\n| `htmlForm` | `string` | 支付宝 PC/H5 返回的 HTML 表单 |\n| `qrCode` | `string` | Native 支付的二维码内容 |\n| `payUrl` | `string` | H5/APP 支付的跳转 URL |\n| `jsapiParams` | `Record<string, string>` | 微信 JSAPI 支付参数 |\n| `tradeNo` | `string` | 第三方交易号 |\n| `raw` | `unknown` | 原始返回数据 |\n\n### CallbackData\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| `orderId` | `string` | 商户订单号 |\n| `channel` | `PaymentChannel` | 支付渠道 |\n| `tradeNo` | `string` | 第三方交易号 |\n| `amount` | `number` | 金额（分） |\n| `status` | `\"SUCCESS\" \\| \"FAILED\"` | 支付状态 |\n| `paidAt` | `Date` | 支付时间 |\n| `buyerId` | `string` | 买家 ID |\n| `raw` | `Record<string, unknown>` | 原始回调数据 |\n\n### 支付场景（scene）\n\n| 场景 | 支付宝 | 微信 | 说明 | 返回值 |\n|------|--------|------|------|--------|\n| `pc_web` | ✅ | — | PC 网页支付 | `htmlForm` |\n| `h5` | ✅ | ✅ | 手机浏览器支付 | `htmlForm` / `payUrl` |\n| `native` | ✅ | ✅ | 扫码支付 | `qrCode` |\n| `jsapi` | ✅ | ✅ | 公众号/小程序支付 | `jsapiParams` |\n| `app` | ✅ | ✅ | APP 支付 | `payUrl` |\n\n### 金额说明\n\n所有金额统一使用 **分** 为单位，避免浮点数精度问题：\n\n```typescript\namount: 990    // 9.9 元\namount: 2990   // 29.9 元\namount: 19900  // 199 元\n```\n\n---\n\n## 查询、退款、关闭\n\n### 查询支付状态\n\n```typescript\nconst result = await manager.queryPayment(\"alipay\", \"ORDER_20240101_001\")\n\nconsole.log(result.status)    // \"PAID\" | \"PENDING\" | \"CLOSED\" | \"REFUND\" | \"REFUND_PARTIAL\"\nconsole.log(result.tradeNo)   // 第三方交易号\nconsole.log(result.amount)    // 金额（分）\n```\n\n> ⚠️ **重要：支付宝 queryPayment 返回状态说明**\n>\n> 在某些情况下，支付宝渠道的 `queryPayment` 可能返回 `status: \"PENDING\"`，但 `raw.tradeStatus` 实际为 `\"TRADE_SUCCESS\"`。\n> 这是因为 SDK 内部对支付宝交易状态的映射可能不完整。**务必同时检查 `raw.tradeStatus`**：\n> ```typescript\n> const result = await manager.queryPayment(\"alipay\", orderId)\n> const isPaid = result.status === \"PAID\" ||\n>   (result.raw as Record<string, string>)?.tradeStatus === \"TRADE_SUCCESS\"\n> if (isPaid) {\n>   await handlePaymentSuccess(orderId, result.tradeNo)\n> }\n> ```\n> 支付宝 `tradeStatus` 的可能值：\n> - `WAIT_BUYER_PAY` — 等待买家付款\n> - `TRADE_SUCCESS` — 交易支付成功（**需要处理**）\n> - `TRADE_FINISHED` — 交易完结（**需要处理**）\n> - `TRADE_CLOSED` — 交易关闭\n\n> 💡 **主动查询作为回调兜底**：回调可能因网络等原因丢失，建议在查询支付状态接口中，当订单仍为 PENDING 时主动调用 `queryPayment` 查询支付渠道的真实状态：\n> ```typescript\n> if (order.status === \"PENDING\") {\n>   const queryResult = await manager.queryPayment(order.channel, order.id)\n>   if (queryResult.status === \"PAID\") {\n>     await handlePaymentSuccess(order.id, queryResult.tradeNo)\n>   }\n> }\n> ```\n\n### 退款\n\n```typescript\nconst result = await manager.refund(\"alipay\", {\n  orderId: \"ORDER_20240101_001\",\n  refundId: \"REFUND_20240101_001\",   // 退款单号\n  totalAmount: 990,                   // 原订单金额（分）\n  refundAmount: 990,                  // 退款金额（分）\n  reason: \"用户申请退款\",\n})\n\nconsole.log(result.status)  // \"SUCCESS\" | \"PROCESSING\" | \"FAILED\" | \"CHANGE\"\n```\n\n### 关闭订单\n\n```typescript\nconst result = await manager.closePayment(\"wechat\", \"ORDER_20240101_002\")\nconsole.log(result.success)  // true | false\n```\n\n---\n\n## 实战指南：Next.js 完整支付流程\n\n以下是一个完整的 Next.js 支付流程实现，涵盖创建支付、回调处理、轮询检测、前端交互等所有环节。\n\n### 整体架构\n\n```\n┌──────────┐    POST /api/payment/create     ┌──────────┐\n│  前端页面  │ ──────────────────────────────→ │  创建支付  │\n│ purchase  │ ←────────────────────────────── │  API 路由  │\n│  page.tsx │    返回 htmlForm / qrCode       └──────────┘\n└────┬─────┘\n     │\n     │  支付宝：新窗口打开支付页面\n     │  微信：展示二维码\n     │\n     │  前端每3秒轮询 GET /api/payment/status/[orderId]\n     │ ──────────────────────────────────────────────→\n     │ ←──────────────────────────────────────────────\n     │  返回 { status: \"pending\" } 或 { status: \"paid\", activationCode: \"xxx\" }\n     │\n     │  同时，支付宝/微信服务器异步回调 POST /api/payment/notify/[channel]\n     │  ┌──────────────────────────────────────────────────────────────┐\n     │  │  支付宝/微信服务器 → POST /api/payment/notify/alipay        │\n     │  │  → 验签 + 解析回调 → 更新订单状态为 paid                    │\n     │  └──────────────────────────────────────────────────────────────┘\n     │\n     │  当轮询检测到 status === \"paid\" 时\n     │  → 跳转到成功页面，展示激活码\n     ▼\n┌──────────┐\n│  成功页面  │\n│ success   │\n│  page.tsx │\n└──────────┘\n```\n\n### 1. 后端：PaymentManager 初始化（懒加载）\n\n```typescript\n// src/lib/payment.ts\nlet manager: PaymentManager | null = null\n\nexport async function getPaymentManager() {\n  if (!manager) {\n    const { PaymentManager } = await import(\"@daviekong/payment-core\")\n\n    const formatKey = (key: string | undefined): string | undefined => {\n      if (!key) return undefined\n      return key.replace(/\\\\n/g, \"\\n\")\n    }\n\n    manager = new PaymentManager({\n      config: {\n        alipay:\n          process.env.ALIPAY_APP_ID && process.env.ALIPAY_PRIVATE_KEY && process.env.ALIPAY_PUBLIC_KEY\n            ? {\n                appId: process.env.ALIPAY_APP_ID,\n                privateKey: formatKey(process.env.ALIPAY_PRIVATE_KEY)!,\n                alipayPublicKey: formatKey(process.env.ALIPAY_PUBLIC_KEY)!,\n              }\n            : undefined,\n        wechat:\n          process.env.WECHAT_APP_ID && process.env.WECHAT_MCH_ID && process.env.WECHAT_API_KEY_V3\n            ? {\n                appId: process.env.WECHAT_APP_ID,\n                mchId: process.env.WECHAT_MCH_ID,\n                apiKeyV3: process.env.WECHAT_API_KEY_V3,\n                privateKey: formatKey(process.env.WECHAT_PRIVATE_KEY)!,\n                serialNo: process.env.WECHAT_SERIAL_NO || \"\",\n                wechatPayPublicKey: formatKey(process.env.WECHAT_PAY_PUBLIC_KEY),\n              }\n            : undefined,\n        notifyUrl: process.env.PAYMENT_NOTIFY_URL || \"\",\n        sandbox: process.env.PAYMENT_SANDBOX === \"true\",\n      },\n    })\n  }\n  return manager\n}\n```\n\n> 💡 **懒加载的优势**：\n> - 避免在开发环境缺少支付配置时启动报错\n> - 按需加载，减少冷启动时间\n> - 动态 `import()` 确保 `@daviekong/payment-core` 不影响 SSR\n\n### 2. 后端：创建支付 API\n\n```typescript\n// src/app/api/payment/create/route.ts\nimport { NextRequest, NextResponse } from 'next/server'\nimport { prisma } from '@/lib/db'\nimport { getPaymentManager, MEMBERSHIP_PRICE, MEMBERSHIP_SUBJECT } from '@/lib/payment'\n\nexport async function POST(request: NextRequest) {\n  try {\n    const body = await request.json()\n    const { channel } = body as { channel: 'alipay' | 'wechat' }\n\n    if (!channel || !['alipay', 'wechat'].includes(channel)) {\n      return NextResponse.json({ success: false, error: '请选择支付方式' }, { status: 400 })\n    }\n\n    const manager = await getPaymentManager()\n    const orderId = `PAY_${Date.now()}_${Math.random().toString(36).substring(2, 8).toUpperCase()}`\n\n    // 创建订单记录\n    const order = await prisma.paymentOrder.create({\n      data: {\n        orderId,\n        channel,\n        amount: MEMBERSHIP_PRICE,\n        subject: MEMBERSHIP_SUBJECT,\n        status: 'pending',\n      },\n    })\n\n    // 根据设备类型选择支付场景\n    const userAgent = request.headers.get('user-agent') || ''\n    const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent)\n\n    let scene: string\n    if (channel === 'alipay') {\n      scene = isMobile ? 'h5' : 'pc_web'\n    } else {\n      scene = 'native'\n    }\n\n    // 调用 payment-core 创建支付\n    const result = await manager.createPayment({\n      orderId: order.orderId,\n      amount: MEMBERSHIP_PRICE,\n      subject: MEMBERSHIP_SUBJECT,\n      channel,\n      scene,\n    })\n\n    return NextResponse.json({\n      success: true,\n      data: {\n        orderId: order.orderId,\n        channel,\n        htmlForm: result.htmlForm,   // 支付宝 PC/H5 返回\n        qrCode: result.qrCode,       // 微信 Native 返回\n        payUrl: result.payUrl,        // H5/APP 返回\n      },\n    })\n  } catch (error) {\n    console.error('Create payment error:', error)\n    return NextResponse.json({ success: false, error: '创建支付订单失败' }, { status: 500 })\n  }\n}\n```\n\n### 3. 后端：支付回调处理\n\n```typescript\n// src/app/api/payment/notify/[channel]/route.ts\nimport { NextRequest, NextResponse } from 'next/server'\nimport { prisma } from '@/lib/db'\nimport { getPaymentManager, generateActivationCode } from '@/lib/payment'\n\nasync function handlePaymentSuccess(orderId: string, tradeNo: string) {\n  const order = await prisma.paymentOrder.findUnique({ where: { orderId } })\n\n  // 幂等处理：已处理过则直接返回\n  if (!order || order.status === 'paid') return\n\n  const activationCode = generateActivationCode()\n\n  await prisma.$transaction(async (tx) => {\n    const code = await tx.activationCode.create({\n      data: {\n        code: activationCode,\n        batch: `payment_${orderId}`,\n        status: 'sold',\n      },\n    })\n\n    await tx.paymentOrder.update({\n      where: { orderId },\n      data: {\n        status: 'paid',\n        tradeNo,\n        paidAt: new Date(),\n        activationCodeId: code.id,\n      },\n    })\n  })\n}\n\nexport async function POST(\n  req: NextRequest,\n  { params }: { params: { channel: string } }\n) {\n  const channel = params.channel as 'alipay' | 'wechat'\n\n  try {\n    const manager = await getPaymentManager()\n    const rawBody = await req.text()\n    const headers: Record<string, string> = {}\n    req.headers.forEach((value, key) => {\n      headers[key] = value\n    })\n\n    console.log(`[payment] Received ${channel} callback, body length: ${rawBody.length}`)\n    const callbackData = await manager.handleCallback(channel, rawBody, headers)\n    console.log(`[payment] Callback result:`, JSON.stringify(callbackData))\n\n    // ⚠️ 重要：同时检查 SUCCESS 和 TRADE_SUCCESS\n    // 支付宝回调可能返回 status=\"TRADE_SUCCESS\" 而非 \"SUCCESS\"\n    if (callbackData.status === 'SUCCESS' || callbackData.status === 'TRADE_SUCCESS') {\n      await handlePaymentSuccess(callbackData.orderId, callbackData.tradeNo)\n    }\n\n    // 返回成功响应（必须返回正确格式，否则支付平台会持续重试回调）\n    const successResponse = manager.buildSuccessResponse(channel)\n    if (channel === 'alipay') {\n      return new Response(successResponse as string, {\n        headers: { 'Content-Type': 'text/plain' },\n      })\n    }\n    return NextResponse.json(successResponse)\n  } catch (error) {\n    console.error('Payment callback error:', error)\n    try {\n      const manager = await getPaymentManager()\n      const failResponse = manager.buildFailResponse(channel, '处理失败')\n      if (channel === 'alipay') {\n        return new Response(failResponse as string, { status: 500 })\n      }\n      return NextResponse.json(failResponse, { status: 500 })\n    } catch {\n      if (channel === 'alipay') {\n        return new Response('fail', { status: 500 })\n      }\n      return NextResponse.json({ code: 'FAIL', message: 'ERROR' }, { status: 500 })\n    }\n  }\n}\n```\n\n### 4. 后端：支付状态查询 API（含主动查询兜底）\n\n```typescript\n// src/app/api/payment/status/[orderId]/route.ts\nimport { NextRequest, NextResponse } from 'next/server'\nimport { prisma } from '@/lib/db'\nimport { getPaymentManager, generateActivationCode } from '@/lib/payment'\n\nexport async function GET(\n  request: NextRequest,\n  { params }: { params: { orderId: string } }\n) {\n  try {\n    const { orderId } = params\n\n    const order = await prisma.paymentOrder.findUnique({\n      where: { orderId },\n      include: { activationCode: { select: { code: true } } },\n    })\n\n    if (!order) {\n      return NextResponse.json({ success: false, error: '订单不存在' }, { status: 404 })\n    }\n\n    // ⚠️ 关键：当订单仍为 pending 时，主动查询支付渠道的真实状态\n    if (order.status === 'pending') {\n      try {\n        const manager = await getPaymentManager()\n        const queryResult = await manager.queryPayment(\n          order.channel as 'alipay' | 'wechat',\n          order.orderId\n        )\n        console.log(`[payment] queryPayment result for ${order.orderId}:`, JSON.stringify(queryResult))\n\n        // ⚠️ 重要：同时检查 result.status 和 raw.tradeStatus\n        // 支付宝 queryPayment 可能返回 status=\"PENDING\" 但 raw.tradeStatus=\"TRADE_SUCCESS\"\n        const isPaid = queryResult.status === 'PAID' ||\n          (queryResult.raw as Record<string, string>)?.tradeStatus === 'TRADE_SUCCESS'\n\n        if (isPaid) {\n          const activationCodeStr = generateActivationCode()\n\n          await prisma.$transaction(async (tx) => {\n            const code = await tx.activationCode.create({\n              data: {\n                code: activationCodeStr,\n                batch: `payment_${order.orderId}`,\n                status: 'sold',\n              },\n            })\n            await tx.paymentOrder.update({\n              where: { orderId: order.orderId },\n              data: {\n                status: 'paid',\n                tradeNo: queryResult.tradeNo,\n                paidAt: new Date(),\n                activationCodeId: code.id,\n              },\n            })\n          })\n\n          const updatedOrder = await prisma.paymentOrder.findUnique({\n            where: { orderId },\n            include: { activationCode: { select: { code: true } } },\n          })\n\n          return NextResponse.json({\n            success: true,\n            data: {\n              orderId: updatedOrder!.orderId,\n              status: 'paid',\n              activationCode: updatedOrder!.activationCode?.code,\n            },\n          })\n        }\n      } catch (queryError) {\n        // 查询失败不影响，返回当前数据库状态\n        console.error(`[payment] queryPayment failed for ${order.orderId}:`, queryError)\n      }\n    }\n\n    return NextResponse.json({\n      success: true,\n      data: {\n        orderId: order.orderId,\n        status: order.status,\n        activationCode: order.status === 'paid' ? order.activationCode?.code : null,\n      },\n    })\n  } catch (error) {\n    console.error('Check payment status error:', error)\n    return NextResponse.json({ success: false, error: '查询订单状态失败' }, { status: 500 })\n  }\n}\n```\n\n### 5. 前端：支付页面（含支付宝/微信不同处理）\n\n```typescript\n// src/app/purchase/page.tsx\n'use client'\n\nimport { useState } from 'react'\nimport { useRouter } from 'next/navigation'\n\ntype PaymentChannel = 'alipay' | 'wechat'\ntype PaymentStatus = 'idle' | 'creating' | 'waiting' | 'polling'\n\nexport default function PurchasePage() {\n  const router = useRouter()\n  const [selectedChannel, setSelectedChannel] = useState<PaymentChannel>('alipay')\n  const [isLoading, setIsLoading] = useState(false)\n  const [error, setError] = useState('')\n  const [qrCode, setQrCode] = useState('')\n  const [orderId, setOrderId] = useState('')\n  const [paymentStatus, setPaymentStatus] = useState<PaymentStatus>('idle')\n\n  async function handlePayment() {\n    setError('')\n    setIsLoading(true)\n    setPaymentStatus('creating')\n\n    try {\n      const response = await fetch('/api/payment/create', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({ channel: selectedChannel }),\n      })\n\n      const result = await response.json()\n\n      if (!result.success) {\n        setError(result.error || '创建订单失败')\n        setIsLoading(false)\n        setPaymentStatus('idle')\n        return\n      }\n\n      const { orderId, htmlForm, qrCode: qr, payUrl } = result.data\n      setOrderId(orderId)\n\n      // ========== 支付宝 PC 网页支付 ==========\n      // 在新窗口打开支付页面，原页面显示等待状态并启动轮询\n      if (selectedChannel === 'alipay' && htmlForm) {\n        const container = document.createElement('div')\n        container.innerHTML = htmlForm\n        const form = container.querySelector('form')\n        if (form) {\n          form.setAttribute('target', '_blank')  // 新窗口打开\n          document.body.appendChild(form)\n          form.submit()\n          setPaymentStatus('waiting')  // 显示\"等待支付\"提示\n          setIsLoading(false)\n          startPolling(orderId)\n          return\n        }\n      }\n\n      // ========== 微信扫码支付 ==========\n      // 显示二维码，启动轮询\n      if (selectedChannel === 'wechat' && qr) {\n        setQrCode(qr)\n        setPaymentStatus('polling')\n        setIsLoading(false)\n        startPolling(orderId)\n        return\n      }\n\n      // ========== H5/APP 支付 ==========\n      if (payUrl) {\n        window.location.href = payUrl\n        return\n      }\n\n      setError('支付方式异常，请重试')\n      setIsLoading(false)\n      setPaymentStatus('idle')\n    } catch (err) {\n      setError('创建订单失败，请稍后重试')\n      setIsLoading(false)\n      setPaymentStatus('idle')\n    }\n  }\n\n  // 轮询检测支付状态（每3秒一次，最多120次=6分钟）\n  function startPolling(orderId: string) {\n    let count = 0\n    const maxCount = 120\n    const timer = setInterval(async () => {\n      count++\n      if (count > maxCount) {\n        clearInterval(timer)\n        setPaymentStatus('idle')\n        return\n      }\n      try {\n        const res = await fetch(`/api/payment/status/${orderId}`)\n        const data = await res.json()\n        if (data.success && data.data.status === 'paid') {\n          clearInterval(timer)\n          // 跳转到成功页面展示激活码\n          router.push(`/purchase/success?orderId=${orderId}`)\n        }\n      } catch {\n        // continue polling\n      }\n    }, 3000)\n  }\n\n  return (\n    <div>\n      {/* 支付宝等待状态 */}\n      {paymentStatus === 'waiting' && (\n        <div className=\"text-center\">\n          <h3>请在支付宝页面完成支付</h3>\n          <p>支付完成后，请关闭支付宝页面返回此处</p>\n          <p>等待支付完成...</p>\n          <p>如果已完成支付，页面将自动跳转</p>\n        </div>\n      )}\n\n      {/* 微信二维码状态 */}\n      {paymentStatus === 'polling' && qrCode && (\n        <div className=\"text-center\">\n          <img src={`https://api.qrserver.com/v1/create-qr-code/?size=180x180&data=${encodeURIComponent(qrCode)}`} />\n          <p>请使用微信扫描二维码完成支付</p>\n          <p>等待支付中...</p>\n        </div>\n      )}\n\n      {/* 选择支付方式 */}\n      {paymentStatus === 'idle' && (\n        <div>\n          <button onClick={() => setSelectedChannel('alipay')}>支付宝</button>\n          <button onClick={() => setSelectedChannel('wechat')}>微信支付</button>\n          <button onClick={handlePayment} disabled={isLoading}>\n            {isLoading ? '正在创建订单...' : '立即支付'}\n          </button>\n        </div>\n      )}\n    </div>\n  )\n}\n```\n\n---\n\n## ⚠️ 已知问题与注意事项\n\n### 1. 支付宝 queryPayment 状态映射问题\n\n**问题**：`queryPayment` 对支付宝的查询结果可能返回 `status: \"PENDING\"`，但 `raw.tradeStatus` 实际为 `\"TRADE_SUCCESS\"`。\n\n**原因**：SDK 内部对支付宝交易状态的映射可能不完整，未将 `TRADE_SUCCESS` 正确映射为 `PAID`。\n\n**解决方案**：在判断支付状态时，同时检查 `result.status` 和 `result.raw.tradeStatus`：\n\n```typescript\nconst isPaid = result.status === 'PAID' ||\n  (result.raw as Record<string, string>)?.tradeStatus === 'TRADE_SUCCESS'\n```\n\n### 2. 支付宝 PC 网页支付的同步跳转问题\n\n**问题**：支付宝 PC 网页支付完成后，用户停留在支付宝页面，不会自动跳转回商户网站。\n\n**原因**：支付宝的同步跳转（return_url）需要在支付宝开放平台后台配置，且只能配置一个地址。如果使用 CallbackRelay 中转架构，return_url 指向的是中转服务，无法直接跳转到业务项目。\n\n**解决方案**：不依赖 return_url，而是使用前端轮询机制：\n1. 支付表单在新窗口打开（`target=\"_blank\"`）\n2. 原页面显示\"等待支付完成\"提示\n3. 后台每 3 秒轮询支付状态 API\n4. 检测到支付成功后自动跳转到成功页面\n\n### 3. 本地开发环境回调不可达\n\n**问题**：本地开发时，支付宝/微信的异步回调无法访问 `localhost`，导致回调通知无法到达。\n\n**解决方案**：在支付状态查询 API 中，当订单仍为 `pending` 时主动调用 `queryPayment` 查询支付渠道的真实状态，作为回调的兜底方案。\n\n### 4. 回调必须返回正确格式\n\n- **支付宝**：成功返回纯文本 `\"success\"`，失败返回 `\"fail\"`\n- **微信**：成功返回 JSON `{\"code\": \"SUCCESS\", \"message\": \"\"}`，失败返回 `{\"code\": \"FAIL\", \"message\": \"原因\"}`\n\n如果返回格式不正确，支付平台会持续重试回调（支付宝最多重试 24 小时，微信最多重试 10 次）。\n\n---\n\n## 扩展新支付渠道\n\n继承 `BaseChannel` 实现自定义渠道：\n\n```typescript\nimport { BaseChannel, PaymentChannel, CreatePaymentParams, PaymentResult, CallbackData, QueryResult, RefundParams, RefundResult, CloseResult } from \"@daviekong/payment-core\"\n\nexport class StripeChannel extends BaseChannel {\n  readonly channelName: PaymentChannel = \"stripe\" as PaymentChannel\n\n  constructor(private config: { secretKey: string }) {\n    super()\n  }\n\n  async createPayment(params: CreatePaymentParams): Promise<PaymentResult> {\n    // 调用 Stripe API 创建支付\n    return {\n      orderId: params.orderId,\n      channel: this.channelName,\n      payUrl: \"https://checkout.stripe.com/...\",\n    }\n  }\n\n  async handleCallback(rawBody: string, headers?: Record<string, string>): Promise<CallbackData> {\n    // 解析 Stripe Webhook\n  }\n\n  async queryPayment(orderId: string): Promise<QueryResult> {\n    // 查询 Stripe 支付状态\n  }\n\n  async refund(params: RefundParams): Promise<RefundResult> {\n    // 调用 Stripe 退款 API\n  }\n\n  async closePayment(orderId: string): Promise<CloseResult> {\n    // 关闭 Stripe 支付\n  }\n\n  verifyCallbackSign(rawBody: string, headers?: Record<string, string>): boolean {\n    // 验证 Stripe Webhook 签名\n  }\n\n  buildSuccessResponse(): Record<string, string> {\n    return { received: \"true\" }\n  }\n\n  buildFailResponse(message?: string): Record<string, string> {\n    return { error: message || \"ERROR\" }\n  }\n}\n\n// 注册到 PaymentManager\nmanager.registerChannel(new StripeChannel({ secretKey: \"sk_xxx\" }))\n\n// 使用\nconst result = await manager.createPayment({\n  orderId: \"ORDER_001\",\n  amount: 990,\n  subject: \"商品名称\",\n  channel: \"stripe\",\n})\n```\n\n> 💡 **提示**：如果需要让 TypeScript 识别新的渠道名称，可以扩展 `PaymentChannel` 类型：\n> ```typescript\n> declare module \"@daviekong/payment-core\" {\n>   interface PaymentChannelMap {\n>     stripe: StripeChannel\n>   }\n> }\n> ```\n\n---\n\n## 多项目回调中转（CallbackRelay）\n\n### 问题背景\n\n你有多个项目需要接入支付，但：\n- 支付宝只需要**一个应用**（每次请求可传不同的 `notifyUrl`）\n- 微信支付只需要**一个商户号**（V3 API 每次请求可传不同的 `notify_url`）\n\n但如果你希望**只配置一次回调地址**，让所有项目的支付回调都走同一个入口，然后自动转发到对应项目，就可以使用 `CallbackRelay`。\n\n### 架构图\n\n```\n支付宝/微信服务器\n       │\n       ▼\n┌──────────────────────────────┐\n│  中转服务（唯一回调入口）      │\n│  https://pay.your.com/notify │\n│                              │\n│  1. 验签 + 解析回调           │\n│  2. 根据 orderId 路由到项目   │\n│  3. HMAC-SHA256 签名转发      │\n└──────┬───────┬───────┬───────┘\n       │       │       │\n       ▼       ▼       ▼\n   项目A     项目B    项目C\n  /notify   /notify  /notify\n```\n\n### 使用方式\n\n#### 1. 中转服务端（部署一次）\n\n```typescript\nimport { CallbackRelay } from \"@daviekong/payment-core\"\n\nconst relay = new CallbackRelay(\n  {\n    alipay: {\n      appId: process.env.ALIPAY_APP_ID!,\n      privateKey: process.env.ALIPAY_PRIVATE_KEY!,\n      alipayPublicKey: process.env.ALIPAY_PUBLIC_KEY!,\n    },\n    wechat: {\n      appId: process.env.WECHAT_APP_ID!,\n      mchId: process.env.WECHAT_MCH_ID!,\n      apiKeyV3: process.env.WECHAT_API_KEY_V3!,\n      privateKey: process.env.WECHAT_PRIVATE_KEY!.replace(/\\\\n/g, \"\\n\"),\n      serialNo: process.env.WECHAT_SERIAL_NO!,\n      wechatPayPublicKey: process.env.WECHAT_PAY_PUBLIC_KEY!.replace(/\\\\n/g, \"\\n\"),\n    },\n    notifyUrl: \"https://pay.your-domain.com/api/notify\",\n  },\n  {\n    projects: [\n      {\n        id: \"test-my-iq\",\n        name: \"IQ测试项目\",\n        notifyUrl: \"https://iq.your-domain.com/api/payment/internal-notify\",\n        secret: \"project-a-secret-key\",\n      },\n      {\n        id: \"project-b\",\n        name: \"项目B\",\n        notifyUrl: \"https://b.your-domain.com/api/payment/internal-notify\",\n        secret: \"project-b-secret-key\",\n      },\n    ],\n    routeBy: \"prefix\",\n  }\n)\n\n// 创建支付时自动绑定订单和项目\nconst result = await relay.createPayment(\"test-my-iq\", {\n  orderId: \"test-my-iq_ORDER_001\",  // orderId 以项目ID为前缀，自动路由\n  amount: 990,\n  subject: \"IQ测试报告解锁\",\n  channel: \"alipay\",\n  scene: \"pc_web\",\n})\n\n// 处理回调（中转服务唯一入口）\n// Next.js 示例: app/api/notify/[channel]/route.ts\nexport async function POST(req: Request, { params }: { params: { channel: string } }) {\n  const channel = params.channel as \"alipay\" | \"wechat\"\n  const rawBody = await req.text()\n  const headers: Record<string, string> = {}\n  req.headers.forEach((v, k) => { headers[k] = v })\n\n  try {\n    const dispatchResult = await relay.handleRelayCallback(channel, rawBody, headers)\n\n    if (dispatchResult.success) {\n      const successResp = relay.getManager().buildSuccessResponse(channel)\n      if (channel === \"alipay\") {\n        return new Response(successResp as string, { headers: { \"Content-Type\": \"text/plain\" } })\n      }\n      return Response.json(successResp)\n    }\n\n    const failResp = relay.getManager().buildFailResponse(channel, \"Forward failed\")\n    if (channel === \"alipay\") {\n      return new Response(failResp as string, { status: 500 })\n    }\n    return Response.json(failResp, { status: 500 })\n  } catch (error) {\n    console.error(\"Relay callback error:\", error)\n    const failResp = relay.getManager().buildFailResponse(channel, \"Error\")\n    if (channel === \"alipay\") {\n      return new Response(failResp as string, { status: 500 })\n    }\n    return Response.json(failResp, { status: 500 })\n  }\n}\n```\n\n#### 2. 业务项目端（接收中转回调）\n\n```typescript\n// 项目内部回调接口（不对外暴露，只接受中转服务的调用）\n// app/api/payment/internal-notify/route.ts\nimport { CallbackRelay } from \"@daviekong/payment-core\"\n\nexport async function POST(req: Request) {\n  const signature = req.headers.get(\"X-Relay-Signature\")!\n  const timestamp = req.headers.get(\"X-Relay-Timestamp\")!\n  const project = req.headers.get(\"X-Relay-Project\")!\n  const channel = req.headers.get(\"X-Relay-Channel\")!\n\n  const rawBody = await req.text()\n\n  // 验证中转签名（确保请求来自你的中转服务）\n  const isValid = CallbackRelay.verifyRelaySignature(\n    rawBody,\n    signature,\n    timestamp,\n    process.env.RELAY_SECRET!  // 与中转服务配置的 secret 一致\n  )\n\n  if (!isValid) {\n    return Response.json({ error: \"Invalid signature\" }, { status: 401 })\n  }\n\n  // 解析回调数据\n  const callbackData = CallbackRelay.parseRelayCallback(rawBody)\n\n  // 处理业务逻辑\n  if (callbackData.status === \"SUCCESS\") {\n    await updateOrderStatus(callbackData.orderId, {\n      status: \"PAID\",\n      tradeNo: callbackData.tradeNo,\n      paidAt: callbackData.paidAt,\n    })\n  }\n\n  return Response.json({ received: true })\n}\n```\n\n### 路由策略\n\n`CallbackRelay` 支持两种路由策略来确定回调应该转发给哪个项目：\n\n#### 策略 1：前缀匹配（默认，推荐）\n\n订单号以项目 ID 为前缀，自动匹配：\n\n```typescript\n// orderId = \"test-my-iq_ORDER_001\" → 路由到 test-my-iq 项目\n// orderId = \"project-b_ORDER_002\"  → 路由到 project-b 项目\n\nconst relay = new CallbackRelay(paymentConfig, {\n  projects: [...],\n  routeBy: \"prefix\",  // 默认值\n})\n```\n\n#### 策略 2：显式绑定\n\n在创建支付时手动绑定订单和项目：\n\n```typescript\n// 创建支付时自动绑定\nawait relay.createPayment(\"test-my-iq\", { orderId: \"ANY_ORDER_ID\", ... })\n\n// 或手动绑定\nrelay.bindOrderToProject(\"ANY_ORDER_ID\", \"test-my-iq\")\n```\n\n> ⚠️ 显式绑定的数据存储在内存中，服务重启后会丢失。生产环境建议配合数据库持久化，或使用前缀匹配策略。\n\n### CallbackRelay API\n\n| 方法 | 说明 |\n|------|------|\n| `createPayment(projectId, params)` | 创建支付并自动绑定项目 |\n| `handleRelayCallback(channel, rawBody, headers?)` | 处理回调并转发到对应项目 |\n| `registerProject(project)` | 动态注册新项目 |\n| `bindOrderToProject(orderId, projectId)` | 手动绑定订单到项目 |\n| `getManager()` | 获取底层 PaymentManager 实例 |\n| `CallbackRelay.verifyRelaySignature(body, signature, timestamp, secret)` | 静态方法：验证中转签名 |\n| `CallbackRelay.parseRelayCallback(rawBody)` | 静态方法：解析中转回调数据 |\n\n---\n\n## 回调处理最佳实践\n\n### 1. 幂等处理（必须）\n\n回调可能重复推送，必须确保业务逻辑幂等：\n\n```typescript\nasync function handlePaymentSuccess(orderId: string, tradeNo: string) {\n  const order = await db.order.findUnique({ where: { id: orderId } })\n\n  // 已处理过则直接返回\n  if (order.status === \"PAID\") return\n\n  await db.order.update({\n    where: { id: orderId },\n    data: { status: \"PAID\", tradeNo, paidAt: new Date() },\n  })\n}\n```\n\n### 2. 金额校验（必须）\n\n```typescript\nconst callbackData = await manager.handleCallback(channel, rawBody, headers)\n\nconst order = await db.order.findUnique({ where: { id: callbackData.orderId } })\nif (callbackData.amount !== order.amountCents) {\n  throw new Error(\"金额不一致，可能存在风险\")\n}\n```\n\n### 3. 主动查询兜底\n\n回调可能丢失，建议在查询支付状态时主动查询支付渠道：\n\n```typescript\n// 前端轮询支付状态接口\nasync function checkPaymentStatus(orderId: string) {\n  const order = await db.order.findUnique({ where: { id: orderId } })\n\n  if (order.status === \"PENDING\") {\n    // 主动查询支付渠道的真实状态\n    try {\n      const result = await manager.queryPayment(order.channel as PaymentChannel, orderId)\n      // ⚠️ 同时检查 result.status 和 raw.tradeStatus\n      const isPaid = result.status === \"PAID\" ||\n        (result.raw as Record<string, string>)?.tradeStatus === \"TRADE_SUCCESS\"\n      if (isPaid) {\n        await handlePaymentSuccess(orderId, result.tradeNo)\n        return \"PAID\"\n      }\n    } catch {\n      // 查询失败不影响，返回当前状态\n    }\n  }\n\n  return order.status\n}\n```\n\n### 4. 超时关闭\n\n```typescript\n// 定时关闭超时未支付的订单\nsetInterval(async () => {\n  const expiredOrders = await db.order.findMany({\n    where: { status: \"PENDING\", createdAt: { lt: new Date(Date.now() - 30 * 60 * 1000) } },\n  })\n\n  for (const order of expiredOrders) {\n    await manager.closePayment(order.paymentMethod as PaymentChannel, order.id)\n    await db.order.update({ where: { id: order.id }, data: { status: \"CANCELLED\" } })\n  }\n}, 10 * 60 * 1000)\n```\n\n---\n\n## 项目结构\n\n```\npackages/payment-core/\n├── src/\n│   ├── index.ts                     # 统一导出\n│   ├── types.ts                     # 核心类型定义\n│   ├── core/\n│   │   ├── base-channel.ts          # 渠道基类（扩展新渠道用）\n│   │   ├── payment-manager.ts       # 支付管理器（单项目使用）\n│   │   └── callback-relay.ts        # 回调中转器（多项目使用）\n│   ├── channels/\n│   │   ├── alipay/\n│   │   │   ├── index.ts             # 支付宝渠道实现（基于 alipay-sdk v4）\n│   │   │   └── types.ts             # 支付宝特有类型\n│   │   └── wechat/\n│   │       ├── index.ts             # 微信支付渠道实现（基于 V3 API 自行实现）\n│   │       └── types.ts             # 微信支付特有类型\n│   └── utils/\n│       └── crypto.ts                # 加密工具（RSA-SHA256、AES-256-GCM、HMAC-SHA256 等）\n├── CREDENTIALS.md                   # 账号配置指南\n├── package.json\n├── tsconfig.json\n└── tsup.config.ts\n```\n\n## 技术实现说明\n\n### 支付宝\n\n- 基于 `alipay-sdk` v4 官方 SDK\n- PC/H5 支付使用 `pageExec()` 返回 HTML 表单\n- Native 扫码支付使用 `exec()` 返回二维码链接\n- APP 支付使用 `sdkExec()` 返回订单字符串\n- 回调验签使用 `checkNotifySign()`\n\n### 微信支付\n\n- 基于 V3 API 自行实现，无第三方依赖\n- 请求签名：RSA-SHA256，Authorization 头格式 `WECHATPAY2-SHA256-RSA2048`\n- 回调解密：AES-256-GCM，使用 APIv3 密钥作为密钥\n- 回调验签：RSA-SHA256，使用微信支付平台公钥验证签名\n- JSAPI 支付参数签名：RSA-SHA256，生成 `paySign`\n\n## 账号配置\n\n请参考 [CREDENTIALS.md](./CREDENTIALS.md) 获取各支付渠道的账号密钥配置指南。\n\n## License\n\nMIT\n","readmeFilename":"README.md"}