如何在不使用 payment gateway 的情况下处理信用卡付款?
明确结论
从技术上说,可以不使用独立的 payment gateway 或第三方托管结账页面,但无法绕过收单银行、支付处理机构和银行卡组织。信用卡交易也不可能完全免除手续费。
如果自行接收卡号并直接对接收单机构,就要负责支付授权、PCI DSS 合规、数据加密、退款、拒付和欺诈控制等事项。对多数网站来说,由此产生的成本和风险通常远高于 gateway 的服务费。
如果主要目的是减少手续费,更实际的选择是使用银行转账等非信用卡付款方式,而不是自行处理信用卡数据。
为什么不能直接向信用卡”收款”
信用卡在线支付通常涉及以下角色:
- Payment gateway:安全接收支付信息,并将交易请求传给处理机构。
- Payment processor:负责授权、清算和结算。
- Acquiring bank:为商户提供收单服务和商户账户。
- Card network:例如 Visa、Mastercard、American Express。
- Issuing bank:向持卡人发卡,并决定是否批准交易。
即使不使用独立的 gateway,交易仍要经过收单机构、处理机构和银行卡组织,交换费、网络费、处理费和拒付费等成本也不会消失。
只有大型商户才可能直接与收单机构或处理机构集成。这里的”直接处理”通常是将 gateway 的部分功能放入自己的系统,并不意味着绕过信用卡支付网络。
可行的处理方式
方案一:直接对接收单机构或处理机构
需要先向银行或支付处理机构申请:
- 互联网商户账户。
- Card-not-present 交易权限。
- 处理接口或 API 凭证。
- 清算、退款和拒付处理能力。
- PCI DSS 合规评估。
典型流程如下:
- 浏览器收集信用卡信息。
- 支付信息通过 TLS 加密传输。
- 服务端或受托支付组件创建交易请求。
- 处理机构将请求发送到银行卡网络和发卡行。
- 发卡行返回批准或拒绝结果。
- 商户捕获交易,资金随后进入结算流程。
这种方式可能省下一部分独立 gateway 费用,但通常还会产生开户费、接口费、处理费和合规成本,也可能有最低交易量要求。具体费用取决于收单合同,不能默认这种方式更便宜。
方案二:使用令牌化 API 或 Hosted Fields
如果只是希望保留自定义结账界面,通常没必要让服务器直接接触卡号。可以使用处理机构提供的 Hosted Fields、支付组件或 tokenization API,将信用卡信息直接发送给支付服务商,应用只接收一次性 token。
下面是通用示意代码,其中的 API 名称需要替换为实际处理机构提供的接口:
<form id="payment-form">
<div id="hosted-card-number"></div>
<div id="hosted-expiry"></div>
<div id="hosted-cvc"></div>
<button type="submit">支付</button>
</form>
<script>
const fields = paymentProvider.createHostedFields({
cardNumber: "#hosted-card-number",
expiry: "#hosted-expiry",
cvc: "#hosted-cvc"
});
document
.getElementById("payment-form")
.addEventListener("submit", async (event) => {
event.preventDefault();
const { token } = await fields.tokenize();
const response = await fetch("/api/payments", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
paymentToken: token,
amount: 4990,
currency: "CNY"
})
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.message || "Payment failed");
}
});
</script>
服务端只使用 token 发起扣款:
app.post("/api/payments", async (req, res) => {
const { paymentToken, amount, currency } = req.body;
if (!Number.isInteger(amount) || amount <= 0) {
return res.status(400).json({ message: "Invalid amount" });
}
try {
const payment = await paymentProcessor.payments.create({
token: paymentToken,
amount,
currency,
capture: true
});
res.json({
paymentId: payment.id,
status: payment.status
});
} catch (error) {
res.status(402).json({
message: "Payment was declined"
});
}
});
金额应使用最小货币单位。例如,4990 表示 49.90 元,这样可以避免用浮点数计算货币。浏览器提交的商品金额不能直接采信,服务端必须根据订单重新计算并校验。
这种方案仍依赖支付处理服务,但可以减少系统直接保存和传输银行卡数据的范围。
方案三:改用非信用卡付款方式
如果目标是切实降低支付成本,可以考虑:
- 银行转账。
- 企业对公转账。
- 电子支票或本地账户扣款。
- 货到付款。
- 线下刷卡或人工开票后付款。
这些方式可能降低单笔手续费,但也会增加对账、到账确认、退款和用户操作成本。是否适合,要根据客户所在地、订单金额和业务类型判断。
不应采用的做法
不要在普通数据库、日志、邮件或客服工单中保存完整卡号,更不能保存信用卡安全码。
下面的实现存在严重风险:
// 不要这样处理信用卡数据
app.post("/checkout", async (req, res) => {
await database.cards.insert({
cardNumber: req.body.cardNumber,
expiry: req.body.expiry,
cvc: req.body.cvc
});
res.sendStatus(200);
});
即使自行加密这些字段,也不代表已经满足 PCI DSS。系统还要处理密钥隔离、访问控制、审计、漏洞管理、网络分段和事件响应等问题。
也不要通过普通邮件、即时消息、URL 查询参数或应用日志传递卡号。URL 内容可能被写入浏览器历史、代理日志和分析系统。
必须注意的事项
PCI DSS 合规
只要系统存储、处理或传输持卡人数据,就可能被纳入 PCI DSS 的适用范围。具体要求取决于集成方式、业务规模、收单机构和适用地区,应向收单机构或合格的支付安全顾问确认。
使用 tokenization 或 Hosted Fields 可以缩小合规范围,但通常不会自动免除全部责任。
安全码不能保存
CVV、CVC 等敏感认证数据不能在交易授权后保存,也不能写入数据库、日志、缓存或备份。
授权不等于收款完成
信用卡交易通常分为以下操作:
authorize:确认额度并预授权。capture:正式提交扣款。void:在结算前撤销。refund:结算后退款。
订单系统需要记录支付状态,并使用幂等键,防止网络重试造成重复扣款。
需要处理欺诈和拒付
自行集成还意味着需要处理:
- 地址或身份验证。
- 3-D Secure 等持卡人认证机制。
- 交易限额和风控规则。
- 拒付通知与证据提交。
- 退款和部分退款。
- 异步回调签名验证。
- 支付失败后的订单状态恢复。
Payment gateway 或支付服务商收取的费用,通常就包含了其中一部分工作。
实际建议
小型和中型网站不适合为了节省 gateway 费用而自行保存或处理信用卡数据。更稳妥的办法是比较多家收单机构和支付处理商的总成本,选择支持令牌化、Hosted Fields 或托管结账的方案。
评估时不能只看单笔费率,还要把开户费、月费、最低消费、跨境费、退款费、拒付费、PCI 合规成本以及开发维护成本算进去。
如果业务量足以支撑直接集成,可以与收单银行协商专用费率和处理接口。如果目标是完全避免信用卡手续费,就应提供银行转账等替代付款方式,而不是试图绕开信用卡支付体系。
备注:内容仅供参考。
