PAYATHON 2026

如何将 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 要求的代码。服务器验证这些代码后,再将它们写入支付接口对应的字段。

备注:内容仅供参考。