AgentEquityService.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. <?php
  2. namespace App\Services\Agent;
  3. use App\Models\Agent\Agent;
  4. use App\Models\Agent\AgentEquityOrder;
  5. use App\Models\Agent\AgentSetting;
  6. use Illuminate\Support\Facades\DB;
  7. use Illuminate\Support\Str;
  8. /**
  9. * 入股:代理在独立后台选择购买比例,金额 = 比例 × 每 1% 价格。
  10. * 余额足够立即扣款开通;不够则生成待付款订单,USDT 充值到账后自动扣款开通,无需审核。
  11. * 分红每月由总后台根据报表人工发放。
  12. */
  13. class AgentEquityService
  14. {
  15. public function __construct(private AgentWalletService $wallets)
  16. {
  17. }
  18. public function overview(Agent $agent): array
  19. {
  20. $setting = AgentSetting::current();
  21. $pending = AgentEquityOrder::query()->where('agent_id', $agent->id)->where('status', AgentEquityOrder::STATUS_PENDING)->first();
  22. $held = bcadd((string) $agent->equity_percent, '0', 4);
  23. $remaining = bcsub((string) $setting->equity_max_percent, $held, 4);
  24. return [
  25. 'enabled' => (int) $setting->equity_enabled,
  26. 'price_per_percent' => (string) $setting->equity_price_per_percent,
  27. 'min_percent' => (string) $setting->equity_min_percent,
  28. 'max_percent' => (string) $setting->equity_max_percent,
  29. 'equity_percent' => $held,
  30. 'remaining_percent' => bccomp($remaining, '0', 4) > 0 ? $remaining : '0.0000',
  31. 'balance' => (string) $agent->balance,
  32. 'pending_order' => $pending,
  33. ];
  34. }
  35. public function order(Agent $agent, string $percent): AgentEquityOrder
  36. {
  37. $order = DB::transaction(function () use ($agent, $percent) {
  38. $agent = Agent::query()->lockForUpdate()->findOrFail($agent->id);
  39. if (!$agent->isEquity()) throw new \RuntimeException('只有入股代理可以购买股份');
  40. $setting = AgentSetting::current();
  41. if (!(int) $setting->equity_enabled || bccomp((string) $setting->equity_price_per_percent, '0', 4) <= 0) {
  42. throw new \RuntimeException('入股暂未开放');
  43. }
  44. if (bccomp($percent, (string) $setting->equity_min_percent, 4) < 0 || bccomp($percent, (string) $setting->equity_max_percent, 4) > 0) {
  45. throw new \InvalidArgumentException("购买比例须在 {$setting->equity_min_percent}% 至 {$setting->equity_max_percent}% 之间");
  46. }
  47. if (AgentEquityOrder::query()->where('agent_id', $agent->id)->where('status', AgentEquityOrder::STATUS_PENDING)->exists()) {
  48. throw new \RuntimeException('已有待付款的入股订单,请先充值或取消');
  49. }
  50. if (bccomp(bcadd((string) $agent->equity_percent, $percent, 4), (string) $setting->equity_max_percent, 4) > 0) {
  51. throw new \RuntimeException("累计持股不能超过 {$setting->equity_max_percent}%");
  52. }
  53. return AgentEquityOrder::query()->create([
  54. 'order_no' => 'AE' . now()->format('YmdHis') . strtoupper(Str::random(6)),
  55. 'agent_id' => $agent->id, 'percent' => bcadd($percent, '0', 4),
  56. 'price_per_percent' => (string) $setting->equity_price_per_percent,
  57. 'amount' => bcmul($percent, (string) $setting->equity_price_per_percent, 4),
  58. 'status' => AgentEquityOrder::STATUS_PENDING,
  59. ]);
  60. });
  61. $this->fulfillPending((int) $agent->id);
  62. return $order->fresh();
  63. }
  64. public function cancel(Agent $agent, int $orderId): AgentEquityOrder
  65. {
  66. return DB::transaction(function () use ($agent, $orderId) {
  67. $order = AgentEquityOrder::query()->where('agent_id', $agent->id)->lockForUpdate()->findOrFail($orderId);
  68. if ($order->status !== AgentEquityOrder::STATUS_PENDING) throw new \RuntimeException('只有待付款订单可以取消');
  69. $order->status = AgentEquityOrder::STATUS_CANCELLED;
  70. $order->save();
  71. return $order;
  72. });
  73. }
  74. /** 余额足够时完成待付款订单(下单后、充值到账后调用)。 */
  75. public function fulfillPending(int $agentId): int
  76. {
  77. return DB::transaction(function () use ($agentId) {
  78. $agent = Agent::query()->lockForUpdate()->findOrFail($agentId);
  79. if (!$agent->isEquity()) return 0;
  80. $done = 0;
  81. $orders = AgentEquityOrder::query()->where('agent_id', $agentId)->where('status', AgentEquityOrder::STATUS_PENDING)
  82. ->orderBy('id')->lockForUpdate()->get();
  83. foreach ($orders as $order) {
  84. if (bccomp((string) $agent->balance, (string) $order->amount, 4) < 0) break;
  85. $this->wallets->adjust($agentId, (string) $order->amount, 'equity_purchase', '购买股份 ' . $order->percent . '%',
  86. 'equity-order:' . $order->id, 'agent', $agentId, 'agent_equity_order', (int) $order->id);
  87. $agent = Agent::query()->lockForUpdate()->findOrFail($agentId);
  88. $agent->equity_percent = bcadd((string) $agent->equity_percent, (string) $order->percent, 4);
  89. $agent->save();
  90. $order->status = AgentEquityOrder::STATUS_COMPLETED;
  91. $order->completed_at = now();
  92. $order->save();
  93. ++$done;
  94. }
  95. return $done;
  96. });
  97. }
  98. }