PAYATHON 2026

iPhone 自动识别短信时重复填写支付宝验证码

支付老李

结论

这类情况通常不是支付宝重复发送了验证码,而是页面同时处理了 iPhone 的短信验证码自动填充事件和普通输入事件,或者在事件回调中以“追加字符”的方式更新输入框,结果将同一验证码写入了两次。

处理时应统一验证码输入框的监听入口,始终用输入框的完整值覆盖业务状态,同时对重复事件和重复提交进行幂等处理。

原因分析

iPhone 自动填充验证码时,可能会一次写入完整内容,并触发 input、change 等事件。事件的具体触发顺序会因 iOS 版本、浏览器内核和页面实现而有所不同。

以下情况容易造成重复填写:

  • 同时监听 input、change、keyup 等多个事件,并在每个回调中写入验证码。
  • 原生事件与 Vue、React 等框架事件同时更新同一个字段。
  • 使用 currentCode += value 追加内容,没有直接覆盖原值。
  • 每次组件渲染或弹窗打开时都重新绑定事件。
  • 自动填充完成后,脚本又读取短信内容并再次回填。
  • 验证码填满后,多次触发支付确认或表单提交。

推荐处理方式

1. 正确声明验证码输入框

使用 autocomplete="one-time-code",可以提示 iOS 将该输入框识别为一次性验证码输入框:

<input
  id="smsCode"
  name="smsCode"
  type="text"
  inputmode="numeric"
  autocomplete="one-time-code"
  maxlength="6"
/>

maxlength 要根据验证码的实际长度设置,不能认为所有支付宝支付场景都使用 6 位验证码。

2. 只监听 input 事件

不要在多个事件中重复执行相同的验证码更新逻辑:

const codeInput = document.getElementById('smsCode');

codeInput.addEventListener('input', (event) => {
  const code = event.target.value.replace(/\D/g, '').slice(0, 6);

  // 使用完整值覆盖,不要使用 += 追加
  event.target.value = code;
  updateVerifyCode(code);
});

错误示例:

let verifyCode = '';

codeInput.addEventListener('input', (event) => {
  verifyCode += event.target.value;
});

当自动填充一次写入完整验证码时,这段错误逻辑可能会重复追加同一组数字。

3. 对重复事件进行幂等处理

如果现有业务暂时无法合并事件监听,可以保存最近一次处理的完整值:

let lastCode = '';

function handleVerifyCode(event) {
  const maxLength = 6;
  const code = event.target.value
    .replace(/\D/g, '')
    .slice(0, maxLength);

  event.target.value = code;

  if (code === lastCode) {
    return;
  }

  lastCode = code;
  updateVerifyCode(code);
}

codeInput.addEventListener('input', handleVerifyCode);

这里应比较完整的验证码,而不是判断事件类型。即使 iPhone 连续触发两次相同事件,业务逻辑也只处理一次。

4. 防止重复绑定监听器

组件重新渲染、弹窗再次打开或页面局部刷新时,应先移除旧的监听器:

function mountCodeInput() {
  const input = document.getElementById('smsCode');

  input.removeEventListener('input', handleVerifyCode);
  input.addEventListener('input', handleVerifyCode);
}

function unmountCodeInput() {
  const input = document.getElementById('smsCode');

  if (input) {
    input.removeEventListener('input', handleVerifyCode);
  }
}

removeEventListener 无法准确移除之前注册的匿名函数,因此这里应使用具名函数。

5. 防止重复提交支付请求

如果验证码自动填满后,页面会立即发起验证或支付请求,还需要增加防重复提交控制:

let submitting = false;

async function submitPayment(code) {
  if (submitting) {
    return;
  }

  submitting = true;

  try {
    await verifyAndPay(code);
  } finally {
    submitting = false;
  }
}

前端防重只能改善交互。服务端仍需通过订单号、请求号或业务幂等键拦截重复支付请求,不能只依靠禁用按钮或 JavaScript 状态。

框架中的处理示例

以 Vue 为例,不要同时使用 v-model 和额外事件来重复拼接数据:

<input
  :value="verifyCode"
  type="text"
  inputmode="numeric"
  autocomplete="one-time-code"
  maxlength="6"
  @input="handleInput"
/>
function handleInput(event) {
  verifyCode.value = event.target.value
    .replace(/\D/g, '')
    .slice(0, 6);
}

如果已经使用 v-model,额外的 @input 回调只用于校验或格式化,不应再次追加相同内容。

排查方法

可以临时记录相关事件,判断重复值来自浏览器事件还是业务代码:

['beforeinput', 'input', 'change', 'keyup'].forEach((eventName) => {
  codeInput.addEventListener(eventName, (event) => {
    console.log(eventName, {
      value: event.target.value,
      inputType: event.inputType,
      data: event.data,
      time: Date.now()
    });
  });
});

排查时主要检查以下几项:

  • 一次自动填充触发了哪些事件。
  • 同一个处理函数是否注册了多次。
  • 输入框中的值是否正确,而业务变量却被重复拼接。
  • 页面是否同时使用了框架绑定和原生 DOM 绑定。
  • 验证码填满后是否发出了两次接口请求。

注意事项

不要依赖固定的 iOS 事件触发顺序,也不建议使用 setTimeout、判断设备型号或关闭 iPhone 自动填充来绕过问题。这些做法很容易受到系统升级和浏览器环境变化的影响。

如果重复内容出现在支付宝托管页面或支付宝提供的内嵌控件中,而且商户页面没有操作对应的输入框,商户侧通常无法直接修改。此时需要保存复现机型、iOS 版本、浏览器类型、支付链路、录屏和请求日志,再通过支付宝技术支持渠道核实。仅凭重复填写这一现象,无法断定问题一定出在 iOS 或支付宝。

备注:内容仅供参考。