feat(moneris): 添加唯一订单ID生成和自动重试逻辑
实现唯一订单ID生成函数以避免Moneris主机拒绝重复订单 在API请求失败时自动重试一次并生成新订单ID 增加请求/响应日志记录和敏感信息脱敏处理
This commit is contained in:
parent
a9acdacf80
commit
73dc9ca3c3
|
|
@ -121,22 +121,51 @@ class Yoone_Moneris_API implements Yoone_Moneris_API_Interface {
|
|||
if ( empty( $this->store_id ) || empty( $this->api_token ) ) {
|
||||
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';
|
||||
$payload = array(
|
||||
'dataKey' => (string) $token,
|
||||
'orderId' => (string) $order_id,
|
||||
'orderId' => (string) $unique_order_id,
|
||||
'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);
|
||||
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 );
|
||||
if ( $res['ok'] ) {
|
||||
return array(
|
||||
'success' => true,
|
||||
'transaction_id' => (string) ( $res['receipt']['txnNumber'] ?? '' ),
|
||||
'order_id_used' => $unique_order_id,
|
||||
'retry' => false,
|
||||
);
|
||||
}
|
||||
return array( 'success' => false, 'error' => (string) $res['error'] );
|
||||
// 如果出现系统错误,尝试一次自动重试(更换新的唯一 orderId)
|
||||
$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 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -195,6 +224,13 @@ class Yoone_Moneris_API implements Yoone_Moneris_API_Interface {
|
|||
|
||||
$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(
|
||||
'method' => $this->http_method ?: 'POST',
|
||||
'timeout' => $this->timeout,
|
||||
|
|
@ -221,6 +257,8 @@ class Yoone_Moneris_API implements Yoone_Moneris_API_Interface {
|
|||
|
||||
// 解析 XML
|
||||
$parsed = $this->parse_moneris_xml( $raw );
|
||||
// 打印部分响应原文,便于调试
|
||||
error_log('【Yoone Moneris API】响应原文前 300 字符:' . substr($raw, 0, 300));
|
||||
if ( ! $parsed['parsed'] ) {
|
||||
error_log('【Yoone Moneris API】XML 解析失败:' . ($parsed['error'] ?? 'Unknown'));
|
||||
return array( 'ok' => false, 'error' => 'Parse error: ' . ( $parsed['error'] ?? 'Unknown' ) );
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
<?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;
|
||||
}
|
||||
Loading…
Reference in New Issue