{"_id":"@douyinpay_sdk/douyinpay-nodejs","_rev":"3-ff730c9d57d3a46bebd46b7af0d5ff3c","name":"@douyinpay_sdk/douyinpay-nodejs","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@douyinpay_sdk/douyinpay-nodejs","version":"1.0.0","keywords":["douyinpay","douyin-pay","payment","sdk"],"license":"Apache-2.0","_id":"@douyinpay_sdk/douyinpay-nodejs@1.0.0","maintainers":[{"name":"bytednpm","email":"bnpm@bytedance.com"},{"name":"shuyang.007","email":"shuyang.007@bytedance.com"}],"homepage":"https://github.com/douyinpay/douyinpay-nodejs#readme","bugs":{"url":"https://github.com/douyinpay/douyinpay-nodejs/issues"},"dist":{"shasum":"1432176e4b1aec65a948ff9a245e4718236391ce","tarball":"https://registry.npmjs.org/@douyinpay_sdk/douyinpay-nodejs/-/douyinpay-nodejs-1.0.0.tgz","fileCount":93,"integrity":"sha512-58/RGEEBeUQtd0FPXTqfely/4WGHtLGgSoRaWHTxR1BRiLh42Wm3cgHNCRdRrJh7ZdAgnWIpdJRwJLAvZ43YBg==","signatures":[{"sig":"MEUCIQD9oI90hqs92SxtJoQ7lvegyESm1H3ncuZYZzdv/L0WpgIgUclnpf72RSGaT/BjvV0yKN5GQ/KiGaCOA9p4GfJofaI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":184797},"main":"./dist/cjs/index.cjs","type":"module","types":"./dist/types/index.d.ts","module":"./dist/esm/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/types/index.d.ts","import":"./dist/esm/index.js","require":"./dist/cjs/index.cjs"},"./package.json":"./package.json"},"gitHead":"9f8049939a1974d987f9ffbfdc641792676204ea","scripts":{"test":"vitest run","build":"npm run clean && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && tsc -p tsconfig.types.json && node scripts/fix-cjs.cjs","clean":"rimraf dist","prepack":"npm run build","typecheck":"tsc --noEmit -p tsconfig.json","test:order:online":"vitest run test/online-order.test.ts --reporter=verbose","test:refund:online":"vitest run test/online-refund.test.ts --reporter=verbose"},"_npmUser":{"name":"shuyang.007","email":"shuyang.007@bytedance.com"},"repository":{"url":"git+https://github.com/douyinpay/douyinpay-nodejs.git","type":"git"},"_npmVersion":"11.11.0","description":"Douyin Pay server-side SDK for Node.js","directories":{},"_nodeVersion":"25.8.0","dependencies":{"urllib":"^4.9.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"qrcode":"^1.5.4","rimraf":"^6.0.1","vitest":"^3.2.4","typescript":"^5.8.3","@types/node":"^20.19.1"},"_npmOperationalInternal":{"tmp":"tmp/douyinpay-nodejs_1.0.0_1782446431674_0.2995715573423565","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@douyinpay_sdk/douyinpay-nodejs","version":"1.0.1","keywords":["douyinpay","douyin-pay","payment","sdk"],"license":"Apache-2.0","_id":"@douyinpay_sdk/douyinpay-nodejs@1.0.1","maintainers":[{"name":"bytednpm","email":"bnpm@bytedance.com"},{"name":"shuyang.007","email":"shuyang.007@bytedance.com"}],"homepage":"https://github.com/douyinpay/douyinpay-nodejs#readme","bugs":{"url":"https://github.com/douyinpay/douyinpay-nodejs/issues"},"dist":{"shasum":"83dafa40629fdbbfd26bfe6d79ec8cb7580a9ebd","tarball":"https://registry.npmjs.org/@douyinpay_sdk/douyinpay-nodejs/-/douyinpay-nodejs-1.0.1.tgz","fileCount":48,"integrity":"sha512-ke5b9A0hzcO2n/Hhm7FIP6B2jSbvSgGmUIv/sz79tg5A2yQn+1fQr+O/7A1s6CkPuvIcIjAIGt99dUjfBPrMbw==","signatures":[{"sig":"MEUCIQCGRIer2zc7Ql//diEGsMznFQBPmt70eshrukDQkWBJzQIgIX5jEbFzwwPSMO0RcA7cVsZOXzgCEQw1xbn6tLQmYx0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":108155},"main":"./dist/cjs/index.cjs","type":"module","types":"./dist/types/index.d.ts","module":"./dist/esm/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/types/index.d.ts","import":"./dist/esm/index.js","require":"./dist/cjs/index.cjs"},"./package.json":"./package.json"},"gitHead":"40d9c6d291da12a2999661aa756b7262718ece0c","scripts":{"test":"vitest run","build":"npm run clean && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && tsc -p tsconfig.types.json && node scripts/fix-cjs.cjs","clean":"rimraf dist","prepack":"npm run build","typecheck":"tsc --noEmit -p tsconfig.json","test:order:online":"vitest run test/online-order.test.ts --reporter=verbose","test:refund:online":"vitest run test/online-refund.test.ts --reporter=verbose"},"_npmUser":{"name":"shuyang.007","email":"shuyang.007@bytedance.com"},"repository":{"url":"git+https://github.com/douyinpay/douyinpay-nodejs.git","type":"git"},"_npmVersion":"11.11.0","description":"Douyin Pay server-side SDK for Node.js","directories":{},"_nodeVersion":"25.8.0","dependencies":{"urllib":"^4.9.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"rimraf":"^6.0.1","vitest":"^3.2.4","typescript":"^5.8.3","@types/node":"^20.19.1"},"_npmOperationalInternal":{"tmp":"tmp/douyinpay-nodejs_1.0.1_1782463987864_0.10081757775307665","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2026-06-26T04:00:31.518Z","modified":"2026-06-26T09:03:01.372Z","1.0.0":"2026-06-26T04:00:31.819Z","1.0.1":"2026-06-26T08:53:08.039Z"},"bugs":{"url":"https://github.com/douyinpay/douyinpay-nodejs/issues"},"license":"Apache-2.0","homepage":"https://github.com/douyinpay/douyinpay-nodejs#readme","keywords":["douyinpay","douyin-pay","payment","sdk"],"repository":{"url":"git+https://github.com/douyinpay/douyinpay-nodejs.git","type":"git"},"description":"Douyin Pay server-side SDK for Node.js","maintainers":[{"email":"bnpm@bytedance.com","name":"bytednpm"},{"email":"shuyang.007@bytedance.com","name":"shuyang.007"},{"email":"lizhonglv.1102@bytedance.com","name":"lizhonglv.1102"}],"readme":"# douyinpay-nodejs\n\n抖音支付 Node.js 服务端 SDK。\n\n本 SDK 使用 TypeScript 实现，HTTP Client 基于 [`urllib`](https://github.com/node-modules/urllib)，同时支持 CommonJS 与 ESM 引入方式。\n\n## 安装\n\n```bash\nnpm install @douyinpay_sdk/douyinpay-nodejs\n```\n\n## 快速开始\n\n### ESM\n\n```ts\nimport { createAutoClientRSA } from '@douyinpay_sdk/douyinpay-nodejs';\n\nconst sdk = await createAutoClientRSA({\n  mchid: 'your_mchid',\n  serial: 'your_merchant_cert_serial',\n  privateKey: process.env.DOUYINPAY_PRIVATE_KEY!,\n  encryptKey: process.env.DOUYINPAY_API_ENCRYPT_KEY!,\n});\n```\n\n### CommonJS\n\n```js\nconst { createClientRSA } = require('@douyinpay_sdk/douyinpay-nodejs');\nconst { readFileSync } = require('node:fs');\n\nconst sdk = createClientRSA({\n  mchid: 'your_mchid',\n  serial: 'your_merchant_cert_serial',\n  privateKey: process.env.DOUYINPAY_PRIVATE_KEY,\n  platformCertificate: readFileSync('/path/to/douyinpay_platform_certificate.pem', 'utf8'),\n});\n```\n\n## 核心入口与依赖关系\n\nSDK 对外入口统一从包根导出。业务代码通常只需要关注三类入口：初始化 client、调用 API、处理证书/回调。\n\n| 入口 | 作用 | 依赖 | 说明 |\n| --- | --- | --- | --- |\n| `createClientRSA(options)` | 用本地平台证书/平台公钥初始化 client | `mchid`、商户证书 `serial`、商户 `privateKey`、`platformCertificate` | 本地已持有平台证书，或希望自己管理证书文件 |\n| `createAutoClientRSA(options)` | 自动下载平台证书后初始化 client，并支持后台刷新 | `mchid`、商户证书 `serial`、商户 `privateKey`、接口加密 `encryptKey` | 希望 SDK 管理平台证书下载、缓存和刷新 |\n| `sdk.getClient(path)` | 获取某个接口路径的请求 client | 已初始化的 `sdk` | 下单、查单、退款等接口都通过它发起请求 |\n| `downloadPlatformCertificates(options)` | 单独下载平台证书 | `mchid`、商户证书 `serial`、商户 `privateKey`、接口加密 `encryptKey` | 只想下载证书并自行管理时使用 |\n| `parseCallback(...)` / `CallbackHandler` | 回调通知验签与解密 | 平台证书或自动证书管理器、接口加密 `encryptKey`、原始 headers/body | 支付、退款等异步通知处理 |\n\n依赖关系简述：\n\n```text\n商户配置\n  ├─ 本地证书模式：createClientRSA\n  │    └─ 使用传入的平台证书/公钥做响应验签\n  │         └─ sdk.getClient(path) 调用下单、查单、退款等 API\n  │\n  └─ 自动证书模式：createAutoClientRSA\n       └─ AutoCertificateManager\n            ├─ downloadPlatformCertificates 下载并解密平台证书\n            ├─ 内存缓存平台证书\n            └─ sdk.getClient(path) 调用 API 时使用最新证书做响应验签\n\n回调处理\n  └─ parseCallback / CallbackHandler\n       ├─ 使用平台证书验签回调原始 body\n       └─ 使用接口加密密钥 encryptKey 解密 resource\n```\n\n> 说明：`DouyinPaySdk` 是底层 SDK 对象，推荐业务代码优先使用 `createClientRSA` 或 `createAutoClientRSA` 初始化。\n\n## Client 初始化方式\n\nSDK 目前提供两种 RSA client 初始化方式。两种方式创建出的 `sdk` 都是同一个调用入口，后续下单、查单、退款等 API 调用方式完全一致。\n\n### 1. 本地单证书初始化\n\n适合你已经在本地持有抖音支付平台证书，或希望证书文件完全由业务系统自行管理的场景。\n\n必填项：\n\n- `mchid`：商户号\n- `serial`：商户证书序列号，用于请求签名头\n- `privateKey`：商户私钥 PEM 内容\n- `platformCertificate`：抖音支付平台证书 PEM 内容；也支持平台公钥内容\n\n如果 `platformCertificate` 是完整证书 PEM，SDK 会自动解析平台证书序列号；如果传入的是平台公钥内容，则必须额外传 `platformSerial`。\n\n```ts\nimport { readFileSync } from 'node:fs';\nimport { createClientRSA } from '@douyinpay_sdk/douyinpay-nodejs';\n\nconst sdk = createClientRSA({\n  mchid: 'your_mchid',\n  serial: 'your_merchant_cert_serial',\n  privateKey: readFileSync('/path/to/merchant_private_key.pem', 'utf8'),\n  platformCertificate: readFileSync('/path/to/douyinpay_platform_certificate.pem', 'utf8'),\n});\n```\n\n如果只传平台公钥，需要同时指定平台证书序列号：\n\n```ts\nconst sdk = createClientRSA({\n  mchid: 'your_mchid',\n  serial: 'your_merchant_cert_serial',\n  privateKey: process.env.DOUYINPAY_PRIVATE_KEY!,\n  platformCertificate: process.env.DOUYINPAY_PLATFORM_PUBLIC_KEY!,\n  platformSerial: 'your_platform_cert_serial',\n});\n```\n\n这种方式不会自动下载平台证书；响应验签使用初始化时传入的证书或公钥。\n\n### 2. 自动下载平台证书初始化\n\n适合服务端长期运行使用。初始化时不需要传平台证书，SDK 会调用平台证书下载接口，用接口加密密钥解密平台证书，并按配置定时刷新证书。响应验签失败时会直接抛错，不会在验签链路中触发证书下载补偿。\n\n必填项：\n\n- `mchid`：商户号\n- `serial`：商户证书序列号，用于请求签名头\n- `privateKey`：商户私钥 PEM 内容\n- `encryptKey`：接口加密密钥，用于解密平台证书下载接口返回的证书内容\n\n```ts\nimport { readFileSync } from 'node:fs';\nimport { createAutoClientRSA } from '@douyinpay_sdk/douyinpay-nodejs';\n\nconst sdk = await createAutoClientRSA({\n  mchid: 'your_mchid',\n  serial: 'your_merchant_cert_serial',\n  privateKey: readFileSync('/path/to/merchant_private_key.pem', 'utf8'),\n  encryptKey: 'your_api_encrypt_key',\n  // 可选：默认 24 小时后台刷新一次；传 0 可关闭定时刷新。\n  refreshIntervalMs: 24 * 60 * 60 * 1000,\n});\n```\n\n自动下载的证书会缓存在当前进程内存中，不会由 SDK 自动写入本地文件。\n\n两种初始化方式创建出的 `sdk` 调用 API 的方式完全一致：\n\n```ts\nconst response = await sdk\n  .getClient('/v1/trade/transactions/native')\n  .post(requestBody);\n```\n\n## API 调用\n\nSDK 的接口调用方式与常见 Node.js 支付 SDK 类似：先创建 client，然后通过 `getClient(path)` 获取指定接口客户端。\n\n```ts\nconst client = sdk.getClient('/your/api/path');\n\nawait client.get(options);\nawait client.post(data, options);\nawait client.put(data, options);\nawait client.patch(data, options);\nawait client.delete(options);\n```\n\nSDK 会自动完成：\n\n- 请求签名\n- `Authorization` 头生成\n- `Douyinpay-Sdk-Agent` 客户端版本上报\n- HTTP 请求发送\n- 抖音支付响应验签\n\n## 回调通知验签与解密\n\n收到抖音支付回调后，应先使用平台证书对原始请求体进行验签，验签通过后再解密 `resource`。SDK 提供 `parseCallback` 和 `CallbackHandler` 两种入口。\n\n### 使用本地平台证书\n\n```ts\nimport { parseCallback } from '@douyinpay_sdk/douyinpay-nodejs';\n\nconst notify = await parseCallback(\n  request.headers,\n  rawBody,\n  {\n    encryptKey: 'your_api_encrypt_key',\n    certs: {\n      your_platform_cert_serial: platformCertificatePem,\n    },\n  },\n);\n\nconsole.log(notify.event_type);\nconsole.log(notify.content); // resource 解密后的业务内容\n```\n\n### 使用自动证书管理器\n\n如果传入 `certificateProvider`，SDK 会使用其中当前已缓存的平台证书参与回调验签；回调验签失败会直接抛错，不会触发证书下载补偿。\n\n```ts\nimport { CallbackHandler, createAutoClientRSAWithManager } from '@douyinpay_sdk/douyinpay-nodejs';\n\nconst { sdk, certificateManager } = await createAutoClientRSAWithManager({\n  mchid: 'your_mchid',\n  serial: 'your_merchant_cert_serial',\n  privateKey: merchantPrivateKeyPem,\n  encryptKey: 'your_api_encrypt_key',\n});\n\nconst handler = new CallbackHandler({\n  encryptKey: 'your_api_encrypt_key',\n  certificateProvider: certificateManager,\n});\n\nconst notify = await handler.parse(request.headers, rawBody);\n```\n\n`createAutoClientRSAWithManager` 与 `createAutoClientRSA` 使用同一套 AutoClient 初始化逻辑，只是额外返回 `certificateManager`，便于回调验签复用已初始化并定时刷新的平台证书。\n`sdk` 可继续用于下单、查单、退款等 API 调用。\n\n验签使用的原文格式为：\n\n```text\ntimestamp + \"\\n\" + nonce + \"\\n\" + body + \"\\n\"\n```\n\n当前 Node.js SDK 支持 `AEAD_AES_256_GCM` 回调资源解密。\n\n## 1. 下单\n\n当前示例使用 Native 下单接口：\n\n```text\nPOST /v1/trade/transactions/native\n```\n\n```ts\nconst outTradeNo = `ORDER_${Date.now()}`;\n\nconst response = await sdk\n  .getClient('/v1/trade/transactions/native')\n  .post({\n    mchid: 'your_mchid',\n    appid: 'your_appid',\n    description: '抖音支付测试订单',\n    out_trade_no: outTradeNo,\n    time_expire: new Date(Date.now() + 10 * 60 * 1000).toISOString(),\n    notify_url: 'https://www.example.com/douyinpay/notify',\n    attach: '',\n    amount: {\n      currency: 'CNY',\n      total: 1,\n    },\n    ip: '127.0.0.1',\n  });\n\nconsole.log(response.status);\nconsole.log(response.data);\n```\n\n返回示例：\n\n```json\n{\n  \"code_url\": \"https://qr.douyinpay.com/xxxxx\"\n}\n```\n\n## 2. 查单\n\n通过商户订单号查询订单：\n\n```text\nGET /v1/trade/transactions/out-trade-no/{out_trade_no}?mchid={mchid}\n```\n\n```ts\nconst response = await sdk\n  .getClient(`/v1/trade/transactions/out-trade-no/${encodeURIComponent(outTradeNo)}`)\n  .get({\n    query: {\n      mchid: 'your_mchid',\n    },\n  });\n\nconsole.log(response.status);\nconsole.log(response.data);\n```\n\n返回示例：\n\n```json\n{\n  \"mchid\": \"your_mchid\",\n  \"appid\": \"your_appid\",\n  \"out_trade_no\": \"ORDER_123456\",\n  \"trade_state\": \"NOTPAY\",\n  \"trade_state_desc\": \"未支付\"\n}\n```\n\n## 3. 平台证书下载\n\n平台证书用于校验抖音支付响应签名。证书下载接口本身也需要商户私钥签名，请求成功后返回加密证书，SDK 会使用接口加密密钥进行 AES-256-GCM 解密。\n\n```ts\nimport { readFileSync } from 'node:fs';\nimport { downloadPlatformCertificates } from '@douyinpay_sdk/douyinpay-nodejs';\n\nconst certificates = await downloadPlatformCertificates({\n  mchid: 'your_mchid',\n  serial: 'your_merchant_cert_serial',\n  privateKey: readFileSync('/path/to/merchant_private_key.pem', 'utf8'),\n  // 首次下载证书时还没有可信平台证书，可传 bootstrap 占位，并显式关闭本次下载响应验签。\n  // 后续已有平台证书后，请传入实际 certs 并保持 verifyResponse 为默认 true。\n  certs: { bootstrap: '' },\n  verifyResponse: false,\n  encryptKey: 'your_api_encrypt_key',\n});\n\nconsole.log(certificates);\n```\n\n返回结构：\n\n```ts\n[\n  {\n    serialNo: 'platform_cert_serial',\n    certificate: '-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----',\n    effectiveTime: '2026-01-01T00:00:00+08:00',\n    expireTime: '2031-01-01T00:00:00+08:00'\n  }\n]\n```\n\n下载完成后，SDK 只返回证书内容，不会自动落盘。你可以自行保存，或将平台证书配置到 `createClientRSA` 中使用；如果使用 `createAutoClientRSA`，通常不需要手动调用本接口。\n\n## TypeScript 支持\n\nSDK 内置 TypeScript 类型声明，无需额外安装 `@types/*`。\n\n```ts\nimport type { DouYinPayConfig, DouYinPayResponse } from '@douyinpay_sdk/douyinpay-nodejs';\n```\n\n## 错误处理\n\n```ts\ntry {\n  const response = await sdk\n    .getClient('/v1/trade/transactions/native')\n    .post(requestBody);\n\n  console.log(response.data);\n} catch (error) {\n  console.error('DouyinPay request failed:', error);\n}\n```\n\n常见错误包括：\n\n- 商户号、证书序列号、私钥或平台证书缺失\n- 请求签名失败\n- HTTP 请求失败\n- 响应缺少验签头\n- 平台证书序列号不匹配\n- 响应签名验签失败\n- 回调验签失败或 resource 解密失败\n\n## 安全建议\n\n- 不要把商户私钥、接口加密密钥、平台证书本地配置提交到 GitHub\n- 不要在日志中打印商户私钥和接口加密密钥\n- 回调处理必须先验签，再解密和处理业务\n- 平台证书可能轮换，建议使用自动下载证书模式或定期更新本地证书\n\n## License\n\nMIT\n","readmeFilename":"README.md"}