OpenCart 选择付款方式时添加银行账号必填字段
结论
可以实现。从代码风格来看,这套结账流程应该属于 OpenCart 1.5.x:第 5 步通过 checkout/payment_method 选择付款方式,payment/incasso.tpl 通常要到第 6 步确认订单时才加载。
所以,如果把银行账号输入框放在 payment/incasso.tpl 中,它只能出现在第 6 步。要让输入框显示在第 5 步,需要:
- 在
checkout/payment_method.tpl中添加一个默认隐藏的银行账号字段。 - 用户选择
incasso时,通过 JavaScript 显示该字段。 - 在
checkout/payment_method控制器中完成服务端校验。 - 校验通过后,将账号保存到 session。
- 在
payment/incasso/confirm中读取 session,并将账号写入数据库。
下面的代码采用 OpenCart 1.5.x 的常见结构。具体方法名可能因小版本或自定义主题而异,一般是 validate() 或 save()。
在第 5 步添加输入框
编辑当前主题的文件:
catalog/view/theme/你的主题/template/checkout/payment_method.tpl
不要只修改 default 主题。如果商店使用的是其他主题,应当修改实际生效的模板。
在付款方式列表之后、Continue 按钮之前加入:
<div id="incasso-bank-account" style="display:none;">
<label for="bankAccountNumber">
Bank account number <span class="required">*</span>
</label>
<input
type="text"
name="bank_account_number"
id="bankAccountNumber"
value="<?php echo isset($bank_account_number)
? htmlspecialchars($bank_account_number, ENT_QUOTES, 'UTF-8')
: ''; ?>"
autocomplete="off"
/>
</div>
<script type="text/javascript">
function toggleIncassoBankAccount() {
var selectedMethod = $('input[name="payment_method"]:checked').val();
if (selectedMethod && selectedMethod.split('.')[0] === 'incasso') {
$('#incasso-bank-account').show();
} else {
$('#incasso-bank-account').hide();
}
}
$('input[name="payment_method"]').bind('change', function() {
toggleIncassoBankAccount();
});
toggleIncassoBankAccount();
</script>
这里使用 split('.')[0],是因为部分 OpenCart 付款方式的值可能带有子方式,例如 module.method。如果单选框的值始终是 incasso,也可以直接比较。
标签和错误信息最好放进对应的语言文件,不要直接写死英文。
把已填写的内容传给模板
编辑:
catalog/controller/checkout/payment_method.php
在渲染 payment_method.tpl 前添加:
$this->data['bank_account_number'] = '';
if (isset($this->session->data['incasso_bank_account'])) {
$this->data['bank_account_number'] =
$this->session->data['incasso_bank_account'];
}
这样,客户返回上一步或重新加载付款方式区域时,之前填写的内容仍然会保留。
在第 5 步进行服务端校验
JavaScript 可以改善操作体验,但不能代替服务端校验。用户既可以关闭 JavaScript,也可以直接构造请求。
在 catalog/controller/checkout/payment_method.php 的 validate() 方法中,找到处理以下内容的位置:
$this->request->post['payment_method']
以及最终执行以下代码的位置:
if (!$json) {
// 保存 payment_method
}
在最终保存前加入:
$selected_method = '';
if (isset($this->request->post['payment_method'])) {
$selected_method = $this->request->post['payment_method'];
}
$method_parts = explode('.', $selected_method);
$method_code = $method_parts[0];
$bank_account_number = '';
if (isset($this->request->post['bank_account_number'])) {
$bank_account_number = strtoupper(
preg_replace(
'/\s+/',
'',
trim($this->request->post['bank_account_number'])
)
);
}
if ($method_code == 'incasso') {
if ($bank_account_number == '') {
$json['error']['warning'] =
'Please enter your bank account number.';
} elseif (!preg_match('/^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/', $bank_account_number)) {
$json['error']['warning'] =
'Please enter a valid IBAN.';
}
}
随后,在原有的 if (!$json) 代码块中保存 session:
if (!$json) {
// 保留 OpenCart 原有的 payment_method 保存代码。
if ($method_code == 'incasso') {
$this->session->data['incasso_bank_account'] =
$bank_account_number;
} else {
unset($this->session->data['incasso_bank_account']);
}
}
上面的正则表达式只检查 IBAN 的基本结构,不会验证 IBAN 校验位。正式环境中应使用完整的 IBAN Mod-97 校验器,也不能只依赖前端正则表达式。
如果当前版本使用的错误结构是:
$json['error'] = '...';
而不是:
$json['error']['warning'] = '...';
应当沿用现有控制器的格式,否则结账页面可能无法正常显示错误信息。
在确认订单时读取 session
现在的 confirm() 使用:
$rekNum = $_GET['rn'];
这种方式不适合传递银行账号。URL 查询参数可能被记录在浏览器历史、访问日志和统计系统中。
改为从 session 读取:
public function confirm() {
$this->language->load('payment/incasso');
$json = array();
if (!isset($this->session->data['incasso_bank_account'])
|| $this->session->data['incasso_bank_account'] == '') {
$json['error'] = 'Bank account number is missing.';
$this->response->setOutput(json_encode($json));
return;
}
$this->load->model('checkout/order');
$this->load->model('payment/incasso');
$rekNum = $this->session->data['incasso_bank_account'];
$comment = $this->language->get('text_instruction') . "\n\n";
$comment .= $this->config->get(
'incasso_bank_' . $this->config->get('config_language_id')
) . "\n\n";
$comment .= $this->language->get('text_payment');
$this->model_payment_incasso->insertRekNum(
$this->session->data['order_id'],
$rekNum
);
$this->model_checkout_order->confirm(
$this->session->data['order_id'],
$this->config->get('incasso_order_status_id'),
$comment,
true
);
unset($this->session->data['incasso_bank_account']);
$json['redirect'] = $this->url->link('checkout/success');
$this->response->setOutput(json_encode($json));
}
对应的 incasso.tpl 不再读取 #bankAccountNumber,也不要将账号拼接到 URL 中:
$('#button-confirm').bind('click', function() {
$.ajax({
type: 'post',
url: 'index.php?route=payment/incasso/confirm',
dataType: 'json',
beforeSend: function() {
$('#button-confirm').attr('disabled', true);
},
complete: function() {
$('#button-confirm').attr('disabled', false);
},
success: function(json) {
if (json.error) {
alert(json.error);
return;
}
if (json.redirect) {
location = json.redirect;
}
}
});
});
修复数据库写入方式
当前模型直接将 $orderNum 和 $rekNum 拼接到 SQL 中:
VALUES ('$orderNum', '$rekNum')
这会带来 SQL 注入风险。至少要将订单编号强制转换为整数,并使用 OpenCart 数据库驱动提供的 escape():
public function insertRekNum($orderNum, $rekNum) {
$orderNum = (int)$orderNum;
$rekNum = $this->db->escape($rekNum);
$sql = "INSERT INTO `" . DB_PREFIX . "order_incasso`
SET order_id = '" . $orderNum . "',
iban = '" . $rekNum . "'";
$this->db->query($sql);
}
如果每个订单只能保存一个 IBAN,可以为 order_id 添加唯一索引,并按实际需求使用更新操作或 ON DUPLICATE KEY UPDATE。这样可以避免用户重复点击确认按钮后生成多条记录。
还需要注意的地方
代码中的路径似乎有拼写错误:
/catelog/controller/paymemt/incasso.php
OpenCart 的标准目录通常是:
catalog/controller/payment/incasso.php
如果这只是问题描述中的笔误,可以忽略。如果实际目录也是这样,OpenCart 的默认路由将无法正常加载该控制器。
另外,如果 Automatic Incasso 指的是 SEPA Direct Debit,只收集 IBAN 通常还不够。实际业务可能还需要账户姓名、授权声明、mandate reference、授权时间和客户的明确同意。IBAN 也属于敏感个人数据,应限制后台访问并避免写入日志。是否需要加密保存,则要根据业务所在地的数据保护要求决定。
备注:内容仅供参考。
