如何将 ISO codes 发送给 payment processor?
结论
ISO code 不需要直接“插入”到 print_country() 或 print_state() 的调用中。下拉框应显示国家、州或省的名称,并把对应的 ISO code 写入 <option> 的 value 属性。提交表单时,浏览器发送的是 value,不是用户看到的文字。
例如:
<option value="ID">Indonesia</option>
<option value="ID-JK">Jakarta</option>
用户看到的是 Indonesia 和 Jakarta,服务器收到的值是 ID 和 ID-JK。
实现前需要先确认 payment processor 接受哪种代码格式。国家通常使用 ISO 3166-1 alpha-2,例如印度尼西亚是 ID。省级行政区可能使用 ISO 3166-2,例如 Jakarta 是 ID-JK。有些支付接口只接受省份缩写、内部代码或自由文本,不能只根据字段名称判断格式。
为什么现有下拉框不会自动发送 ISO code
print_country() 和 print_state() 只负责生成下拉选项。最终提交什么内容,取决于它们生成的 HTML。
如果结果是:
<option value="Indonesia">Indonesia</option>
提交的就是:
Indonesia
如果省份下拉框生成的是:
<option value="Jakarta">Jakarta</option>
提交的就是:
Jakarta
浏览器不会自动把国家名称转换成 ISO code。数据源必须保存名称与代码的对应关系,并将代码设为选项值。
推荐的数据结构
不要只保存行政区名称。每一项至少要包含显示名称和提交代码:
const countries = [
{
code: "ID",
name: "Indonesia",
subdivisions: [
{ code: "ID-JK", name: "Jakarta" },
{ code: "ID-JB", name: "Jawa Barat" },
{ code: "ID-JT", name: "Jawa Tengah" }
]
},
{
code: "CA",
name: "Canada",
subdivisions: [
{ code: "CA-ON", name: "Ontario" },
{ code: "CA-QC", name: "Quebec" },
{ code: "CA-BC", name: "British Columbia" }
]
}
];
其中,名称用于页面显示,code 用于表单提交。
上面只列出了少量示例。实际使用时,需要根据业务覆盖范围维护完整的行政区数据,不能直接把示例当成完整清单。
HTML 示例
<form id="payment-form" method="post" action="/process-payment">
<label for="country">Country</label>
<select id="country" name="country_code" required>
<option value="">Select Country</option>
</select>
<label for="state">City/District/State</label>
<select id="state" name="state_code" required disabled>
<option value="">Select State</option>
</select>
<button type="submit">Continue</button>
</form>
需要注意的是字段名称和选项值:
<select name="country_code">
<option value="ID">Indonesia</option>
</select>
<select name="state_code">
<option value="ID-JK">Jakarta</option>
</select>
提交后,服务器会收到类似的数据:
country_code=ID
state_code=ID-JK
JavaScript 示例
const countries = [
{
code: "ID",
name: "Indonesia",
subdivisions: [
{ code: "ID-JK", name: "Jakarta" },
{ code: "ID-JB", name: "Jawa Barat" },
{ code: "ID-JT", name: "Jawa Tengah" }
]
},
{
code: "CA",
name: "Canada",
subdivisions: [
{ code: "CA-ON", name: "Ontario" },
{ code: "CA-QC", name: "Quebec" },
{ code: "CA-BC", name: "British Columbia" }
]
}
];
const countrySelect = document.getElementById("country");
const stateSelect = document.getElementById("state");
for (const country of countries) {
countrySelect.add(
new Option(country.name, country.code)
);
}
countrySelect.addEventListener("change", function () {
stateSelect.replaceChildren(new Option("Select State", ""));
stateSelect.disabled = true;
const country = countries.find(
item => item.code === this.value
);
if (!country) {
return;
}
for (const subdivision of country.subdivisions) {
stateSelect.add(
new Option(subdivision.name, subdivision.code)
);
}
stateSelect.disabled = country.subdivisions.length === 0;
});
countrySelect.value = "ID";
countrySelect.dispatchEvent(new Event("change"));
这样设置后,默认国家显示为 Indonesia,实际值是 ID。用户选择 Jakarta 后,state 字段的值是 ID-JK。
如果必须保留现有的 print_country() 和 print_state()
先检查这两个函数使用的数据,以及它们生成 <option> 的代码。如果当前逻辑是:
select.options.add(new Option(countryName, countryName));
应改为:
select.options.add(new Option(countryName, countryCode));
省份使用相同的处理方式:
select.options.add(new Option(stateName, stateCode));
如果现有数据只有名称:
["Indonesia", "Canada", "United States"]
就无法直接提交可靠的 ISO code。需要先把数据改成名称与代码的映射:
[
{ name: "Indonesia", code: "ID" },
{ name: "Canada", code: "CA" },
{ name: "United States", code: "US" }
]
只在页面加载后执行下面的代码,无法解决选项值的问题:
$("#country").val("Indonesia");
如果 <option> 的值已经改为 ISO code,应写成:
$("#country").val("ID").trigger("change");
在服务器端发送给 payment processor
浏览器提交 ISO code 后,服务器还要把它放入支付接口要求的参数中。假设接口字段名是 country 和 state,PHP 可以这样处理:
<?php
$countryCode = $_POST['country_code'] ?? '';
$stateCode = $_POST['state_code'] ?? '';
$allowedCountries = ['ID', 'CA', 'US'];
if (!in_array($countryCode, $allowedCountries, true)) {
throw new InvalidArgumentException('Invalid country code');
}
$paymentData = [
'country' => $countryCode,
'state' => $stateCode,
];
// 使用 payment processor 提供的 SDK 或服务端 API 提交 $paymentData。
country 和 state 只是示例字段名。实际名称可能是 country_code、billing_country、province 或其他形式,应以 payment processor 的接口文档为准。
还要确认省级代码需要完整的 ISO 3166-2 值:
ID-JK
还是只需要后半部分:
JK
这两种格式不能混用。
City、District 和 State 不能当作同一种代码
ISO 3166-2 主要用于一个国家的一级或相关行政区划,并不保证每个城市或区县都有对应代码。因此,把 City/District/State 合并成一个字段,可能会造成语义混乱。
可以将字段拆开:
<input name="city" type="text">
<select name="state_code">
<option value="ID-JK">Jakarta</option>
</select>
<select name="country_code">
<option value="ID">Indonesia</option>
</select>
如果 payment processor 分别提供 city、state 和 country 字段,就分别提交相应内容,不要用城市或区县代码冒充省级 ISO code。
实施时的注意事项
- 查看 payment processor 的 API 文档,确认国家和省份字段接受的标准及长度。
- 国家列表建议使用 ISO 3166-1 alpha-2,例如
ID、US、CA。 - 只有支付接口支持时,省级行政区才使用 ISO 3166-2,例如
ID-JK。 - 不要通过国家下拉框的
selectedIndex判断国家。列表排序发生变化后,索引就会失效,应始终使用明确的代码值。 - 前端下拉框只能规范输入,不能代替服务器端验证。用户可以绕过页面,自行提交任意值。
- 服务器应验证国家代码是否有效,并确认省份代码属于所选国家。
- 向 payment processor 发送数据的操作应放在服务器端,不要在浏览器代码中暴露私钥或商户密钥。
- ISO 清单和行政区划可能调整。生产环境应使用可信、便于维护的数据源,不要长期使用一份无人更新的数组。
需要修改的不是 print_country("country") 这一行,而是它所使用的选项数据。页面显示便于用户阅读的名称,value 保存 payment processor 要求的代码。服务器验证这些代码后,再将它们写入支付接口对应的字段。
备注:内容仅供参考。
