WalletService.php 8.7 KB

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