fix(payment): re-pay with fresh provider out_trade_no

WeChat/Alipay/Epay reject reusing the same merchant order number when
params change. Allocate attempt-suffixed out_trade_no on resume pay,
map callbacks back to the local order, and best-effort close prior trades.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Fable 5 committed 2026-07-26 13:28:45 +08:00
1 parent 723dd180e4
commit ed55d302e9
7 files changed
+322 -24

No files matched your search

+33 -7
View File
@@ -44,9 +44,15 @@ class EpayGateway implements \AKSRT\Payment\GatewayInterface {
}
$order_number = (string) ($order['order_number'] ?? '');
$out_trade_no = (string) ($order['out_trade_no'] ?? '');
if ($out_trade_no === '') {
$out_trade_no = class_exists('\AKSRT\Payment\OrderRepository')
? \AKSRT\Payment\OrderRepository::provider_out_trade_no($order)
: $order_number;
}
$amount = (float) ($order['amount'] ?? 0);
$subject = (string) ($order['subject'] ?? __('订单支付', 'aksrt'));
if ($order_number === '' || $amount <= 0) {
if ($order_number === '' || $out_trade_no === '' || $amount <= 0) {
return ['success' => false, 'message' => __('订单参数无效', 'aksrt')];
}
@@ -54,7 +60,7 @@ class EpayGateway implements \AKSRT\Payment\GatewayInterface {
$params = [
'pid' => $config['pid'],
'type' => $this->channel,
'out_trade_no' => $order_number,
'out_trade_no' => $out_trade_no,
'notify_url' => rest_url('aksrt/v1/payment/' . $this->get_id() . '/notify'),
'return_url' => rest_url('aksrt/v1/payment/' . $this->get_id() . '/return'),
'name' => mb_substr(wp_strip_all_tags($subject), 0, 127),
@@ -78,7 +84,7 @@ class EpayGateway implements \AKSRT\Payment\GatewayInterface {
$body = json_decode((string) wp_remote_retrieve_body($response), true);
if (!is_array($body)) {
// Fall back to browser submit if mapi is unavailable.
return $this->browser_submit_result($config['apiurl'], $params, $order_number);
return $this->browser_submit_result($config['apiurl'], $params, $order_number, $out_trade_no);
}
$code = (int) ($body['code'] ?? 0);
if ($code !== 1) {
@@ -94,6 +100,7 @@ class EpayGateway implements \AKSRT\Payment\GatewayInterface {
'success' => true,
'payment_data' => [
'order_number' => $order_number,
'out_trade_no' => $out_trade_no,
'code_url' => $qrcode,
'channel' => $this->channel,
],
@@ -107,7 +114,7 @@ class EpayGateway implements \AKSRT\Payment\GatewayInterface {
return $result;
}
return $this->browser_submit_result($config['apiurl'], $params, $order_number);
return $this->browser_submit_result($config['apiurl'], $params, $order_number, $out_trade_no);
}
public function verify_callback(\WP_REST_Request $request): array {
@@ -177,13 +184,14 @@ class EpayGateway implements \AKSRT\Payment\GatewayInterface {
return ['success' => false, 'message' => __('易支付未配置', 'aksrt')];
}
$out_trade_no = $this->resolve_out_trade_no($order_number);
$config = $this->get_config();
// Common compatible query: api.php?act=order&pid&key&out_trade_no
$url = add_query_arg([
'act' => 'order',
'pid' => $config['pid'],
'key' => $config['key'],
'out_trade_no' => $order_number,
'out_trade_no' => $out_trade_no,
], $this->endpoint($config['apiurl'], 'api.php'));
$response = wp_remote_get($url, ['timeout' => 20]);
@@ -205,7 +213,7 @@ class EpayGateway implements \AKSRT\Payment\GatewayInterface {
return [
'success' => true,
'order_number' => (string) ($body['out_trade_no'] ?? $order_number),
'order_number' => (string) ($body['out_trade_no'] ?? $out_trade_no),
'amount' => $amount,
'transaction_id' => $tx,
'status' => $paid ? 'paid' : 'pending',
@@ -297,16 +305,34 @@ class EpayGateway implements \AKSRT\Payment\GatewayInterface {
* @param array<string,string> $params
* @return array{success:bool,payment_url?:string,payment_data?:array,message?:string}
*/
private function browser_submit_result(string $apiurl, array $params, string $order_number): array {
/**
* @param array<string,string> $params
* @return array{success:bool,payment_url?:string,payment_data?:array,message?:string}
*/
private function browser_submit_result(string $apiurl, array $params, string $order_number, string $out_trade_no = ''): array {
$url = add_query_arg($params, $this->endpoint($apiurl, 'submit.php'));
return [
'success' => true,
'payment_url' => $url,
'payment_data' => [
'order_number' => $order_number,
'out_trade_no' => $out_trade_no !== '' ? $out_trade_no : $order_number,
'channel' => $this->channel,
],
'message' => __('易支付支付链接已生成', 'aksrt'),
];
}
/**
* Map local order_number → provider out_trade_no (supports resume attempts).
*/
private function resolve_out_trade_no(string $order_number): string {
if (class_exists('\AKSRT\Payment\OrderRepository')) {
$order = \AKSRT\Payment\OrderRepository::get_instance()->get($order_number);
if ($order) {
return \AKSRT\Payment\OrderRepository::provider_out_trade_no($order);
}
}
return $order_number;
}
}