巴西订阅服务如何选择 recurrent billing 支付网关?
结论
不要用小型刷卡机配合自行保存信用卡号的方式处理订阅扣款。刷卡机适合客户在场的一次性交易,不适合无人值守的每月续费。自行保存卡片数据还会大幅扩大 PCI DSS 合规范围。一旦数据泄露,造成的损失往往远高于接入支付服务商的成本。
更合适的做法是选择具备以下能力的巴西本地或跨境支付服务商(PSP):
- 支持
recurring payment或card-on-file; - 支持卡片 tokenization,由服务商保存完整卡号;
- 可以在后台手动扣款,或通过 API 发起
merchant-initiated transaction; - 支持 Pix、boleto 等本地支付方式;
- 提供 webhook、失败重试、退款和拒付管理;
- 符合 PCI DSS,并能明确说明数据存储地点和责任边界。
你的系统只需保存支付服务商返回的 token、客户标识和卡片摘要,例如卡片品牌、末四位和有效期。不要保存完整卡号或 CVV。
可行的支付处理方式
1. 托管式支付页面加 tokenization
小型订阅业务通常可以优先考虑这种方式。
客户首次付款时进入服务商托管的结账页,或者在服务商提供的安全组件中填写卡片信息。卡号会直接提交给服务商,不经过你的服务器。服务商随后返回一个不能直接用于消费的 token。
后续操作分为两类:
- 每月自动续费:由服务商按照订阅计划自动扣款,或者由你的系统定期调用 API。
- 临时手动收费:员工在后台选择客户和金额,后端再使用 token 发起扣款。
这种方式既支持自动续费,也保留了人工收费能力,同时不需要员工查看或保存完整卡号。
选择供应商时,不能只问对方是否支持订阅,还应确认:
- 是否支持巴西发行的信用卡;
- 是否支持 BRL 结算;
- 是否支持巴西常见的分期付款;
- 保存支付凭证后,是否允许发起金额不固定的扣款;
- 是否区分
customer-initiated transaction和merchant-initiated transaction; - 是否支持失败重试、换卡和过期卡更新;
- 是否可以通过控制台完成手动收费;
- 资金结算周期,以及退款费、拒付费和跨境费用;
- 是否能提供 PCI DSS 合规证明;
- 是否支持你的公司类型和开户注册材料。
有些服务商虽然提供 token,但只允许固定周期、固定金额的续费,未必支持任意金额的临时扣款。应要求对方针对你的具体业务场景作出书面确认。
2. 由你的系统管理订阅,PSP 只负责扣款
如果收费周期或金额比较复杂,可以在自己的数据库中维护订阅状态,并在账单到期时调用 PSP API。
典型流程如下:
客户填写卡片
↓
PSP 托管页面或安全组件
↓
PSP 返回 payment_method_token
↓
你的系统保存 token 和客户授权记录
↓
定时任务创建每月扣款
↓
webhook 更新支付结果
下面是示意代码。API 路径和字段只是通用结构,实际名称应以所选服务商的文档为准:
const payment = await paymentProvider.createPayment({
amount: 9900,
currency: "BRL",
customer_id: customer.providerCustomerId,
payment_method_token: customer.paymentMethodToken,
off_session: true,
idempotency_key: `invoice-${invoice.id}`,
metadata: {
invoice_id: invoice.id
}
});
金额通常使用最小货币单位,例如 9900 表示 R$ 99,00,但仍要确认具体 API 对金额格式的要求。
这种方案更灵活,但以下工作需要由你负责:
- 生成账单;
- 做好幂等控制;
- 制定重试策略;
- 验证 webhook 签名;
- 管理欠费状态;
- 通知客户;
- 处理取消订阅和退款。
3. 使用 PSP 自带的订阅管理
如果套餐数量不多,价格也固定,可以把计划、账单周期和自动重试交给 PSP 管理,你的系统只负责同步订阅状态。
这种方式开发成本较低,但需要留意供应商锁定。迁移服务商时,卡片 token 通常不能直接转移,除非双方支持合规的支付凭证迁移。
4. 组合使用 Pix、boleto 和银行卡
巴西用户可能更习惯 Pix 或 boleto,但这两种方式未必能直接取代信用卡自动续费。
可以这样安排:
- 信用卡默认用于自动续费;
- Pix 用于首次付款、补缴失败账单或客户主动续费;
- boleto 作为不方便使用信用卡时的备用方式;
- 如果 PSP 支持自动周期扣款类 Pix 产品,再评估它是否适合订阅业务。
Pix 自动扣款能力、银行覆盖范围和 PSP 接口可能随时间变化。接入前要确认当前支持范围、客户授权流程、撤销机制和失败处理方式。
5. 人工发送付款链接
临时收费并不意味着员工必须手工录入卡号。更安全的做法是从后台生成一次性付款链接,让客户自行填写支付信息。
这种方式适合:
- 收取额外服务费;
- 补缴欠费;
- 购买订阅之外的一次性项目;
- 原卡失效后重新绑定支付方式。
如果业务量不大,自动订阅配合付款链接通常已经能满足大多数需求。
为什么不建议使用小型刷卡机
刷卡机并非完全不能使用,但不适合充当订阅系统的核心。
交易场景不匹配
实体刷卡通常属于 card-present 交易,需要客户本人和实体卡在场。每月续费则属于客户不在场的 card-not-present 交易或凭证在档交易。
如果员工每月把保存的卡号手工输入终端,可能遇到以下问题:
- 终端或收单合同不允许这样使用;
- 申报的交易类型与实际场景不符;
- 风控系统更容易拒绝交易;
- 发生拒付时,很难证明客户已经授权;
- 无法可靠处理换卡、卡片过期和失败重试;
- 人工录入可能输错金额或造成重复扣款;
- 员工可以接触完整卡号,内部泄露风险很高。
把客户卡号抄在纸上,再拿到刷卡机输入,并不能绕过 PCI DSS。只要企业接收、保存或处理这些数据,就已经承担了相应责任。
成本未必更低
刷卡机看似没有开发成本,但还会产生不少隐性成本:
- 每月人工操作;
- 对账和补扣;
- 处理错误收费;
- 处理拒付与退款;
- 保护敏感数据;
- 管理员工权限;
- 响应安全事故;
- 接受合规审计。
如果暂时找不到合适的自动扣款服务商,宁可每月向客户发送付款链接,也不要长期保存卡号并由员工手工录入。
正确的“手动收费”设计
手动收费应当是“由员工决定是否发起交易”,而不是“由员工查看并输入卡号”。
后台只需展示以下信息:
Customer: Maria Silva
Card: Visa •••• 4242
Expires: 08/28
Outstanding balance: R$ 149,00
员工点击收费按钮后,浏览器将客户 ID、账单 ID 和金额发送给后端:
POST /admin/invoices/inv_2026_0081/charge
Content-Type: application/json
{
"amount": 14900
}
后端先检查权限和账单状态,然后使用已保存的 token 调用 PSP:
app.post("/admin/invoices/:invoiceId/charge", requireFinanceRole, async (req, res) => {
const invoice = await invoices.findById(req.params.invoiceId);
if (!invoice || invoice.status !== "open") {
return res.status(409).json({ error: "Invoice is not chargeable" });
}
const payment = await paymentProvider.createPayment({
amount: req.body.amount,
currency: "BRL",
customer_id: invoice.providerCustomerId,
payment_method_token: invoice.paymentMethodToken,
off_session: true,
idempotency_key: `manual-charge-${invoice.id}-${req.body.amount}`
});
await auditLog.record({
action: "manual_charge_requested",
invoice_id: invoice.id,
operator_id: req.user.id,
provider_payment_id: payment.id
});
res.status(202).json({
payment_id: payment.id,
status: payment.status
});
});
这个流程至少要有三层保护:
- 只有获得授权的员工才能发起收费;
- 每次操作都要写入不能随意修改的审计日志;
- 使用
idempotency_key,防止重复提交导致重复扣款。
支付是否成功,应以验签后的 webhook 或服务端查询结果为准,不能只看浏览器的跳转结果。
客户授权不可省略
保存 token 不等于可以任意扣款。客户首次绑定卡片时,应取得清晰授权,并说明:
- 收费金额或金额的计算方式;
- 收费周期;
- 首次扣款和后续扣款的时间;
- 哪些额外项目可能单独收费;
- 如何取消;
- 退款规则;
- 失败后是否重试,以及重试频率;
- 如何更新支付方式。
对于金额不固定的临时费用,最好在收费前单独取得客户确认,或者在合同中写明可收费项目、计价方式和上限。保存授权时间、授权文本版本、IP、客户操作记录和相关账单,有助于降低争议与拒付风险。
如果确实必须接触卡片信息
原则很简单:能不保存就不保存,能少接触就少接触。
至少要遵守当前适用的 PCI DSS 要求,并请合格的支付安全顾问或收单机构确认合规范围。PCI DSS 的具体版本、商户等级和验证方式会发生变化,不能只凭一份通用清单判断自己已经合规。
永远不要保存 CVV
交易授权完成后,无论是否加密,都不应保存以下信息:
- CVV、CVC、CID 等卡片验证码;
- 完整的磁条数据;
- 芯片中的敏感认证数据;
- PIN 或 PIN block。
这些数据并不是加密后就可以保存。
限制保存内容
业务数据库通常只需要保存:
{
"provider_customer_id": "cus_xxxxx",
"payment_method_token": "pm_xxxxx",
"card_brand": "visa",
"last4": "4242",
"expiry_month": 8,
"expiry_year": 2028
}
不要把完整卡号写入以下位置:
- 应用数据库;
- 日志;
- 客服工单;
- 分析平台;
- 错误追踪系统;
- 邮件或聊天工具;
- 数据库备份;
- 屏幕截图。
日志过滤也很重要。任何疑似卡号的字段,都应在进入日志系统前删除或遮蔽:
function redactPaymentData(payload) {
const sanitized = structuredClone(payload);
delete sanitized.card_number;
delete sanitized.cvv;
delete sanitized.cvc;
return sanitized;
}
字段过滤只能作为辅助措施,不能代替“不让卡号进入应用服务器”的架构设计。
如果业务迫使你保存 PAN
PAN 是完整的 Primary Account Number。一旦保存 PAN,系统就会进入更严格的 PCI DSS 合规范围,通常至少需要:
- 使用强加密,并妥善隔离密钥;
- 定期轮换密钥,并使用独立的密钥管理系统;
- 实行最小权限和基于角色的访问控制;
- 启用多因素认证;
- 进行网络分段;
- 保留完整的访问审计记录;
- 持续进行漏洞管理和安全更新;
- 定期扫描、渗透测试和合规验证;
- 规定数据保留期限,并建立安全删除机制;
- 制定事件响应方案;
- 对员工、供应商和备份系统采取同等控制。
不要自行设计卡片数据加密协议,也不要把加密密钥和密文放在同一个数据库中。对小型公司来说,建立并长期维护这些控制通常并不划算。
还要考虑 LGPD
卡片 token、账单、姓名、CPF、邮箱和交易记录通常都与个人有关,因此还要按照巴西《通用数据保护法》LGPD 管理这些数据。
至少应明确:
- 收集每项数据的目的和法律依据;
- PSP、收单机构和你的公司分别承担什么数据处理角色;
- 数据保存多久;
- 哪些供应商可以访问数据;
- 是否涉及跨境传输;
- 如何响应访问、更正和删除等请求;
- 如何处理数据泄露事件。
PCI DSS 主要处理支付卡数据安全问题,并不意味着已经符合 LGPD。满足 LGPD 也不等于符合 PCI DSS。
推荐的落地步骤
第一步:明确需求范围
先把支付场景整理成清单:
- 金额是固定还是浮动;
- 扣款日是每月固定日期还是注册周年日;
- 是否需要试用期;
- 是否需要分期;
- 是否需要收取额外的临时费用;
- 是否接受 Pix 和 boleto;
- 每月交易量和平均客单价;
- 可以接受的结算周期和失败率。
第二步:向候选 PSP 提交同一份问题表
重点确认 recurring billing、tokenization、后台收费、webhook、退款、拒付、BRL 结算和巴西卡支持情况。不要只看营销页面,还要核对合同和技术文档。
第三步:优先使用托管页面
尽量让客户的浏览器直接与 PSP 交互。服务器不接收 PAN 和 CVV,可以大幅缩小 PCI DSS 合规范围。不过,企业通常仍需完成相应的商户合规验证,并非完全免除责任。
第四步:实现账单状态机
账单至少应区分以下状态:
draft
open
processing
paid
failed
past_due
cancelled
refunded
不要把一次 API 超时直接判定为付款失败。遇到网络超时,应使用相同的幂等键查询或重试,以免重复扣款。
第五步:处理扣款失败
建议设置有限次数的重试,不要无限扣款。例如:
首次扣款失败
↓
通知客户并提示更新支付方式
↓
按预先公布的策略重试
↓
仍失败则暂停服务或转为待补缴
具体的重试间隔应结合 PSP 建议、发卡行反馈和客户协议确定。对于明确拒绝或疑似欺诈的交易,不应盲目重试。
第六步:准备替代付款路径
自动扣款失败后,可以向客户发送新的托管付款链接,或者提供 Pix、boleto 等补缴方式。不要要求客户通过邮件、聊天或电话发送完整卡号和 CVV。
最终建议
对小型订阅公司来说,比较稳妥的组合通常是:
- 使用 PSP 托管结账页或安全卡片组件;
- 由 PSP 保存卡片并返回 token;
- 正常月费通过自动订阅收取;
- 临时费用由后台通过 token 发起,并经过适当授权;
- 使用付款链接、Pix 或 boleto 处理补缴;
- 自己只保存 token、账单状态、授权证据和卡片摘要。
如果现有供应商不能同时满足自动续费和临时收费,可以采用“订阅 PSP + 托管付款链接”的组合,不要退回到自行保存卡号并由员工录入刷卡机的做法。只有当业务规模足以承担专业安全团队、审计和持续的 PCI DSS 合规成本时,才值得考虑自行保存完整卡号。
备注:内容仅供参考。
