如何在 Rails 用户模型中保存 Balanced bank account URI?
结论
Balanced 返回银行账户 URI 后,把它写入隐藏字段并提交给 Rails 控制器,再由控制器保存到当前用户的数据库记录中。
处理流程如下:
- 浏览器调用
balanced.bankAccount.create,对银行账户信息进行令牌化。 - 从
response.data.uri中取出 URI。 - 将 URI 写入表单的隐藏字段,如
user[balanced_bank_account_uri]。 - 向 Rails 路由提交表单。
- 控制器通过允许的参数保存 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、验证流程和合规要求。
备注:内容仅供参考。
