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:
1 parent
723dd180e4
commit
ed55d302e9
7 files changed
+322
-24
No files matched your search
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user