PAYATHON 2026

将信用卡绑定至用户账户时验证持卡人的最佳实践

支付阿杰

明确结论

随机小额扣款可以作为补充验证手段,但不适合充当主要防线。用户能查看银行卡交易记录,只能说明其可以访问账单渠道,无法可靠证明其是合法持卡人。随机扣款的确认速度较慢,体验也不好,还可能带来短信提醒、退款延迟、汇率差异以及银行风控拦截等问题。

更稳妥的方案是:

  1. 绑定银行卡时,由收单机构执行一次卡片验证,并优先触发 EMV 3-D Secure(3DS)。
  2. 由支付服务商保存卡片并返回支付令牌,业务系统不保存完整卡号或 CVV。
  3. 综合 AVS、CVV 校验、设备信息、账户历史、IP 和交易行为等信号进行风险评分。
  4. 对高风险绑定增加验证步骤,例如重新执行 3DS、核验账户中已有的身份信息,或转入人工审核。
  5. 后续每次付款仍要进行动态风控。银行卡绑定成功,不代表之后的所有交易都可信。

需要说明的是,在纯线上场景中,仅凭卡号、有效期和 CVV,没有一种通用方法可以绝对证明操作者就是持卡人。系统能做的是让发卡行参与认证,再通过多层风控将盗卡成功率控制在可接受范围内。

首选方案:使用 3-D Secure 验证

3DS 是常见的持卡人认证机制之一。认证时,发卡行可能通过银行 App、生物识别、一次性验证码或其他方式确认用户身份。对于低风险请求,发卡行也可能采用无感认证,不要求用户额外操作。

绑定卡片时,应使用支付服务商提供的”保存支付方式”或”零金额/小额授权”流程,并声明该卡将用于后续交易。支付服务商通常会结合当地法规、发卡行要求和风险判断,决定是否触发 3DS。

典型流程如下:

  1. 服务端创建银行卡绑定会话。
  2. 客户端使用支付服务商提供的组件采集卡片信息。
  3. 支付服务商验证卡片,必要时展示 3DS 页面。
  4. 服务端查询支付服务商的状态,或验证 Webhook,以确认绑定结果。
  5. 只保存支付令牌、卡组织、尾号和有效期等非敏感信息。
  6. 在绑定记录中保存验证结果、认证时间和风险信息。

不要看到客户端返回”成功”页面就更新绑定状态。最终结果应以服务端查询结果或经过签名验证的 Webhook 为准。

零金额授权与小额预授权

如果发卡行和支付服务商支持,可以通过零金额授权检查卡片是否有效。无法执行零金额授权时,也可以发起固定金额的小额预授权,然后立即撤销。

这类授权通常可以检查:

  • 卡号是否有效;
  • 卡片是否过期;
  • 发卡行是否允许这类交易;
  • CVV、账单地址等附加数据是否匹配;
  • 卡片能否用于后续支付。

不过,授权成功不代表持卡人身份已经得到确认。盗卡者如果掌握完整的卡片资料,也可能通过普通授权。因此,预授权最好与 3DS 和风险控制配合使用,不应单独作为身份验证手段。

如果使用随机金额确认,还要考虑以下问题:

  • 部分银行显示待入账交易的速度较慢;
  • 用户可能收到真实扣款提醒并发起投诉;
  • 跨币种交易可能因汇率或附加费用出现金额差异;
  • 某些账单只显示结算金额,不显示原始授权金额;
  • 如果攻击者同时控制了银行账户、短信或电子邮箱,仍然可以完成验证;
  • 大量小额授权可能被发卡行识别为卡片测试攻击;
  • 撤销授权后,额度不一定会立即释放。

因此,随机金额验证更适合用作特定业务场景下的备用方案,而不是默认流程。

AVS 和 CVV 只能作为风险信号

CVV

CVV 校验只能说明用户知道卡片上的安全码,不能证明其是合法持卡人。题目中的攻击者已经掌握 CVV,因此不能把 CVV 匹配当作所有权证明。

授权完成后不应保存 CVV。即使进行加密,长期存储 CVV 通常也不符合支付卡行业的数据安全要求。

AVS

对于支持地址验证服务(AVS)的国家或发卡行,可以将用户提交的账单地址、邮政编码与发卡行记录进行比较。

AVS 结果更适合作为风控信号,不宜简单地根据”匹配或不匹配”决定是否允许绑定,原因包括:

  • 并非所有国家和发卡行都支持 AVS;
  • 用户搬家后可能还没有更新银行地址;
  • 地址格式和字符集的差异可能造成误判;
  • 被盗资料也可能包含账单地址。

比较实用的处理方式是,在 CVV 或 AVS 不匹配时提高风险分数,再结合其他信号决定拒绝绑定、触发 3DS,还是转入人工审核。

建立绑定阶段的风险控制

有效的方案通常不是再增加一个验证问题,而是为”添加银行卡”建立独立的风险模型。

可以关注以下信号:

  • 新注册账户立即添加多张银行卡;
  • 同一设备、IP 或账户连续尝试不同卡号;
  • 同一张卡被绑定到多个无关联账户;
  • 发卡国家、IP 地区和手机号地区明显不一致;
  • 使用代理、匿名网络、数据中心 IP 或异常设备环境;
  • 账户刚修改密码、邮箱或手机号,随后立即添加银行卡;
  • CVV、有效期或 3DS 多次验证失败;
  • 卡片 BIN 对应的国家、类型或卡组织不符合业务场景;
  • 用户历史交易、退款或拒付表现异常;
  • 短时间内出现大量小额支付或支付失败。

绑定接口应同时在账户、设备、IP 和卡片维度实施限速。只限制单个账户很容易被绕过,因为攻击者可以批量注册新账户。

客户端也不应收到过于具体的拒绝原因。与其分别提示”CVV 正确但邮编错误”或”有效期错误”,不如统一显示”无法验证该支付方式”。详细原因可以记录在内部日志中,避免接口被用作卡片信息测试工具。

分级处理比一律拒绝更可靠

可以根据风险等级采取不同措施:

风险等级建议处理
低风险完成服务商验证后允许绑定
中风险强制执行 3DS,限制首次付款金额或功能
高风险拒绝绑定、设置一段冷却期,或转人工审核
已确认攻击封禁相关设备、账户和支付令牌,并保留审计记录

对于市场平台、数字商品、礼品卡、钱包充值和提现等欺诈风险较高的场景,还可以采取以下措施:

  • 为新绑定卡片设置冷却期;
  • 限制首次交易金额;
  • 暂缓发货、提现或价值转移;
  • 首次付款成功后逐步提高额度;
  • 更换卡片后重新认证;
  • 高价值交易再次执行 3DS。

与单纯确认一笔随机金额相比,这些限制通常更能减少实际损失。

推荐的服务端状态设计

以下 TypeScript 示例说明一种不依赖特定支付服务商的状态控制思路。PaymentProvider 是业务自己的适配层,不是任何支付平台的真实 API。

type CardBindingStatus =
  | "pending"
  | "requires_action"
  | "verified"
  | "rejected";

interface PaymentProvider {
  createCardVerification(input: {
    customerToken: string;
    requireCardholderAuthentication: boolean;
    futureUsage: "off_session";
  }): Promise<{
    verificationId: string;
    clientToken?: string;
    status: "pending" | "requires_action" | "succeeded" | "failed";
  }>;

  getCardVerification(
    verificationId: string
  ): Promise<{
    status: "pending" | "requires_action" | "succeeded" | "failed";
    paymentMethodToken?: string;
    authenticationResult?: "successful" | "failed" | "unavailable";
    avsResult?: string;
    cvvResult?: string;
  }>;
}

创建绑定请求前,先执行限速和风险检查:

async function beginCardBinding(
  userId: string,
  provider: PaymentProvider
) {
  await enforceBindingRateLimits({
    userId,
    ipAddress: requestIp(),
    deviceId: requestDeviceId()
  });

  const risk = await evaluateBindingRisk({
    userId,
    ipAddress: requestIp(),
    deviceId: requestDeviceId()
  });

  if (risk.decision === "reject") {
    throw new Error("PAYMENT_METHOD_VERIFICATION_FAILED");
  }

  const verification = await provider.createCardVerification({
    customerToken: await getProviderCustomerToken(userId),
    requireCardholderAuthentication:
      risk.decision === "step_up" || risk.decision === "review",
    futureUsage: "off_session"
  });

  await saveBindingAttempt({
    userId,
    verificationId: verification.verificationId,
    status: mapProviderStatus(verification.status),
    riskScore: risk.score
  });

  return {
    bindingId: verification.verificationId,
    status: verification.status,
    clientToken: verification.clientToken
  };
}

确认绑定时,必须重新向支付服务商获取状态,不能相信客户端提交的认证结果:

async function finalizeCardBinding(
  userId: string,
  verificationId: string,
  provider: PaymentProvider
) {
  const attempt = await findBindingAttempt(verificationId);

  if (!attempt || attempt.userId !== userId) {
    throw new Error("BINDING_NOT_FOUND");
  }

  const result =
    await provider.getCardVerification(verificationId);

  if (
    result.status !== "succeeded" ||
    !result.paymentMethodToken
  ) {
    await updateBindingStatus(verificationId, "rejected");
    throw new Error("PAYMENT_METHOD_VERIFICATION_FAILED");
  }

  await savePaymentMethod({
    userId,
    providerToken: result.paymentMethodToken,
    verificationId,
    authenticationResult: result.authenticationResult,
    avsResult: result.avsResult,
    cvvResult: result.cvvResult
  });

  await updateBindingStatus(verificationId, "verified");
}

实际接入时,应使用支付服务商的官方 SDK 和真实字段,并按照服务商文档处理异步状态、3DS 跳转、授权撤销、幂等键和 Webhook 签名。各服务商的 API 名称和状态模型不同,不能把示例中的接口名称直接当作真实参数使用。

Webhook 和并发安全

支付认证可能异步完成,因此绑定接口需要正确处理重复通知和乱序事件。

至少需要做到:

  • 验证 Webhook 签名;
  • 使用支付服务商的事件 ID 去重;
  • 通过事务或条件更新修改状态;
  • 防止旧事件把最终成功状态改回处理中;
  • 确保绑定记录关联当前用户和服务商客户标识;
  • 为同一支付令牌的重复绑定制定明确规则;
  • 不在日志中记录完整卡号、CVV、3DS 验证值或客户端密钥。

状态转换可以限制为:

pending -> requires_action
pending -> verified
pending -> rejected
requires_action -> verified
requires_action -> rejected

状态一旦进入 verified 或 rejected,除非支付服务商明确提供可逆事件,否则普通的重复通知不应覆盖该状态。

不要自行保存卡片敏感信息

应优先使用支付服务商托管的输入组件、客户端令牌化方案或托管支付页面,避免完整卡号和 CVV 经过业务服务器。

业务数据库通常只需保存:

  • 支付服务商生成的支付方式令牌;
  • 卡片品牌;
  • 卡号末四位;
  • 有效期;
  • 发卡国家等非敏感展示信息;
  • 绑定时间和认证结果;
  • 风险决策及审计信息。

即使采用第三方令牌化,也不能直接认定业务完全不受 PCI DSS 约束。具体合规范围取决于支付流程、页面集成方式、系统架构和服务商安排,应向支付服务商或合规人员确认。

后续付款仍需重新判断风险

一次成功的绑定认证不能永久证明账户与卡片之间的关系。账户可能被接管,支付令牌可能遭到滥用,发卡行也可能在绑定后将卡片标记为遗失或被盗。

后续交易仍需:

  • 使用每笔交易的风控结果;
  • 在适用时提交设备、账单地址和客户信息;
  • 正确区分用户在场交易与用户不在场交易;
  • 保存首次绑定时用户对后续扣款的授权证据;
  • 对高金额、异常地点或行为突变执行再次认证;
  • 处理发卡行拒绝、争议、退款和拒付通知;
  • 允许用户删除支付方式,并及时停止后续使用。

实际落地建议

一个合理的默认组合是:

  1. 使用支付服务商提供的卡片保存和令牌化功能。
  2. 绑定时进行零金额或小额授权检查。
  3. 优先启用 3DS,并对中高风险请求强制认证。
  4. 将 CVV 和 AVS 结果作为风险信号,不把它们当作所有权证明。
  5. 对账户、设备、IP 和卡片尝试实施联合限速。
  6. 对新账户、新设备和高风险业务设置冷却期与金额限制。
  7. 通过服务端查询或签名 Webhook 确认最终状态。
  8. 持续监控拒付率、误拒率和攻击模式,并据此调整规则。

如果只能先做一项改进,应优先把绑定流程接入支持 3DS 的支付服务商验证能力。完整方案则应采用”3DS + 令牌化 + 风险评分 + 限速 + 后续交易监控”的分层防护。

备注:内容仅供参考。