WalletService.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. <?php
  2. namespace App\Services\Client;
  3. use App\Models\Wallet;
  4. use App\Enums\UserStatus;
  5. use App\Models\MemberUser;
  6. use App\Enums\WithdrawStatus;
  7. use App\Models\WalletTransRecord;
  8. use Illuminate\Support\Facades\DB;
  9. use Illuminate\Support\Facades\Log;
  10. use App\Models\WalletWithdrawRecord;
  11. use App\Models\Order;
  12. class WalletService
  13. {
  14. /**
  15. * [钱包管理]获取钱包明细
  16. *
  17. * @param int $userId 用户ID
  18. * @param int $perPage 每页记录数
  19. * @return array 包含 items 和 total 的数组
  20. *
  21. * @throws \Exception
  22. */
  23. public function getWalletRecords($userId, $perPage = 10)
  24. {
  25. try {
  26. // 获取当前用户
  27. $user = MemberUser::find($userId);
  28. // 检查用户是否存在
  29. abort_if(! $user, 400, '用户不存在');
  30. // 检查用户状态
  31. abort_if($user->state != UserStatus::OPEN->value, 400, '用户状态异常');
  32. // 获取钱包交易记录
  33. $paginator = $user->wallet->transRecords()
  34. ->orderBy('created_at', 'desc')
  35. ->paginate($perPage);
  36. // 转换为统一的分页格式
  37. return [
  38. 'items' => $paginator->items(),
  39. 'total' => $paginator->total(),
  40. ];
  41. } catch (\Exception $e) {
  42. // 记录错误日志
  43. \Log::error('获取钱包明细失败', [
  44. 'userId' => $userId,
  45. 'error' => $e->getMessage(),
  46. 'trace' => $e->getTraceAsString(),
  47. ]);
  48. throw $e;
  49. }
  50. }
  51. /**
  52. * 获取用户钱包
  53. */
  54. public function getUserWallet($userId)
  55. {
  56. try {
  57. $user = MemberUser::find($userId);
  58. abort_if(! $user, 400, '用户不存在');
  59. abort_if($user->state != UserStatus::OPEN->value, 400, '用户状态异常');
  60. $wallet = $user->wallet;
  61. if (! $wallet) {
  62. Log::warning('用户钱包不存在', ['user_id' => $user->id]);
  63. throw new \Exception('钱包不存在');
  64. }
  65. return $wallet;
  66. } catch (\Exception $e) {
  67. Log::error('获取用户钱包失败', ['error' => $e->getMessage()]);
  68. throw $e;
  69. }
  70. }
  71. /**
  72. * 用户钱包提现
  73. *
  74. * @param int $userId 用户ID
  75. * @param array $data 提现数据
  76. * @return array
  77. *
  78. * @throws \Exception
  79. */
  80. public function withdraw(int $userId, array $data)
  81. {
  82. return DB::transaction(function () use ($userId, $data) {
  83. try {
  84. // 获取用户信息
  85. $user = MemberUser::findOrFail($userId);
  86. abort_if($user->state != UserStatus::OPEN->value, 400, '用户状态异常');
  87. abort_if(! $user->wallet, 404, '钱包信息不存在');
  88. // 锁定钱包记录
  89. $wallet = Wallet::where('id', $user->wallet->id)
  90. ->lockForUpdate()
  91. ->first();
  92. // 验证提现金额
  93. $amount = $data['amount'];
  94. abort_if($amount <= 100, 422, '提现金额必须大于100元');
  95. abort_if($amount > $wallet->available_balance, 422, '可提现余额不足');
  96. // 生成交易流水号
  97. $transNo = 'CW' . date('YmdHis') . mt_rand(1000, 9999);
  98. // 创建提现记录
  99. $withdraw = WalletWithdrawRecord::create([
  100. 'wallet_id' => $wallet->id, // 钱包ID
  101. 'trans_no' => $transNo, // 交易流水号
  102. 'amount' => $amount, // 提现金额
  103. 'withdraw_type' => $data['withdraw_type'], // 提现方式(1:微信 2:支付宝 3:银行卡)
  104. 'withdraw_account' => $data['withdraw_account'], // 提现账号
  105. 'withdraw_account_name' => $data['withdraw_account_name'], // 提现账户名称
  106. 'state' => WithdrawStatus::PROCESSING, // 提现状态
  107. ]);
  108. // 创建交易记录
  109. WalletTransRecord::create([
  110. 'wallet_id' => $wallet->id,
  111. 'trans_no' => $transNo,
  112. 'trans_type' => 2, // 支出
  113. 'amount' => -$amount,
  114. 'before_balance' => $wallet->available_balance,
  115. 'after_balance' => $wallet->available_balance - $amount,
  116. 'owner_type' => WalletWithdrawRecord::class,
  117. 'owner_id' => $withdraw->id,
  118. 'remark' => '提现申请',
  119. 'state' => 2, // 处理中
  120. ]);
  121. // 冻结提现金额
  122. $wallet->decrement('available_balance', $amount);
  123. $wallet->increment('frozen_amount', $amount);
  124. // 记录日志
  125. Log::info('用户提现申请成功', [
  126. 'user_id' => $userId,
  127. 'trans_no' => $transNo,
  128. 'amount' => $amount,
  129. 'wallet_id' => $wallet->id,
  130. ]);
  131. return [
  132. 'message' => '提现申请已提交',
  133. 'trans_no' => $transNo,
  134. 'amount' => number_format($amount, 2, '.', ''),
  135. 'state' => '处理中',
  136. ];
  137. } catch (\Exception $e) {
  138. Log::error('用户提现申请失败', [
  139. 'user_id' => $userId,
  140. 'data' => $data,
  141. 'error' => $e->getMessage(),
  142. 'file' => $e->getFile(),
  143. 'line' => $e->getLine(),
  144. ]);
  145. throw $e;
  146. }
  147. });
  148. }
  149. /**
  150. * 扣减钱包余额
  151. *
  152. * @param int $userId 用户ID
  153. * @param float $amount 扣减金额
  154. * @param string $type 交易类型
  155. * @param int|null $objectId 关联对象ID
  156. * @param string $remark 备注说明
  157. * @return bool
  158. * @throws \Exception
  159. */
  160. public function deduct(
  161. int $userId,
  162. float $amount,
  163. string $type,
  164. ?int $objectId = null,
  165. string $remark = ''
  166. ): bool {
  167. return DB::transaction(function () use ($userId, $amount, $type, $objectId, $remark) {
  168. // 获取用户钱包
  169. $wallet = $this->getUserWallet($userId);
  170. // 检查余额是否足够
  171. abort_if(
  172. $wallet->available_balance < $amount,
  173. 422,
  174. '钱包余额不足'
  175. );
  176. // 扣减余额
  177. $wallet->decrement('total_balance', $amount);
  178. $wallet->decrement('available_balance', $amount);
  179. // 创建交易记录
  180. $wallet->transRecords()->create([
  181. 'amount' => -$amount, // 负数表示支出
  182. 'trans_type' => $type,
  183. 'owner_type' => Order::class,
  184. 'owner_id' => $objectId,
  185. 'remark' => $remark ?: '订单支付',
  186. 'before_balance' => $wallet->total_balance + $amount,
  187. 'after_balance' => $wallet->total_balance,
  188. 'state' => 1,
  189. 'storage_type' => 1, // 添加 storage_type 字段,1 表示余额
  190. ]);
  191. return true;
  192. });
  193. }
  194. }