Skip to main content
Tokenization 与卡支付是主页面:token 是什么、如何绑卡、token 生命周期、卡的会话和 token 有效期、请求层面的 CIT 与 MIT,以及设置默认卡。请先读那一页。 本页是补充。它回答在基础卡集成之后会出现的问题:非卡的 token、tokenization 尝试被拒绝的业务原因、商户发起扣款的行为差异,以及能在卡支付发出之前就将其拦下的限制。

tokenType 永远是 CARD 吗?

不是。CARD 是大多数集成使用的值,也是公开规范唯一记录的值,但 tokenType 是一个通用字段:token 服务维护着一组类型,也涵盖非卡工具,尤其是钱包类 token,在必填字段和有效期上遵循不同的规则(均见下文)。 你应该据此做的:
  • 不要把 "CARD" 硬编码为你的代码唯一能产生或解析的值。从查询响应和通知中回读 tokenType,遇到不熟悉的值时存储它而不是失败。
  • **不要假设某种类型对你可用。**你的账户实际能 tokenize 哪些类型取决于你的支付方式配置。不支持的类型在生成时作为参数错误被拒绝。
  • 请询问你的 Waffo 集成联系人哪些类型适用于你拥有的支付方式,而不是去探测 API。

钱包类 token 需要什么卡 token 不需要的东西?

token 数据内的三个额外字段,全部必填: 缺任何一个,生成都会以 A0003 Parameter validation failed 失败。 orderId 要求是值得理解的结构性差异:卡可以独立 tokenize,而钱包绑定是用户在支付过程中授权的。所以钱包 token 是针对真实订单创建的,不是凭空创建的。实际上你是把它作为支付的一部分来请求,而不是作为单独的”添加支付方式”步骤。

钱包 token 能用多久?

所以钱包 token 没有卡有效期上限的等价物——没有需要遵守的底层有效期——并且到期落在日边界而不是月边界。 由此得出两点:
  • **不要用卡的逻辑推导钱包 token 的剩余寿命。**token 数据中没有 expiry 字段可供推理。
  • token 仍可能在计算出的到期之前很久就失效,因为用户在钱包侧解除了授权。这对你是不可见的,直到某次扣款失败。把 A0037(the user payment token is invalid or has expired)当作重新授权的提示来处理,而不是瞬时错误。

同一用户再次授权同一钱包,我拿到的是同一个 token 吗?

不一定——这是钱包 token 与卡差别最大的地方。钱包类 token 的复用键包括绑定发生时所在的订单,以及你的商户 ID(和你发送的子商户 ID,如有)、你的 merchantUserId、token 类型和支付方式。 因为新授权发生在新订单上,同一用户对同一钱包的第二次绑定通常会产生一个新的 tokenId。卡 token 的行为不同:其键基于凭据本身,所以重新绑定同一张卡返回现有 token(见卡 FAQ)。 设计后果:
  • 以 tokenId 为存储键,并预期用户会随时间积累多个钱包 token。
  • 向用户展示可管理的列表而不是不断堆积:暂停或解绑他们不再需要的(本页最后一节)。
  • 不要把存在一个 token 当作用户恰好只有一个钱包绑定的证明。

非卡 token 的查询和移除必须发什么?

两个接口都是条件形态,而不是单一的必填标识: 实际的解读:在这两个调用上 tokenType 不是装饰。如果你围绕卡构建了集成且不传 tokenType,增加第二种 token 类别会以看起来像”token 不存在”的方式破坏你的查找。 所有三个 token 接口还会断言 body 中的 merchantId 与你 API key 所签发的商户 ID 一致,不一致时以 A0003 拒绝,详情为 mid in params is inconsistent with apiKey。

为什么我的 tokenization 提交被拒绝?

除签名和字段形态问题外,卡提交被拒绝有三个业务原因。三者都在存储任何内容之前决定,对该输入都是永久性的——用相同数据重试会得到相同的失败。 两个让人意外的细节:
  • **校验位不是门槛。**未通过标准校验和的卡号会被记为警告并继续处理——权威答案来自发卡号段查找。所以不要期待校验和拒绝,但请保留你自己的客户端校验和检查,以便为用户尽早捕获输错。
  • 采集 SDK 接受 MM/YY 输入,但查询和通知始终返回 MM/YYYY。请存储四位年份形式。
你可能在支付时而非绑定时看到的相关错误码:A0029(card data invalid)、A0030(card issuing range not found)、A0032(payment token data invalid)、A0023(order cannot be created due to expired token)。

为什么持卡人姓名被拒绝?

因为它包含拉丁字母之外的字符。规则很严格:持卡人姓名必须只包含英文字母。带重音字符或非拉丁文字的姓名会以失败原因 INVALID_CARDHOLDER_NAME 和消息 Cardholder name must contain only English letters 被拒绝,在 API 上表现为 A0029。 如何妥善处理:
  • **在采集时转写。**要求填写”卡面印刷的姓名”;卡是以拉丁字符压印的,所以正确的值几乎总是存在。
  • 在你自己的表单中校验并给出清晰提示,而不是让用户在支付时才发现。
  • 记住姓名是卡 token 复用键的一部分:同一张卡以不同拼写的姓名提交会被视为不同凭据,并获得自己的 token。

MIT 扣款与 CIT 扣款有什么不同?

请求层面的差异(merchantInitiatedMode,以及对已可用凭据的 A0045 要求)在卡 tokenization 页面上。行为上的差异同样重要: 需要记住的一句话:**MIT 扣款没有用户界面也没有用户。**你的流程依赖收银台页面做的任何事——跳转、同意步骤、用户填写的字段——在这里都不存在。

我的 MIT 扣款失败了,直接重试就行吗?

不要盲目重试,有三个独立的原因:
  1. **没有用户侧的重试。**CIT 失败可以以用户在收银台页面选择其他方式收尾。MIT 失败就是结束。
  2. 有些失败对该凭据是永久的。A0045(存储的凭据不处于可用状态)和 A0037(token 无效或已过期)重试不会变成功。解决方法是一次新的客户发起授权。
  3. 重试本身受卡组织规则限速——见下一个问题。
有效的模式:对失败分类。凭据问题意味着”请用户重新授权”。真正的瞬时失败意味着”稍后按计划重试”。永远不要在紧循环中重试,也永远不要把 MIT 拒绝当作用户无需被告知就能修复的事情。 对于订阅,不要自己构建——Waffo 自己的重试排程和备用支付方式处理已经做了,见订阅生命周期与支付方式管理。

为什么卡支付在还没尝试之前就被拒绝了?

因为卡组织对同一笔卡支付可重试的次数施加合规限制,Waffo 在本地强制执行。当一次尝试会突破限制时,它会被系统拒绝而不是转发——这就是为什么失败可能比正常拒绝返回得更快,以及为什么卡账单上什么都不出现。 关于计数你需要知道的:
  • 它按卡组织计数,并再按卡组织自身对商户的分类分别计数。同一订单上来自不同卡组织的两张卡不合并计算。
  • Waffo 不公布阈值,你也不应尝试反推:它们由卡组织设定,可能在你账户没有任何变化的情况下改变。
  • 该失败表现为失败原因 CARD_PAYMENT_RETRY_LIMIT,在 API 上映射为 C0005。
正确的处理是行为上的,不是数字上的:
  • **永远不要立即自动重试被拒绝的卡支付。**它消耗额度却没有得到不同答案的机会。
  • 在两次尝试之间改变点什么——不同的支付方式、重新输入的卡、更晚的排期——或者询问用户。
  • 如果你经常撞到这个限制,问题在上游:对不会授权的卡重试太多了。

我的用户能自己管理已保存的支付方式吗?

能,通过一个接口而不是通过创建订单:POST /api/v1/saved-payment-methods/config,接收你的 merchantId、merchantUserId、已保存方式的 token,以及一个 action: 配合 /api/v1/tokenization/remove(完全解绑),足以构建完整的”管理我的支付方式”页面:列出、设默认、隐藏、删除。每项操作对收银台排序的影响见卡 tokenization 页面。 设计页面之前值得了解的两个边界:
  • 该接口位于 API key 的 payin scope 内,所以它是后端调用——永远不要直接暴露给浏览器或移动客户端。
  • 移除是最终的:已移除的 token 被排除在查找之外,且永不重新签发。暂停是看起来可逆的选项;请把移除当作永久的。

这些问题分别属于哪一页?

仍不确定某次具体绑定或扣款为何被拒?请联系支持,附上 tokenRequestId 或订单 ID——失败原因同时记录在两者上。