feat(payment): rebuild order details and asset records
This commit is contained in:
1 parent
476e2f961d
commit
7b55cfb190
57 files changed
+2080
-1294
No files matched your search
@@ -133,6 +133,46 @@ class Withdrawal {
|
||||
if ($net_minor < 1) {
|
||||
return new \WP_Error('aksrt_withdraw_net', __('扣除手续费后到账金额必须至少为 0.01 元', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
$request_hash = hash('sha256', (string) wp_json_encode([
|
||||
'user_id' => $user_id,
|
||||
'gross_minor' => $gross_minor,
|
||||
'net_minor' => $net_minor,
|
||||
'fee_rate' => $fee_rate,
|
||||
'method' => sanitize_key($method),
|
||||
'account' => sanitize_text_field($account),
|
||||
'source' => $source,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||
$existing_request = $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT * FROM {$this->table()} WHERE operation_key = %s LIMIT 1",
|
||||
$operation_key
|
||||
), ARRAY_A);
|
||||
if (is_array($existing_request)) {
|
||||
$existing_meta = $this->parse_account_meta((string) ($existing_request['account_info'] ?? ''));
|
||||
if ((int) ($existing_request['user_id'] ?? 0) !== $user_id
|
||||
|| !hash_equals((string) ($existing_meta['request_hash'] ?? ''), $request_hash)) {
|
||||
return new \WP_Error('aksrt_withdraw_idempotency_conflict', __('相同提现操作标识已用于其他请求', 'aksrt'), ['status' => 409]);
|
||||
}
|
||||
$window = $this->daily_window((string) ($settings['reset_time'] ?? '00:00'));
|
||||
$usage = $this->daily_usage_minor_in_window($user_id, $settings, $window, false, $source);
|
||||
return [
|
||||
'withdrawal_id' => (int) $existing_request['id'],
|
||||
'withdrawal_number' => (string) ($existing_request['withdrawal_number'] ?? ''),
|
||||
'net_amount' => $this->database_balance_decimal((string) ($existing_request['amount'] ?? '0.00')),
|
||||
'source' => $source,
|
||||
'wallet_balance' => $this->source_balance_decimal($user_id, $source),
|
||||
'transaction_id' => max(0, (int) ($existing_request['ledger_transaction_id'] ?? 0)),
|
||||
'flow_number' => $source === self::SOURCE_COMMISSION
|
||||
? \AKSRT\Finance\FinancialFlowService::get_instance()->number_for_operation($operation_key)
|
||||
: '',
|
||||
'replayed' => true,
|
||||
'usage' => [
|
||||
'count' => $usage['count'],
|
||||
'total' => $this->balance_decimal($usage['total_minor']),
|
||||
'remaining_count' => $usage['remaining_count'],
|
||||
'remaining_amount' => $this->balance_decimal($usage['remaining_minor']),
|
||||
],
|
||||
];
|
||||
}
|
||||
$window = $this->daily_window((string) ($settings['reset_time'] ?? '00:00'));
|
||||
$usage = null;
|
||||
$withdrawal_id = 0;
|
||||
@@ -163,7 +203,7 @@ class Withdrawal {
|
||||
}
|
||||
return true;
|
||||
};
|
||||
$mutation = function (int $transaction_id) use ($user_id, $gross_minor, $net_minor, $fee_rate, $method, $account, $operation_key, $source, &$withdrawal_id, &$withdrawal_number) {
|
||||
$mutation = function (int $transaction_id) use ($user_id, $gross_minor, $net_minor, $fee_rate, $method, $account, $operation_key, $request_hash, $source, &$withdrawal_id, &$withdrawal_number) {
|
||||
global $wpdb;
|
||||
$payload = wp_json_encode([
|
||||
'account' => $account,
|
||||
@@ -171,11 +211,12 @@ class Withdrawal {
|
||||
'fee_rate' => $fee_rate,
|
||||
'source' => $source,
|
||||
'operation_key' => $operation_key,
|
||||
'request_hash' => $request_hash,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: $account;
|
||||
$ok = false;
|
||||
for ($attempt = 1; $attempt <= 5; $attempt++) {
|
||||
$withdrawal_number = \AKSRT\Finance\BusinessNumber::allocate();
|
||||
$ok = $wpdb->insert($this->table(), [
|
||||
$insert_data = [
|
||||
'withdrawal_number' => $withdrawal_number,
|
||||
'user_id' => $user_id,
|
||||
'amount' => \AKSRT\Ledger\AssetAmount::from_minor(\AKSRT\Ledger\AssetType::BALANCE, $net_minor)->decimal(),
|
||||
@@ -183,9 +224,15 @@ class Withdrawal {
|
||||
'method' => sanitize_key($method),
|
||||
'account_info' => $payload,
|
||||
'status' => self::STATUS_PENDING,
|
||||
'ledger_transaction_id' => $transaction_id,
|
||||
'operation_key' => $operation_key,
|
||||
'created_at' => current_time('mysql'),
|
||||
], ['%s', '%d', '%s', '%s', '%s', '%s', '%s', '%d', '%s']);
|
||||
];
|
||||
$insert_formats = ['%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s'];
|
||||
if ($transaction_id > 0) {
|
||||
$insert_data['ledger_transaction_id'] = $transaction_id;
|
||||
$insert_formats[] = '%d';
|
||||
}
|
||||
$ok = $wpdb->insert($this->table(), $insert_data, $insert_formats);
|
||||
if ($ok === 1) {
|
||||
break;
|
||||
}
|
||||
@@ -214,7 +261,9 @@ class Withdrawal {
|
||||
}
|
||||
if ($withdrawal_id <= 0) {
|
||||
$replayed_row = $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT id, withdrawal_number FROM {$this->table()} WHERE ledger_transaction_id = %d",
|
||||
"SELECT id, withdrawal_number FROM {$this->table()} WHERE operation_key = %s OR (%d > 0 AND ledger_transaction_id = %d) ORDER BY id DESC LIMIT 1",
|
||||
$operation_key,
|
||||
$result->transaction_id,
|
||||
$result->transaction_id
|
||||
), ARRAY_A);
|
||||
$withdrawal_id = (int) ($replayed_row['id'] ?? 0);
|
||||
@@ -329,7 +378,7 @@ class Withdrawal {
|
||||
return $preflight_replay;
|
||||
}
|
||||
if (is_array($preflight_replay)) {
|
||||
$preflight_replay['wallet_balance'] = \AKSRT\Ledger\LedgerService::get_instance()->balance_decimal((int) ($row['user_id'] ?? 0));
|
||||
$preflight_replay['wallet_balance'] = $this->source_balance_decimal((int) ($row['user_id'] ?? 0), $this->normalize_source((string) ($meta['source'] ?? self::SOURCE_BALANCE)));
|
||||
$preflight_replay['withdrawal_number'] = (string) ($row['withdrawal_number'] ?? '');
|
||||
$preflight_replay['flow_number'] = \AKSRT\Finance\FinancialFlowService::get_instance()->number_for_operation($operation_key);
|
||||
return $preflight_replay;
|
||||
@@ -497,10 +546,16 @@ class Withdrawal {
|
||||
'flow_number' => $ledger_result instanceof \AKSRT\Ledger\LedgerResult ? $ledger_result->flow_number : $completed_flow_number,
|
||||
'withdrawal_number' => (string) ($row['withdrawal_number'] ?? ''),
|
||||
'replayed' => $replayed,
|
||||
'wallet_balance' => \AKSRT\Ledger\LedgerService::get_instance()->balance_decimal($user_id),
|
||||
'wallet_balance' => $this->source_balance_decimal($user_id, $refund_source),
|
||||
];
|
||||
}
|
||||
|
||||
private function source_balance_decimal(int $user_id, string $source): string {
|
||||
$asset = $this->source_asset($source);
|
||||
$minor = \AKSRT\Ledger\LedgerService::get_instance()->balance($user_id, $asset);
|
||||
return \AKSRT\Ledger\AssetAmount::from_minor($asset, $minor)->decimal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether user has open (pending/processing) withdrawals.
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user