Compare commits

..

No commits in common. "4727881e05af50eba7045b7730f09ef29ee8b640" and "ca5e7a62aed465f93f33ce34f357ce59926f732a" have entirely different histories.

4 changed files with 28 additions and 164 deletions

View File

@ -19,7 +19,6 @@ function yoone_moneris_store_payment_data($order, $result, $request) {
} elseif (is_array($result) && isset($result['payment_method'])) { } elseif (is_array($result) && isset($result['payment_method'])) {
$pm = (string) $result['payment_method']; $pm = (string) $result['payment_method'];
} }
error_log('【Yoone Moneris Blocks】订单处理 hookpayment_method=' . ($pm ?: '未提供'));
if ('yoone_moneris' !== $pm) { if ('yoone_moneris' !== $pm) {
return; return;
} }
@ -35,13 +34,9 @@ function yoone_moneris_store_payment_data($order, $result, $request) {
} else { } else {
$payload = is_array($data) ? $data : array(); $payload = is_array($data) ? $data : array();
} }
error_log('【Yoone Moneris Blocks】解析 payment_method_data' . print_r($payload, true));
if (! empty($payload)) { if (! empty($payload)) {
$order->update_meta_data('_yoone_moneris_pm_data', $payload); $order->update_meta_data('_yoone_moneris_pm_data', $payload);
$order->save(); $order->save();
error_log('【Yoone Moneris Blocks】已保存 Blocks 支付数据到订单 _yoone_moneris_pm_data');
} else {
error_log('【Yoone Moneris Blocks】未收到有效的 payment_method_data');
} }
} }

View File

@ -293,13 +293,11 @@ class Yoone_Gateway_Moneris extends WC_Payment_Gateway_CC
if ($selected_token_id) { if ($selected_token_id) {
return true; return true;
} }
// 新卡:需要确保卡片字段完整 // 新卡:需要确保卡片字段完整
$card = $this->get_posted_card(); $card = $this->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'])) { if (empty($card['number']) || empty($card['exp_month']) || empty($card['exp_year']) || empty($card['cvc'])) {
wc_add_notice(__('请填写完整的银行卡信息。', 'yoone-moneris'), 'error'); wc_add_notice(__('请填写完整的银行卡信息。' . $card, '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 false;
} }
return true; return true;
@ -331,13 +329,9 @@ class Yoone_Gateway_Moneris extends WC_Payment_Gateway_CC
*/ */
protected function get_posted_card() 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 = $raw_pm; $pm = isset($_POST['payment_method']) ? wc_clean(wp_unslash($_POST['payment_method'])) : '';
if ($pm && $pm !== $this->id) { if ($pm && $pm !== $this->id) {
error_log('【Yoone Moneris】get_posted_card 非本网关提交,返回空卡数据');
return array('number' => '', 'exp_month' => '', 'exp_year' => '', 'cvc' => ''); return array('number' => '', 'exp_month' => '', 'exp_year' => '', 'cvc' => '');
} }
@ -383,8 +377,6 @@ class Yoone_Gateway_Moneris extends WC_Payment_Gateway_CC
'card_cvc', '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( $exp_month = $read_post(array(
$this->id . '-exp-month', $this->id . '-exp-month',
@ -404,7 +396,6 @@ class Yoone_Gateway_Moneris extends WC_Payment_Gateway_CC
'card-expiry-year', 'card-expiry-year',
'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); $number = preg_replace('/\D+/', '', (string) $number);
@ -429,9 +420,6 @@ class Yoone_Gateway_Moneris extends WC_Payment_Gateway_CC
$exp_year = '20' . $exp_year; $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( return array(
'number' => $number, 'number' => $number,
'exp_month' => $exp_month, 'exp_month' => $exp_month,
@ -453,15 +441,10 @@ class Yoone_Gateway_Moneris extends WC_Payment_Gateway_CC
// 当用户选择“已保存的卡”,此字段会传递一个 token 的 ID当选择“使用新卡”该字段值为 'new'。 // 当用户选择“已保存的卡”,此字段会传递一个 token 的 ID当选择“使用新卡”该字段值为 'new'。
// 注意:此字段由 Woo 默认信用卡表单生成,适用于经典结账;如果使用 Blocks 结账,需要对应 Blocks 集成来产生等效数据。 // 注意:此字段由 Woo 默认信用卡表单生成,适用于经典结账;如果使用 Blocks 结账,需要对应 Blocks 集成来产生等效数据。
$field = 'wc-' . $this->id . '-payment-token'; $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]) { if (isset($_POST[$field]) && 'new' !== $_POST[$field]) {
// 将提交的令牌 ID 转为整数,后续用 WC_Payment_Tokens::get( $id ) 读取具体令牌对象 // 将提交的令牌 ID 转为整数,后续用 WC_Payment_Tokens::get( $id ) 读取具体令牌对象
return absint($_POST[$field]); return absint($_POST[$field]);
} }
error_log('【Yoone Moneris】get_selected_token_id 未选择已保存令牌或选择了新卡');
return 0; return 0;
} }
@ -501,29 +484,15 @@ class Yoone_Gateway_Moneris extends WC_Payment_Gateway_CC
*/ */
protected function create_wc_token($res, $user_id) 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(); $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_token($res['token']);
$token->set_gateway_id($this->id); $token->set_gateway_id($this->id);
$token->set_user_id($user_id); $token->set_user_id($user_id);
$token->set_last4(substr(preg_replace('/\D+/', '', (string) $res['last4'] ?? ''), -4) ?: '0000'); $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_month(preg_replace('/\D+/', '', (string) ($res['exp_month'] ?? '')) ?: '01');
$token->set_expiry_year(preg_replace('/\D+/', '', (string) ($res['exp_year'] ?? '')) ?: (date('Y') + 1)); $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(); $token->save();
$tid = $token->get_id(); return $token->get_id();
if (! $tid) {
error_log('【Yoone Moneris】create_wc_token 保存失败WC_Token 未生成 ID检查必填字段。');
}
return $tid;
} }
/** /**
@ -535,22 +504,17 @@ class Yoone_Gateway_Moneris extends WC_Payment_Gateway_CC
*/ */
public function process_payment($order_id) public function process_payment($order_id)
{ {
error_log('【Yoone Moneris】process_payment 开始order_id=' . $order_id);
$order = wc_get_order($order_id); $order = wc_get_order($order_id);
if (! $order) {
error_log('【Yoone Moneris】无法获取订单对象终止');
return array('result' => 'fail');
}
$user_id = $order->get_user_id(); $user_id = $order->get_user_id();
$pm_meta = $order->get_meta('_yoone_moneris_pm_data', true);
if (! empty($pm_meta)) { // 支持 Blocks若从 Store API 捕获到支付数据,优先使用该数据
error_log('【Yoone Moneris】订单存在 Blocks/Store API 支付数据 _yoone_moneris_pm_data=' . print_r($pm_meta, true)); $blocks_data = is_object($order) && method_exists($order, 'get_meta') ? (array) $order->get_meta('_yoone_moneris_pm_data') : array();
} else {
error_log('【Yoone Moneris】订单未找到 _yoone_moneris_pm_data 元数据');
}
// 选择已保存的令牌或创建新令牌 // 选择已保存的令牌或创建新令牌
$selected_token_id = $this->get_selected_token_id(); $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 = ''; $token_string = '';
if ($selected_token_id) { if ($selected_token_id) {
@ -563,40 +527,31 @@ class Yoone_Gateway_Moneris extends WC_Payment_Gateway_CC
} }
} else { } else {
// 创建新令牌(占位) // 创建新令牌(占位)
// 优先使用 Blocks/Store API 保存到订单的支付数据 $card = $this->get_posted_card();
$card = array(); // 若 Blocks 提供了卡片数据,使用其值覆盖经典表单读取结果
if (is_array($pm_meta) && ! empty($pm_meta)) { 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; }
$card = array( $card = array(
'number' => preg_replace('/\D+/', '', (string) ($pm_meta['number'] ?? '')), 'number' => $number ?: $card['number'],
'exp_month' => preg_replace('/\D+/', '', (string) ($pm_meta['exp_month'] ?? '')), 'exp_month' => $exp_month ?: $card['exp_month'],
'exp_year' => preg_replace('/\D+/', '', (string) ($pm_meta['exp_year'] ?? '')), 'exp_year' => $exp_year ?: $card['exp_year'],
'cvc' => preg_replace('/\D+/', '', (string) ($pm_meta['cvc'] ?? '')), 'cvc' => $cvc ?: $card['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); $res = $this->api()->tokenize_card($card);
error_log('【Yoone Moneris】tokenize_card 返回=' . print_r($res, true));
if (empty($res['success'])) { if (empty($res['success'])) {
wc_add_notice(__('令牌化失败:', 'yoone-moneris') . $res['error'], 'error'); wc_add_notice(__('令牌化失败:', 'yoone-moneris') . $res['error'], 'error');
error_log('【Yoone Moneris】令牌化失败' . ($res['error'] ?? '未知错误'));
return array('result' => 'fail'); 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']; $token_string = $res['token'];
error_log('【Yoone Moneris】新令牌创建成功token_len=' . strlen($token_string) . ' last4=' . ($res['last4'] ?? ''));
// 保存到用户 // 保存到用户
if ($user_id) { if ($user_id) {
$wc_token_id = $this->create_wc_token($res + ['exp_month' => $card['exp_month'], 'exp_year' => $card['exp_year']], $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); $order->add_payment_token($wc_token_id);
} }
} }
@ -604,19 +559,15 @@ class Yoone_Gateway_Moneris extends WC_Payment_Gateway_CC
// 首笔扣款 // 首笔扣款
$amount = $order->get_total(); $amount = $order->get_total();
$currency = $order->get_currency(); $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); $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'])) { if (empty($charge['success'])) {
wc_add_notice(__('支付失败:', 'yoone-moneris') . ($charge['error'] ?? ''), 'error'); wc_add_notice(__('支付失败:', 'yoone-moneris') . ($charge['error'] ?? ''), 'error');
error_log('【Yoone Moneris】支付失败' . ($charge['error'] ?? '未知错误'));
return array('result' => 'fail'); return array('result' => 'fail');
} }
// 标记订单已支付 // 标记订单已支付
$order->payment_complete($charge['transaction_id'] ?? ''); $order->payment_complete($charge['transaction_id'] ?? '');
$order->add_order_note(sprintf('Moneris 首付成功,交易号:%s', $charge['transaction_id'] ?? 'N/A')); $order->add_order_note(sprintf('Moneris 首付成功,交易号:%s', $charge['transaction_id'] ?? 'N/A'));
error_log('【Yoone Moneris】支付成功transaction_id=' . ($charge['transaction_id'] ?? ''));
return array( return array(
'result' => 'success', 'result' => 'success',

View File

@ -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->http_method = isset( $config['http_method'] ) ? strtoupper( (string) $config['http_method'] ) : $this->http_method;
$this->timeout = isset( $config['timeout'] ) ? absint( $config['timeout'] ) : $this->timeout; $this->timeout = isset( $config['timeout'] ) ? absint( $config['timeout'] ) : $this->timeout;
} }
error_log( 'Moneris API 配置' . print_r( array( yoone_moneris_log_debug( 'Moneris API 配置', array(
'store_id' => $this->store_id, 'store_id' => $this->store_id,
'sandbox' => $this->sandbox, 'sandbox' => $this->sandbox,
'country_code' => $this->country_code, 'country_code' => $this->country_code,
@ -61,7 +61,7 @@ class Yoone_Moneris_API implements Yoone_Moneris_API_Interface {
'path_override'=> $this->path_override, 'path_override'=> $this->path_override,
'http_method' => $this->http_method, 'http_method' => $this->http_method,
'timeout' => $this->timeout, 'timeout' => $this->timeout,
) ,true)); ) );
} }
/** /**
@ -88,11 +88,7 @@ class Yoone_Moneris_API implements Yoone_Moneris_API_Interface {
'cvdValue' => preg_replace( '/\D+/', '', (string) $card['cvc'] ), '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 ); $res = $this->send_moneris_xml( 'res_add_cc', $payload );
error_log('$res11111111'. print_r($res, true));
if ( $res['ok'] ) { if ( $res['ok'] ) {
return array( return array(
'success' => true, 'success' => true,
@ -121,51 +117,21 @@ class Yoone_Moneris_API implements Yoone_Moneris_API_Interface {
if ( empty( $this->store_id ) || empty( $this->api_token ) ) { if ( empty( $this->store_id ) || empty( $this->api_token ) ) {
return array( 'success' => false, 'error' => 'Missing Moneris credentials' ); return array( 'success' => false, 'error' => 'Missing Moneris credentials' );
} }
// 为避免重复 orderId 导致主机拒绝,生成唯一订单 ID
if ( ! function_exists( 'yoone_moneris_unique_order_id' ) ) {
require_once __DIR__ . '/utils/orderid.php';
}
$unique_order_id = yoone_moneris_unique_order_id( $order_id );
$type = $capture ? 'res_purchase_cc' : 'res_preauth_cc'; $type = $capture ? 'res_purchase_cc' : 'res_preauth_cc';
$payload = array( $payload = array(
'dataKey' => (string) $token, 'dataKey' => (string) $token,
'orderId' => (string) $unique_order_id, 'orderId' => (string) $order_id,
'amount' => $this->format_amount( $amount ), 'amount' => $this->format_amount( $amount ),
'cryptType' => $this->crypt_type, 'cryptType' => $this->crypt_type,
); );
error_log('【Yoone Moneris API】charge_token 请求type=' . $type . ' token_len=' . strlen($token) . ' amount=' . $payload['amount'] . ' currency=' . $currency . ' orderId=' . $unique_order_id . ' (源Woo订单ID=' . $order_id . ')');
$res = $this->send_moneris_xml( $type, $payload ); $res = $this->send_moneris_xml( $type, $payload );
if ( $res['ok'] ) { if ( $res['ok'] ) {
return array( return array(
'success' => true, 'success' => true,
'transaction_id' => (string) ( $res['receipt']['txnNumber'] ?? '' ), 'transaction_id' => (string) ( $res['receipt']['txnNumber'] ?? '' ),
'order_id_used' => $unique_order_id,
'retry' => false,
); );
} }
// 如果出现系统错误,尝试一次自动重试(更换新的唯一 orderId return array( 'success' => false, 'error' => (string) $res['error'] );
$err_msg = isset($res['error']) ? (string)$res['error'] : '';
$is_system_error = stripos($err_msg, 'SYSTEM ERROR') !== false;
if ( $is_system_error ) {
$retry_order_id = yoone_moneris_unique_order_id( $order_id );
$payload['orderId'] = (string) $retry_order_id;
error_log('【Yoone Moneris API】检测到 SYSTEM ERROR准备重试一次。新的 orderId=' . $retry_order_id);
// 短暂延时,避免立即重复错误
usleep(300 * 1000);
$res_retry = $this->send_moneris_xml( $type, $payload );
if ( $res_retry['ok'] ) {
return array(
'success' => true,
'transaction_id' => (string) ( $res_retry['receipt']['txnNumber'] ?? '' ),
'order_id_used' => $retry_order_id,
'retry' => true,
);
}
// 记录重试失败
error_log('【Yoone Moneris API】重试仍失败' . (string) ($res_retry['error'] ?? 'Unknown'));
return array( 'success' => false, 'error' => (string) ($res_retry['error'] ?? $err_msg) );
}
return array( 'success' => false, 'error' => (string) $err_msg );
} }
/** /**
@ -210,7 +176,6 @@ class Yoone_Moneris_API implements Yoone_Moneris_API_Interface {
if ( ! $endpoint ) { if ( ! $endpoint ) {
return array( 'ok' => false, 'error' => 'Endpoint not configured' ); return array( 'ok' => false, 'error' => 'Endpoint not configured' );
} }
error_log('【Yoone Moneris API】发送 XMLtype=' . $type . ' endpoint=' . $endpoint);
// 构建请求体 // 构建请求体
$body = array( $body = array(
@ -224,13 +189,6 @@ class Yoone_Moneris_API implements Yoone_Moneris_API_Interface {
$xml = $this->array_to_moneris_xml( array( 'request' => $body ) ); $xml = $this->array_to_moneris_xml( array( 'request' => $body ) );
// 日志:打印部分已脱敏的 XML 以便排查格式问题(掩码 pan、data_key、api_token
$xml_log = $xml;
$xml_log = preg_replace('/<pan>[^<]*<\/pan>/', '<pan>****</pan>', $xml_log);
$xml_log = preg_replace('/<data_key>[^<]*<\/data_key>/', '<data_key>****</data_key>', $xml_log);
$xml_log = preg_replace('/<api_token>[^<]*<\/api_token>/', '<api_token>****</api_token>', $xml_log);
error_log('【Yoone Moneris API】请求 XML脱敏前 400 字符:' . substr($xml_log, 0, 400));
$args = array( $args = array(
'method' => $this->http_method ?: 'POST', 'method' => $this->http_method ?: 'POST',
'timeout' => $this->timeout, 'timeout' => $this->timeout,
@ -245,33 +203,26 @@ class Yoone_Moneris_API implements Yoone_Moneris_API_Interface {
// 仅支持 POST // 仅支持 POST
$resp = wp_remote_post( $endpoint, $args ); $resp = wp_remote_post( $endpoint, $args );
if ( is_wp_error( $resp ) ) { if ( is_wp_error( $resp ) ) {
error_log('【Yoone Moneris API】HTTP 错误:' . $resp->get_error_message());
return array( 'ok' => false, 'error' => $resp->get_error_message() ); return array( 'ok' => false, 'error' => $resp->get_error_message() );
} }
$code = wp_remote_retrieve_response_code( $resp ); $code = wp_remote_retrieve_response_code( $resp );
$raw = wp_remote_retrieve_body( $resp ); $raw = wp_remote_retrieve_body( $resp );
if ( $code < 200 || $code >= 300 ) { if ( $code < 200 || $code >= 300 ) {
error_log('【Yoone Moneris API】HTTP 非 2xxcode=' . $code . ' body=' . substr($raw, 0, 200));
return array( 'ok' => false, 'error' => 'HTTP ' . $code . ' ' . $raw ); return array( 'ok' => false, 'error' => 'HTTP ' . $code . ' ' . $raw );
} }
// 解析 XML // 解析 XML
$parsed = $this->parse_moneris_xml( $raw ); $parsed = $this->parse_moneris_xml( $raw );
// 打印部分响应原文,便于调试
error_log('【Yoone Moneris API】响应原文前 300 字符:' . substr($raw, 0, 300));
if ( ! $parsed['parsed'] ) { if ( ! $parsed['parsed'] ) {
error_log('【Yoone Moneris API】XML 解析失败:' . ($parsed['error'] ?? 'Unknown'));
return array( 'ok' => false, 'error' => 'Parse error: ' . ( $parsed['error'] ?? 'Unknown' ) ); return array( 'ok' => false, 'error' => 'Parse error: ' . ( $parsed['error'] ?? 'Unknown' ) );
} }
$receipt = $parsed['receipt']; $receipt = $parsed['receipt'];
$approved = $this->is_approved( $receipt ); $approved = $this->is_approved( $receipt );
if ( ! $approved ) { if ( ! $approved ) {
$message = isset( $receipt['message'] ) ? $receipt['message'] : ( $receipt['Message'] ?? 'Declined' ); $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 ); 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 ); return array( 'ok' => true, 'receipt' => $receipt );
} }

View File

@ -1,33 +0,0 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* 生成用于 Moneris 的唯一订单 ID避免重复 orderId 导致主机拒绝)。
*
* 规则:<原始订单ID>-<毫秒时间戳>-<四位随机数>
* 示例1234-1730835123456-4821
*
* 注意Moneris orderId 的长度是有限制的(通常不超过 50 字符)。本实现控制在合理长度范围内。
* 如需自定义格式,可在此函数中调整。
*
* @param int|string $order_id WooCommerce 订单 ID
* @return string 唯一订单 ID 字符串
*/
function yoone_moneris_unique_order_id( $order_id ) {
// 毫秒级时间戳
$ms = (int) floor( microtime( true ) * 1000 );
// 四位随机数
$rand = mt_rand( 1000, 9999 );
// 仅保留数字和字母,避免特殊字符
$base = preg_replace( '/[^a-zA-Z0-9_-]/', '', (string) $order_id );
// 拼接唯一后缀
$unique = $base . '-' . $ms . '-' . $rand;
// 限长保护(最多 50 字符)
if ( strlen( $unique ) > 50 ) {
// 若过长,截断前半部分(保留后缀)
$unique = substr( $base, 0, max( 1, 50 - ( 1 + strlen( (string) $ms ) + 1 + 4 ) ) ) . '-' . $ms . '-' . $rand;
}
return $unique;
}