PAYATHON 2026

如何用两个 radio button 和一个按钮合并两个 form

支付老李

结论

保留两个独立的 <form>,并把 radio button 和共用按钮放在表单外。两个 radio button 共用一个 ng-model,用于记录当前选择的支付网关。点击按钮后调用统一的处理函数,再提交对应的 PaymentForm 或 PaymentForm2。

两个 form 不能嵌套,公共按钮也不要使用默认的 submit 类型。否则浏览器可能直接提交其中一个表单,绕过 AngularJS 发起的预支付请求。

推荐实现

<div ng-controller="PaymentController">
    <h3>选择支付网关</h3>

    <label>
        <input
            type="radio"
            name="paymentGateway"
            ng-model="selectedGateway"
            value="gateway1">
        支付网关 1
    </label>

    <label>
        <input
            type="radio"
            name="paymentGateway"
            ng-model="selectedGateway"
            value="gateway2">
        支付网关 2
    </label>

    <button
        type="button"
        ng-click="submitPayment()"
        ng-disabled="!selectedGateway || paymentSubmitting">
        {{ paymentSubmitting ? '处理中...' : '支付' }}
    </button>

    <form
        id="PaymentForm"
        action="/gateway1/payment"
        method="post"
        style="display: none;">
        <input type="hidden" name="token" value="{{ payment.token }}">
        <!-- PaymentForm 的其他字段 -->
    </form>

    <form
        id="PaymentForm2"
        action="/gateway2/payment"
        method="post"
        style="display: none;">
        <input type="hidden" name="token" value="{{ payment.token }}">
        <!-- PaymentForm2 的其他字段 -->
    </form>
</div>

在 AngularJS 中,可以用一份网关配置统一管理请求参数和目标 form:

$scope.paymentSubmitting = false;

$scope.submitPayment = function () {
    var gatewayConfig = {
        gateway1: {
            formId: 'PaymentForm',
            includeOrderId: false
        },
        gateway2: {
            formId: 'PaymentForm2',
            includeOrderId: true
        }
    };

    var config = gatewayConfig[$scope.selectedGateway];

    if (!config || $scope.paymentSubmitting) {
        return;
    }

    var requestData = {
        Amount: $scope.totalPrice,
        Item: $scope.item.Id,
        Description: $scope.item.Title,
        Count: $scope.defaultQuantity
    };

    if (config.includeOrderId) {
        requestData.OrderId = $scope.item.Id;
    }

    $scope.paymentSubmitting = true;

    $http.post('/payment/happy_payment_register/', requestData)
        .then(function (response) {
            if (response.data === 'Fail') {
                throw new Error('Payment registration failed');
            }

            $scope.payment = response.data;

            // 等待 AngularJS 将 payment 更新到表单字段后再提交
            $timeout(function () {
                var form = document.getElementById(config.formId);

                if (!form) {
                    $scope.paymentSubmitting = false;
                    throw new Error('Payment form not found: ' + config.formId);
                }

                form.submit();
            });
        })
        .catch(function (error) {
            $scope.paymentSubmitting = false;
            console.error(error);
        });
};

这里必须使用 type="button"。点击按钮时只会执行 submitPayment(),再由代码提交当前网关对应的表单。

如果希望保留两个现有函数

如果不想合并现有函数,可以增加一个分发函数:

<label>
    <input
        type="radio"
        name="paymentGateway"
        ng-model="selectedGateway"
        value="gateway1">
    支付网关 1
</label>

<label>
    <input
        type="radio"
        name="paymentGateway"
        ng-model="selectedGateway"
        value="gateway2">
    支付网关 2
</label>

<button
    type="button"
    ng-click="submitSelectedPayment()"
    ng-disabled="!selectedGateway">
    支付
</button>
$scope.submitSelectedPayment = function () {
    if ($scope.selectedGateway === 'gateway1') {
        $scope.SetPrePayment();
    } else if ($scope.selectedGateway === 'gateway2') {
        $scope.SetPrePayment2();
    }
};

需要注意的是,原代码把两个函数都声明成了:

$scope.SetPrePayment = function () {
    // ...
};

后面的声明会覆盖前面的声明,因此第二个函数必须改名:

$scope.SetPrePayment2 = function () {
    // ...
};

注意事项

  • 两个 form 必须并列放置,不能互相嵌套。
  • 两个 radio button 应使用相同的 ng-model,但必须设置不同的 value。
  • 公共按钮应使用 type="button",以免意外触发表单的原生提交。
  • form.submit() 会直接调用浏览器的原生提交,不会触发 AngularJS 的 ng-submit。用于跳转到外部支付网关时,这种方式通常没有问题。
  • $timeout 用于等待 $scope.payment 更新到对应的隐藏字段,一般不需要像原代码那样嵌套两层 $timeout。
  • 处理支付期间应禁用按钮,避免用户重复点击,创建多笔预支付记录。
  • 支付金额、商品信息和订单号都必须由服务端校验,不能只信任前端提交的数据。

备注:内容仅供参考。