diff --git a/includes/blocks/class-yoone-blocks-moneris.php b/includes/blocks/class-yoone-blocks-moneris.php index 0c0bd18..e7088c1 100644 --- a/includes/blocks/class-yoone-blocks-moneris.php +++ b/includes/blocks/class-yoone-blocks-moneris.php @@ -19,6 +19,7 @@ function yoone_moneris_store_payment_data($order, $result, $request) { } elseif (is_array($result) && isset($result['payment_method'])) { $pm = (string) $result['payment_method']; } + error_log('【Yoone Moneris Blocks】订单处理 hook,payment_method=' . ($pm ?: '未提供')); if ('yoone_moneris' !== $pm) { return; } @@ -34,15 +35,22 @@ function yoone_moneris_store_payment_data($order, $result, $request) { } else { $payload = is_array($data) ? $data : array(); } + error_log('【Yoone Moneris Blocks】解析 payment_method_data:' . print_r($payload, true)); if (! empty($payload)) { $order->update_meta_data('_yoone_moneris_pm_data', $payload); $order->save(); + error_log('【Yoone Moneris Blocks】已保存 Blocks 支付数据到订单 _yoone_moneris_pm_data'); + } else { + error_log('【Yoone Moneris Blocks】未收到有效的 payment_method_data'); } } // Only declare the Blocks payment method class when the Blocks base type exists to prevent type resolution issues. if (class_exists('Automattic\WooCommerce\Blocks\Payments\PaymentMethodType') && ! class_exists('Yoone_Moneris_Blocks_Payment_Method')) { - class Yoone_Moneris_Blocks_Payment_Method extends Automattic\WooCommerce\Blocks\Payments\PaymentMethodType { + /** + * 使用 use 导入基类,避免 IDE 报 Undefined type + */ +class Yoone_Moneris_Blocks_Payment_Method extends Automattic\WooCommerce\Blocks\Payments\PaymentMethodType { /** * Unique name of the payment method in Blocks. * This MUST match the gateway id used by WooCommerce (yoone_moneris). diff --git a/includes/blocks/ide-stub-wc-blocks.php b/includes/blocks/ide-stub-wc-blocks.php new file mode 100644 index 0000000..6dcd559 --- /dev/null +++ b/includes/blocks/ide-stub-wc-blocks.php @@ -0,0 +1,34 @@ +get_posted_card(); + error_log('【Yoone Moneris】validate_fields 读取到卡片字段:' . print_r($card, true)); if (empty($card['number']) || empty($card['exp_month']) || empty($card['exp_year']) || empty($card['cvc'])) { - wc_add_notice(__('请填写完整的银行卡信息。' . $card, 'yoone-moneris'), 'error'); + wc_add_notice(__('请填写完整的银行卡信息。', 'yoone-moneris'), 'error'); + error_log('【Yoone Moneris】validate_fields 缺少字段:number=' . (empty($card['number']) ? '空' : '已填') . ' exp_month=' . (empty($card['exp_month']) ? '空' : $card['exp_month']) . ' exp_year=' . (empty($card['exp_year']) ? '空' : $card['exp_year']) . ' cvc=' . (empty($card['cvc']) ? '空' : '已填')); return false; } return true; @@ -329,9 +331,13 @@ class Yoone_Gateway_Moneris extends WC_Payment_Gateway_CC */ protected function get_posted_card() { + // 记录原始支付方式选择 + $raw_pm = isset($_POST['payment_method']) ? wc_clean(wp_unslash($_POST['payment_method'])) : ''; + error_log('【Yoone Moneris】get_posted_card payment_method=' . ($raw_pm ?: '未提交') . ' 当前网关=' . $this->id); // 如果当前提交并非选择我们网关,直接返回空卡数据,避免误读其他网关字段 - $pm = isset($_POST['payment_method']) ? wc_clean(wp_unslash($_POST['payment_method'])) : ''; + $pm = $raw_pm; if ($pm && $pm !== $this->id) { + error_log('【Yoone Moneris】get_posted_card 非本网关提交,返回空卡数据'); return array('number' => '', 'exp_month' => '', 'exp_year' => '', 'cvc' => ''); } @@ -377,6 +383,8 @@ class Yoone_Gateway_Moneris extends WC_Payment_Gateway_CC 'card_cvc', )); + error_log('【Yoone Moneris】get_posted_card 原始读取:number=' . (isset($number) ? ('len=' . strlen((string)$number)) : '未读') . ' expiry_raw=' . ($expiry_raw ?: '空') . ' cvc=' . ($cvc ? ('len=' . strlen((string)$cvc)) : '空')); + // 如果没有组合有效期,尝试读取分拆的 月/年 字段 $exp_month = $read_post(array( $this->id . '-exp-month', @@ -396,6 +404,7 @@ class Yoone_Gateway_Moneris extends WC_Payment_Gateway_CC 'card-expiry-year', 'expiry_year', )); + error_log('【Yoone Moneris】get_posted_card 原始读取:exp_month=' . ($exp_month ?: '空') . ' exp_year=' . ($exp_year ?: '空')); // 规范化:仅保留数字 $number = preg_replace('/\D+/', '', (string) $number); @@ -420,6 +429,9 @@ class Yoone_Gateway_Moneris extends WC_Payment_Gateway_CC $exp_year = '20' . $exp_year; } + $masked = $number ? str_repeat('*', max(strlen($number) - 4, 0)) . substr($number, -4) : ''; + error_log('【Yoone Moneris】get_posted_card 规范化:card=' . ($masked ?: '空') . ' len=' . strlen($number) . ' exp_month=' . ($exp_month ?: '空') . ' exp_year=' . ($exp_year ?: '空') . ' cvc_len=' . ($cvc ? strlen($cvc) : 0)); + return array( 'number' => $number, 'exp_month' => $exp_month, @@ -441,10 +453,15 @@ class Yoone_Gateway_Moneris extends WC_Payment_Gateway_CC // 当用户选择“已保存的卡”,此字段会传递一个 token 的 ID;当选择“使用新卡”,该字段值为 'new'。 // 注意:此字段由 Woo 默认信用卡表单生成,适用于经典结账;如果使用 Blocks 结账,需要对应 Blocks 集成来产生等效数据。 $field = 'wc-' . $this->id . '-payment-token'; + if (isset($_POST[$field])) { + $posted = wc_clean(wp_unslash($_POST[$field])); + error_log('【Yoone Moneris】get_selected_token_id 提交字段 ' . $field . ' = ' . $posted); + } if (isset($_POST[$field]) && 'new' !== $_POST[$field]) { // 将提交的令牌 ID 转为整数,后续用 WC_Payment_Tokens::get( $id ) 读取具体令牌对象 return absint($_POST[$field]); } + error_log('【Yoone Moneris】get_selected_token_id 未选择已保存令牌或选择了新卡'); return 0; } @@ -484,15 +501,29 @@ class Yoone_Gateway_Moneris extends WC_Payment_Gateway_CC */ protected function create_wc_token($res, $user_id) { + error_log('【yoone moneris 】创建 WC 支付令牌,tokenize_card 返回结果' . print_r($res, true)); $token = new WC_Payment_Token_CC(); + // 兼容 data_key 命名 + if (empty($res['token']) && ! empty($res['data_key'])) { + $res['token'] = $res['data_key']; + error_log('【Yoone Moneris】create_wc_token 使用 data_key 作为 token'); + } $token->set_token($res['token']); $token->set_gateway_id($this->id); $token->set_user_id($user_id); $token->set_last4(substr(preg_replace('/\D+/', '', (string) $res['last4'] ?? ''), -4) ?: '0000'); $token->set_expiry_month(preg_replace('/\D+/', '', (string) ($res['exp_month'] ?? '')) ?: '01'); $token->set_expiry_year(preg_replace('/\D+/', '', (string) ($res['exp_year'] ?? '')) ?: (date('Y') + 1)); + // 设置卡类型(若有),避免 Woo 对必填字段校验失败 + if (! empty($res['brand'])) { + $token->set_card_type((string) $res['brand']); + } $token->save(); - return $token->get_id(); + $tid = $token->get_id(); + if (! $tid) { + error_log('【Yoone Moneris】create_wc_token 保存失败:WC_Token 未生成 ID,检查必填字段。'); + } + return $tid; } /** @@ -504,17 +535,22 @@ class Yoone_Gateway_Moneris extends WC_Payment_Gateway_CC */ public function process_payment($order_id) { + error_log('【Yoone Moneris】process_payment 开始,order_id=' . $order_id); $order = wc_get_order($order_id); + if (! $order) { + error_log('【Yoone Moneris】无法获取订单对象,终止'); + return array('result' => 'fail'); + } $user_id = $order->get_user_id(); - - // 支持 Blocks:若从 Store API 捕获到支付数据,优先使用该数据 - $blocks_data = is_object($order) && method_exists($order, 'get_meta') ? (array) $order->get_meta('_yoone_moneris_pm_data') : array(); + $pm_meta = $order->get_meta('_yoone_moneris_pm_data', true); + if (! empty($pm_meta)) { + error_log('【Yoone Moneris】订单存在 Blocks/Store API 支付数据 _yoone_moneris_pm_data=' . print_r($pm_meta, true)); + } else { + error_log('【Yoone Moneris】订单未找到 _yoone_moneris_pm_data 元数据'); + } // 选择已保存的令牌或创建新令牌 $selected_token_id = $this->get_selected_token_id(); - if (! $selected_token_id && ! empty($blocks_data['saved_token_id']) && 'new' !== $blocks_data['saved_token_id']) { - $selected_token_id = absint($blocks_data['saved_token_id']); - } $token_string = ''; if ($selected_token_id) { @@ -527,31 +563,40 @@ class Yoone_Gateway_Moneris extends WC_Payment_Gateway_CC } } else { // 创建新令牌(占位) - $card = $this->get_posted_card(); - // 若 Blocks 提供了卡片数据,使用其值覆盖经典表单读取结果 - if (! empty($blocks_data)) { - $normalize = function($v){ return preg_replace('/\D+/', '', (string) $v); }; - $number = isset($blocks_data['number']) ? $normalize($blocks_data['number']) : ''; - $cvc = isset($blocks_data['cvc']) ? $normalize($blocks_data['cvc']) : ''; - $exp_month = isset($blocks_data['exp_month']) ? $normalize($blocks_data['exp_month']) : ''; - $exp_year = isset($blocks_data['exp_year']) ? $normalize($blocks_data['exp_year']) : ''; - if (strlen($exp_year) === 2) { $exp_year = '20' . $exp_year; } + // 优先使用 Blocks/Store API 保存到订单的支付数据 + $card = array(); + if (is_array($pm_meta) && ! empty($pm_meta)) { $card = array( - 'number' => $number ?: $card['number'], - 'exp_month' => $exp_month ?: $card['exp_month'], - 'exp_year' => $exp_year ?: $card['exp_year'], - 'cvc' => $cvc ?: $card['cvc'], + 'number' => preg_replace('/\D+/', '', (string) ($pm_meta['number'] ?? '')), + 'exp_month' => preg_replace('/\D+/', '', (string) ($pm_meta['exp_month'] ?? '')), + 'exp_year' => preg_replace('/\D+/', '', (string) ($pm_meta['exp_year'] ?? '')), + 'cvc' => preg_replace('/\D+/', '', (string) ($pm_meta['cvc'] ?? '')), ); + error_log('【Yoone Moneris】使用 Blocks/Store API 数据作为卡片来源:' . print_r($card, true)); + } + if (empty($card['number']) || empty($card['exp_month']) || empty($card['exp_year']) || empty($card['cvc'])) { + // 退回到经典结账表单的 POST 字段 + $card = $this->get_posted_card(); + error_log('【Yoone Moneris】使用经典结账 POST 字段作为卡片来源:' . print_r($card, true)); } $res = $this->api()->tokenize_card($card); + error_log('【Yoone Moneris】tokenize_card 返回=' . print_r($res, true)); if (empty($res['success'])) { wc_add_notice(__('令牌化失败:', 'yoone-moneris') . $res['error'], 'error'); + error_log('【Yoone Moneris】令牌化失败:' . ($res['error'] ?? '未知错误')); return array('result' => 'fail'); } + // 兼容 data_key 命名 + if (empty($res['token']) && ! empty($res['data_key'])) { + $res['token'] = $res['data_key']; + error_log('【Yoone Moneris】tokenize_card 返回使用 data_key 作为 token'); + } $token_string = $res['token']; + error_log('【Yoone Moneris】新令牌创建成功,token_len=' . strlen($token_string) . ' last4=' . ($res['last4'] ?? '')); // 保存到用户 if ($user_id) { $wc_token_id = $this->create_wc_token($res + ['exp_month' => $card['exp_month'], 'exp_year' => $card['exp_year']], $user_id); + error_log('【Yoone Moneris】已保存令牌到用户,wc_token_id=' . $wc_token_id . ' user_id=' . $user_id); $order->add_payment_token($wc_token_id); } } @@ -559,15 +604,19 @@ class Yoone_Gateway_Moneris extends WC_Payment_Gateway_CC // 首笔扣款 $amount = $order->get_total(); $currency = $order->get_currency(); + error_log('【Yoone Moneris】准备扣款:amount=' . $amount . ' currency=' . $currency . ' order_id=' . $order_id); $charge = $this->api()->charge_token($token_string, $amount, $currency, $order_id); + error_log('【Yoone Moneris】charge_token 返回=' . print_r($charge, true)); if (empty($charge['success'])) { wc_add_notice(__('支付失败:', 'yoone-moneris') . ($charge['error'] ?? ''), 'error'); + error_log('【Yoone Moneris】支付失败:' . ($charge['error'] ?? '未知错误')); return array('result' => 'fail'); } // 标记订单已支付 $order->payment_complete($charge['transaction_id'] ?? ''); $order->add_order_note(sprintf('Moneris 首付成功,交易号:%s', $charge['transaction_id'] ?? 'N/A')); + error_log('【Yoone Moneris】支付成功,transaction_id=' . ($charge['transaction_id'] ?? '')); return array( 'result' => 'success', diff --git a/includes/class-yoone-moneris-api.php b/includes/class-yoone-moneris-api.php index 6ff575a..32ee23b 100644 --- a/includes/class-yoone-moneris-api.php +++ b/includes/class-yoone-moneris-api.php @@ -49,7 +49,7 @@ class Yoone_Moneris_API implements Yoone_Moneris_API_Interface { $this->http_method = isset( $config['http_method'] ) ? strtoupper( (string) $config['http_method'] ) : $this->http_method; $this->timeout = isset( $config['timeout'] ) ? absint( $config['timeout'] ) : $this->timeout; } - yoone_moneris_log_debug( 'Moneris API 配置', array( + error_log( 'Moneris API 配置' . print_r( array( 'store_id' => $this->store_id, 'sandbox' => $this->sandbox, 'country_code' => $this->country_code, @@ -61,7 +61,7 @@ class Yoone_Moneris_API implements Yoone_Moneris_API_Interface { 'path_override'=> $this->path_override, 'http_method' => $this->http_method, 'timeout' => $this->timeout, - ) ); + ) ,true)); } /** @@ -88,7 +88,11 @@ class Yoone_Moneris_API implements Yoone_Moneris_API_Interface { 'cvdValue' => preg_replace( '/\D+/', '', (string) $card['cvc'] ), ); } + $num = isset($card['number']) ? preg_replace('/\D+/', '', (string) $card['number']) : ''; + $masked = $num ? str_repeat('*', max(strlen($num) - 4, 0)) . substr($num, -4) : ''; + error_log('【Yoone Moneris API】tokenize_card 请求:last4=' . ($num ? substr($num, -4) : '') . ' len=' . strlen($num) . ' expdate=' . $payload['expdate'] . ' cryptType=' . $payload['cryptType']); $res = $this->send_moneris_xml( 'res_add_cc', $payload ); + error_log('$res11111111'. print_r($res, true)); if ( $res['ok'] ) { return array( 'success' => true, @@ -124,6 +128,7 @@ class Yoone_Moneris_API implements Yoone_Moneris_API_Interface { 'amount' => $this->format_amount( $amount ), 'cryptType' => $this->crypt_type, ); + error_log('【Yoone Moneris API】charge_token 请求:type=' . $type . ' token_len=' . strlen($token) . ' amount=' . $payload['amount'] . ' currency=' . $currency . ' orderId=' . $order_id); $res = $this->send_moneris_xml( $type, $payload ); if ( $res['ok'] ) { return array( @@ -176,6 +181,7 @@ class Yoone_Moneris_API implements Yoone_Moneris_API_Interface { if ( ! $endpoint ) { return array( 'ok' => false, 'error' => 'Endpoint not configured' ); } + error_log('【Yoone Moneris API】发送 XML:type=' . $type . ' endpoint=' . $endpoint); // 构建请求体 $body = array( @@ -203,26 +209,31 @@ class Yoone_Moneris_API implements Yoone_Moneris_API_Interface { // 仅支持 POST $resp = wp_remote_post( $endpoint, $args ); if ( is_wp_error( $resp ) ) { + error_log('【Yoone Moneris API】HTTP 错误:' . $resp->get_error_message()); return array( 'ok' => false, 'error' => $resp->get_error_message() ); } $code = wp_remote_retrieve_response_code( $resp ); $raw = wp_remote_retrieve_body( $resp ); if ( $code < 200 || $code >= 300 ) { + error_log('【Yoone Moneris API】HTTP 非 2xx:code=' . $code . ' body=' . substr($raw, 0, 200)); return array( 'ok' => false, 'error' => 'HTTP ' . $code . ' ' . $raw ); } // 解析 XML $parsed = $this->parse_moneris_xml( $raw ); if ( ! $parsed['parsed'] ) { + error_log('【Yoone Moneris API】XML 解析失败:' . ($parsed['error'] ?? 'Unknown')); return array( 'ok' => false, 'error' => 'Parse error: ' . ( $parsed['error'] ?? 'Unknown' ) ); } $receipt = $parsed['receipt']; $approved = $this->is_approved( $receipt ); if ( ! $approved ) { $message = isset( $receipt['message'] ) ? $receipt['message'] : ( $receipt['Message'] ?? 'Declined' ); + error_log('【Yoone Moneris API】交易未批准:ResponseCode=' . ($receipt['responseCode'] ?? $receipt['response_code'] ?? '') . ' message=' . $message); return array( 'ok' => false, 'error' => $message ); } - + error_log('【Yoone Moneris API】交易批准:ResponseCode=' . ($receipt['responseCode'] ?? $receipt['response_code'] ?? '') . ' txnNumber=' . ($receipt['txnNumber'] ?? $receipt['txn_number'] ?? '')); + error_log('receipt'. print_r($receipt,true)); return array( 'ok' => true, 'receipt' => $receipt ); }