WalletService.php 8.7 KB

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