PAYATHON 2026

Vue SPA 中 Checkout() 获取数据后再跳转支付网关

支付阿杰

结论

先阻止表单默认提交,再在 Checkout() 中等待 axios.get() 完成。更新表单数据并等待 Vue 刷新 DOM 后,调用原生表单提交方法。支付网关收到的将是更新后的完整数据,而不是尚未赋值的空字段。

推荐实现

在模板中使用 @submit.prevent,避免浏览器立即跳转,同时给表单添加 ref:

<form
  ref="paymentForm"
  method="post"
  :action="Action"
  @submit.prevent="Checkout"
>
  <input type="hidden" name="invoiceNumber" :value="InvoiceNumber">
  <input type="hidden" name="invoiceDate" :value="InvoiceDate">
  <input type="hidden" name="amount" :value="Amount">
  <input type="hidden" name="terminalCode" :value="TerminalCode">
  <input type="hidden" name="merchantCode" :value="MerchantCode">
  <input type="hidden" name="redirectAddress" :value="RedirectAddress">
  <input type="hidden" name="timeStamp" :value="TimeStamp">
  <input type="hidden" name="action" :value="Action">
  <input type="hidden" name="sign" :value="Sign">

  <button type="submit" :disabled="submitting">
    Checkout
  </button>
</form>

Vue 2 中可以这样写:

methods: {
  async Checkout() {
    if (this.submitting) {
      return;
    }

    this.submitting = true;

    try {
      const response = await axios.get('./api/pasargad');
      const data = response.data;

      this.InvoiceNumber = data.InvoiceNumber;
      this.InvoiceDate = data.InvoiceDate;
      this.Amount = data.Amount;
      this.TerminalCode = data.TerminalCode;
      this.MerchantCode = data.MerchantCode;
      this.RedirectAddress = data.RedirectAddress;
      this.TimeStamp = data.TimeStamp;
      this.Action = data.Action;
      this.Sign = data.Sign;

      // 等待 Vue 将最新数据写入 input 和 form action
      await this.$nextTick();

      const form = this.$refs.paymentForm;

      // 使用原生 submit,避免再次触发 @submit.prevent 而形成循环
      HTMLFormElement.prototype.submit.call(form);
    } catch (error) {
      this.submitting = false;
      console.error('Unable to prepare payment data:', error);
      // 此处可以显示“支付信息获取失败,请重试”等提示
    }
  }
}

这些响应式字段还需要在组件的 data 中声明:

data() {
  return {
    InvoiceNumber: '',
    InvoiceDate: '',
    Amount: '',
    TerminalCode: '',
    MerchantCode: '',
    RedirectAddress: '',
    TimeStamp: '',
    Action: '',
    Sign: '',
    submitting: false
  };
}

为什么需要 nextTick

await axios.get() 只能保证接口请求已经结束。组件属性赋值后,Vue 通常会把真实 DOM 的更新放入异步队列。

如果此时立即提交表单,隐藏输入框的 value 或表单的 action 可能仍是旧值。因此,赋值后需要执行:

await this.$nextTick();

等 DOM 中的表单字段更新完毕,再提交表单。

也可以使用 Promise 写法

如果不使用 async/await,也可以继续使用 then()。不过,提交操作必须放在请求成功回调和 $nextTick() 之后:

methods: {
  Checkout() {
    if (this.submitting) {
      return;
    }

    this.submitting = true;

    axios.get('./api/pasargad')
      .then(response => {
        const data = response.data;

        this.InvoiceNumber = data.InvoiceNumber;
        this.InvoiceDate = data.InvoiceDate;
        this.Amount = data.Amount;
        this.TerminalCode = data.TerminalCode;
        this.MerchantCode = data.MerchantCode;
        this.RedirectAddress = data.RedirectAddress;
        this.TimeStamp = data.TimeStamp;
        this.Action = data.Action;
        this.Sign = data.Sign;

        return this.$nextTick();
      })
      .then(() => {
        HTMLFormElement.prototype.submit.call(
          this.$refs.paymentForm
        );
      })
      .catch(error => {
        this.submitting = false;
        console.error('Unable to prepare payment data:', error);
      });
  }
}

箭头函数会保留 Vue 方法中的 this,所以不需要 var vm = this,也不需要 .bind(vm)。

注意事项

  • 必须使用 @submit.prevent,否则浏览器可能在异步请求完成前提交表单中的原有内容。
  • 不要在成功回调之外调用 form.submit(),否则无法保证接口已经返回。
  • 所有需要发送给网关的 <input> 都必须设置正确的 name。浏览器提交表单时依据的是 name,不是 Vue 属性名。
  • HTMLFormElement.prototype.submit.call(form) 不会再次触发 submit 事件,因此不会重新进入 Checkout()。
  • 原生 submit() 会绕过浏览器的表单校验。如果表单中还有用户填写的字段,应在提交前调用 form.checkValidity(),必要时再调用 form.reportValidity()。
  • 支付金额、订单号和签名必须由服务端生成并校验,不能信任前端传入的数据。
  • 如果接口返回的 Action 是支付网关地址,应由服务端限制允许的目标域名,避免直接跳转到未经验证的地址。
  • submitting 可以防止用户连续点击,避免生成多个订单或重复提交。接口失败时不要跳转,并恢复按钮状态。

备注:内容仅供参考。