$transferAppId, 'user_id' => $userId, 'amount' => $amount, 'password' => $password, 'google_code' => $googleCode, 'out_user_id' => $outUserId, 'remark' => $remark, 'callback_data' => $callbackData, ]; return self::createTransferOutFromArray($data); } /** * 创建第三方应用转出订单(跳过密码验证) * 我 =》 三方 * * @param int $transferAppId 划转应用ID * @param int $userId 用户ID * @param string $amount 转出金额(外部金额,第三方系统的金额) * @param string|null $outUserId 外部用户ID * @param string|null $remark 备注 * @param array $callbackData 回调数据 * @return TransferOrder * @throws \Exception */ public static function createTransferOutForThirdParty( int $transferAppId, int $userId, string $amount, ?string $outUserId = null, ?string $remark = null, array $callbackData = [] ): TransferOrder { $data = [ 'transfer_app_id' => $transferAppId, 'user_id' => $userId, 'amount' => $amount, 'out_user_id' => $outUserId, 'remark' => $remark, 'callback_data' => $callbackData, ]; return self::createTransferOutFromArrayForThirdParty($data); } /** * 创建第三方应用转出订单(内部实现,跳过密码验证) * 我 =》 三方 * 注意:第三方应用传入的金额是外部金额,需要转换为内部金额进行处理 * * @param array $data 订单数据 * @return TransferOrder * @throws \Exception */ public static function createTransferOutFromArrayForThirdParty(array $data): TransferOrder { // 获取应用配置(需要先获取以便进行金额转换) /** * @var TransferApp $app */ $app = TransferApp::findOrFail($data['transfer_app_id']); if (!$app->is_enabled) { throw new \Exception('应用已禁用'); } // 验证汇率是否合理 if (bccomp((string)$app->exchange_rate, '0', 10) <= 0) { throw new \Exception('汇率配置错误:汇率必须大于0'); } // 第三方应用金额处理:传入的是外部金额,需要转换为内部金额 $outAmount = (string)$data['amount']; // 外部金额(第三方系统金额) $amount = bcmul($outAmount, (string)$app->exchange_rate, 10); // 内部金额 = 外部金额 × 汇率 // 验证外部金额是否合理 if (bccomp($outAmount, '0', 10) <= 0) { throw new \Exception('转出金额必须大于0'); } // 验证内部金额计算结果是否合理 if (bccomp($amount, '0', 10) <= 0) { throw new \Exception('汇率计算结果异常:内部金额必须大于0'); } // 修改验证数据:将外部金额替换为内部金额,这样验证器就能正确验证余额 $validationData = $data; $validationData['amount'] = $amount; // 传递内部金额给验证器 // 使用第三方应用专用验证类(跳过密码验证) $validation = new \App\Module\Transfer\Validations\TransferOutThirdPartyValidation($validationData); $validation->validated(); // 计算手续费(基于内部金额) $FeeCalculatedEvent = \App\Module\Transfer\Services\FeeService::calculateOutFee($app, $amount, [ 'user_id' => $data['user_id'] ]); // 生成外部订单ID $outOrderId = self::generateOutOrderId('OUT'); // 创建订单 /** * @var TransferOrder $order */ $order = TransferOrder::create([ 'transfer_app_id' => $app->id, 'out_id' => $app->out_id2 ?? 0, 'out_order_id' => $outOrderId, 'out_user_id' => $data['out_user_id'] ?? null, 'user_id' => $data['user_id'], 'currency_id' => $app->currency_id, 'fund_id' => $app->fund_id, 'type' => TransferType::OUT, 'status' => TransferStatus::CREATED, 'out_amount' => $outAmount, 'amount' => $FeeCalculatedEvent->amount, 'exchange_rate' => $app->exchange_rate, 'fee_rate' => $FeeCalculatedEvent->feeRate, 'fee_amount' => $FeeCalculatedEvent->feeAmount, 'actual_amount' => $FeeCalculatedEvent->actualAmount, 'totle_amount' => $FeeCalculatedEvent->totleAmount, 'callback_data' => $data['callback_data'] ?? [], 'remark' => $data['remark'] ?? null, ]); // 验证fund_to_uid必须存在 if (empty($app->fund_to_uid)) { $order->updateStatus(TransferStatus::FAILED, '应用配置错误:未配置转入目标账户'); throw new \Exception('应用配置错误:未配置转入目标账户(fund_to_uid)'); } // 验证目标账户是否存在 $targetFundService = new FundService($app->fund_to_uid, $app->fund_id); if (!$targetFundService->getAccount()) { $order->updateStatus(TransferStatus::FAILED, '目标账户不存在'); throw new \Exception('目标账户不存在'); } // 预先验证用户余额是否充足 $userFundService = new FundService($data['user_id'], $app->fund_id); if ($userFundService->balance() < $order->actual_amount) { $order->updateStatus(TransferStatus::FAILED, '用户余额不足'); throw new \Exception('用户余额不足'); } // 使用trade方法从用户转账到目标账户(完整转出金额,不扣除手续费) $tradeResult = $userFundService->trade( $app->fund_to_uid, $order->amount, // 转出完整金额(不扣除手续费) 'transfer_out', $order->id, "转出到{$app->title}" ); // 如果有手续费,处理手续费收取 if ($order->hasFee()) { // 获取手续费收取账户UID(默认为1) $feeAccountUid = $app->getFeeAccountUid(); $feeTradeResult = $userFundService->trade( $feeAccountUid, $order->fee_amount, 'transfer_out_fee', $order->id, "转出手续费-{$app->title}" ); if (is_string($feeTradeResult)) { Log::warning('Transfer out fee collection failed', [ 'order_id' => $order->id, 'fee_amount' => $order->fee_amount, 'fee_account_uid' => $feeAccountUid, 'error' => $feeTradeResult ]); } else { Log::info('Transfer out fee collected successfully', [ 'order_id' => $order->id, 'fee_amount' => $order->fee_amount, 'fee_account_uid' => $feeAccountUid, 'configured_account' => $app->fee_account_uid ]); } } if (is_string($tradeResult)) { $order->updateStatus(TransferStatus::FAILED, '资金转移失败: ' . $tradeResult); throw new \Exception('余额不足或资金操作失败: ' . $tradeResult); } // 检查是否需要调用外部API if (!empty($app->order_out_create_url)) { // 配置了外部转出API,调用外部API处理 OrderLogic::processTransferOut($order); } else { // 没有配置外部API,直接完成订单 $order->updateStatus(TransferStatus::COMPLETED); } return $order; } /** * 创建转出订单(内部实现) * 我 =》 三方 * 处理流程: * 1. 验证请求数据 * 2. 获取应用配置 * 3. 创建转出订单 * 4. 验证fund_to_uid必须存在,进行用户间资金转移 * 5. 根据是否配置外部API决定后续处理(调用外部API或直接完成) * * @param array $data 订单数据 * @return TransferOrder * @throws \Exception */ public static function createTransferOutFromArray(array $data): TransferOrder { // 验证请求数据 $validation = new TransferOutValidation($data); $validation->validated(); // 获取应用配置 $app = TransferApp::findOrFail($data['transfer_app_id']); if (!$app->is_enabled) { throw new \Exception('应用已禁用'); } // 计算金额(转出:内部金额转换为外部金额) $amount = (string)$data['amount']; // 内部金额 // 验证汇率是否合理 if (bccomp((string)$app->exchange_rate, '0', 10) <= 0) { throw new \Exception('汇率配置错误:汇率必须大于0'); } // 验证内部金额是否合理 if (bccomp($amount, '0', 10) <= 0) { throw new \Exception('转出金额必须大于0'); } $outAmount = bcdiv($amount, (string)$app->exchange_rate, 10); // 外部金额 = 内部金额 ÷ 汇率 // 验证计算结果是否合理 if (bccomp($outAmount, '0', 10) <= 0) { throw new \Exception('汇率计算结果异常:外部金额必须大于0'); } // 计算手续费 $feeInfo = $app->calculateOutFee($amount, [ 'user_id' => $data['user_id'] ]); // 生成外部订单ID $outOrderId = self::generateOutOrderId('OUT'); // 创建订单 $order = TransferOrder::create([ 'transfer_app_id' => $app->id, 'out_id' => $app->out_id2 ?? 0, 'out_order_id' => $outOrderId, 'out_user_id' => $data['out_user_id'] ?? null, 'user_id' => $data['user_id'], 'currency_id' => $app->currency_id, 'fund_id' => $app->fund_id, 'type' => TransferType::OUT, 'status' => TransferStatus::CREATED, 'out_amount' => $outAmount, 'amount' => $amount, 'exchange_rate' => $app->exchange_rate, 'fee_rate' => $feeInfo['fee_rate'], 'fee_amount' => $feeInfo['fee_amount'], 'actual_amount' => $feeInfo['actual_amount'], 'callback_data' => $data['callback_data'] ?? [], 'remark' => $data['remark'] ?? null, ]); // 验证fund_to_uid必须存在 if (empty($app->fund_to_uid)) { $order->updateStatus(TransferStatus::FAILED, '应用配置错误:未配置转入目标账户'); throw new \Exception('应用配置错误:未配置转入目标账户(fund_to_uid)'); } // 验证目标账户是否存在 $targetFundService = new FundService($app->fund_to_uid, $app->fund_id); if (!$targetFundService->getAccount()) { $order->updateStatus(TransferStatus::FAILED, '目标账户不存在'); throw new \Exception('目标账户不存在'); } // 预先验证用户余额是否充足(转出金额 + 手续费) $userFundService = new FundService($data['user_id'], $app->fund_id); $totalRequired = bcadd($order->amount, $order->fee_amount, 10); if (bccomp((string)$userFundService->balance(), $totalRequired, 10) < 0) { $order->updateStatus(TransferStatus::FAILED, '用户余额不足'); throw new \Exception('用户余额不足'); } // 使用trade方法从用户转账到目标账户(完整转出金额,不扣除手续费) $tradeResult = $userFundService->trade( $app->fund_to_uid, $order->amount, // 转出完整金额(不扣除手续费) 'transfer_out', $order->id, "转出到{$app->title}" ); // 如果有手续费,处理手续费收取 if ($order->hasFee()) { // 获取手续费收取账户UID(默认为1) $feeAccountUid = $app->getFeeAccountUid(); $feeTradeResult = $userFundService->trade( $feeAccountUid, $order->fee_amount, 'transfer_out_fee', $order->id, "转出手续费-{$app->title}" ); if (is_string($feeTradeResult)) { Log::warning('Transfer out fee collection failed', [ 'order_id' => $order->id, 'fee_amount' => $order->fee_amount, 'fee_account_uid' => $feeAccountUid, 'error' => $feeTradeResult ]); } else { Log::info('Transfer out fee collected successfully', [ 'order_id' => $order->id, 'fee_amount' => $order->fee_amount, 'fee_account_uid' => $feeAccountUid, 'configured_account' => $app->fee_account_uid ]); } } if (is_string($tradeResult)) { $order->updateStatus(TransferStatus::FAILED, '资金转移失败: ' . $tradeResult); throw new \Exception('余额不足或资金操作失败: ' . $tradeResult); } // 检查是否需要调用外部API if (!empty($app->order_out_create_url)) { // 配置了外部转出API,调用外部API处理 OrderLogic::processTransferOut($order); } else { // 没有配置外部API,直接完成订单 $order->updateStatus(TransferStatus::COMPLETED); } return $order; } /** * 创建转入订单(对外接口) * 三方 =》 我 * * @param int $transferAppId 划转应用ID * @param int $userId 用户ID * @param string $businessId 业务订单ID * @param string $amount 转入金额 * @param string|null $outUserId 外部用户ID * @param string|null $remark 备注 * @param array $callbackData 回调数据 * @return TransferOrder * @throws \Exception */ public static function createTransferIn( int $transferAppId, int $userId, string $businessId, string $amount, ?string $outUserId = null, ?string $remark = null, array $callbackData = [] ): TransferOrder { $data = [ 'transfer_app_id' => $transferAppId, 'user_id' => $userId, 'business_id' => $businessId, 'amount' => $amount, 'out_user_id' => $outUserId, 'remark' => $remark, 'callback_data' => $callbackData, ]; return self::createTransferInFromArray($data, $userId); } /** * 创建转入订单(内部实现) * 三方 =》 我 * 处理流程: * 1. 验证请求数据 * 2. 获取应用配置 * 3. 创建转入订单 * 4. 验证fund_in_uid必须存在,从指定账户转账到用户 * 5. 根据是否配置回调URL决定后续处理(发送回调或直接完成) * * @param array $data 订单数据 * @return TransferOrder * @throws \Exception */ public static function createTransferInFromArray(array $data, $user_id): TransferOrder { // 验证请求数据 $validation = new TransferInValidation($data); $validation->validated(); // 获取应用配置 /** * @var TransferApp $app */ $app = TransferApp::findOrFail($data['transfer_app_id']); if (!$app->is_enabled) { throw new \Exception('应用已禁用'); } // 检查外部订单ID是否已存在 $existingOrder = TransferOrder::where('out_order_id', $data['business_id']) ->where('out_id', $app->out_id2 ?? 0) ->first(); if ($existingOrder) { throw new \Exception('订单已存在'); } // 计算金额(转入:外部金额转换为内部金额) $outAmount = (string)$data['amount']; // 外部金额 $amount = bcmul($outAmount, (string)$app->exchange_rate, 10); // 内部金额 = 外部金额 × 汇率 // 计算手续费 $feeInfo = FeeService::calculateInFee($app, $amount, [ 'user_id' => $user_id ]); // 创建订单 /** * @var TransferOrder $order */ $order = TransferOrder::create([ 'transfer_app_id' => $app->id, 'out_id' => $app->out_id2 ?? 0, 'out_order_id' => $data['business_id'], 'out_user_id' => $data['out_user_id'] ?? null, 'user_id' => $data['user_id'], 'currency_id' => $app->currency_id, 'fund_id' => $app->fund_id, 'type' => TransferType::IN, 'status' => TransferStatus::CREATED, 'out_amount' => $outAmount, 'amount' => $feeInfo->amount, 'exchange_rate' => $app->exchange_rate, 'fee_rate' => $feeInfo->feeRate, 'fee_amount' => $feeInfo->feeAmount, 'actual_amount' => $feeInfo->actualAmount, 'totle_amount' => $feeInfo->totleAmount, 'callback_data' => $data['callback_data'] ?? [], 'remark' => $data['remark'] ?? null, ]); // 验证fund_in_uid必须存在 if (empty($app->fund_in_uid)) { $order->updateStatus(TransferStatus::FAILED, '应用配置错误:未配置转入来源账户'); throw new \Exception('应用配置错误:未配置转入来源账户(fund_in_uid)'); } // 验证来源账户是否存在 $sourceFundService = new FundService($app->fund_in_uid, $app->fund_id); if (!$sourceFundService->getAccount()) { $order->updateStatus(TransferStatus::FAILED, '来源账户不存在'); throw new \Exception('来源账户不存在'); } // 验证来源账户余额是否充足 if ($sourceFundService->balance() < $order->totle_amount) { $order->updateStatus(TransferStatus::FAILED, '来源账户余额不足'); throw new \Exception('来源账户余额不足'); } // 使用trade方法从来源账户转账到用户账户(扣除手续费后的实际金额) $tradeResult = $sourceFundService->trade( $data['user_id'], $order->actual_amount, // 转入实际到账金额(扣除手续费后) 'transfer_in', $order->id, "从{$app->title}转入" ); // 如果有手续费,处理手续费收取 if ($order->hasFee()) { // 获取手续费收取账户UID(默认为1) $feeAccountUid = $app->getFeeAccountUid(); $feeTradeResult = $sourceFundService->trade( $feeAccountUid, $order->fee_amount, 'transfer_in_fee', $order->id, "转入手续费-{$app->title}" ); if (is_string($feeTradeResult)) { Log::warning('Transfer in fee collection failed', [ 'order_id' => $order->id, 'fee_amount' => $order->fee_amount, 'fee_account_uid' => $feeAccountUid, 'error' => $feeTradeResult ]); } else { Log::info('Transfer in fee collected successfully', [ 'order_id' => $order->id, 'fee_amount' => $order->fee_amount, 'fee_account_uid' => $feeAccountUid, 'configured_account' => $app->fee_account_uid ]); } } if (is_string($tradeResult)) { $order->updateStatus(TransferStatus::FAILED, '资金转移失败: ' . $tradeResult); throw new \Exception('来源账户资金不足或操作失败: ' . $tradeResult); } // 检查是否需要发送回调通知 if ($app->supportsCallback()) { // 配置了回调URL,更新状态为处理中并发送回调 $order->updateStatus(TransferStatus::PROCESSING); CallbackLogic::sendCallback($order); } else { // 没有配置回调URL,直接完成订单 $order->updateStatus(TransferStatus::COMPLETED); } return $order; } /** * 处理回调结果 * * @param array $callbackData 回调数据 * @return bool */ public static function processCallback(array $callbackData): bool { // 查找订单 $order = TransferOrder::where('out_order_id', $callbackData['business_id']) ->where('out_id', $callbackData['out_id']) ->first(); if (!$order) { Log::warning('Transfer callback order not found', $callbackData); return false; } // 更新订单状态 $status = $callbackData['success'] ? TransferStatus::COMPLETED : TransferStatus::FAILED; $message = $callbackData['message'] ?? null; return $order->updateStatus($status, $message); } /** * 生成外部订单ID * * @param string $prefix 前缀 * @return string */ private static function generateOutOrderId(string $prefix = 'TR'): string { return $prefix . date('YmdHis') . Str::random(6); } /** * 重试订单处理 * * @param int $orderId 订单ID * @return bool */ public static function retryOrder(int $orderId): bool { $order = TransferOrder::findOrFail($orderId); if (!$order->canRetry()) { throw new \Exception('订单状态不允许重试'); } // 重置状态为已创建 $order->updateStatus(TransferStatus::CREATED); // 根据订单类型重新处理 if ($order->isTransferOut()) { OrderLogic::processTransferOut($order); } else { // 转入订单重试主要是重新发送回调 if ($order->transferApp->supportsCallback()) { CallbackLogic::sendCallback($order); } } return true; } /** * 手动完成订单 * * @param int $orderId 订单ID * @param string $remark 备注 * @return bool */ public static function manualComplete(int $orderId, string $remark = ''): bool { $order = TransferOrder::findOrFail($orderId); if ($order->isFinalStatus()) { throw new \Exception('订单已处于最终状态'); } // 更新状态为已完成 $order->updateStatus(TransferStatus::COMPLETED, $remark); return true; } }