Keep Pay 开发者文档
控制台

KeepPay 开发者文档

把卡数据攥回自己手里:在用户浏览器侧令牌化,明文永不落你的服务器;用令牌向任意能收卡的通道发起扣款,一套卡库对接谁都行。

Keep your data · Keep your choices · Keep your revenue

概述

KeepPay 是基于 PCI Level 1 合规底座的支付编排平台。集成只有三步:

  1. 前端采集 —— 用采集组件在用户浏览器把卡号换成令牌,明文不经过你的前后端。
  2. 首次支付 —— 后端使用令牌完成首次支付,并存进你名下的卡库(你只存令牌)。
  3. 用令牌扣款 —— 用令牌向支付通道中立转发发起扣款,明文在合规环境内还原注入,你的系统不碰 PAN。
🔐
合规要点:明文 PAN 始终在 PCI Level 1 合规底座内,你的 PCI 范围收敛到 SAQ A。控制台与你的服务器全程只接触令牌。

API 基址:

# 测试环境 https://payment-test.keeppay.net # 正式环境 https://payment.keeppay.net

快速开始

从令牌化一张测试卡到发起一笔扣款,最小闭环:

前端 · JavaScript

使用 Keeppay 托管的 Card iframe 采集卡号、有效期和 CVC。浏览器端只能使用具备 Card Token 创建权限的 Public Application Key,不要放入 Management Key 或 Private Key。

<!-- Card 输入框由 Keeppay 托管 --> <div id="bt-card"></div> <button id="card-tokenize-button" type="button">获取 tokenId</button> const btConfig = { cardPublicKey: '<BT_CARD_PUBLIC_KEY>', environment: 'test', cardSdkUrl: 'https://js.keeppay.com/web-elements/2.15.0/index.js' } let cardSdkPromise let btCardInstance let cardElement function loadBtCardSdk() { if (typeof window.keeppay === 'function') { return Promise.resolve(window.keeppay) } if (cardSdkPromise) return cardSdkPromise cardSdkPromise = new Promise((resolve, reject) => { const script = document.createElement('script') script.src = btConfig.cardSdkUrl script.async = true script.onload = () => { if (typeof window.keeppay === 'function') { resolve(window.keeppay) } else { reject(new Error('Keeppay SDK 初始化失败')) } } script.onerror = () => reject(new Error('Keeppay SDK 加载失败')) document.head.appendChild(script) }).catch(error => { cardSdkPromise = undefined throw error }) return cardSdkPromise } async function initCard() { const keeppay = await loadBtCardSdk() btCardInstance ||= await keeppay(btConfig.cardPublicKey, { elements: true, environment: btConfig.environment, useSameOriginApi: true }) cardElement = btCardInstance.createElement('card', { style: { fonts: [], base: { color: '#111827', fontSize: '16px' } } }) cardElement.mount('#bt-card') } async function getCardTokenId() { if (!btCardInstance || !cardElement) { throw new Error('Card 组件尚未初始化') } const token = await btCardInstance.tokens.create({ type: 'card', data: cardElement }) if (!token?.id) throw new Error('未获得 Card tokenId') return token.id } const tokenizeButton = document.querySelector('#card-tokenize-button') tokenizeButton.addEventListener('click', async () => { tokenizeButton.disabled = true try { const tokenId = await getCardTokenId() // 把 tokenId 发送到你的后端,作为 /api/payments 请求中的 token_id。 // 不要在控制台、埋点或错误日志中记录完整 tokenId / token 对象。 } catch (error) { console.error('Card Tokenization 失败:', error.message) } finally { tokenizeButton.disabled = false } }) function destroyCard() { cardElement?.unmount() cardElement = undefined } window.addEventListener('beforeunload', destroyCard) initCard().catch(error => console.error('Card 初始化失败:', error.message))
🔐
token.id 就是后端请求里的 token_id。Tokenization 只表示卡数据已被安全令牌化, 不代表付款成功;支付结果以后端调用 /api/payments 的响应为准。卡号、有效期和 CVC 始终保留在 Keeppay 托管 iframe 内。
后端 · cURL

以下示例直接调用测试环境。示例使用 Stripe 作为最终支付通道。

# 1) 首次支付(Stripe · CIT) curl https://payment-test.keeppay.net/api/payments \ -H "X-Keep-Sign: keep_uhmwb1t87…" \ -H "Authorization: Bearer sk_test_…" \ -H "Idempotency-Key: ord_first_250612_5521" \ -H "Content-Type: application/json" \ --data-raw '{ "merchant_id": "merchant_001", "token_id": "tok_1Nf8…a9K2", "payment": "card", "psp": "stripe", "action": "first_charge", "stripe_request_params": { "amount": 1990, "currency": "usd", "confirm": true, "setup_future_usage": "off_session", "payment_method_data": { "type": "card" }, "metadata": { "trade_no": "ord_first_250612_5521" }, "expand": [ "latest_charge" ] }, "antom_request_params": {} }'
# 2) 续费(Stripe · MIT,用户无需在场) curl https://payment-test.keeppay.net/api/payments \ -H "X-Keep-Sign: keep_uhmwb1t87…" \ -H "Authorization: Bearer sk_test_…" \ -H "Idempotency-Key: ord_renew_2026_07" \ -H "Content-Type: application/json" \ --data-raw '{ "merchant_id": "merchant_001", "token_id": "tok_1Nf8…a9K2", "payment": "card", "psp": "stripe", "action": "recurring_charge", "stripe_request_params": { "amount": 1990, "currency": "usd", "confirm": true, "off_session": true, "payment_method_data": { "type": "card" }, "automatic_payment_methods": { "enabled": true, "allow_redirects": "never" }, "metadata": { "trade_no": "ord_renew_2026_07" } }, "antom_request_params": {} }'
🧾
必填参数:merchant_idtoken_idpspaction,以及 stripe_request_params 中的 amountcurrencyconfirm。续费时还应传 off_session:truenetworkTransactionId;每笔业务订单使用唯一的 Idempotency-Key,同一笔请求重试时保持不变。
💾
首付成功后请保存:本次使用的长期 token_id、响应中的 network_transaction_id 和实际 psp。首付最终由 Stripe 或 Antom 完成均可继续续费。

核心概念

概念 说明
项目(应用) 你的一个 App,一一对应。每个项目 = 一套前端公钥 / 后端私钥 / 授权域名;令牌、通道、计费都按项目归集。
令牌 Token(token_id) 卡号令牌化后的引用。你只存令牌,明文在底座保险箱。
通道 Channel 能用 API 收卡的 PSP( Stripe / Antom…)。用令牌向通道中立转发发起扣款。通道由 KeepPay 协助接通(见能力边界)。
环境 测试 / 正式,数据互相隔离。

名词不熟?查 出海支付术语表(编排 / 令牌化 / 网络令牌 / 软硬拒付 / MIT·CIT / 3DS / BIN…)。

鉴权与密钥

密钥按项目隔离,分三类,权限各不相同:

类型 用途 权限 放哪
前端公钥 采集组件 / 前端会话 (只能创建令牌) 可公开,放前端
后端私钥 服务器调用 API keeppay分配,用于服务器端调用api 商户保管,只放服务器
PSP密钥 商户PSP端的密钥 商户用于请求PSP鉴权 商户保管

后端请求 header头中传入

X-Keep-Sign: keep_uhmwb1t87…
⚠️
后端私钥绝不下发到前端。前端只用 pk_ 公钥(即使暴露也安全,权限仅创建令牌)。
商户根据PSP要求,传入对应的鉴权参数。例如:stripe鉴权,是在header头中加入请求参数:Authorization: Bearer sk_test_…。keeppay不参与商户与PSP之间的鉴权参数配置。

① 前端采集组件

Card 采集组件是由 Keeppay 托管的嵌入式 iframe。你的页面只负责加载 SDK、使用 Public Application Key 初始化组件并指定挂载位置;卡号、有效期和 CVC 不会进入你的页面 DOM 或服务器。

HTML + JavaScript · 引用 Card 组件
<!-- 1. 准备 Card 组件挂载点 --> <div id="bt-card"></div> // 2. 配置浏览器端 Public Application Key 和 SDK 地址 const btConfig = { cardPublicKey: '<BT_CARD_PUBLIC_KEY>', environment: 'test', cardSdkUrl: 'https://js.keeppay.com/web-elements/2.15.0/index.js' } let cardSdkPromise let cardElement function loadKeeppayCardSdk() { if (typeof window.keeppay === 'function') { return Promise.resolve(window.keeppay) } if (cardSdkPromise) return cardSdkPromise cardSdkPromise = new Promise((resolve, reject) => { const script = document.createElement('script') script.src = btConfig.cardSdkUrl script.async = true script.onload = () => typeof window.keeppay === 'function' ? resolve(window.keeppay) : reject(new Error('Keeppay SDK 初始化失败')) script.onerror = () => reject(new Error('Keeppay SDK 加载失败')) document.head.appendChild(script) }).catch(error => { cardSdkPromise = undefined throw error }) return cardSdkPromise } // 3. 初始化并挂载托管的 Card iframe async function mountCardElement() { const keeppay = await loadKeeppayCardSdk() const instance = await keeppay(btConfig.cardPublicKey, { elements: true, environment: btConfig.environment, useSameOriginApi: true }) cardElement = instance.createElement('card', { style: { fonts: [], base: { color: '#111827', fontSize: '16px' } } }) cardElement.mount('#bt-card') } mountCardElement().catch(error => console.error('Card 组件加载失败:', error.message)) // 页面或弹窗销毁时卸载 iframe function unmountCardElement() { cardElement?.unmount() cardElement = undefined }
🌐
浏览器中只能使用 Public Application Key。获取 tokenId、提交后端并发起支付的完整流程请参见 快速开始;采集组件只在已配置的授权域名加载。

② 首次支付

前端取得 Card tokenId 后,由你的后端调用 KeepPay 完成首次支付。首次支付属于持卡人发起交易(CIT), 示例选择 Stripe,并通过 setup_future_usage:"off_session" 为后续离线续费建立使用条件。

POST/api/payments
curl https://payment-test.keeppay.net/api/payments \ -H "X-KEEP-SIGN: keep_uhmwb1t87…" \ -H "Authorization: Bearer sk_test_…" \ -H "Idempotency-Key: ord_first_250612_5521" \ -H "Content-Type: application/json" \ --data-raw '{ "merchant_id": "merchant_001", "token_id": "tok_1Nf8…a9K2", "payment": "card", "psp": "stripe", "action": "first_charge", "stripe_request_params": { "amount": 1990, "currency": "usd", "confirm": true, "setup_future_usage": "off_session", "payment_method_data": { "type": "card" }, "metadata": { "trade_no": "ord_first_250612_5521" }, "expand": [ "latest_charge" ] }, "antom_request_params": {} }'
🧾
请求要点:X-KEEP-SIGN 用于 KeepPay 鉴权;Stripe 示例还需要 Authorization: Bearer sk_test_…token_id 来自前端 Card Tokenization, action 固定为 first_charge。同一笔订单重试时保持相同的 Idempotency-Key
💾
首付成功后必须保存:长期 token_id、响应中的 network_transaction_id。无论首付由 Stripe 还是 Antom 完成, 这些数据都用于后续订阅续费;切换到 Antom 续费时必须提供首付 NTID(network_transaction_id)。

③ 令牌扣款与订阅续费

订阅到期后,使用首付保存的长期 token_id 发起离线续费(MIT),用户无需再次输入卡信息。 当前续费仍调用统一的 /api/payments,并通过 action:"recurring_charge" 标识续费。

POST/api/payments
curl https://payment-test.keeppay.net/api/payments \ -H "X-KEEP-SIGN: keep_uhmwb1t87…" \ -H "Authorization: Bearer sk_test_…" \ -H "Idempotency-Key: ord_renew_2026_07" \ -H "Content-Type: application/json" \ --data-raw '{ "merchant_id": "merchant_001", "token_id": "tok_1Nf8…a9K2", "payment": "card", "psp": "stripe", "action": "recurring_charge", "stripe_request_params": { "amount": 1990, "currency": "usd", "confirm": true, "off_session": true, "payment_method_data": { "type": "card" }, "automatic_payment_methods": { "enabled": true, "allow_redirects": "never" }, "metadata": { "trade_no": "ord_renew_2026_07" } }, "antom_request_params": {} }'

续费通道与首付通道

首付最终由 Stripe 或 Antom 完成都可以续费。本次请求的 psp 决定实际续费通道:设置为 stripe 时读取 stripe_request_params;设置为 antom 时读取 antom_request_params

⚠️
Stripe 续费需要 off_session:true。Antom 续费必须在 antom_request_params.paymentMethod.paymentMethodMetaData.networkTransactionId 中传入首付保存的 NTID。 每一期续费使用新的业务订单号和幂等键;同一期因网络问题重试时保持幂等键不变。

令牌管理

令牌查询和删除都必须在请求头中传入 X-KEEP-SIGN。接口只接收或返回 Token 标识及安全的掩码信息, 不会把卡号、有效期或 CVC 明文返回给你的服务器。

读取令牌

GET/api/token/info?token_id=xxx

token_id 是必填查询参数,填写前端 Tokenization 返回的 Token ID。

curl --get https://payment-test.keeppay.net/api/token/info \ -H "X-KEEP-SIGN: keep_uhmwb1t87…" \ --data-urlencode "token_id=tok_1Nf8…a9K2"

删除令牌

POST/api/token/delete

请求体中的 token_id 必填。删除成功后,该 Token 不能再用于首次支付或续费。

curl https://payment-test.keeppay.net/api/token/delete \ -H "X-KEEP-SIGN: keep_uhmwb1t87…" \ -H "Content-Type: application/json" \ --data-raw '{ "token_id": "tok_1Nf8…a9K2" }'
🗂️
删除操作不可通过 API 恢复。删除前请确认该 Token 不再关联有效订阅;删除后令牌不可继续扣款,相关审计记录仍可保留。

错误处理

所有 JSON 响应都会包含顶层字段 statuserror_code。支付接口仍会在 params 中原样返回 Stripe 或 Antom 的官方响应,客户端应先检查顶层状态,再处理 params 中的支付状态或 3DS 信息。

{
  "status": "error",
  "error_code": "payment_failed",
  "merchant_id": "merchant_001",
  "psp": "stripe",
  "params": {
    "error": {
      "message": "Your card was declined."
    }
  }
}
HTTP status error_code 含义
400 error 具体参数校验错误信息 请求参数校验失败
402 error payment_failed PSP 返回支付失败;具体原因保留在 params
403 error merchant_error / sign_error 商户号有误或者密钥验证失败
404 error not_found 接口或路由不存在
429 error rate_limited 触发 Worker 限流
500 ok server_error 内部异常

正常支付和需要进行 3DS 验证的支付都返回 HTTP 200status:"ok"error_code:""。需要 3DS 时,请继续读取 params 中 PSP 官方返回的 statusnext_action、跳转地址或验证表单。

上生产前必读

幂等键

/api/payments 请求带 Idempotency-Key 头(你生成的唯一串,建议使用订单号)。同一笔支付发生网络重试时保持相同 key, 返回首次结果。

curl https://payment-test.keeppay.net/api/payments \ -H "X-KEEP-SIGN: keep_uhmwb1t87…" \ -H "Authorization: Bearer sk_test_…" \ -H "Idempotency-Key: ord_250612_5521" \ -H "Content-Type: application/json" \ --data-raw '{ "merchant_id": "merchant_001", "token_id": "tok_1Nf8…a9K2", "payment": "card", "psp": "stripe", "action": "first_charge", "stripe_request_params": { "amount": 1990, "currency": "usd", "confirm": true, "setup_future_usage": "off_session", "payment_method_data": { "type": "card" }, "metadata": { "trade_no": "ord_250612_5521" } }, "antom_request_params": {} }'

限流

按应用限流;超限返回 429 + Retry-After 头,请指数退避重试。

超时与重试

支付请求超时后不要立即更换 PSP 重复扣款。先使用原 Idempotency-Key 重试或确认 PSP 最终状态,再决定是否切换通道。

API 参考

当前对外提供以下三个 API。点击端点可跳转到对应的参数或使用说明。

方法 端点 说明
POST /api/payments 首次支付与订阅续费
GET /api/token/info?token_id=xxx 查询令牌信息
POST /api/token/delete 删除令牌

POST /api/payments

统一的首次支付和续费接口。Keeppay读取 psp 后,只把对应的 PSP 原生参数对象发送到该通道: psp:"stripe" 使用 stripe_request_paramspsp:"antom" 使用 antom_request_params

请求头

参数 必填 说明
X-KEEP-SIGN KeepPay 商户请求签名。
Content-Type 固定为 application/json
Authorization Stripe 必填 Stripe 示例使用 Bearer sk_test_…;具体鉴权值由商户保管并提供。
Idempotency-Key 支付请求必填 每笔业务订单唯一;同一笔请求重试时保持不变。

公共请求参数

参数 类型 必填 说明
merchant_id string KeepPay 商户标识,用于加载商户配置。
token_id string 前端 Card Tokenization 返回的长期 Token ID。
payment string Card 场景固定传 card;未传时 Keeppay默认使用 card
psp string 本次实际支付通道:stripeantom
action string 首次支付传 first_charge;续费传 recurring_charge
stripe_request_params object Stripe 必填 Stripe PaymentIntent 原生参数对象,见下表。
antom_request_params object Antom 必填 Antom Pay 原生参数对象,见下表。

stripe_request_params

该对象按照 Stripe PaymentIntent 创建参数传递。完整字段、类型和约束以 Stripe Create a PaymentIntent 官方文档为准。

参数 类型 必填 说明
amount integer 最小货币单位金额,例如 1990 表示 19.90 USD。
currency string 三位小写货币代码,例如 usd
confirm boolean 示例固定为 true,创建后立即确认支付。
payment_method_data.type string Token 支付必填 Card Token 场景固定为 card;不要在该对象内提交卡号明文。
setup_future_usage string 首付续费场景必填 首付传 off_session,表示计划后续离线使用。
off_session boolean 续费必填 续费传 true,表示持卡人不在场。
automatic_payment_methods object 续费可设置 enabled:trueallow_redirects:"never"
metadata object 业务元数据,建议传唯一交易号 trade_no
receipt_email string Stripe 支付收据邮箱。
return_url string 按支付方式 仅在所选支付方式可能发生跳转时提供。

antom_request_params

该对象按照 Antom Pay 请求参数传递。完整字段、类型和地区差异以 Antom Pay 官方文档Antom Card/MIT 集成文档为准。

参数 类型 必填 说明
productCode string 当前 Card 支付固定使用 CASHIER_PAYMENT
paymentRequestId string 商户生成的唯一支付请求号,也是 Antom 侧幂等标识。
paymentAmount object {currency,value};币种使用大写,金额为最小货币单位字符串。
settlementStrategy object 按商户配置 结算币种对象,例如 {settlementCurrency:"USD"}
paymentMethod object paymentMethodType 使用 CARD,卡数据由 Token 注入。
paymentMethod.paymentMethodMetaData.isCardOnFile string 首付通常传 "false";续费传 "true"
paymentMethod.paymentMethodMetaData.tokenize string 首付按需 需要 Antom 保存后续使用信息时传 "true"
paymentMethod.paymentMethodMetaData.recurringType string 续费必填 定期订阅传 SCHEDULED,非固定周期可按官方规则使用 UNSCHEDULED
paymentMethod.paymentMethodMetaData.networkTransactionId string 续费必填 首付返回并保存的 NTID。
order object 包含 referenceOrderIdorderDescriptionorderAmount,可包含 buyer
paymentFactor object 当前 Card 授权请求使用 {isAuthorization:"true"}
env object 终端环境,例如 {terminalType:"WEB"},可按需传 clientIp
paymentRedirectUrl string 按支付流程 支付完成后的商户结果页地址。
paymentNotifyUrl string Antom 异步支付结果通知地址。

安全与合规1

明文卡号在用户浏览器侧就换成令牌,不经过你的前后端;你的系统、日志、数据库里只有令牌,没有明文 PAN

认证

认证 说明
PCI DSS Level 1 最高等级支付卡合规(合规底座)
SOC 2 Type II 安全 / 可用性控制审计
ISO 27001 信息安全管理体系
HIPAA 敏感数据处理合规

安全特性

  • 采集即令牌化 —— 卡号在采集点(浏览器侧)当场换令牌
  • 端到端加密 —— 传输与存储全程加密
  • 数据驻留 —— 按地区选择数据存放位置;企业版支持私有化部署
  • 访问控制 + 审计日志 —— 谁能看明文 / 用令牌扣款 / 只读,按角色与容器规则;全量操作可审计
  • PCI 范围收敛 —— 你的合规范围降到 SAQ A
🛡️
合规文档(PCI AOC / 责任分担矩阵 SRM / 数据处理协议 DPA)可在控制台「合规中心」下载,接 PSP 开通 S2S 时提交。

能力边界与计费

白标

KeepPay 是中立平台,对接任意能用 API 收卡的 PSP。文档与你的集成中只出现 KeepPay 命名。

保险箱阶段的边界(诚实)

  • 无交易语义:转发是无状态透传,KeepPay 不解析扣款成败、不记交易账本。成败 / 金额 / 拒付在通道响应 + 你的系统里。
  • 通道平台级:通道(转发管线)由 KeepPay 协助接通(提交 PSP 凭证 → 我们配置 + AOC + 出站 IP 白名单,通常几天),不通过项目自助创建。
  • 统一账本 / 智能路由 / 失败级联 / 智能重试 / AI 追回 / 报表KeepPay Flow即将上线 —— 卡已在你名下,升级零迁移。
  • 本地非卡支付(钱包 / 转账)取决于所接 PSP,部分属路线图。

计费三计量

计量 口径
令牌存储费 按保存时长(token-天),按项目归集;删除的按「保存 → 删除」计入
令牌使用费 按支付请求次数(每次 /api/payments 计一次)
KeepPay 开发者文档。