feat: 新增赔率版本和玩法配置版本的删除接口,支持删除草稿版本
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Admin\Config;
|
||||
|
||||
use App\Models\AdminUser;
|
||||
use App\Models\OddsVersion;
|
||||
use App\Lottery\ErrorCode;
|
||||
use App\Support\ApiResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Lottery\ConfigVersionStatus;
|
||||
use App\Services\Config\OddsStreamService;
|
||||
|
||||
/** DELETE /api/v1/admin/config/odds-versions/{id} */
|
||||
final class OddsVersionDestroyController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request, int $id, OddsStreamService $service): JsonResponse
|
||||
{
|
||||
/** @var AdminUser $admin */
|
||||
$admin = $request->lotteryAdmin();
|
||||
|
||||
/** @var OddsVersion $version */
|
||||
$version = OddsVersion::query()->whereKey($id)->firstOrFail();
|
||||
|
||||
if ($version->status === ConfigVersionStatus::Active->value) {
|
||||
return ApiResponse::error(
|
||||
'cannot delete active config version',
|
||||
ErrorCode::ConfigVersionCannotDeleteActive->value,
|
||||
null,
|
||||
400,
|
||||
);
|
||||
}
|
||||
|
||||
$service->deleteVersion($version, $admin, $request);
|
||||
|
||||
return ApiResponse::success(['deleted' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Admin\Config;
|
||||
|
||||
use App\Models\AdminUser;
|
||||
use App\Lottery\ErrorCode;
|
||||
use App\Support\ApiResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\PlayConfigVersion;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Lottery\ConfigVersionStatus;
|
||||
use App\Services\Config\PlayConfigStreamService;
|
||||
|
||||
/** DELETE /api/v1/admin/config/play-versions/{id} */
|
||||
final class PlayConfigVersionDestroyController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request, int $id, PlayConfigStreamService $service): JsonResponse
|
||||
{
|
||||
/** @var AdminUser $admin */
|
||||
$admin = $request->lotteryAdmin();
|
||||
|
||||
/** @var PlayConfigVersion $version */
|
||||
$version = PlayConfigVersion::query()->whereKey($id)->firstOrFail();
|
||||
|
||||
if ($version->status === ConfigVersionStatus::Active->value) {
|
||||
return ApiResponse::error(
|
||||
'cannot delete active config version',
|
||||
ErrorCode::ConfigVersionCannotDeleteActive->value,
|
||||
null,
|
||||
400,
|
||||
);
|
||||
}
|
||||
|
||||
$service->deleteVersion($version, $admin, $request);
|
||||
|
||||
return ApiResponse::success(['deleted' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Admin\Config;
|
||||
|
||||
use App\Models\AdminUser;
|
||||
use App\Models\RiskCapVersion;
|
||||
use App\Lottery\ErrorCode;
|
||||
use App\Support\ApiResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Lottery\ConfigVersionStatus;
|
||||
use App\Services\Config\RiskCapStreamService;
|
||||
|
||||
/** DELETE /api/v1/admin/config/risk-cap-versions/{id} */
|
||||
final class RiskCapVersionDestroyController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request, int $id, RiskCapStreamService $service): JsonResponse
|
||||
{
|
||||
/** @var AdminUser $admin */
|
||||
$admin = $request->lotteryAdmin();
|
||||
|
||||
/** @var RiskCapVersion $version */
|
||||
$version = RiskCapVersion::query()->whereKey($id)->firstOrFail();
|
||||
|
||||
if ($version->status === ConfigVersionStatus::Active->value) {
|
||||
return ApiResponse::error(
|
||||
'cannot delete active config version',
|
||||
ErrorCode::ConfigVersionCannotDeleteActive->value,
|
||||
null,
|
||||
400,
|
||||
);
|
||||
}
|
||||
|
||||
$service->deleteVersion($version, $admin, $request);
|
||||
|
||||
return ApiResponse::success(['deleted' => true]);
|
||||
}
|
||||
}
|
||||
31
app/Http/Requests/Ticket/TicketBetRequest.php
Normal file
31
app/Http/Requests/Ticket/TicketBetRequest.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Ticket;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
abstract class TicketBetRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'draw_id' => ['required', 'string', 'max:32'],
|
||||
'currency_code' => ['required', 'string', 'max:16'],
|
||||
'client_trace_id' => ['nullable', 'string', 'max:64'],
|
||||
'lines' => ['required', 'array', 'min:1', 'max:100'],
|
||||
'lines.*.number' => ['required', 'string', 'max:32'],
|
||||
'lines.*.play_code' => ['required', 'string', 'max:32'],
|
||||
'lines.*.amount' => ['required', 'integer', 'min:1'],
|
||||
'lines.*.digit_slot' => ['nullable', 'integer', 'min:0', 'max:3'],
|
||||
'lines.*.dimension' => ['nullable', 'string', 'in:D2,D3,D4'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Http\Requests\Ticket;
|
||||
|
||||
final class TicketPlaceRequest extends TicketPreviewRequest
|
||||
final class TicketPlaceRequest extends TicketBetRequest
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
|
||||
@@ -2,30 +2,6 @@
|
||||
|
||||
namespace App\Http\Requests\Ticket;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class TicketPreviewRequest extends FormRequest
|
||||
final class TicketPreviewRequest extends TicketBetRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'draw_id' => ['required', 'string', 'max:32'],
|
||||
'currency_code' => ['required', 'string', 'max:16'],
|
||||
'client_trace_id' => ['nullable', 'string', 'max:64'],
|
||||
'lines' => ['required', 'array', 'min:1', 'max:100'],
|
||||
'lines.*.number' => ['required', 'string', 'max:32'],
|
||||
'lines.*.play_code' => ['required', 'string', 'max:32'],
|
||||
'lines.*.amount' => ['required', 'integer', 'min:1'],
|
||||
'lines.*.digit_slot' => ['nullable', 'integer', 'min:0', 'max:3'],
|
||||
'lines.*.dimension' => ['nullable', 'string', 'in:D2,D3,D4'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,9 @@ enum ErrorCode: int
|
||||
/** 赔率 / 目录币种未启用或不可下注 */
|
||||
case ConfigCurrencyInvalid = 2103;
|
||||
|
||||
/** 不能删除当前生效(active)的配置版本 */
|
||||
case ConfigVersionCannotDeleteActive = 2104;
|
||||
|
||||
/* ========== 8000–8999 玩家 SSO / Bearer 鉴权 ========== */
|
||||
|
||||
/** 无 Bearer / 格式错误 / token 为空 */
|
||||
|
||||
@@ -149,6 +149,26 @@ final class OddsStreamService
|
||||
);
|
||||
}
|
||||
|
||||
public function deleteVersion(OddsVersion $version, AdminUser $admin, ?Request $request = null): void
|
||||
{
|
||||
$before = $this->snapshotVersion($version);
|
||||
|
||||
DB::transaction(function () use ($version): void {
|
||||
$version->delete();
|
||||
});
|
||||
|
||||
AuditLogger::recordForAdmin(
|
||||
$admin,
|
||||
$request,
|
||||
moduleCode: 'odds',
|
||||
actionCode: 'delete',
|
||||
targetType: 'odds_version',
|
||||
targetId: (string) $version->id,
|
||||
beforeJson: $before,
|
||||
afterJson: null,
|
||||
);
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
private function snapshotVersion(OddsVersion $v): array
|
||||
{
|
||||
|
||||
@@ -147,6 +147,26 @@ final class PlayConfigStreamService
|
||||
);
|
||||
}
|
||||
|
||||
public function deleteVersion(PlayConfigVersion $version, AdminUser $admin, ?Request $request = null): void
|
||||
{
|
||||
$before = $this->snapshotVersion($version);
|
||||
|
||||
DB::transaction(function () use ($version): void {
|
||||
$version->delete();
|
||||
});
|
||||
|
||||
AuditLogger::recordForAdmin(
|
||||
$admin,
|
||||
$request,
|
||||
moduleCode: 'play_config',
|
||||
actionCode: 'delete',
|
||||
targetType: 'play_config_version',
|
||||
targetId: (string) $version->id,
|
||||
beforeJson: $before,
|
||||
afterJson: null,
|
||||
);
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
private function snapshotVersion(PlayConfigVersion $v): array
|
||||
{
|
||||
|
||||
@@ -131,6 +131,26 @@ final class RiskCapStreamService
|
||||
);
|
||||
}
|
||||
|
||||
public function deleteVersion(RiskCapVersion $version, AdminUser $admin, ?Request $request = null): void
|
||||
{
|
||||
$before = $this->snapshotVersion($version);
|
||||
|
||||
DB::transaction(function () use ($version): void {
|
||||
$version->delete();
|
||||
});
|
||||
|
||||
AuditLogger::recordForAdmin(
|
||||
$admin,
|
||||
$request,
|
||||
moduleCode: 'risk_cap',
|
||||
actionCode: 'delete',
|
||||
targetType: 'risk_cap_version',
|
||||
targetId: (string) $version->id,
|
||||
beforeJson: $before,
|
||||
afterJson: null,
|
||||
);
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
private function snapshotVersion(RiskCapVersion $v): array
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Services\Draw;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use App\Models\Draw;
|
||||
use App\Models\RiskPool;
|
||||
use App\Lottery\DrawStatus;
|
||||
use App\Models\DrawResultItem;
|
||||
use App\Models\DrawResultBatch;
|
||||
@@ -144,6 +145,33 @@ final class DrawHallSnapshotBuilder
|
||||
'seconds_remaining_in_cooldown' => $coolingRemain,
|
||||
];
|
||||
|
||||
$riskAlerts = RiskPool::query()
|
||||
->where('draw_id', $target->id)
|
||||
->where(function ($q): void {
|
||||
$q->where('sold_out_status', 1)
|
||||
->orWhereRaw('(locked_amount * 1.0 / NULLIF(total_cap_amount, 0)) >= 0.8');
|
||||
})
|
||||
->orderByRaw('(locked_amount * 1.0 / NULLIF(total_cap_amount, 0)) DESC')
|
||||
->orderByDesc('locked_amount')
|
||||
->orderBy('normalized_number')
|
||||
->limit(500)
|
||||
->get(['normalized_number', 'total_cap_amount', 'locked_amount', 'remaining_amount', 'sold_out_status'])
|
||||
->map(fn ($row) => [
|
||||
'normalized_number' => (string) $row->normalized_number,
|
||||
'total_cap_amount' => (int) $row->total_cap_amount,
|
||||
'locked_amount' => (int) $row->locked_amount,
|
||||
'remaining_amount' => (int) $row->remaining_amount,
|
||||
'sold_out_status' => (int) $row->sold_out_status,
|
||||
'is_sold_out' => (int) $row->sold_out_status === 1,
|
||||
'usage_ratio' => (int) $row->total_cap_amount > 0
|
||||
? round(((int) $row->locked_amount) / (int) $row->total_cap_amount, 6)
|
||||
: null,
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$payload['risk_pool_alerts'] = $riskAlerts;
|
||||
|
||||
if ($this->showsPublishedResults((string) $target->status)) {
|
||||
$batchId = DrawResultBatch::query()
|
||||
->where('draw_id', $target->id)
|
||||
|
||||
@@ -41,12 +41,12 @@ final class TicketPlacementService
|
||||
$expectedVersions = null;
|
||||
}
|
||||
|
||||
$order = DB::transaction(function () use (
|
||||
$placement = DB::transaction(function () use (
|
||||
$player,
|
||||
$currencyCode,
|
||||
$payload,
|
||||
$expectedVersions
|
||||
): TicketOrder {
|
||||
): array {
|
||||
$draw = Draw::query()
|
||||
->where('draw_no', (string) $payload['draw_id'])
|
||||
->lockForUpdate()
|
||||
@@ -103,7 +103,7 @@ final class TicketPlacementService
|
||||
'client_trace_id' => $payload['client_trace_id'] ?? null,
|
||||
]);
|
||||
|
||||
$this->ticketWalletService->deduct($player, $currencyCode, $totalActualDeduct, $order);
|
||||
$balanceAfter = $this->ticketWalletService->deduct($player, $currencyCode, $totalActualDeduct, $order);
|
||||
|
||||
foreach ($evaluatedLines as $evaluated) {
|
||||
$item = TicketItem::query()->create([
|
||||
@@ -157,9 +157,15 @@ final class TicketPlacementService
|
||||
$this->jackpotContribution->recordFromPlacedTicketItem($item, $draw, $currencyCode);
|
||||
}
|
||||
|
||||
return $order;
|
||||
return [
|
||||
'order' => $order,
|
||||
'balance_after' => $balanceAfter,
|
||||
];
|
||||
});
|
||||
|
||||
$order = $placement['order'];
|
||||
$balanceAfter = $placement['balance_after'];
|
||||
|
||||
$draw = Draw::query()->whereKey($order->draw_id)->firstOrFail();
|
||||
|
||||
return [
|
||||
@@ -174,6 +180,7 @@ final class TicketPlacementService
|
||||
'total_actual_deduct' => (int) $order->total_actual_deduct,
|
||||
'total_estimated_payout' => (int) $order->total_estimated_payout,
|
||||
],
|
||||
'balance_after' => $balanceAfter,
|
||||
'items' => TicketItem::query()
|
||||
->where('order_id', $order->id)
|
||||
->orderBy('id')
|
||||
|
||||
@@ -17,7 +17,7 @@ final class TicketWalletService
|
||||
|
||||
private const TXN_DIR_IN = 1;
|
||||
|
||||
public function deduct(Player $player, string $currencyCode, int $amountMinor, TicketOrder $order): void
|
||||
public function deduct(Player $player, string $currencyCode, int $amountMinor, TicketOrder $order): int
|
||||
{
|
||||
$wallet = PlayerWallet::query()
|
||||
->where('player_id', $player->id)
|
||||
@@ -65,6 +65,8 @@ final class TicketWalletService
|
||||
'idempotent_key' => $order->client_trace_id,
|
||||
'remark' => null,
|
||||
]);
|
||||
|
||||
return $after;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -137,28 +137,17 @@ final class LotteryTransferService
|
||||
|
||||
DB::transaction(function () use ($player, $currencyCode, $amountMinor, $order, $main, $transferNo, $idempotentKey): void {
|
||||
$wallet = $this->lockLotteryWallet($player, $currencyCode);
|
||||
$before = (int) $wallet->balance;
|
||||
$after = $before + $amountMinor;
|
||||
$wallet->forceFill([
|
||||
'balance' => $after,
|
||||
'version' => (int) $wallet->version + 1,
|
||||
])->save();
|
||||
|
||||
WalletTxn::query()->create([
|
||||
'txn_no' => $this->newTxnNo(),
|
||||
'player_id' => $player->id,
|
||||
'wallet_id' => $wallet->id,
|
||||
'biz_type' => self::BIZ_TRANSFER_IN,
|
||||
'biz_no' => $transferNo,
|
||||
'direction' => self::TXN_DIR_IN,
|
||||
'amount' => $amountMinor,
|
||||
'balance_before' => $before,
|
||||
'balance_after' => $after,
|
||||
'status' => self::TXN_POSTED,
|
||||
'external_ref_no' => $main->externalRefNo,
|
||||
'idempotent_key' => $idempotentKey,
|
||||
'remark' => null,
|
||||
]);
|
||||
$this->postLotteryWalletMovement(
|
||||
wallet: $wallet,
|
||||
bizType: self::BIZ_TRANSFER_IN,
|
||||
direction: self::TXN_DIR_IN,
|
||||
amountMinor: $amountMinor,
|
||||
bizNo: $transferNo,
|
||||
externalRefNo: $main->externalRefNo,
|
||||
idempotentKey: $idempotentKey,
|
||||
remark: null,
|
||||
deltaSign: 1,
|
||||
);
|
||||
|
||||
$order->forceFill([
|
||||
'status' => self::ST_SUCCESS,
|
||||
@@ -222,34 +211,18 @@ final class LotteryTransferService
|
||||
try {
|
||||
DB::transaction(function () use ($player, $currencyCode, $amountMinor, $transferNo, $idempotentKey): void {
|
||||
$wallet = $this->lockLotteryWallet($player, $currencyCode);
|
||||
$before = (int) $wallet->balance;
|
||||
if ($before < $amountMinor) {
|
||||
throw new WalletOperationException(
|
||||
'insufficient_balance',
|
||||
ErrorCode::WalletInsufficientBalance->value,
|
||||
);
|
||||
}
|
||||
$after = $before - $amountMinor;
|
||||
$wallet->forceFill([
|
||||
'balance' => $after,
|
||||
'version' => (int) $wallet->version + 1,
|
||||
])->save();
|
||||
|
||||
WalletTxn::query()->create([
|
||||
'txn_no' => $this->newTxnNo(),
|
||||
'player_id' => $player->id,
|
||||
'wallet_id' => $wallet->id,
|
||||
'biz_type' => self::BIZ_TRANSFER_OUT,
|
||||
'biz_no' => $transferNo,
|
||||
'direction' => self::TXN_DIR_OUT,
|
||||
'amount' => $amountMinor,
|
||||
'balance_before' => $before,
|
||||
'balance_after' => $after,
|
||||
'status' => self::TXN_POSTED,
|
||||
'external_ref_no' => null,
|
||||
'idempotent_key' => $idempotentKey,
|
||||
'remark' => null,
|
||||
]);
|
||||
$this->postLotteryWalletMovement(
|
||||
wallet: $wallet,
|
||||
bizType: self::BIZ_TRANSFER_OUT,
|
||||
direction: self::TXN_DIR_OUT,
|
||||
amountMinor: $amountMinor,
|
||||
bizNo: $transferNo,
|
||||
externalRefNo: null,
|
||||
idempotentKey: $idempotentKey,
|
||||
remark: null,
|
||||
deltaSign: -1,
|
||||
requireBalance: true,
|
||||
);
|
||||
});
|
||||
} catch (WalletOperationException $e) {
|
||||
if ($e->lotteryCode === ErrorCode::WalletInsufficientBalance->value) {
|
||||
@@ -292,28 +265,17 @@ final class LotteryTransferService
|
||||
|
||||
DB::transaction(function () use ($player, $currencyCode, $amountMinor, $transferNo, $idempotentKey, $order, $main): void {
|
||||
$wallet = $this->lockLotteryWallet($player, $currencyCode);
|
||||
$before = (int) $wallet->balance;
|
||||
$after = $before + $amountMinor;
|
||||
$wallet->forceFill([
|
||||
'balance' => $after,
|
||||
'version' => (int) $wallet->version + 1,
|
||||
])->save();
|
||||
|
||||
WalletTxn::query()->create([
|
||||
'txn_no' => $this->newTxnNo(),
|
||||
'player_id' => $player->id,
|
||||
'wallet_id' => $wallet->id,
|
||||
'biz_type' => self::BIZ_TRANSFER_OUT_REFUND,
|
||||
'biz_no' => $transferNo,
|
||||
'direction' => self::TXN_DIR_IN,
|
||||
'amount' => $amountMinor,
|
||||
'balance_before' => $before,
|
||||
'balance_after' => $after,
|
||||
'status' => self::TXN_POSTED,
|
||||
'external_ref_no' => null,
|
||||
'idempotent_key' => $idempotentKey,
|
||||
'remark' => 'withdraw_failed_refund',
|
||||
]);
|
||||
$this->postLotteryWalletMovement(
|
||||
wallet: $wallet,
|
||||
bizType: self::BIZ_TRANSFER_OUT_REFUND,
|
||||
direction: self::TXN_DIR_IN,
|
||||
amountMinor: $amountMinor,
|
||||
bizNo: $transferNo,
|
||||
externalRefNo: null,
|
||||
idempotentKey: $idempotentKey,
|
||||
remark: 'withdraw_failed_refund',
|
||||
deltaSign: 1,
|
||||
);
|
||||
|
||||
$order->forceFill([
|
||||
'status' => self::ST_FAILED,
|
||||
@@ -386,28 +348,17 @@ final class LotteryTransferService
|
||||
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',
|
||||
]);
|
||||
$this->postLotteryWalletMovement(
|
||||
wallet: $wallet,
|
||||
bizType: self::BIZ_REVERSAL,
|
||||
direction: self::TXN_DIR_IN,
|
||||
amountMinor: (int) $order->amount,
|
||||
bizNo: $order->transfer_no,
|
||||
externalRefNo: null,
|
||||
idempotentKey: null,
|
||||
remark: $remark ?: 'reversal_pending_reconcile',
|
||||
deltaSign: 1,
|
||||
);
|
||||
|
||||
$order->forceFill([
|
||||
'status' => self::ST_REVERSED,
|
||||
@@ -701,6 +652,61 @@ final class LotteryTransferService
|
||||
return 'WX_'.Str::lower(str_replace('-', '', Str::uuid()->toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一执行彩票钱包余额变更并记录流水。
|
||||
*
|
||||
* @return array{before: int, after: int}
|
||||
*/
|
||||
private function postLotteryWalletMovement(
|
||||
PlayerWallet $wallet,
|
||||
string $bizType,
|
||||
int $direction,
|
||||
int $amountMinor,
|
||||
string $bizNo,
|
||||
?string $externalRefNo,
|
||||
?string $idempotentKey,
|
||||
?string $remark,
|
||||
int $deltaSign,
|
||||
bool $requireBalance = false,
|
||||
): array {
|
||||
$before = (int) $wallet->balance;
|
||||
if ($requireBalance && $deltaSign < 0 && $before < $amountMinor) {
|
||||
throw new WalletOperationException(
|
||||
'insufficient_balance',
|
||||
ErrorCode::WalletInsufficientBalance->value,
|
||||
);
|
||||
}
|
||||
|
||||
$delta = $amountMinor * $deltaSign;
|
||||
$after = $before + $delta;
|
||||
|
||||
$wallet->forceFill([
|
||||
'balance' => $after,
|
||||
'version' => (int) $wallet->version + 1,
|
||||
])->save();
|
||||
|
||||
WalletTxn::query()->create([
|
||||
'txn_no' => $this->newTxnNo(),
|
||||
'player_id' => $wallet->player_id,
|
||||
'wallet_id' => $wallet->id,
|
||||
'biz_type' => $bizType,
|
||||
'biz_no' => $bizNo,
|
||||
'direction' => $direction,
|
||||
'amount' => $amountMinor,
|
||||
'balance_before' => $before,
|
||||
'balance_after' => $after,
|
||||
'status' => self::TXN_POSTED,
|
||||
'external_ref_no' => $externalRefNo,
|
||||
'idempotent_key' => $idempotentKey,
|
||||
'remark' => $remark,
|
||||
]);
|
||||
|
||||
return [
|
||||
'before' => $before,
|
||||
'after' => $after,
|
||||
];
|
||||
}
|
||||
|
||||
private function failedOrderToException(TransferOrder $order): WalletOperationException
|
||||
{
|
||||
if (($order->fail_reason ?? '') === 'insufficient_balance') {
|
||||
|
||||
Reference in New Issue
Block a user