在 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 更稳妥。 - 扣款请求需要设置业务侧的幂等控制,防止超时重试导致重复扣款。
备注:内容仅供参考。
