121 lines
4.4 KiB
PHP
121 lines
4.4 KiB
PHP
<?php
|
||
|
||
namespace App\Services\AgentSettlement;
|
||
|
||
use App\Models\Player;
|
||
use App\Services\Player\PlayerCreditService;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Validation\ValidationException;
|
||
|
||
final class SettlementPaymentService
|
||
{
|
||
public function __construct(
|
||
private readonly AgentSettlementBillGuard $billGuard,
|
||
private readonly PlayerCreditService $playerCreditService,
|
||
private readonly PeriodCloseRebateService $periodCloseRebate,
|
||
private readonly AgentSettlementPeriodCompletionService $periodCompletion,
|
||
) {}
|
||
|
||
public function confirmBill(int $billId): void
|
||
{
|
||
$this->billGuard->markConfirmed($billId);
|
||
}
|
||
|
||
/**
|
||
* @param array{method?: string|null, proof?: string|null, remark?: string|null} $meta
|
||
*/
|
||
public function recordPayment(int $billId, int $amount, int $adminUserId, array $meta = []): void
|
||
{
|
||
DB::transaction(function () use ($billId, $amount, $adminUserId, $meta): void {
|
||
$bill = DB::table('settlement_bills')->where('id', $billId)->lockForUpdate()->first();
|
||
if ($bill === null) {
|
||
throw new \InvalidArgumentException('bill_not_found');
|
||
}
|
||
|
||
$this->billGuard->assertPeriodMutable($billId);
|
||
if (! in_array((string) $bill->status, ['confirmed', 'partial_paid', 'overdue'], true)) {
|
||
throw ValidationException::withMessages([
|
||
'bill' => ['not_payable'],
|
||
]);
|
||
}
|
||
|
||
$payAmount = min($amount, abs((int) $bill->unpaid_amount));
|
||
if ($payAmount <= 0) {
|
||
return;
|
||
}
|
||
|
||
[$payerType, $payerId, $payeeType, $payeeId] = $this->resolvePayerPayee($bill);
|
||
|
||
DB::table('payment_records')->insert([
|
||
'settlement_bill_id' => $billId,
|
||
'payer_type' => $payerType,
|
||
'payer_id' => $payerId,
|
||
'payee_type' => $payeeType,
|
||
'payee_id' => $payeeId,
|
||
'amount' => $payAmount,
|
||
'method' => $meta['method'] ?? null,
|
||
'proof' => $meta['proof'] ?? null,
|
||
'remark' => $meta['remark'] ?? null,
|
||
'status' => 'confirmed',
|
||
'created_by' => $adminUserId,
|
||
'confirmed_by' => $adminUserId,
|
||
'confirmed_at' => now(),
|
||
'created_at' => now(),
|
||
'updated_at' => now(),
|
||
]);
|
||
|
||
$newPaid = (int) $bill->paid_amount + $payAmount;
|
||
$newUnpaid = max(0, (int) $bill->unpaid_amount - $payAmount);
|
||
$status = $newUnpaid === 0 ? 'settled' : 'partial_paid';
|
||
|
||
DB::table('settlement_bills')->where('id', $billId)->update([
|
||
'paid_amount' => $newPaid,
|
||
'unpaid_amount' => $newUnpaid,
|
||
'status' => $status,
|
||
'updated_at' => now(),
|
||
]);
|
||
|
||
if ($bill->owner_type === 'player' && (int) $bill->owner_id > 0) {
|
||
$player = Player::query()->find((int) $bill->owner_id);
|
||
if ($player !== null) {
|
||
if ((int) $bill->net_amount > 0) {
|
||
$this->playerCreditService->releaseFromSettlement($player, $payAmount, $billId);
|
||
} elseif ((int) $bill->net_amount < 0) {
|
||
$this->playerCreditService->applySettlementPayout($player, $payAmount, $billId);
|
||
}
|
||
|
||
if ($status === 'settled') {
|
||
$this->periodCloseRebate->markRebatesSettledForBill($billId);
|
||
}
|
||
}
|
||
}
|
||
|
||
$this->periodCompletion->syncIfReady((int) $bill->settlement_period_id);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* net_amount > 0:owner 应付 counterparty;< 0:counterparty 应付 owner。
|
||
*
|
||
* @return array{0: string, 1: int, 2: string, 3: int}
|
||
*/
|
||
private function resolvePayerPayee(object $bill): array
|
||
{
|
||
if ((int) $bill->net_amount < 0) {
|
||
return [
|
||
(string) $bill->counterparty_type,
|
||
(int) $bill->counterparty_id,
|
||
(string) $bill->owner_type,
|
||
(int) $bill->owner_id,
|
||
];
|
||
}
|
||
|
||
return [
|
||
(string) $bill->owner_type,
|
||
(int) $bill->owner_id,
|
||
(string) $bill->counterparty_type,
|
||
(int) $bill->counterparty_id,
|
||
];
|
||
}
|
||
}
|