소스 검색

feat:技师端-申请提现

刘学玺 4 달 전
부모
커밋
3779c24976
3개의 변경된 파일178개의 추가작업 그리고 3개의 파일을 삭제
  1. 79 0
      app/Http/Controllers/Coach/WalletController.php
  2. 96 2
      app/Services/Coach/WalletService.php
  3. 3 1
      routes/api.php

+ 79 - 0
app/Http/Controllers/Coach/WalletController.php

@@ -98,4 +98,83 @@ class WalletController extends Controller
 
         return $this->success($this->service->getWalletRecords(Auth::user()->id, $params));
     }
+
+    /**
+     * [钱包]申请提现
+     *
+     * @description 技师申请提现到指定账户
+     *
+     * @authenticated
+     *
+     * @bodyParam amount numeric required 提现金额(最低100元) Example: 100.00
+     * @bodyParam withdraw_type integer required 提现方式(1:微信 2:支付宝 3:银行卡) Example: 1
+     * @bodyParam withdraw_account string required 提现账号 Example: example@alipay.com
+     * @bodyParam withdraw_account_name string required 提现账户名称 Example: 张三
+     *
+     * @response {
+     *   "message": "提现申请已提交",
+     *   "trans_no": "W202403211234567890",
+     *   "amount": "100.00",
+     *   "status": "处理中"
+     * }
+     * @response 422 {
+     *   "message": "可提现余额不足"
+     * }
+     */
+    public function withdraw(Request $request)
+    {
+        // 验证基本参数
+        $data = $request->validate([
+            'amount' => 'required|numeric|min:100',
+            'withdraw_type' => 'required|integer|in:1,2,3',
+            'withdraw_account' => 'required|string|max:100',
+            'withdraw_account_name' => 'required|string|max:50',
+        ]);
+
+        // 根据提现方式验证账号格式
+        switch ($data['withdraw_type']) {
+            case 1: // 微信
+                $request->validate([
+                    'withdraw_account' => [
+                        'required',
+                        'string',
+                        'regex:/^[a-zA-Z][a-zA-Z\d_-]{5,19}$/',
+                        'max:20',
+                    ],
+                ], [
+                    'withdraw_account.regex' => '微信号格式不正确',
+                ]);
+                break;
+            case 2: // 支付宝
+                $request->validate([
+                    'withdraw_account' => [
+                        'required',
+                        'string',
+                        function ($attribute, $value, $fail) {
+                            if (! filter_var($value, FILTER_VALIDATE_EMAIL) && ! preg_match('/^1[3-9]\d{9}$/', $value)) {
+                                $fail('支付宝账号必须是有效的邮箱或手机号');
+                            }
+                        },
+                    ],
+                ]);
+                break;
+            case 3: // 银行卡
+                $request->validate([
+                    'withdraw_account' => [
+                        'required',
+                        'string',
+                        'regex:/^\d{16,19}$/',
+                    ],
+                ], [
+                    'withdraw_account.regex' => '银行卡号格式不正确',
+                ]);
+                break;
+        }
+
+        // 验证提现账户名称
+        abort_if(mb_strlen($data['withdraw_account_name']) < 2, 422, '提现账户名称至少2个字符');
+        abort_if(! preg_match('/^[\x{4e00}-\x{9fa5}a-zA-Z\s]+$/u', $data['withdraw_account_name']), 422, '提现账户名称只能包含中文、英文字母和空格');
+
+        return $this->success($this->service->withdraw(Auth::user()->id, $data));
+    }
 }

+ 96 - 2
app/Services/Coach/WalletService.php

@@ -2,8 +2,12 @@
 
 namespace App\Services\Coach;
 
+use App\Enums\WithdrawStatus;
 use App\Models\MemberUser;
+use App\Models\Wallet;
 use App\Models\WalletTransRecord;
+use App\Models\WalletWithdrawRecord;
+use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 
 class WalletService
@@ -83,7 +87,7 @@ class WalletService
                 })
                 // 添加交易状态筛选
                 ->when(isset($params['status']), function ($query) use ($params) {
-                    return $query->where('status', $params['status']);
+                    return $query->where('state', $params['status']);
                 })
                 // 添加排序选项
                 ->when(
@@ -131,7 +135,7 @@ class WalletService
                     'owner_type' => $this->formatOwnerType($record->owner_type),
                     'owner_id' => $record->owner_id,
                     'remark' => $record->remark,
-                    'status' => $this->formatStatus($record->status),
+                    'state' => $this->formatStatus($record->state),
                     'created_at' => $record->created_at->format('Y-m-d H:i:s'),
                 ];
             });
@@ -161,6 +165,96 @@ class WalletService
         }
     }
 
+    /**
+     * 技师钱包提现
+     *
+     * @param  int  $userId  技师用户ID
+     * @param  array  $data  提现数据
+     * @return array
+     *
+     * @throws \Exception
+     */
+    public function withdraw(int $userId, array $data)
+    {
+        return DB::transaction(function () use ($userId, $data) {
+            try {
+                // 获取用户和技师信息
+                $user = MemberUser::with(['coach', 'coach.wallet'])->findOrFail($userId);
+                abort_if(! $user->coach, 404, '技师信息不存在');
+                abort_if(! $user->coach->wallet, 404, '钱包信息不存在');
+
+                // 锁定钱包记录
+                $wallet = Wallet::where('id', $user->coach->wallet->id)
+                    ->lockForUpdate()
+                    ->first();
+                // TODO: 提现金额限制
+                // 验证提现金额
+                $amount = $data['amount'];
+                abort_if($amount <= 100, 422, '提现金额必须大于100元');
+                abort_if($amount > $wallet->available_balance, 422, '可提现余额不足');
+
+                // 生成交易流水号
+                $transNo = 'W'.date('YmdHis').mt_rand(1000, 9999);
+
+                // 创建提现记录
+                $withdraw = WalletWithdrawRecord::create([
+                    'wallet_id' => $wallet->id,                                // 钱包ID
+                    'trans_no' => $transNo,                                    // 交易流水号
+                    'amount' => $amount,                                       // 提现金额
+                    'withdraw_type' => $data['withdraw_type'],                 // 提现方式(1:微信 2:支付宝 3:银行卡)
+                    'withdraw_account' => $data['withdraw_account'],           // 提现账号
+                    'withdraw_account_name' => $data['withdraw_account_name'], // 提现账户名称
+                    'state' => WithdrawStatus::PROCESSING,                    // 提现状态
+                ]);
+
+                // TODO: 创建交易记录字段需关联枚举
+                // 创建交易记录
+                $record = WalletTransRecord::create([
+                    'wallet_id' => $wallet->id,
+                    'trans_no' => $transNo,
+                    'trans_type' => 2, // 支出
+                    'amount' => -$amount,
+                    'before_balance' => $wallet->available_balance,
+                    'after_balance' => $wallet->available_balance - $amount,
+                    'owner_type' => WalletWithdrawRecord::class,
+                    'owner_id' => $withdraw->id,
+                    'remark' => '提现申请',
+                    'state' => 2, // 处理中
+                ]);
+
+                // 冻结提现金额
+                $wallet->decrement('available_balance', $amount);
+                $wallet->increment('frozen_amount', $amount);
+
+                // 记录日志
+                \Log::info('技师提现申请成功', [
+                    'user_id' => $userId,
+                    'coach_id' => $user->coach->id,
+                    'trans_no' => $transNo,
+                    'amount' => $amount,
+                    'wallet_id' => $wallet->id,
+                ]);
+
+                return [
+                    'message' => '提现申请已提交',
+                    'trans_no' => $transNo,
+                    'amount' => number_format($amount, 2, '.', ''),
+                    'state' => '处理中',
+                ];
+
+            } catch (\Exception $e) {
+                \Log::error('技师提现申请失败', [
+                    'user_id' => $userId,
+                    'data' => $data,
+                    'error' => $e->getMessage(),
+                    'file' => $e->getFile(),
+                    'line' => $e->getLine(),
+                ]);
+                throw $e;
+            }
+        });
+    }
+
     /**
      * 格式化交易类型
      */

+ 3 - 1
routes/api.php

@@ -25,7 +25,7 @@ Route::prefix('client')->group(function () {
 
     // 无需认证的公开路由
     Route::prefix('account')->group(function () {
-        // 发��验证码
+        // 发验证码
         Route::post('send-code', [AccountController::class, 'sendVerifyCode']);
         // 手机号登录
         Route::post('login', [AccountController::class, 'login']);
@@ -165,6 +165,8 @@ Route::middleware(['auth:sanctum', 'verified'])->prefix('coach')->group(function
     Route::prefix('wallet')->group(function () {
         Route::get('/', [CoachWalletController::class, 'getWallet']);
         Route::get('/records', [CoachWalletController::class, 'getWalletRecords']);
+        Route::post('/withdraw', [CoachWalletController::class, 'withdraw'])
+            ->middleware(['throttle:10,1']); // 限制提现频率
     });
 });