支持向信用卡转账的 Payment Gateway API
结论
普通的 Payment Gateway 通常只负责信用卡收款,不能将商户余额随意转入他人的信用卡。
如果业务需要“向卡转账”,应选择支持 card payout 或 push-to-card 的服务。这类服务底层通常使用 Visa Direct、Mastercard Send 等网络。实际能否使用,要看国家或地区、卡种、业务模式、牌照要求以及服务商的审核结果,不能仅根据“支持信用卡支付”来判断。
如果收款方是平台卖家、司机或自由职业者,更常见的做法是使用 marketplace 或 connected account 产品。支付服务商负责身份验证和资金分账,收款方再将资金提现到银行账户或符合条件的借记卡。
为什么普通网关做不到
信用卡收款与向卡付款使用的是不同的资金链路:
- 收款(acquiring):持卡人授权商户扣款,资金进入商户账户。
- 退款(refund):将某笔原始交易的部分或全部金额按原路径退回。
- 向卡转账(push-to-card):创建一笔新的付款,将资金推送到符合条件的卡。
- 平台打款(payout):向已通过验证的收款人结算,通常转入银行账户或借记卡。
退款不能当作转账使用。退款通常必须关联原始付款,金额也不能随意超过原交易。通过伪造交易后退款的方式向他人付款,可能违反服务协议,还可能触发风控或导致账户被冻结。
另外,很多服务所说的“向信用卡转账”,要求目标卡本身具备收款能力。部分服务只支持借记卡或预付卡。即使 API 接受卡号,也不意味着所有信用卡都能收到资金。
推荐的解决路径
1. 先明确业务关系
首先要判断业务属于哪种场景:
- 给原付款人退钱:使用退款 API。
- 给平台内的卖家或服务提供者结算:使用 connected accounts、marketplace payouts 或 split payments。
- 给任意个人的卡付款:选择专门的 push-to-card 服务。
- 用户之间转账:通常需要受监管的钱包、汇款或支付机构方案,只有支付网关并不够。
场景不同,平台能否代收资金、是否需要保存余额,以及由谁负责 KYC、AML 和制裁名单筛查,都会随之变化。
2. 向服务商确认能力
选型时,不能只询问服务商是否提供 payout API,还要确认以下事项:
- 是否支持目标国家或地区;
- 支持信用卡、借记卡,还是仅支持部分卡段;
- 是否接入 Visa Direct、Mastercard Send 或当地的同类网络;
- 是否接受你的具体业务类型;
- 收款人是否必须完成 KYC;
- 能否直接使用商户余额付款;
- 是否提供卡片资格查询;
- 到账时效、手续费、限额以及付款失败后的处理方式;
- 是否支持 webhook、幂等键和交易状态查询;
- 由谁处理属于 PCI DSS 范围的卡数据。
即使服务商公开提供相关产品,也可能只对审核通过的企业开放。
3. 优先使用托管式卡片采集
Rails 应用不应直接接收或记录完整卡号、有效期及 CVV。可以采用下面的流程:
- 通过浏览器组件或服务商托管页面采集卡片信息;
- 服务商返回 token;
- Rails 后端只保存 token 和必要的业务标识;
- 后端使用 token 创建收款或打款请求;
- 根据 webhook 更新最终状态。
这种方式可以减少进入应用服务器的敏感卡数据,但不会自动免除全部 PCI DSS 责任。具体责任范围仍需向服务商和合规顾问确认。
Rails 端的实现示例
不同服务商使用的 endpoint 和字段差异很大。下面只演示一种通用的服务封装方式。/v1/card-payouts、请求字段及状态值都是占位示例,正式接入时必须按照所选服务商的官方文档替换。
# app/services/card_payout_client.rb
require "net/http"
require "json"
require "securerandom"
class CardPayoutClient
def initialize(api_key: ENV.fetch("PAYMENT_API_KEY"))
@api_key = api_key
end
def create(recipient_token:, amount_cents:, currency:, reference:)
uri = URI("https://api.provider.example/v1/card-payouts")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{@api_key}"
request["Content-Type"] = "application/json"
request["Idempotency-Key"] = SecureRandom.uuid
request.body = {
recipient_token: recipient_token,
amount: amount_cents,
currency: currency,
reference: reference
}.to_json
response = Net::HTTP.start(
uri.hostname,
uri.port,
use_ssl: true,
open_timeout: 5,
read_timeout: 15
) do |http|
http.request(request)
end
body = JSON.parse(response.body)
unless response.is_a?(Net::HTTPSuccess)
raise "Card payout failed: #{body.fetch("message", response.code)}"
end
body
end
end
控制器不能直接采用浏览器提交的金额或收款人信息,而应从数据库中读取已经审核的记录:
class PayoutsController < ApplicationController
def create
payout = current_account.payouts.pending.find(params[:id])
result = CardPayoutClient.new.create(
recipient_token: payout.recipient.provider_token,
amount_cents: payout.amount_cents,
currency: payout.currency,
reference: payout.id.to_s
)
payout.update!(
provider_payout_id: result.fetch("id"),
status: result.fetch("status")
)
render json: { id: payout.id, status: payout.status }, status: :accepted
end
end
生产环境还需要为每次请求保存一个稳定的幂等键。如果网络超时后重新提交,应继续使用同一个幂等键,防止重复付款。不能在每次重试时生成新值。
Webhook 才是最终状态依据
打款 API 返回成功,通常只说明服务商已经受理请求,并不代表资金已经到账。最终状态可能是 pending、completed、failed,也可能是服务商定义的其他状态。
Webhook 处理至少应包括以下逻辑:
class PaymentWebhooksController < ApplicationController
skip_before_action :verify_authenticity_token
def create
payload = request.raw_post
signature = request.headers["Provider-Signature"]
event = ProviderWebhook.verify!(
payload: payload,
signature: signature,
secret: ENV.fetch("PAYMENT_WEBHOOK_SECRET")
)
payout = Payout.find_by!(provider_payout_id: event.fetch("payout_id"))
payout.with_lock do
payout.update!(status: event.fetch("status"))
end
head :ok
end
end
ProviderWebhook.verify! 也是示意接口,实际接入时要按照服务商的要求验证签名。未完成验签时不能更新付款状态。同时还应记录已经处理过的事件 ID,以便应对 webhook 重复投递。
必须注意的风险
- 合规要求:代收资金后再向第三方付款,可能构成支付、汇款或资金传输业务。是否需要牌照,取决于经营地区和业务结构。
- KYC/AML:收款人通常需要完成实名验证,平台也可能需要开展交易监控。
- 拒付风险:信用卡收款可能在打款完成后发生 chargeback,平台需要提前设计负余额和追偿机制。
- 卡片资格:并非所有卡都能接收资金。发起付款前,最好调用服务商提供的 eligibility 查询能力。
- 资金可用性:刚收取的款项不一定可以立即用于打款,可能受到结算期、滚动准备金或提现限制的影响。
- 金额单位:支付 API 经常使用最小货币单位,例如人民币分或美元美分。零小数货币的规则不同,必须以服务商的定义为准。
- 安全性:API 密钥只能保存在服务端。日志中不得出现完整卡号、CVV、access token 或 webhook secret。
- 账务设计:平台应建立内部不可变账本,分别记录收款、手续费、退款、拒付和打款,不能只依赖网关返回的余额。
同时支持收款和向卡付款的服务确实存在,但这类产品通常不是开通后就能任意转账的通用网关。接入前应先确认业务属于退款、平台结算还是 push-to-card,再选择明确支持目标地区和具体场景,并愿意审核承接该业务的支付服务商。
备注:内容仅供参考。
