PAYATHON 2026

在 Rails 中使用 CURL 调用支付 API 扣款的问题

支付老李

明确结论

这些值都是文档中的测试示例,不能直接复制到你的应用中:

  • df6000d8f2ec11e294cf026ba7cd33d0:API secret/API key,供服务器端进行身份认证。
  • CCwpuGSqIjnOxUoUrSE4IdV:信用卡资源 ID。完整的 card_uri 通常由客户端完成信用卡 tokenization 后返回。
  • AC6VSiS3WD7G1z1BjrMIL4Kk:account 资源 ID。创建 account 后,可以从 API 响应中取得。
  • TEST-MP6NFmfjuy4Os0LSSywJbmk0:测试 marketplace ID,也是 marketplace URI 的一部分。

实际调用时,请使用 Balanced 返回的完整资源 URI,不要手动拼接,也不要复制文档中的 CC...、AC... 示例值。

各个值从哪里获取

API key

下面这个参数:

-u df6000d8f2ec11e294cf026ba7cd33d0:

采用 HTTP Basic Authentication。冒号前是服务器端 API secret,密码部分为空。

该值应从 Balanced 管理后台或创建 marketplace 时的响应中获取,并且只能保存在 Rails 服务端,例如放入环境变量:

BALANCED_API_SECRET=your_api_secret

代码中这样读取:

api_secret = ENV.fetch("BALANCED_API_SECRET")

不要把 API secret 写入 JavaScript、HTML 或 Git 仓库,也不要发送给浏览器。

Card token 和 card URI

CCwpuGSqIjnOxUoUrSE4IdV 是一张已完成 tokenization 的信用卡资源 ID,完整形式如下:

/v1/marketplaces/TEST-MP6NFmfjuy4Os0LSSywJbmk0/cards/CCwpuGSqIjnOxUoUrSE4IdV

这个值来自收集信用卡信息后的 tokenization 响应。具体字段名取决于所用的 Balanced API 或客户端库版本。常见响应会直接包含类似下面的内容:

{
  "uri": "/v1/marketplaces/TEST-.../cards/CC..."
}

应用应保存并传递响应中的 uri,不要只截取 CC... 部分。

信用卡号和 CVV 应直接交给支付服务的 tokenization 组件处理,不应先发送到 Rails 服务器再生成 token,否则会扩大 PCI 合规范围。

Account ID 和 account URI

AC6VSiS3WD7G1z1BjrMIL4Kk 是 account 的资源 ID。创建 account 后,API 会返回对应的资源,其 URI 类似:

/v1/marketplaces/TEST-.../accounts/AC6VSiS3WD7G1z1BjrMIL4Kk

之后关联信用卡或发起扣款时,应使用创建 account 后响应中的 uri。文档中的不同命令可能使用不同的 AC...,因为它们是各自独立的示例,不能混用。

正确的调用流程

方式一:创建 account 时直接关联信用卡

取得 card_uri 后,可以在创建 account 时一并提交:

curl "https://api.balancedpayments.com${MARKETPLACE_URI}/accounts" \
  -u "${BALANCED_API_SECRET}:" \
  -d "card_uri=${CARD_URI}"

其中:

MARKETPLACE_URI=/v1/marketplaces/TEST-你的市场ID
CARD_URI=/v1/marketplaces/TEST-你的市场ID/cards/CC-实际卡片ID

创建成功后,从 JSON 响应中读取 account 的 uri,例如:

/v1/marketplaces/TEST-.../accounts/AC...

方式二:把信用卡关联到已有 account

如果 account 已经存在,可以向它的 URI 发送 PUT 请求:

curl "https://api.balancedpayments.com${ACCOUNT_URI}" \
  -u "${BALANCED_API_SECRET}:" \
  -X PUT \
  -d "card_uri=${CARD_URI}"

这里的 ACCOUNT_URI 和 CARD_URI 都应取自之前的 API 响应。

“创建 account 时传入 card_uri”与”更新已有 account 并关联 card_uri”适用于不同场景。对于同一个新 account,通常不需要连续执行这两种操作。

从 account 扣款

扣款接口位于 account URI 的 /debits 子资源下:

curl "https://api.balancedpayments.com${ACCOUNT_URI}/debits" \
  -u "${BALANCED_API_SECRET}:" \
  -d "amount=1000"

amount=1000 通常表示 1000 个最小货币单位。如果以美分计价,就是 $10.00。实际币种和金额单位应以当前项目使用的 Balanced API 版本说明为准。

Rails 示例

可以使用 Net::HTTP 发起同样的请求。以下示例假设你已经取得 card_uri:

require "net/http"
require "json"
require "uri"

class BalancedClient
  API_BASE = "https://api.balancedpayments.com"

  def initialize
    @api_secret = ENV.fetch("BALANCED_API_SECRET")
    @marketplace_uri = ENV.fetch("BALANCED_MARKETPLACE_URI")
  end

  def create_account(card_uri:)
    post("#{@marketplace_uri}/accounts", card_uri: card_uri)
  end

  def attach_card(account_uri:, card_uri:)
    put(account_uri, card_uri: card_uri)
  end

  def debit(account_uri:, amount:)
    post("#{account_uri}/debits", amount: amount)
  end

  private

  def post(path, parameters)
    request(Net::HTTP::Post, path, parameters)
  end

  def put(path, parameters)
    request(Net::HTTP::Put, path, parameters)
  end

  def request(request_class, path, parameters)
    uri = URI.join(API_BASE, path)
    request = request_class.new(uri)
    request.basic_auth(@api_secret, "")
    request.set_form_data(parameters)

    response = Net::HTTP.start(
      uri.hostname,
      uri.port,
      use_ssl: uri.scheme == "https"
    ) do |http|
      http.request(request)
    end

    body = JSON.parse(response.body)

    unless response.is_a?(Net::HTTPSuccess)
      raise "Balanced API error #{response.code}: #{body.inspect}"
    end

    body
  end
end

调用示例:

client = BalancedClient.new

account = client.create_account(
  card_uri: params.require(:card_uri)
)

account_uri = account.fetch("uri")

debit = client.debit(
  account_uri: account_uri,
  amount: 1_000
)

生产代码还应检查 card_uri 是否属于当前 marketplace,不能直接信任浏览器提交的任意资源路径。

参数替换对照

文档示例中的值                              实际应替换为
df6000d8f2ec11e294cf026ba7cd33d0           你的服务器端 API secret
TEST-MP6NFmfjuy4Os0LSSywJbmk0               你的测试或生产 marketplace ID
CCwpuGSqIjnOxUoUrSE4IdV                     tokenization 响应中的 card ID/URI
AC6VSiS3WD7G1z1BjrMIL4Kk                    创建或查询 account 后得到的 account ID/URI
1000                                        需要扣取的最小货币单位金额

注意事项

Balanced Payments 已是历史支付平台。以上接口格式针对问题所引用的旧版 API。如果你正在维护遗留系统,应以项目锁定的 API 版本和现存接口文档为准。新项目不能假定这些端点仍然可用。

还要注意以下几点:

  • 测试环境中的 TEST-... URI、卡片和 account 不能用于生产环境。
  • 不要把测试文档里的 API key、card ID 或 account ID 当作通用值。
  • 金额应由服务器根据订单重新计算,不能直接信任浏览器传入的 amount。
  • 保存完整资源 URI 通常比只保存 CC... 或 AC... ID 更稳妥。
  • 扣款请求需要设置业务侧的幂等控制,防止超时重试导致重复扣款。

备注:内容仅供参考。