PAYATHON 2026

支持向信用卡转账的 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。可以采用下面的流程:

  1. 通过浏览器组件或服务商托管页面采集卡片信息;
  2. 服务商返回 token;
  3. Rails 后端只保存 token 和必要的业务标识;
  4. 后端使用 token 创建收款或打款请求;
  5. 根据 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,再选择明确支持目标地区和具体场景,并愿意审核承接该业务的支付服务商。

备注:内容仅供参考。