PAYATHON 2026

如何在 Rails 用户模型中保存 Balanced bank account URI?

支付小周

结论

Balanced 返回银行账户 URI 后,把它写入隐藏字段并提交给 Rails 控制器,再由控制器保存到当前用户的数据库记录中。

处理流程如下:

  1. 浏览器调用 balanced.bankAccount.create,对银行账户信息进行令牌化。
  2. 从 response.data.uri 中取出 URI。
  3. 将 URI 写入表单的隐藏字段,如 user[balanced_bank_account_uri]。
  4. 向 Rails 路由提交表单。
  5. 控制器通过允许的参数保存 URI。

银行账号和 routing number 无需提交给 Rails,也不应保存在应用自己的数据库中。

添加数据库字段

先为 users 表添加一个字符串字段:

rails generate migration AddBalancedBankAccountUriToUsers balanced_bank_account_uri:string
rails db:migrate

通常,string 字段足以保存 URI。如果服务返回的 URI 可能超过数据库字符串字段的长度限制,可以改用 text。

配置 Rails 路由

添加一个专门接收银行账户 URI 的接口:

# config/routes.rb
resources :users do
  patch :bank_account, on: :member
end

生成的地址类似:

PATCH /users/:id/bank_account

如果应用已经接入登录系统,最好不要通过 URL 接收任意用户 ID,而是始终更新 current_user:

# config/routes.rb
resource :bank_account, only: [:update]

修改表单

表单应提交到 Rails,而不是示例中的 requestb.in。原始银行账户输入框继续使用 name: nil,避免敏感数据随普通表单一起发送到服务器。

%form#bank-account-form{
  action: bank_account_path,
  method: "POST",
  data: { user_id: current_user.id }
}
  = hidden_field_tag :authenticity_token, form_authenticity_token
  = hidden_field_tag :_method, "patch"

  = label_tag :bank_name, "Account Holder's Name"
  = text_field_tag :bank_name, nil,
    name: nil,
    autocomplete: "name",
    class: "ba-name"

  %p
    = label_tag :route_num, "Routing Number"
    = text_field_tag :route_num, nil,
      name: nil,
      autocomplete: "off",
      class: "ba-rn"

  %p
    = label_tag :acct_num, "Account Number"
    = text_field_tag :acct_num, nil,
      name: nil,
      autocomplete: "off",
      class: "ba-an"

  %p
    = label_tag :account_type, "Account Type"
    = select_tag :account_type,
      options_for_select([["Checking", "checking"], ["Savings", "savings"]]),
      name: nil,
      class: "ba-type"

  %button.btn{ type: "submit" }
    tokenize

表单保留了 Rails 的 CSRF token,并使用 _method=patch,让 Rails 将请求按 PATCH 处理。

在 JavaScript 中提交 URI

成功创建银行账户资源后,向原表单添加隐藏字段并提交:

var marketplaceUri = "/v1/marketplaces/TEST-MPg9bCIQUZMBoiPMnvWkQJW";
balanced.init(marketplaceUri);

function responseCallbackHandler(response) {
  switch (response.status) {
    case 400:
      console.error(response.error);
      break;

    case 404:
      console.error(response.error);
      break;

    case 201:
      var $form = $("#bank-account-form");
      var bankAccountUri = response.data.uri;

      $("<input/>", {
        type: "hidden",
        name: "user[balanced_bank_account_uri]",
        value: bankAccountUri
      }).appendTo($form);

      $form.get(0).submit();
      break;

    default:
      console.error("Unexpected response from Balanced", response);
  }
}

function tokenizeInstrument(event) {
  event.preventDefault();

  var $form = $("#bank-account-form");

  var bankAccountData = {
    name: $form.find(".ba-name").val(),
    account_number: $form.find(".ba-an").val(),
    bank_code: $form.find(".ba-rn").val(),
    type: $form.find(".ba-type").val()
  };

  balanced.bankAccount.create(
    bankAccountData,
    responseCallbackHandler
  );
}

$("#bank-account-form").on("submit", tokenizeInstrument);

创建隐藏字段时,正确的 jQuery 写法是:

$("<input/>")

问题中的写法:

$('
%input>/
')

不是有效的 JavaScript 或 HTML,可能是 Haml 或 Markdown 转换造成的格式错误。

$form.get(0).submit() 调用的是原生表单提交,因此不会再次触发已绑定的 jQuery submit 处理器,也就不会重复执行令牌化。

在控制器中保存

如果路由使用单数资源,并且用户只能更新自己的账户,可以这样处理:

class BankAccountsController < ApplicationController
  before_action :authenticate_user!

  def update
    if current_user.update(bank_account_params)
      redirect_to account_path, notice: "Bank account saved."
    else
      redirect_to account_path, alert: "Unable to save bank account."
    end
  end

  private

  def bank_account_params
    params.require(:user).permit(:balanced_bank_account_uri)
  end
end

提交后,Rails 收到的参数结构大致如下:

{
  "user" => {
    "balanced_bank_account_uri" => "/v1/bank_accounts/..."
  }
}

也可以单独取出 URI 再保存:

current_user.update!(
  balanced_bank_account_uri: params.dig(
    :user,
    :balanced_bank_account_uri
  )
)

不过,使用 strong parameters 更便于集中管理允许更新的字段。

安全注意事项

客户端提交的 URI 不能直接当作可信数据。隐藏字段只是不显示在页面上,并不能防止篡改。保存前,最好通过支付服务端 API 验证以下内容:

  • URI 确实指向银行账户资源;
  • 该资源属于当前 marketplace;
  • 该资源尚未绑定到其他用户;
  • 当前用户有权绑定该银行账户。

不要将银行账号、routing number 或其他原始银行信息写入日志、异常信息或数据库。Rails 只应接收令牌化后的 URI。

Balanced Payments 已停止运营,因此这些代码主要适用于维护历史系统,或用于理解原有的集成方式。新项目应改用当前支付服务商提供的令牌化 API,并遵循其最新的 SDK、验证流程和合规要求。

备注:内容仅供参考。