fix(core): harden settlement and wallet integration
This commit is contained in:
@@ -2,11 +2,11 @@
|
||||
|
||||
namespace App\Services\Settlement;
|
||||
|
||||
use App\Models\SettlementBatch;
|
||||
use App\Lottery\SettlementBatchStatus;
|
||||
use App\Services\AuditLogger;
|
||||
use App\Models\SettlementBatch;
|
||||
use App\Services\LotterySettings;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Lottery\SettlementBatchStatus;
|
||||
|
||||
/**
|
||||
* draw tick 在自动结算后,按系统设置自动审核并派彩入账。
|
||||
@@ -24,15 +24,38 @@ final class SettlementTickFinalizer
|
||||
$paid = 0;
|
||||
$payoutFailed = 0;
|
||||
|
||||
if (! (bool) LotterySettings::get('settlement.auto_approve_on_tick', true)) {
|
||||
return ['approved' => 0, 'paid' => 0, 'payout_failed' => 0];
|
||||
}
|
||||
$autoApprove = (bool) LotterySettings::get('settlement.auto_approve_on_tick', true);
|
||||
$pending = $autoApprove
|
||||
? SettlementBatch::query()
|
||||
->where('status', SettlementBatchStatus::PendingReview->value)
|
||||
->orderBy('id')
|
||||
->limit((int) config('lottery.draw_tick_finalize_limit', 5))
|
||||
->get()
|
||||
: collect();
|
||||
|
||||
$pending = SettlementBatch::query()
|
||||
->where('status', SettlementBatchStatus::PendingReview->value)
|
||||
->orderBy('id')
|
||||
// 除本轮待审核批次外,也恢复处理上轮已批准但尚未派彩的批次。
|
||||
// 这样进程即使在 approve 提交后、payout 开始前退出,下一 tick 仍能续跑。
|
||||
$approvedDrawIds = DB::table('settlement_batches as approved')
|
||||
->where('approved.status', SettlementBatchStatus::Approved->value)
|
||||
->whereNotExists(function ($query): void {
|
||||
$query->selectRaw('1')
|
||||
->from('settlement_batches as sibling')
|
||||
->whereColumn('sibling.draw_id', 'approved.draw_id')
|
||||
->whereIn('sibling.status', [
|
||||
SettlementBatchStatus::Running->value,
|
||||
SettlementBatchStatus::PendingReview->value,
|
||||
]);
|
||||
})
|
||||
->groupBy('approved.draw_id')
|
||||
->orderByRaw('MAX(approved.auto_payout_attempts)')
|
||||
->orderByRaw('MIN(approved.id)')
|
||||
->limit((int) config('lottery.draw_tick_finalize_limit', 5))
|
||||
->get();
|
||||
->pluck('approved.draw_id');
|
||||
$candidateDrawIds = $pending->pluck('draw_id')
|
||||
->merge($approvedDrawIds)
|
||||
->map(fn ($id): int => (int) $id)
|
||||
->unique()
|
||||
->values();
|
||||
|
||||
foreach ($pending as $batch) {
|
||||
try {
|
||||
@@ -43,32 +66,53 @@ final class SettlementTickFinalizer
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (! (bool) LotterySettings::get('settlement.auto_payout_on_tick', true)) {
|
||||
if (! (bool) LotterySettings::get('settlement.auto_payout_on_tick', true)) {
|
||||
return ['approved' => $approved, 'paid' => 0, 'payout_failed' => 0];
|
||||
}
|
||||
|
||||
foreach ($candidateDrawIds as $drawId) {
|
||||
$hasUnapprovedBatch = SettlementBatch::query()
|
||||
->where('draw_id', $drawId)
|
||||
->whereIn('status', [
|
||||
SettlementBatchStatus::Running->value,
|
||||
SettlementBatchStatus::PendingReview->value,
|
||||
])
|
||||
->exists();
|
||||
if ($hasUnapprovedBatch) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->workflow->payout($batch->fresh());
|
||||
$paid++;
|
||||
AuditLogger::recordForSystem(
|
||||
moduleCode: 'settlement',
|
||||
actionCode: 'auto_payout',
|
||||
targetType: 'settlement_batch',
|
||||
targetId: (string) $batch->id,
|
||||
afterJson: ['draw_id' => (int) $batch->draw_id],
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
$this->markAutoPayoutFailed($batch, $e);
|
||||
$payoutFailed++;
|
||||
$approvedBatches = SettlementBatch::query()
|
||||
->where('draw_id', $drawId)
|
||||
->where('status', SettlementBatchStatus::Approved->value)
|
||||
->orderBy('id')
|
||||
->get();
|
||||
|
||||
foreach ($approvedBatches as $batch) {
|
||||
try {
|
||||
$this->workflow->payout($batch);
|
||||
$paid++;
|
||||
AuditLogger::recordForSystem(
|
||||
moduleCode: 'settlement',
|
||||
actionCode: 'auto_payout',
|
||||
targetType: 'settlement_batch',
|
||||
targetId: (string) $batch->id,
|
||||
afterJson: ['draw_id' => (int) $batch->draw_id],
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
$this->recordAutoPayoutFailure($batch, $e);
|
||||
$payoutFailed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ['approved' => $approved, 'paid' => $paid, 'payout_failed' => $payoutFailed];
|
||||
}
|
||||
|
||||
private function markAutoPayoutFailed(SettlementBatch $batch, \Throwable $e): void
|
||||
private function recordAutoPayoutFailure(SettlementBatch $batch, \Throwable $e): void
|
||||
{
|
||||
$message = mb_substr($e->getMessage(), 0, 200);
|
||||
|
||||
@@ -79,7 +123,7 @@ final class SettlementTickFinalizer
|
||||
}
|
||||
|
||||
$locked->forceFill([
|
||||
'status' => SettlementBatchStatus::Failed->value,
|
||||
'auto_payout_attempts' => (int) $locked->auto_payout_attempts + 1,
|
||||
'review_remark' => 'auto_payout_failed: '.$message,
|
||||
])->save();
|
||||
|
||||
@@ -95,4 +139,4 @@ final class SettlementTickFinalizer
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user