AgentService.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. <?php
  2. namespace App\Services\Agent;
  3. use App\Models\Agent\Agent;
  4. use App\Models\Agent\AgentApplication;
  5. use App\Models\Agent\AgentEquityOrder;
  6. use App\Models\Agent\AgentToken;
  7. use Illuminate\Support\Facades\DB;
  8. use Illuminate\Support\Facades\Hash;
  9. use Illuminate\Support\Str;
  10. /** 代理账号(只有一级代理)。分成方案由代理在独立后台申请,总后台审核后生效。 */
  11. class AgentService
  12. {
  13. public function create(array $data, ?int $createdBy = null): Agent
  14. {
  15. return $this->createAccount($data, $createdBy);
  16. }
  17. public function createFromApplication(AgentApplication $application, array $options, int $createdBy): Agent
  18. {
  19. if ($application->status !== 'pending' || !$application->password_hash || !$application->withdraw_password_hash) {
  20. throw new \RuntimeException('申请已处理或凭证不完整');
  21. }
  22. return $this->createAccount(['username' => $application->username] + $options, $createdBy, [
  23. 'password' => $application->password_hash, 'withdraw_password' => $application->withdraw_password_hash,
  24. ]);
  25. }
  26. private function createAccount(array $data, ?int $createdBy, ?array $hashes = null): Agent
  27. {
  28. return DB::transaction(function () use ($data, $createdBy, $hashes): Agent {
  29. $type = (int) ($data['type'] ?? Agent::TYPE_JOINT);
  30. if (!isset(Agent::TYPE_NAMES[$type])) throw new \InvalidArgumentException('代理类型错误');
  31. $agent = Agent::query()->create([
  32. 'parent_id' => null,
  33. 'path' => '/',
  34. 'level' => Agent::LEVEL_ONE,
  35. 'type' => $type,
  36. 'username' => trim((string) $data['username']),
  37. 'password' => $hashes['password'] ?? Hash::make((string) $data['password']),
  38. 'withdraw_password' => $hashes['withdraw_password'] ?? (empty($data['withdraw_password']) ? '' : Hash::make((string) $data['withdraw_password'])),
  39. 'real_name' => trim((string) ($data['real_name'] ?? '')),
  40. 'invitation_code' => $this->invitationCode(),
  41. 'deposit_fee_rate' => (string) ($data['deposit_fee_rate'] ?? 0),
  42. 'withdraw_fee_rate' => (string) ($data['withdraw_fee_rate'] ?? 0),
  43. 'status' => (int) ($data['status'] ?? Agent::STATUS_ENABLED),
  44. 'is_test' => (int) ($data['is_test'] ?? 0),
  45. 'created_by' => $createdBy,
  46. ] + array_intersect_key($data, ['language' => true]));
  47. $agent->path = '/' . $agent->id . '/';
  48. $agent->save();
  49. return $agent->fresh(['profitCommissionProfile']);
  50. });
  51. }
  52. public function update(Agent $agent, array $data): Agent
  53. {
  54. return DB::transaction(function () use ($agent, $data): Agent {
  55. $agent = Agent::query()->lockForUpdate()->findOrFail($agent->id);
  56. if (array_key_exists('type', $data) && $data['type'] !== null && (int) $data['type'] !== (int) $agent->type) {
  57. $this->assertTypeChangeable($agent);
  58. $agent->type = (int) $data['type'];
  59. }
  60. if (array_key_exists('real_name', $data)) {
  61. $agent->real_name = trim((string) ($data['real_name'] ?? ''));
  62. }
  63. foreach (['username', 'language', 'status', 'deposit_fee_rate', 'withdraw_fee_rate', 'is_test'] as $field) {
  64. if (array_key_exists($field, $data) && $data[$field] !== null) {
  65. $agent->{$field} = $data[$field];
  66. }
  67. }
  68. if (!empty($data['password'])) {
  69. $agent->password = Hash::make((string) $data['password']);
  70. }
  71. if (!empty($data['withdraw_password'])) {
  72. $agent->withdraw_password = Hash::make((string) $data['withdraw_password']);
  73. }
  74. $agent->save();
  75. if (!empty($data['password']) || (int) $agent->status === Agent::STATUS_DISABLED) {
  76. AgentToken::query()->where('agent_id', $agent->id)->delete();
  77. }
  78. return $agent->fresh(['profitCommissionProfile']);
  79. });
  80. }
  81. /** 已有资金、方案或股份的代理不能切换类型,避免保证金与股份口径混在一起。 */
  82. private function assertTypeChangeable(Agent $agent): void
  83. {
  84. if (bccomp((string) $agent->balance, '0', 4) !== 0 || bccomp((string) $agent->frozen_balance, '0', 4) !== 0
  85. || bccomp((string) $agent->margin_amount, '0', 4) !== 0 || bccomp((string) $agent->equity_percent, '0', 4) !== 0
  86. || $agent->profit_commission_profile_id
  87. || AgentEquityOrder::query()->where('agent_id', $agent->id)->where('status', AgentEquityOrder::STATUS_PENDING)->exists()) {
  88. throw new \RuntimeException('代理已有余额、保证金、方案或股份,不能修改类型');
  89. }
  90. }
  91. private function invitationCode(): string
  92. {
  93. do {
  94. $code = strtoupper(Str::random(10));
  95. } while (Agent::query()->where('invitation_code', $code)->exists());
  96. return $code;
  97. }
  98. }