feat(payment): rebuild order details and asset records

This commit is contained in:
kate522 committed 2026-10-05 16:42:25 +08:00
1 parent 476e2f961d
commit 7b55cfb190
57 files changed
+2080 -1294

No files matched your search

+12 -44
View File
@@ -403,7 +403,6 @@ class ChargeCard {
if (is_wp_error($order_number)) {
return $order_number;
}
$transaction_number = $this->card_pass_transaction_number();
$guard = function () use ($card_id, $user_id, $submitted_card, $type) {
global $wpdb;
$current = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$this->table()} WHERE id = %d FOR UPDATE", $card_id), ARRAY_A);
@@ -424,9 +423,9 @@ class ChargeCard {
}
return true;
};
$mutation = function () use ($user_id, $type, $meta, $card_id, $order_number, $operation_key, $transaction_number) {
$mutation = function () use ($user_id, $type, $meta, $card_id, $order_number, $operation_key) {
global $wpdb;
$created = $this->create_redeem_order($user_id, $type, $meta, $card_id, '', $operation_key, $order_number, Order::STATUS_PAID, $transaction_number);
$created = $this->create_redeem_order($user_id, $type, $meta, $card_id, '', $operation_key, $order_number, Order::STATUS_PAID);
if ($created !== $order_number) {
return new \WP_Error('aksrt_order_create_failed', __('创建订单失败', 'aksrt'), ['status' => 500]);
}
@@ -464,21 +463,12 @@ class ChargeCard {
if (is_array($existing_order) && (string) ($existing_order['order_number'] ?? '') !== '') {
$order_number = (string) $existing_order['order_number'];
}
} else {
// 账本命令必须保持确定性(payload_hash 含 metadata,塞入随机订单号会破坏幂等重放),
// 因此在账本提交之后再把流水行挂到订单上。
\AKSRT\Finance\FinancialFlowService::get_instance()->attach_order_to_ledger_flow(
(int) $result->transaction_id,
$order_number,
$transaction_number
);
}
$balance = $result->balance($user_id, $asset);
return [
'success' => true,
'type' => $type,
'order_number' => $order_number,
'transaction_id' => $result->transaction_id,
'replayed' => $result->replayed,
'message' => $asset === \AKSRT\Ledger\AssetType::BALANCE
? sprintf(__('卡密充值成功,充值金额:%s 元', 'aksrt'), \AKSRT\Ledger\AssetAmount::from_minor($asset, $amount_minor)->decimal())
@@ -516,8 +506,6 @@ class ChargeCard {
if (is_wp_error($order_number)) {
return $order_number;
}
$transaction_number = $this->card_pass_transaction_number();
for ($attempt = 1; $attempt <= 3; $attempt++) {
if ($wpdb->query('START TRANSACTION') === false) {
return new \WP_Error('aksrt_card_db', __('无法开启卡密兑换事务', 'aksrt'), ['status' => 500]);
@@ -566,6 +554,8 @@ class ChargeCard {
$current_level = max(0, (int) get_user_meta($user_id, 'aksrt_vip_level', true));
$current_expiry = max(0, (int) get_user_meta($user_id, 'aksrt_vip_expires', true));
$activation_base = max($current_expiry, time());
$vip_service = \AKSRT\Content\VipService::get_instance();
$vip_before = $vip_service->get_membership_snapshot($user_id);
$created = $this->create_redeem_order(
$user_id,
self::TYPE_VIP,
@@ -574,29 +564,13 @@ class ChargeCard {
(string) ($current['password'] ?? ''),
$operation_key,
$order_number,
Order::STATUS_PAID,
$transaction_number
Order::STATUS_PAID
);
if ($created !== $order_number) {
throw new \RuntimeException('vip_card_order_create_failed');
}
// 会员卡没有账本参与(入账的是 usermeta 而非账本资产),流水要单独补一条,
// 且它不产生任何现金/资产分录(0 元凭证)。同一事务内写入,失败即整体回滚。
$flow_number = \AKSRT\Finance\FinancialFlowService::get_instance()->record_card_pass_payment([
'order_number' => $order_number,
'user_id' => $user_id,
'amount' => '0.00',
'currency' => 'CNY',
'status' => Order::STATUS_PAID,
'payment_method' => 'card_pass',
'payment_transaction_id' => $transaction_number,
], true);
if (is_wp_error($flow_number)) {
throw new \RuntimeException('vip_card_flow_failed');
}
\AKSRT\Content\VipService::get_instance()->activate_vip($user_id, $level, $days);
$vip_service->activate_vip($user_id, $level, $days);
$actual_level = max(0, (int) get_user_meta($user_id, 'aksrt_vip_level', true));
$actual_expiry = max(0, (int) get_user_meta($user_id, 'aksrt_vip_expires', true));
$permanent_preserved = $days > 0 && $current_level > 0 && $level <= $current_level && $current_expiry === 0;
@@ -606,6 +580,10 @@ class ChargeCard {
if ($actual_level < max($current_level, $level) || !$expiry_valid) {
throw new \RuntimeException('vip_card_fulfillment_failed');
}
$vip_after = $vip_service->get_membership_snapshot($user_id);
if (!OrderRepository::get_instance()->merge_meta($order_number, ['vip_before' => $vip_before, 'vip_after' => $vip_after])) {
throw new \RuntimeException('vip_card_snapshot_failed');
}
$used = $wpdb->query($wpdb->prepare(
"UPDATE {$this->table()} SET status = %s, used_by = %d, used_at = %s, order_number = %s, balance = 0 WHERE id = %d AND status = %s",
@@ -639,7 +617,6 @@ class ChargeCard {
'success' => true,
'type' => self::TYPE_VIP,
'order_number' => $order_number,
'transaction_id' => 0,
'replayed' => false,
'message' => $message,
'level' => $level,
@@ -699,7 +676,6 @@ class ChargeCard {
'success' => true,
'type' => self::TYPE_VIP,
'order_number' => (string) $order['order_number'],
'transaction_id' => 0,
'replayed' => true,
'message' => $this->vip_card_message($level, $days),
'level' => $level,
@@ -742,18 +718,10 @@ class ChargeCard {
return $order_number;
}
/**
* 卡密核销订单的渠道交易号:与资金流水号同源,统一走流水号分配器,
* 不再消费任何后台前缀配置。
*/
private function card_pass_transaction_number(): string {
return \AKSRT\Finance\BusinessNumber::allocate();
}
/**
* @param array<string,mixed> $meta
*/
private function create_redeem_order(int $user_id, string $type, array $meta, int $card_id, string $password, string $request_key = '', string $order_number = '', string $status = '', string $transaction_number = ''): string {
private function create_redeem_order(int $user_id, string $type, array $meta, int $card_id, string $password, string $request_key = '', string $order_number = '', string $status = ''): string {
if (!class_exists('AKSRT\\Payment\\OrderRepository') || !class_exists('\AKSRT\Core\DomainModel')) {
return '';
}
@@ -791,7 +759,7 @@ class ChargeCard {
'currency' => $currency,
'status' => $order_status,
'payment_method' => 'card_pass',
'payment_transaction_id' => $order_status === Order::STATUS_PAID ? $transaction_number : '',
'payment_transaction_id' => '',
'paid_at' => $order_status === Order::STATUS_PAID ? current_time('mysql') : null,
'note' => is_string($note) ? $note : '',
'request_key' => $request_key,