PAYATHON 2026

如何将第三方支付网关集成到 Gravity Forms?

支付小周

结论

可以集成,具体做法取决于 Pay a Trader 是否提供用于创建支付订单并返回收银台 URL 的 API。

如果支付地址和参数固定,可以用 Gravity Forms 的“确认”功能跳转到外部页面。如果金额、订单号、签名等信息需要动态生成,则要由服务端调用支付网关 API,获取支付 URL 后再重定向。支付状态必须通过网关的 webhook 或异步通知确认,不能因为用户返回了网站就把订单标记为已付款。

仅凭 Pay a Trader 的网站地址无法确定其公开接口参数。因此,下面代码中的 API 地址、字段名和签名方式都是占位示例,必须替换为网关实际提供的参数。

方法一:配置固定跳转地址

如果 Pay a Trader 提供固定付款页面,而且不需要动态传递金额、订单号或签名,可以直接在 WordPress 后台配置:

  1. 打开 Forms → 目标表单 → Settings → Confirmations。
  2. 编辑默认确认,或新建一条确认规则。
  3. 将确认类型设为 Redirect。
  4. 填入 Pay a Trader 提供的 HTTPS 支付地址。
  5. 保存并测试。

这种方式只适用于固定链接。如果需要向支付网关传递表单金额、客户信息或订单编号,不建议把敏感参数直接拼接到浏览器地址中。

方法二:提交后动态创建支付订单

更常见的处理流程是:

  1. 用户提交 Gravity Forms 表单。
  2. WordPress 在服务端校验表单并计算最终金额。
  3. 服务端调用 Pay a Trader 的支付订单创建接口。
  4. 支付网关返回托管收银台 URL。
  5. Gravity Forms 将用户重定向到该 URL。
  6. Pay a Trader 通过 webhook 通知支付结果。
  7. 网站验证通知签名,然后更新 Gravity Forms 条目或业务订单状态。

可以用 gform_confirmation_{FORM_ID} 过滤器修改表单提交成功后的确认行为。下面假设表单 ID 为 5:

<?php

add_filter( 'gform_confirmation_5', 'redirect_to_payatrader', 10, 4 );

function redirect_to_payatrader( $confirmation, $form, $entry, $ajax ) {
    // 按实际表单字段 ID 修改。
    $amount         = (float) rgar( $entry, '8' );
    $customer_name  = sanitize_text_field( rgar( $entry, '1' ) );
    $customer_email = sanitize_email( rgar( $entry, '2' ) );

    if ( $amount <= 0 || ! is_email( $customer_email ) ) {
        return array(
            'message' => '无法创建支付订单,请检查付款金额和联系信息。',
        );
    }

    $merchant_reference = 'GF-' . $entry['id'];

    /*
     * 以下地址和字段只是结构示例。
     * 必须替换为 Pay a Trader 文档规定的 API endpoint 和参数名称。
     */
    $endpoint   = 'https://api.example.com/payments';
    $merchant_id = 'YOUR_MERCHANT_ID';
    $api_key     = 'YOUR_API_KEY';

    $payload = array(
        'merchant_id' => $merchant_id,
        'reference'   => $merchant_reference,
        'amount'      => number_format( $amount, 2, '.', '' ),
        'currency'    => 'GBP',
        'customer'    => array(
            'name'  => $customer_name,
            'email' => $customer_email,
        ),
        'return_url'  => home_url( '/payment-return/' ),
        'cancel_url'  => home_url( '/payment-cancelled/' ),
        'webhook_url' => home_url( '/wp-json/payatrader/v1/webhook' ),
    );

    $response = wp_remote_post(
        $endpoint,
        array(
            'timeout' => 20,
            'headers' => array(
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type'  => 'application/json',
                'Accept'        => 'application/json',
            ),
            'body' => wp_json_encode( $payload ),
        )
    );

    if ( is_wp_error( $response ) ) {
        error_log(
            sprintf(
                'Pay a Trader request failed for Gravity Forms entry %d: %s',
                (int) $entry['id'],
                $response->get_error_message()
            )
        );

        return array(
            'message' => '支付服务暂时不可用,请稍后重试。',
        );
    }

    $status_code = wp_remote_retrieve_response_code( $response );
    $body        = json_decode( wp_remote_retrieve_body( $response ), true );

    /*
     * checkout_url 和 payment_id 同样需要按照真实响应格式调整。
     */
    if (
        $status_code < 200 ||
        $status_code >= 300 ||
        empty( $body['checkout_url'] )
    ) {
        error_log(
            sprintf(
                'Pay a Trader returned an invalid response for entry %d. HTTP status: %d',
                (int) $entry['id'],
                (int) $status_code
            )
        );

        return array(
            'message' => '未能创建支付订单,请稍后重试。',
        );
    }

    $checkout_url = esc_url_raw( $body['checkout_url'] );

    if ( 0 !== strpos( $checkout_url, 'https://' ) ) {
        return array(
            'message' => '支付服务返回了无效的跳转地址。',
        );
    }

    // 保存网关订单编号,便于 webhook 与表单条目匹配。
    if ( ! empty( $body['payment_id'] ) ) {
        gform_update_meta(
            $entry['id'],
            'payatrader_payment_id',
            sanitize_text_field( $body['payment_id'] )
        );
    }

    gform_update_meta(
        $entry['id'],
        'payatrader_merchant_reference',
        $merchant_reference
    );

    gform_update_meta( $entry['id'], 'payment_status', 'Pending' );

    return array(
        'redirect' => $checkout_url,
    );
}

这段代码可以放进自定义插件。也可以放在主题的 functions.php 中,但更换主题后代码会失效,所以生产环境更适合使用插件。

如果网关要求浏览器 POST

Gravity Forms 的普通 Redirect 确认主要用于 URL 跳转,也就是让浏览器发起 GET 请求。如果 Pay a Trader 要求浏览器向支付页面提交隐藏字段和签名,就不能只返回一个带参数的跳转 URL。

通常有两种处理方法:

  • 调用网关的服务端 API,创建支付会话后跳转到网关返回的 URL。
  • 先跳转到站内中转页面,再由该页面生成并自动提交 HTML 表单。

中转页面可以采用下面的基本形式:

<form id="payment-form" method="post" action="https://payment-gateway.example/checkout">
    <input type="hidden" name="merchant_id" value="MERCHANT_ID">
    <input type="hidden" name="reference" value="ORDER_REFERENCE">
    <input type="hidden" name="amount" value="49.00">
    <input type="hidden" name="signature" value="SERVER_GENERATED_SIGNATURE">
</form>

<script>
document.getElementById('payment-form').submit();
</script>

这些值必须由服务端生成。不要让浏览器自行计算金额,也不能把 API 密钥写入 HTML、JavaScript、查询参数或 Gravity Forms 隐藏字段。

支付结果回调

用户从支付页面返回,只能证明浏览器访问了返回地址,不能证明付款已经成功。用户可能关闭页面、伪造返回参数,也可能在付款成功后未能返回网站。

支付状态应以 Pay a Trader 的 webhook 通知为准:

<?php

add_action(
    'rest_api_init',
    function () {
        register_rest_route(
            'payatrader/v1',
            '/webhook',
            array(
                'methods'             => 'POST',
                'callback'            => 'handle_payatrader_webhook',
                'permission_callback' => '__return_true',
            )
        );
    }
);

function handle_payatrader_webhook( WP_REST_Request $request ) {
    $raw_body  = $request->get_body();
    $signature = $request->get_header( 'x-payment-signature' );

    /*
     * 必须按 Pay a Trader 的文档验证签名。
     * 在无法确认签名算法和请求头名称时,不应自行猜测实现。
     */
    if ( ! verify_payatrader_signature( $raw_body, $signature ) ) {
        return new WP_REST_Response(
            array( 'error' => 'Invalid signature' ),
            401
        );
    }

    $event = json_decode( $raw_body, true );

    if ( ! is_array( $event ) || empty( $event['reference'] ) ) {
        return new WP_REST_Response(
            array( 'error' => 'Invalid payload' ),
            400
        );
    }

    $reference = sanitize_text_field( $event['reference'] );

    if ( ! preg_match( '/^GF-(\d+)$/', $reference, $matches ) ) {
        return new WP_REST_Response(
            array( 'error' => 'Invalid reference' ),
            400
        );
    }

    $entry_id = (int) $matches[1];
    $status   = isset( $event['status'] )
        ? sanitize_text_field( $event['status'] )
        : '';

    if ( 'paid' === $status ) {
        gform_update_meta( $entry_id, 'payment_status', 'Paid' );
    } elseif ( in_array( $status, array( 'failed', 'cancelled' ), true ) ) {
        gform_update_meta( $entry_id, 'payment_status', 'Failed' );
    }

    return new WP_REST_Response( array( 'received' => true ), 200 );
}

verify_payatrader_signature() 只是示意函数,没有接口文档时不能直接使用。具体实现必须遵循网关规定的签名算法、密钥类型、原始请求体和时间戳校验规则。

集成前需要向支付网关确认的信息

开发前,需要向 Pay a Trader 获取或确认以下信息:

  • 创建支付订单的正式 API 地址
  • 商户 ID、API Key 或其他认证凭据
  • 请求字段及金额单位
  • 支持的币种
  • 签名算法和字段排序规则
  • 成功、取消和失败返回地址的要求
  • webhook 地址的配置方式
  • webhook 签名验证规则
  • 测试环境及测试凭据
  • 支付状态值和退款通知格式
  • 是否已有 Gravity Forms 官方或第三方 Add-On

如果已经有维护良好的 Gravity Forms 支付 Add-On,应优先使用。此类 Add-On 通常已经处理 Feed 配置、支付状态、重复通知、日志和退款等问题。

重要注意事项

  • API 密钥只能保存在服务端,建议存入 wp-config.php、环境变量或安全的配置存储。
  • 金额必须由服务端重新计算,不能信任用户提交的隐藏字段。
  • 每笔付款都要使用唯一订单编号,并做好幂等处理,防止重复创建订单或重复记账。
  • webhook 收到“已付款”事件后,还要核对订单号、金额、币种和商户账号。
  • 日志中不能记录完整密钥、银行卡信息或未经处理的个人数据。
  • 为支付接口设置合理的超时时间,并在出错时向用户显示可恢复的提示。
  • 如果启用了 Gravity Forms AJAX 提交,需要测试外部重定向。某些定制主题或前端脚本可能会拦截跳转。
  • 正式上线前,应分别测试付款成功、付款失败、用户取消、重复通知、接口超时,以及付款后未返回网站等情况。

备注:内容仅供参考。