Online Payment (Hosted Checkout) 集成所需的认证体系详解
更新: 2026-02-07
CodePay 的 RSA 签名认证涉及 3 把密钥,分属两对 RSA 密钥对:
| 密钥 | 谁生成 | 谁持有 | 用途 |
|---|---|---|---|
| app_rsa_private_key | 我方生成 | 我方保留(绝不外泄) | 对发出的请求签名,证明"这个请求是我发的" |
| app_rsa_public_key | 我方生成 | 上传到 CodePay 商户后台 | CodePay 用它验证我方请求签名的合法性 |
| gateway_rsa_public_key | CodePay 生成 | 从 CodePay 商户后台下载 | 我方用它验证 CodePay Webhook/响应的签名 |
gateway_rsa_private_key,但那是他们内部持有的,不会给我们。我们只拿到他们的公钥用于验签。
1组装请求数据
{
"merchant_no": "M123456",
"amount": 5000,
"currency": "USD",
"order_id": "DEP_20260207_001",
"notify_url": "https://api.celoria.ai/api/webhooks/codepay",
"return_url": "https://celoria.ai/booking/success"
}
2参数排序拼接
把所有参数按 key 的字母顺序排列,用 & 拼接成字符串:
amount=5000¤cy=USD&merchant_no=M123456¬ify_url=https://api.celoria.ai/api/webhooks/codepay&order_id=DEP_20260207_001&return_url=https://celoria.ai/booking/success
3RSA 签名
用 app_rsa_private_key 对上面的字符串做 SHA256WithRSA 签名:
const crypto = require('crypto');
function signRequest(params, privateKey) {
// 1. 过滤空值,按 key 排序
const sorted = Object.keys(params)
.filter(k => params[k] !== '' && params[k] !== null && params[k] !== undefined)
.sort()
.map(k => `${k}=${params[k]}`)
.join('&');
// 2. 用私钥签名 (SHA256WithRSA)
const sign = crypto.createSign('RSA-SHA256');
sign.update(sorted);
return sign.sign(privateKey, 'base64');
}
4发送请求
把签名值放入 sign 字段,连同 sign_type: "RSA2" 一起发送:
{
"merchant_no": "M123456",
"amount": 5000,
"currency": "USD",
"order_id": "DEP_20260207_001",
"notify_url": "https://api.celoria.ai/api/webhooks/codepay",
"return_url": "https://celoria.ai/booking/success",
"sign_type": "RSA2",
"sign": "a3f8d2e1b9c7..."
}
function verifyCodePaySignature(params, sign, gatewayPublicKey) {
// 1. 取出 sign 和 sign_type,其余参数排序拼接
const filtered = Object.keys(params)
.filter(k => k !== 'sign' && k !== 'sign_type')
.filter(k => params[k] !== '' && params[k] !== null)
.sort()
.map(k => `${k}=${params[k]}`)
.join('&');
// 2. 用 CodePay 公钥验证签名
const verify = crypto.createVerify('RSA-SHA256');
verify.update(filtered);
return verify.verify(gatewayPublicKey, sign, 'base64');
}
Online Payment 需要在 .env 中新增以下配置(与现有 Cloud Mode 配置并存):
# ===== CodePay Online Payment (Hosted Checkout) ===== CODEPAY_APPID=xxx # CodePay 分配的应用 ID CODEPAY_APP_RSA_PRIVATE_KEY=xxx # 我方 RSA 私钥 (PEM 格式,单行 base64) CODEPAY_GATEWAY_RSA_PUBLIC_KEY=xxx # CodePay 网关公钥 (PEM 格式) CODEPAY_SIGN_TYPE=RSA2 # 签名算法 (SHA256WithRSA) # ===== 以下为已有配置(Cloud Mode 复用) ===== # CODEPAY_MERCHANT_NO=M123456 # 商户号(两种模式共用) # CODEPAY_WEBHOOK_SECRET=xxx # 仅 Cloud Mode HMAC 验签用
CODEPAY_MERCHANT_NO 两种模式共用同一个商户号。Webhook 端点 /api/webhooks/codepay 也可以复用,只需在处理逻辑中根据请求内容区分 Cloud Mode (HMAC) 和 Online Payment (RSA) 的验签方式。
# 1. 生成私钥 openssl genrsa -out app_rsa_private_key.pem 2048 # 2. 从私钥导出公钥 openssl rsa -in app_rsa_private_key.pem -pubout -out app_rsa_public_key.pem # 3. 查看公钥内容(用于上传到 CodePay 商户后台) cat app_rsa_public_key.pem
| 操作 | 去哪里 | 做什么 |
|---|---|---|
| 上传我方公钥 | CodePay 商户后台 | 把 app_rsa_public_key.pem 的内容粘贴上去 |
| 下载 CodePay 公钥 | CodePay 商户后台 | 复制 gateway_rsa_public_key 保存到本地 |
| 保存私钥 | 服务器 .env 文件 |
将私钥内容(去掉换行)存入 CODEPAY_APP_RSA_PRIVATE_KEY |
app_rsa_private_key.pem 绝不能提交到 Git、不能放在前端代码中、不能通过聊天/邮件传输。仅存放在服务器环境变量中。
| 对比项 | Cloud Mode(店内刷卡) | Online Payment(远程定金) |
|---|---|---|
| 场景 | 员工在 Web 端发起,客人在 P5 终端刷卡 | 客人在 Guest App 里远程在线支付 |
| 认证方式 | OAuth2 (Client ID + Secret) | RSA 签名 (appid + RSA 密钥对) |
| 支付界面 | P5 终端上的 CodePay Register App | CodePay 托管的 Web 支付页面 (WebView) |
| API 端点 | POST /v1/orders (Cloud API) |
POST /api/entry/checkout (Hosted Checkout) |
| Webhook 验签 | HMAC-SHA256 | RSA-SHA256 |
| 商户号 | 共用 CODEPAY_MERCHANT_NO |
|
| Webhook 端点 | 共用 /api/webhooks/codepay(内部区分验签方式) |
|
specs/payments/015-pos-payment/codepay-cloud-integration.md — Cloud Mode 集成方案specs/053-booking-deposit-cancellation-policy/spec.md — 定金与退订政策 Spec