You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WooCommerce自定义支付网关Webhook失效返回400错误求助

WooCommerce自定义支付网关Webhook回调400故障修复

故障现象

  • 支付发起、跳转第三方支付平台流程运行正常
  • 支付平台推送的Webhook回调请求返回HTTP 400状态码,响应内容为-1
  • 回调逻辑无法正常执行,站点侧订单状态无法自动更新为已支付

回调规则说明

支付完成后平台会以GET请求向配置的回调地址https://example.com/?wc-api=wc_custom推送支付状态,服务端需按照约定规则拼接字段生成SHA256数字签名,与请求携带的签名做比对校验,校验通过后方可更新订单状态。

代码问题定位

  • 未注册Webhook钩子、参数获取逻辑错误:custom_webhook方法开头直接使用未定义的$order_id变量初始化订单对象,没有从GET请求中读取支付平台传回的external_invoice订单ID字段,直接触发PHP致命错误返回400;多数情况下开发者会漏掉WooCommerce API回调的钩子注册,导致框架无法正确路由回调请求
  • 签名校验逻辑完全颠倒:现有代码在签名不相等时执行支付完成逻辑,签名匹配时反而退出返回签名错误
  • 签名计算规则不统一:发起支付时对金额做了取整格式化(number_format($sum, 0, '.', '')),回调时计算签名直接使用订单原始总金额,没有和支付平台传回的金额、统一格式化规则对齐,导致签名校验永远无法通过
  • 嵌套函数定义风险:将签名生成函数定义在类方法内部,多次调用方法时会触发「函数已声明」的致命错误
  • 缺少前置校验:没有判断回调必填参数是否存在、订单是否合法、订单是否已经处理过,容易出现重复处理、异常报错
  • 表单语法错误:支付表单中act字段的input标签id属性多了冗余空格,可能导致部分参数提交异常

修复步骤

  • 在支付网关类的构造函数中添加Webhook钩子注册代码:add_action( 'woocommerce_api_wc_custom', array( $this, 'custom_webhook' ) );,确保WooCommerce能正确识别回调路由
  • 将嵌套在方法内的签名生成函数提取为类私有方法,避免重复定义报错
  • 修正回调参数获取流程:从GET请求中读取订单ID、金额、签名等字段,先做必填项校验、订单合法性校验
  • 统一签名计算的金额格式化规则,和发起支付时的处理逻辑保持一致
  • 修正签名判断条件:签名不匹配时直接返回错误,签名校验通过后再执行订单支付完成逻辑
  • 修复支付表单的HTML语法错误

修复后完整代码

<?php
// 签名生成方法提取为类私有方法,禁止嵌套在公共方法内定义
private function generate_signature($merch_id, $currency, $sum, $order_id, $secretKey) {
    $hashStr = $merch_id.'{up}'.$currency.'{up}'.$sum.'{up}'.$order_id.'{up}'.$secretKey;
    return hash('sha256', $hashStr);
}

public function generate_form($order_id)
{
    $merch_id = $this->merchant_id;
    $order = new WC_Order($order_id);
    $sum = $order->get_total();
    // 金额格式化规则全局统一
    $sum = number_format($sum, 0, '.', '');
    $currency = $this->currency_type;
    $secretKey = $this->merchant_secretkey;

    $description = sprintf(__('Payment for Order #%1$s', 'payme'), $order_id);
    $label_pay = __('Pay', 'payme');
    $label_cancel = __('Cancel payment and return back', 'payme');
    
    $signature = $this->generate_signature($merch_id, $currency, $sum, $order_id, $secretKey);

    $form = <<<FORM
<form action="{$this->checkout_url}" method="get">
    <input id="amount" name="amount" type="hidden" value="$sum" />
    <!-- 修复原input标签id属性多余空格的语法错误 -->
    <input id="act" name="act" type="hidden" value="request_payment" />
    <input id="test" name="test" type="hidden" value="{$this->test_mode}" />
    <input id="form" name="form" type="hidden" value="Y" />
    <input id="coin" name="coin" type="hidden" value="{$this->coin_type}" />
    <input id="id" name="id" type="hidden" value="$merch_id" />
    <input id="currency" name="currency" type="hidden" value="$currency" />
    <input id="external_invoice" name="external_invoice" type="hidden" value="$order_id" />
    <input id="message" name="message" type="hidden" value="$description" />
    <input id="signature" name="signature" type="hidden" value="$signature" />
    <input type="submit" class="button alt" id="submit_payme_form" value="$label_pay">
    <a class="button cancel" href="{$order->get_cancel_order_url()}">$label_cancel</a>
</form>
FORM;

    return $form;
}

public function process_payment($order_id)
{
    $order = new WC_Order($order_id);
    return [
        'result' => 'success',
        'redirect' => add_query_arg(
            'order_pay',
            $order->get_id(),
            add_query_arg('key', $order->get_order_key(), $order->get_checkout_payment_url(true))
        )
    ];
}

public function receipt_page($order_id)
{
    echo '<p>' . __('Thank you for your order, press "Pay" button to continue.', 'payme') . '</p>';
    echo $this->generate_form($order_id);
}

/**
 * 修复后的Webhook回调处理方法
 */
public function custom_webhook()
{
    // 校验必填请求参数
    $required_params = ['id', 'currency', 'amount_in_currency', 'external_invoice', 'signature'];
    foreach ($required_params as $param) {
        if (!isset($_GET[$param])) {
            wp_die('Missing required parameters', 400);
        }
    }

    // 读取并清洗请求参数
    $merch_id = sanitize_text_field($_GET['id']);
    $currency = sanitize_text_field($_GET['currency']);
    $callback_amount = sanitize_text_field($_GET['amount_in_currency']);
    $order_id = intval($_GET['external_invoice']);
    $callback_signature = sanitize_text_field($_GET['signature']);
    $secretKey = $this->merchant_secretkey;

    // 校验订单合法性
    $order = wc_get_order($order_id);
    if (!$order) {
        wp_die('Invalid order', 400);
    }

    // 已支付订单直接返回成功,避免重复处理
    if ($order->is_paid()) {
        exit('OK');
    }

    // 按统一规则格式化金额、计算签名
    $formatted_amount = number_format($callback_amount, 0, '.', '');
    $expected_signature = $this->generate_signature($merch_id, $currency, $formatted_amount, $order_id, $secretKey);

    // 签名校验不通过直接返回400错误
    if (!hash_equals($expected_signature, $callback_signature)) {
        wp_die('Incorrect Digital Signature', 400);
    }

    // 校验通过,标记订单支付完成、清空购物车
    $order->payment_complete();
    WC()->cart->empty_cart();
    exit('OK');
}
?>

内容的提问来源于stack exchange,提问作者Nick Kaleta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 21:21:39