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
相关产品推荐
相关产品推荐

