WalletWithdrawRecordService.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. <?php
  2. namespace App\Services\Admin;
  3. use App\Models\WalletWithdrawRecord;
  4. use App\Models\WalletTransRecord;
  5. use App\Enums\WithdrawStatus;
  6. use App\Enums\WithdrawAuditStatus;
  7. use App\Enums\TransactionType;
  8. use App\Enums\TransactionStatus;
  9. use Illuminate\Support\Facades\DB;
  10. use Illuminate\Support\Facades\Log;
  11. class WalletWithdrawRecordService
  12. {
  13. /**
  14. * 审核提现申请
  15. *
  16. * @param int $id 提现记录ID
  17. * @param int $status 审核状态(2:通过 3:拒绝)
  18. * @param string|null $remark 审核备注
  19. */
  20. public function audit(int $id, int $status, ?string $remark = null): void
  21. {
  22. DB::transaction(function () use ($id, $status, $remark) {
  23. // 获取提现记录
  24. $record = WalletWithdrawRecord::lockForUpdate()->findOrFail($id);
  25. // 验证记录状态
  26. abort_if($record->audit_state !== WithdrawAuditStatus::PENDING->value, 400, '提现申请状态异常');
  27. // 获取用户钱包
  28. $wallet = $record->wallet;
  29. abort_if(!$wallet, 404, '钱包信息不存在');
  30. // 验证冻结金额
  31. abort_if($wallet->frozen_amount < $record->amount, 400, '钱包冻结金额异常');
  32. if ($status === WithdrawAuditStatus::APPROVED->value) {
  33. // 审核通过,发起自动打款
  34. $this->processApproved($record, $remark);
  35. } else {
  36. // 审核拒绝,解冻余额
  37. $this->processRejected($record, $remark);
  38. }
  39. // 记录审核信息
  40. $record->auditor = auth()->user()->name;
  41. $record->audit_time = now();
  42. $record->audit_remark = $remark;
  43. $record->audit_state = $status;
  44. $record->save();
  45. // 记录日志
  46. Log::info('提现申请审核完成', [
  47. 'withdraw_id' => $record->id,
  48. 'external_no' => $record->external_no,
  49. 'status' => $status,
  50. 'remark' => $remark,
  51. 'auditor' => $record->auditor
  52. ]);
  53. });
  54. }
  55. /**
  56. * 处理审核通过
  57. */
  58. private function processApproved(WalletWithdrawRecord $record, ?string $remark): void
  59. {
  60. try {
  61. // 1. 更新提现记录状态为处理中
  62. $record->state = WithdrawStatus::PROCESSING->value;
  63. $record->save();
  64. // 获取钱包当前余额
  65. $wallet = $record->wallet;
  66. $currentBalance = $wallet->available_amount;
  67. $currentRechargeBalance = $wallet->recharge_amount;
  68. // 2. 创建转账记录
  69. $transRecord = WalletTransRecord::create([
  70. 'trans_no' => $this->generateTransNo(),
  71. 'wallet_id' => $record->wallet_id,
  72. 'owner_id' => $record->id,
  73. 'owner_type' => 'withdraw', // 业务类型:提现
  74. 'trans_type' => 2, // 交易类型:支出
  75. 'amount' => $record->amount,
  76. 'before_balance' => $currentBalance,
  77. 'after_balance' => $currentBalance - $record->amount,
  78. 'before_recharge_balance' => $currentRechargeBalance,
  79. 'after_recharge_balance' => $currentRechargeBalance,
  80. 'trans_time' => now(),
  81. 'remark' => '提现打款',
  82. 'state' => TransactionStatus::PROCESSING->value,
  83. 'province' => $record->area_code ? substr($record->area_code, 0, 2) . '0000' : null,
  84. 'city' => $record->area_code ? substr($record->area_code, 0, 4) . '00' : null,
  85. 'district' => $record->area_code
  86. ]);
  87. // 3. 调用支付服务发起转账
  88. // TODO: 对接实际的支付服务
  89. $paymentResult = $this->processPayment($record);
  90. if ($paymentResult['success']) {
  91. // 4. 打款成功
  92. // 4.1 更新提现记录状态
  93. $record->state = WithdrawStatus::SUCCESS->value;
  94. $record->withdraw_time = now();
  95. $record->trans_no = $paymentResult['trade_no'];
  96. $record->remark = '打款成功';
  97. $record->save();
  98. // 4.2 更新转账记录状态
  99. $transRecord->state = TransactionStatus::SUCCESS->value;
  100. $transRecord->trans_no = $paymentResult['trade_no'];
  101. $transRecord->save();
  102. // 4.3 扣除钱包冻结金额
  103. $record->wallet->decrement('frozen_amount', $record->amount);
  104. } else {
  105. // 5. 打款失败
  106. throw new \Exception($paymentResult['message'] ?? '打款失败');
  107. }
  108. } catch (\Exception $e) {
  109. // 6. 处理异常
  110. Log::error('提现打款失败', [
  111. 'withdraw_id' => $record->id,
  112. 'error' => $e->getMessage()
  113. ]);
  114. // 6.1 更新提现记录状态
  115. $record->state = WithdrawStatus::FAILED->value;
  116. $record->remark = $e->getMessage();
  117. $record->save();
  118. // 6.2 更新转账记录状态
  119. if (isset($transRecord)) {
  120. $transRecord->state = TransactionStatus::FAILED->value;
  121. $transRecord->save();
  122. }
  123. // 6.3 解冻余额
  124. $this->unfreezeBalance($record);
  125. throw $e;
  126. }
  127. }
  128. /**
  129. * 处理审核拒绝
  130. */
  131. private function processRejected(WalletWithdrawRecord $record, ?string $remark): void
  132. {
  133. // 1. 更新提现记录状态
  134. $record->state = WithdrawStatus::FAILED->value;
  135. $record->save();
  136. // 2. 解冻余额
  137. $this->unfreezeBalance($record);
  138. // 3. 记录日志
  139. Log::info('提现申请已拒绝', [
  140. 'withdraw_id' => $record->id,
  141. 'external_no' => $record->external_no,
  142. 'amount' => $record->amount,
  143. 'remark' => $remark
  144. ]);
  145. }
  146. /**
  147. * 解冻余额
  148. */
  149. private function unfreezeBalance(WalletWithdrawRecord $record): void
  150. {
  151. $wallet = $record->wallet;
  152. // 获取当前余额
  153. $currentBalance = $wallet->available_amount;
  154. $currentRechargeBalance = $wallet->recharge_amount;
  155. // 从冻结金额中减少
  156. $wallet->decrement('frozen_amount', $record->amount);
  157. // 增加可用余额
  158. $wallet->increment('available_amount', $record->amount);
  159. // 记录解冻操作
  160. WalletTransRecord::create([
  161. 'trans_no' => $this->generateTransNo(),
  162. 'wallet_id' => $wallet->id,
  163. 'owner_id' => $record->id,
  164. 'owner_type' => 'withdraw',
  165. 'trans_type' => 1, // 交易类型:收入
  166. 'amount' => $record->amount,
  167. 'before_balance' => $currentBalance,
  168. 'after_balance' => $currentBalance + $record->amount,
  169. 'before_recharge_balance' => $currentRechargeBalance,
  170. 'after_recharge_balance' => $currentRechargeBalance,
  171. 'trans_time' => now(),
  172. 'remark' => '提现拒绝解冻',
  173. 'state' => TransactionStatus::SUCCESS->value
  174. ]);
  175. }
  176. /**
  177. * 生成交易流水号
  178. */
  179. private function generateTransNo(): string
  180. {
  181. return 'T' . date('YmdHis') . str_pad(random_int(1, 9999), 4, '0', STR_PAD_LEFT);
  182. }
  183. /**
  184. * 处理实际打款
  185. * TODO: 对接实际支付服务
  186. */
  187. private function processPayment(WalletWithdrawRecord $record): array
  188. {
  189. // 模拟打款成功
  190. return [
  191. 'success' => true,
  192. 'trade_no' => 'PAY' . time() . random_int(1000, 9999),
  193. 'message' => '打款成功'
  194. ];
  195. }
  196. }