修复两个问题
This commit is contained in:
1 parent
58f3f42bf8
commit
406fb8d6a6
4 files changed
+63
-11
No files matched your search
@@ -40,23 +40,50 @@ trait BalanceServiceCharge {
|
|||||||
$provider_transaction_id = sanitize_text_field($provider_transaction_id);
|
$provider_transaction_id = sanitize_text_field($provider_transaction_id);
|
||||||
$locked_status = '';
|
$locked_status = '';
|
||||||
$locked_note = [];
|
$locked_note = [];
|
||||||
$guard = function () use ($order_number, $user_id, &$locked_status, &$locked_note) {
|
$locked_payment_method = '';
|
||||||
|
$locked_transaction_id = '';
|
||||||
|
$locked_paid_at = '';
|
||||||
|
$locked_completed_at = '';
|
||||||
|
$guard = function () use ($order_number, $user_id, $credit, $provider_transaction_id, &$locked_status, &$locked_note, &$locked_payment_method, &$locked_transaction_id, &$locked_paid_at, &$locked_completed_at) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
$table = \AKSRT\Core\DBMigrator::table('order');
|
$table = \AKSRT\Core\DBMigrator::table('order');
|
||||||
$current = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE order_number = %s FOR UPDATE", $order_number), ARRAY_A);
|
$current = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE order_number = %s FOR UPDATE", $order_number), ARRAY_A);
|
||||||
if (!is_array($current)
|
if (!is_array($current)
|
||||||
|| (int) ($current['user_id'] ?? 0) !== $user_id
|
|| (int) ($current['user_id'] ?? 0) !== $user_id
|
||||||
|| sanitize_key((string) ($current['product_type'] ?? '')) !== \AKSRT\Core\DomainModel::ORDER_PRODUCT_RECHARGE
|
|| sanitize_key((string) ($current['product_type'] ?? '')) !== \AKSRT\Core\DomainModel::ORDER_PRODUCT_RECHARGE
|
||||||
|| !in_array((string) ($current['status'] ?? ''), [\AKSRT\Payment\Order::STATUS_CREATED, \AKSRT\Payment\Order::STATUS_PENDING], true)
|
|| !in_array((string) ($current['status'] ?? ''), [
|
||||||
|
\AKSRT\Payment\Order::STATUS_CREATED,
|
||||||
|
\AKSRT\Payment\Order::STATUS_PENDING,
|
||||||
|
\AKSRT\Payment\Order::STATUS_PAID,
|
||||||
|
\AKSRT\Payment\Order::STATUS_COMPLETED,
|
||||||
|
], true)
|
||||||
) {
|
) {
|
||||||
return new \WP_Error('aksrt_order_confirm_failed', __('订单状态确认失败', 'aksrt'), ['status' => 409]);
|
return new \WP_Error('aksrt_order_confirm_failed', __('订单状态确认失败', 'aksrt'), ['status' => 409]);
|
||||||
}
|
}
|
||||||
$locked_status = (string) $current['status'];
|
$locked_status = (string) $current['status'];
|
||||||
$decoded = json_decode((string) ($current['note'] ?? ''), true);
|
$decoded = json_decode((string) ($current['note'] ?? ''), true);
|
||||||
$locked_note = is_array($decoded) ? $decoded : [];
|
$locked_note = is_array($decoded) ? $decoded : [];
|
||||||
|
$current_credit_raw = $locked_note['credit_amount'] ?? null;
|
||||||
|
try {
|
||||||
|
$current_credit = is_string($current_credit_raw)
|
||||||
|
? \AKSRT\Ledger\AssetAmount::balance($current_credit_raw)
|
||||||
|
: null;
|
||||||
|
} catch (\InvalidArgumentException $e) {
|
||||||
|
$current_credit = null;
|
||||||
|
}
|
||||||
|
if (!$current_credit || $current_credit->minor() !== $credit->minor()) {
|
||||||
|
return new \WP_Error('aksrt_recharge_amount_changed', __('充值到账金额已变化', 'aksrt'), ['status' => 409]);
|
||||||
|
}
|
||||||
|
$locked_payment_method = sanitize_key((string) ($current['payment_method'] ?? ''));
|
||||||
|
$locked_transaction_id = sanitize_text_field((string) ($current['payment_transaction_id'] ?? ''));
|
||||||
|
$locked_paid_at = trim((string) ($current['paid_at'] ?? ''));
|
||||||
|
$locked_completed_at = trim((string) ($current['completed_at'] ?? ''));
|
||||||
|
if ($provider_transaction_id !== '' && $locked_transaction_id !== '' && !hash_equals($locked_transaction_id, $provider_transaction_id)) {
|
||||||
|
return new \WP_Error('aksrt_recharge_transaction_conflict', __('充值交易号不一致', 'aksrt'), ['status' => 409]);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
$mutation = function () use ($order_number, $payment_method, $provider_transaction_id, $status_note, $credit, &$locked_status, &$locked_note) {
|
$mutation = function () use ($order_number, $payment_method, $provider_transaction_id, $status_note, $credit, &$locked_status, &$locked_note, &$locked_payment_method, &$locked_transaction_id, &$locked_paid_at, &$locked_completed_at) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
$table = \AKSRT\Core\DBMigrator::table('order');
|
$table = \AKSRT\Core\DBMigrator::table('order');
|
||||||
$locked_note['ledger_credited'] = 1;
|
$locked_note['ledger_credited'] = 1;
|
||||||
@@ -67,13 +94,13 @@ trait BalanceServiceCharge {
|
|||||||
$now = current_time('mysql');
|
$now = current_time('mysql');
|
||||||
$updated = $wpdb->update($table, [
|
$updated = $wpdb->update($table, [
|
||||||
'status' => \AKSRT\Payment\Order::STATUS_COMPLETED,
|
'status' => \AKSRT\Payment\Order::STATUS_COMPLETED,
|
||||||
'payment_method' => $payment_method,
|
'payment_method' => $payment_method !== '' ? $payment_method : $locked_payment_method,
|
||||||
'payment_transaction_id' => $provider_transaction_id,
|
'payment_transaction_id' => $provider_transaction_id !== '' ? $provider_transaction_id : $locked_transaction_id,
|
||||||
'paid_at' => $now,
|
'paid_at' => $locked_paid_at !== '' ? $locked_paid_at : $now,
|
||||||
'completed_at' => $now,
|
'completed_at' => $locked_completed_at !== '' ? $locked_completed_at : $now,
|
||||||
'note' => wp_json_encode($locked_note, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
'note' => wp_json_encode($locked_note, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
||||||
], ['order_number' => $order_number, 'status' => $locked_status], ['%s', '%s', '%s', '%s', '%s', '%s'], ['%s', '%s']);
|
], ['order_number' => $order_number, 'status' => $locked_status], ['%s', '%s', '%s', '%s', '%s', '%s'], ['%s', '%s']);
|
||||||
return $updated === 1 ? true : new \WP_Error('aksrt_order_confirm_failed', __('订单状态确认失败', 'aksrt'), ['status' => 409]);
|
return $updated !== false ? true : new \WP_Error('aksrt_order_confirm_failed', __('订单状态确认失败', 'aksrt'), ['status' => 409]);
|
||||||
};
|
};
|
||||||
$result = \AKSRT\Ledger\LedgerService::get_instance()->credit(
|
$result = \AKSRT\Ledger\LedgerService::get_instance()->credit(
|
||||||
$user_id,
|
$user_id,
|
||||||
|
|||||||
@@ -386,6 +386,16 @@ class Gateway {
|
|||||||
// 用户可能重复付款,不能静默 ACK 为已正确同步。
|
// 用户可能重复付款,不能静默 ACK 为已正确同步。
|
||||||
if (in_array($current, ['paid', 'delivered', 'completed'], true)) {
|
if (in_array($current, ['paid', 'delivered', 'completed'], true)) {
|
||||||
$stored_transaction = sanitize_text_field((string) ($order['payment_transaction_id'] ?? ''));
|
$stored_transaction = sanitize_text_field((string) ($order['payment_transaction_id'] ?? ''));
|
||||||
|
if (sanitize_key((string) ($order['product_type'] ?? '')) === \AKSRT\Core\DomainModel::ORDER_PRODUCT_RECHARGE) {
|
||||||
|
if ($stored_transaction !== '' && !hash_equals($stored_transaction, $transaction_id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $repo->update_status($order_number, Order::STATUS_PAID, [
|
||||||
|
'payment_method' => $gateway_id,
|
||||||
|
'payment_transaction_id' => $transaction_id,
|
||||||
|
'note' => __('支付网关回调补充确认到账。', 'aksrt'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
return $stored_transaction !== '' && hash_equals($stored_transaction, $transaction_id);
|
return $stored_transaction !== '' && hash_equals($stored_transaction, $transaction_id);
|
||||||
}
|
}
|
||||||
// 只允许从未支付态完成
|
// 只允许从未支付态完成
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ trait OrderRepositoryCore {
|
|||||||
public function expire_pending_orders(?int $user_id = null): int {
|
public function expire_pending_orders(?int $user_id = null): int {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$where = "status IN ('created', 'pending') AND expires_at IS NOT NULL AND expires_at <> '' AND expires_at <= %s";
|
$where = "status IN ('created', 'pending') AND expires_at IS NOT NULL AND expires_at <= %s";
|
||||||
$params = [gmdate('Y-m-d H:i:s')];
|
$params = [gmdate('Y-m-d H:i:s')];
|
||||||
if ($user_id !== null && $user_id > 0) {
|
if ($user_id !== null && $user_id > 0) {
|
||||||
$where .= ' AND user_id = %d';
|
$where .= ' AND user_id = %d';
|
||||||
@@ -298,7 +298,10 @@ trait OrderRepositoryCore {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
$new_status === \AKSRT\Core\DomainModel::ORDER_STATUS_PAID
|
in_array($new_status, [
|
||||||
|
\AKSRT\Core\DomainModel::ORDER_STATUS_PAID,
|
||||||
|
\AKSRT\Core\DomainModel::ORDER_STATUS_COMPLETED,
|
||||||
|
], true)
|
||||||
&& sanitize_key((string) ($preflight['product_type'] ?? '')) === \AKSRT\Core\DomainModel::ORDER_PRODUCT_RECHARGE
|
&& sanitize_key((string) ($preflight['product_type'] ?? '')) === \AKSRT\Core\DomainModel::ORDER_PRODUCT_RECHARGE
|
||||||
&& class_exists('\AKSRT\Credits\BalanceService')
|
&& class_exists('\AKSRT\Credits\BalanceService')
|
||||||
) {
|
) {
|
||||||
@@ -579,7 +582,7 @@ trait OrderRepositoryCore {
|
|||||||
// Prefer cancelled_at when set; fall back to created_at for older rows.
|
// Prefer cancelled_at when set; fall back to created_at for older rows.
|
||||||
$sql = "DELETE FROM {$table}
|
$sql = "DELETE FROM {$table}
|
||||||
WHERE status IN ('cancelled', 'expired')
|
WHERE status IN ('cancelled', 'expired')
|
||||||
AND COALESCE(NULLIF(cancelled_at, ''), created_at) < %s";
|
AND COALESCE(cancelled_at, created_at) < %s";
|
||||||
$deleted = $wpdb->query($wpdb->prepare($sql, $cutoff));
|
$deleted = $wpdb->query($wpdb->prepare($sql, $cutoff));
|
||||||
return $deleted === false ? 0 : (int) $deleted;
|
return $deleted === false ? 0 : (int) $deleted;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,3 +204,15 @@ test('asset purchases share the order center and refresh every wallet surface',
|
|||||||
assert.match(header, /data-aksrt-wallet-value/)
|
assert.match(header, /data-aksrt-wallet-value/)
|
||||||
assert.match(header, /data-aksrt-points-value/)
|
assert.match(header, /data-aksrt-points-value/)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('recharge completion always credits the ledger and expiry SQL is strict-mode safe', () => {
|
||||||
|
const repository = fs.readFileSync(path.join(root, 'src', 'Payment', 'OrderRepositoryCore.php'), 'utf8')
|
||||||
|
const charge = fs.readFileSync(path.join(root, 'src', 'Credits', 'BalanceServiceCharge.php'), 'utf8')
|
||||||
|
const gateway = fs.readFileSync(path.join(root, 'src', 'Payment', 'Gateway.php'), 'utf8')
|
||||||
|
|
||||||
|
assert.match(repository, /in_array\(\$new_status,\s*\[\s*\\AKSRT\\Core\\DomainModel::ORDER_STATUS_PAID,\s*\\AKSRT\\Core\\DomainModel::ORDER_STATUS_COMPLETED,\s*\],\s*true\)/)
|
||||||
|
assert.match(charge, /Order::STATUS_CREATED,\s*\\AKSRT\\Payment\\Order::STATUS_PENDING,\s*\\AKSRT\\Payment\\Order::STATUS_PAID,\s*\\AKSRT\\Payment\\Order::STATUS_COMPLETED/)
|
||||||
|
assert.match(gateway, /ORDER_PRODUCT_RECHARGE[\s\S]*?update_status\(\$order_number, Order::STATUS_PAID/)
|
||||||
|
assert.doesNotMatch(repository, /expires_at\s*<>\s*''/)
|
||||||
|
assert.doesNotMatch(repository, /NULLIF\(cancelled_at,\s*''\)/)
|
||||||
|
})
|
||||||
Reference in new issue
Block a user