| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- <?php
- namespace App\Services\Agent;
- use App\Models\Agent\Agent;
- use App\Models\Agent\AgentBalanceLog;
- use Illuminate\Support\Facades\DB;
- class AgentWalletService
- {
- public function transfer(int $fromAgentId, int $toAgentId, string $amount, string $remark, string $idempotencyKey, int $operatorId): array
- {
- if ($fromAgentId === $toAgentId) {
- throw new \InvalidArgumentException('转出与转入代理不能相同');
- }
- if (bccomp($amount, '0', 4) <= 0) {
- throw new \InvalidArgumentException('金额必须大于0');
- }
- return DB::transaction(function () use ($fromAgentId, $toAgentId, $amount, $remark, $idempotencyKey, $operatorId) {
- $existing = AgentBalanceLog::query()->where('idempotency_key', $idempotencyKey . ':out')->first();
- if ($existing) {
- $this->assertSameTransfer($existing, $fromAgentId, $toAgentId, $amount);
- return [
- 'from_balance' => (string) Agent::query()->findOrFail($fromAgentId)->balance,
- 'to_balance' => (string) Agent::query()->findOrFail($toAgentId)->balance,
- ];
- }
- $locked = Agent::query()->whereIn('id', [$fromAgentId, $toAgentId])->orderBy('id')->lockForUpdate()->get()->keyBy('id');
- $existing = AgentBalanceLog::query()->where('idempotency_key', $idempotencyKey . ':out')->first();
- if ($existing) {
- $this->assertSameTransfer($existing, $fromAgentId, $toAgentId, $amount);
- return [
- 'from_balance' => (string) Agent::query()->findOrFail($fromAgentId)->balance,
- 'to_balance' => (string) Agent::query()->findOrFail($toAgentId)->balance,
- ];
- }
- $from = $locked->get($fromAgentId);
- $to = $locked->get($toAgentId);
- if (!$from || !$to) {
- throw new \RuntimeException('代理不存在');
- }
- if (bccomp((string) $from->balance, $amount, 4) < 0) {
- throw new \RuntimeException('代理余额不足');
- }
- $fromBefore = bcadd((string) $from->balance, '0', 4);
- $toBefore = bcadd((string) $to->balance, '0', 4);
- $from->balance = bcsub($fromBefore, $amount, 4);
- $to->balance = bcadd($toBefore, $amount, 4);
- $from->save();
- $to->save();
- AgentBalanceLog::query()->create([
- 'agent_id' => $fromAgentId, 'type' => 'transfer_out', 'amount' => bcsub('0', $amount, 4),
- 'before_balance' => $fromBefore, 'after_balance' => $from->balance,
- 'reference_type' => 'agent_transfer', 'reference_id' => $toAgentId,
- 'operator_type' => 'agent', 'operator_id' => $operatorId,
- 'idempotency_key' => $idempotencyKey . ':out', 'remark' => $remark,
- ]);
- AgentBalanceLog::query()->create([
- 'agent_id' => $toAgentId, 'type' => 'transfer_in', 'amount' => $amount,
- 'before_balance' => $toBefore, 'after_balance' => $to->balance,
- 'reference_type' => 'agent_transfer', 'reference_id' => $fromAgentId,
- 'operator_type' => 'agent', 'operator_id' => $operatorId,
- 'idempotency_key' => $idempotencyKey . ':in', 'remark' => $remark,
- ]);
- return ['from_balance' => (string) $from->balance, 'to_balance' => (string) $to->balance];
- });
- }
- public function adjust(
- int $agentId,
- string $amount,
- string $type,
- string $remark,
- string $idempotencyKey,
- string $operatorType,
- ?int $operatorId,
- string $referenceType = '',
- ?int $referenceId = null
- ): AgentBalanceLog {
- if (!in_array($type, ['credit', 'debit', 'commission', 'withdraw_refund'], true)) {
- throw new \InvalidArgumentException('资金变动类型错误');
- }
- if (bccomp($amount, '0', 4) <= 0) {
- throw new \InvalidArgumentException('金额必须大于0');
- }
- return DB::transaction(function () use ($agentId, $amount, $type, $remark, $idempotencyKey, $operatorType, $operatorId, $referenceType, $referenceId) {
- $existing = AgentBalanceLog::query()->where('idempotency_key', $idempotencyKey)->first();
- if ($existing) {
- $this->assertSameAdjustment($existing, $agentId, $type, $amount);
- return $existing;
- }
- $agent = Agent::query()->lockForUpdate()->findOrFail($agentId);
- $existing = AgentBalanceLog::query()->where('idempotency_key', $idempotencyKey)->first();
- if ($existing) {
- $this->assertSameAdjustment($existing, $agentId, $type, $amount);
- return $existing;
- }
- $before = bcadd((string) $agent->balance, '0', 4);
- $signed = $type === 'debit' ? bcsub('0', $amount, 4) : bcadd($amount, '0', 4);
- $after = bcadd($before, $signed, 4);
- if (bccomp($after, '0', 4) < 0) {
- throw new \RuntimeException('代理余额不足');
- }
- $agent->balance = $after;
- $agent->save();
- return AgentBalanceLog::query()->create([
- 'agent_id' => $agentId, 'type' => $type, 'amount' => $signed,
- 'before_balance' => $before, 'after_balance' => $after,
- 'operator_type' => $operatorType, 'operator_id' => $operatorId,
- 'idempotency_key' => $idempotencyKey, 'remark' => $remark,
- 'reference_type' => $referenceType, 'reference_id' => $referenceId,
- ]);
- });
- }
- private function assertSameTransfer(AgentBalanceLog $log, int $fromAgentId, int $toAgentId, string $amount): void
- {
- $expected = bcsub('0', $amount, 4);
- if ((int) $log->agent_id !== $fromAgentId || (int) $log->reference_id !== $toAgentId || bccomp((string) $log->amount, $expected, 4) !== 0) {
- throw new \RuntimeException('request_id 已用于其他资金操作');
- }
- }
- private function assertSameAdjustment(AgentBalanceLog $log, int $agentId, string $type, string $amount): void
- {
- $expected = $type === 'debit' ? bcsub('0', $amount, 4) : bcadd($amount, '0', 4);
- if ((int) $log->agent_id !== $agentId || $log->type !== $type || bccomp((string) $log->amount, $expected, 4) !== 0) {
- throw new \RuntimeException('request_id 已用于其他资金操作');
- }
- }
- }
|