fix(payment): unify and harden payment attempts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 11:29:57 +08:00
co-authored by Claude Fable 5
parent 53933988bf
commit 089421cd3e
18 changed files with 680 additions and 476 deletions
+38 -2
View File
@@ -546,8 +546,15 @@ trait OrderRepositoryCore {
/**
* Resolve a local order from a provider out_trade_no (base or attempt-suffixed).
*
* The authoritative mapping is the order's own meta (provider_out_trade_no /
* provider_out_trade_nos); the prefix heuristic is only a fallback and must
* never be the sole resolver, otherwise a truncated attempt number would
* strand a real payment.
*/
public function resolve_by_out_trade_no(string $out_trade_no): ?array {
global $wpdb;
$out_trade_no = sanitize_text_field($out_trade_no);
if ($out_trade_no === '') {
return null;
@@ -558,8 +565,37 @@ trait OrderRepositoryCore {
return $direct;
}
// Resume attempts: {order_number}P{n}
if (preg_match('/^(AKS[0-9]{10,})P([0-9]{1,3})$/', $out_trade_no, $m)) {
// Authoritative: the attempt number was recorded in the order note JSON.
$needle = '"provider_out_trade_no":"' . $out_trade_no . '"';
$row = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM {$this->table()} WHERE note LIKE %s ORDER BY id DESC LIMIT 1",
'%' . $wpdb->esc_like($needle) . '%'
),
ARRAY_A
);
if (is_array($row)) {
return $this->normalize_row($row);
}
// Superseded attempts stay in the history array.
$history_needle = '"' . $out_trade_no . '"';
$row = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM {$this->table()}
WHERE note LIKE %s AND note LIKE %s
ORDER BY id DESC LIMIT 1",
'%' . $wpdb->esc_like('"provider_out_trade_nos"') . '%',
'%' . $wpdb->esc_like($history_needle) . '%'
),
ARRAY_A
);
if (is_array($row)) {
return $this->normalize_row($row);
}
// Last resort: {order_number}P{n} prefix heuristic.
if (preg_match('/^(.+)P([0-9]{1,3})$/', $out_trade_no, $m)) {
$base = $this->get($m[1]);
if ($base) {
return $base;