feat: 新增玩家管理与转账对账相关功能
1. 新增完整的后台玩家管理CRUD接口,包括列表、创建、详情、更新、删除 2. 新增转账订单冲正和人工处理功能,支持待对账订单状态变更 3. 扩展钱包流水和转账订单的状态支持,新增reversed、manually_processed等状态 4. 新增玩家API数据统一输出类,标准化玩家信息返回格式
This commit is contained in:
@@ -34,12 +34,20 @@ final class LotteryTransferService
|
||||
/** PRD §6.2/6.7:主站超时待对账 */
|
||||
private const ST_PENDING_RECONCILE = 'pending_reconcile';
|
||||
|
||||
/** PRD §12:对账后冲正 */
|
||||
private const ST_REVERSED = 'reversed';
|
||||
|
||||
/** PRD §12:对账后人工处理 */
|
||||
private const ST_MANUALLY_PROCESSED = 'manually_processed';
|
||||
|
||||
private const BIZ_TRANSFER_IN = 'transfer_in';
|
||||
|
||||
private const BIZ_TRANSFER_OUT = 'transfer_out';
|
||||
|
||||
private const BIZ_TRANSFER_OUT_REFUND = 'transfer_out_refund';
|
||||
|
||||
private const BIZ_REVERSAL = 'reversal';
|
||||
|
||||
private const TXN_POSTED = 'posted';
|
||||
|
||||
private const TXN_PENDING_RECONCILE = 'pending_reconcile';
|
||||
@@ -338,6 +346,115 @@ final class LotteryTransferService
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对账操作:冲正 / 人工处理。
|
||||
*
|
||||
* 冲正(reverse):主站确认未成功,对已扣彩票余额的转出单做反向操作(加回余额),标记为已冲正。
|
||||
* 人工处理(manually_process):管理员确认该订单已通过其它途径解决,仅标记状态,不动钱包。
|
||||
*
|
||||
* @param 'reverse'|'manually_process' $action
|
||||
* @throws WalletOperationException
|
||||
*/
|
||||
public function reconcileTransferOrder(
|
||||
TransferOrder $order,
|
||||
string $action,
|
||||
string $remark = '',
|
||||
): void {
|
||||
if ($order->status !== self::ST_PENDING_RECONCILE) {
|
||||
throw new WalletOperationException(
|
||||
'order_not_pending_reconcile',
|
||||
ErrorCode::WalletExternalRejected->value,
|
||||
422,
|
||||
);
|
||||
}
|
||||
|
||||
if ($action === 'reverse') {
|
||||
$this->doReverse($order, $remark);
|
||||
} elseif ($action === 'manually_process') {
|
||||
$this->doManuallyProcess($order, $remark);
|
||||
} else {
|
||||
throw new WalletOperationException(
|
||||
'invalid_reconcile_action',
|
||||
ErrorCode::WalletExternalRejected->value,
|
||||
422,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function doReverse(TransferOrder $order, string $remark): void
|
||||
{
|
||||
if ($order->direction === self::DIR_OUT) {
|
||||
DB::transaction(function () use ($order, $remark): void {
|
||||
$wallet = $this->lockLotteryWalletById($order->player_id, $order->currency_code);
|
||||
$before = (int) $wallet->balance;
|
||||
$after = $before + (int) $order->amount;
|
||||
$wallet->forceFill([
|
||||
'balance' => $after,
|
||||
'version' => (int) $wallet->version + 1,
|
||||
])->save();
|
||||
|
||||
WalletTxn::query()->create([
|
||||
'txn_no' => $this->newTxnNo(),
|
||||
'player_id' => (int) $order->player_id,
|
||||
'wallet_id' => $wallet->id,
|
||||
'biz_type' => self::BIZ_REVERSAL,
|
||||
'biz_no' => $order->transfer_no,
|
||||
'direction' => self::TXN_DIR_IN,
|
||||
'amount' => (int) $order->amount,
|
||||
'balance_before' => $before,
|
||||
'balance_after' => $after,
|
||||
'status' => self::TXN_POSTED,
|
||||
'external_ref_no' => null,
|
||||
'idempotent_key' => null,
|
||||
'remark' => $remark ?: 'reversal_pending_reconcile',
|
||||
]);
|
||||
|
||||
$order->forceFill([
|
||||
'status' => self::ST_REVERSED,
|
||||
'fail_reason' => 'reversed: '.($remark ?: 'admin_reversal'),
|
||||
'finished_at' => now(),
|
||||
])->save();
|
||||
});
|
||||
} else {
|
||||
$order->forceFill([
|
||||
'status' => self::ST_REVERSED,
|
||||
'fail_reason' => 'reversed: '.($remark ?: 'admin_reversal'),
|
||||
'finished_at' => now(),
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
|
||||
private function doManuallyProcess(TransferOrder $order, string $remark): void
|
||||
{
|
||||
$order->forceFill([
|
||||
'status' => self::ST_MANUALLY_PROCESSED,
|
||||
'fail_reason' => 'manually_processed: '.($remark ?: 'admin_manual'),
|
||||
'finished_at' => now(),
|
||||
])->save();
|
||||
}
|
||||
|
||||
private function lockLotteryWalletById(int $playerId, string $currencyCode): PlayerWallet
|
||||
{
|
||||
$wallet = PlayerWallet::query()
|
||||
->where([
|
||||
'player_id' => $playerId,
|
||||
'wallet_type' => self::WALLET_TYPE_LOTTERY,
|
||||
'currency_code' => $currencyCode,
|
||||
])
|
||||
->lockForUpdate()
|
||||
->first();
|
||||
|
||||
if ($wallet === null) {
|
||||
throw new WalletOperationException(
|
||||
'wallet_not_found',
|
||||
ErrorCode::WalletInvalidCurrency->value,
|
||||
422,
|
||||
);
|
||||
}
|
||||
|
||||
return $wallet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user