Переглянути джерело

Transfer模块第二阶段开发完成 - 核心业务逻辑

第二阶段:核心业务逻辑开发
- DTO对象:创建TransferOrderDto,支持完整的订单数据传输和转换
- 服务层:创建TransferService主服务类,提供完整的对外接口
- 逻辑层:创建TransferLogic、OrderLogic、CallbackLogic三个核心逻辑类
- 外部API:创建ExternalApiService,处理与外部应用的API交互
- 验证系统:创建TransferInValidation验证类,支持完整的数据验证

核心功能:
- 转入转出订单创建和处理
- 农场内部模式和外部API模式支持
- 完整的订单状态流转管理
- 外部API调用和回调处理
- 汇率转换和金额计算
- 资金操作集成(Fund模块)
- 签名验证和安全机制

技术特点:
- 使用DTO对象进行数据传输,避免直接暴露Model
- 逻辑层不开启事务,遵循用户偏好
- 完善的异常处理和日志记录
- 支持批量处理和重试机制
- 灵活的验证系统和错误处理

下一阶段:验证和处理器开发
notfff 7 місяців тому
батько
коміт
97bbc383

+ 130 - 0
app/Module/Transfer/Dtos/TransferOrderDto.php

@@ -0,0 +1,130 @@
+<?php
+
+namespace App\Module\Transfer\Dtos;
+
+use App\Module\Transfer\Enums\TransferStatus;
+use App\Module\Transfer\Enums\TransferType;
+
+/**
+ * 划转订单数据传输对象
+ */
+class TransferOrderDto
+{
+    public function __construct(
+        public readonly int $id,
+        public readonly int $transfer_app_id,
+        public readonly int $out_id,
+        public readonly string $out_order_id,
+        public readonly ?string $out_user_id,
+        public readonly int $user_id,
+        public readonly int $currency_id,
+        public readonly int $fund_id,
+        public readonly TransferType $type,
+        public readonly TransferStatus $status,
+        public readonly string $out_amount,
+        public readonly string $amount,
+        public readonly float $exchange_rate,
+        public readonly array $callback_data,
+        public readonly ?string $error_message,
+        public readonly ?string $remark,
+        public readonly ?string $processed_at,
+        public readonly ?string $callback_at,
+        public readonly ?string $completed_at,
+        public readonly string $created_at,
+        public readonly string $updated_at,
+    ) {}
+
+    /**
+     * 从模型创建DTO
+     */
+    public static function fromModel($model): self
+    {
+        return new self(
+            id: $model->id,
+            transfer_app_id: $model->transfer_app_id,
+            out_id: $model->out_id,
+            out_order_id: $model->out_order_id,
+            out_user_id: $model->out_user_id,
+            user_id: $model->user_id,
+            currency_id: $model->currency_id,
+            fund_id: $model->fund_id,
+            type: $model->type,
+            status: $model->status,
+            out_amount: (string) $model->out_amount,
+            amount: (string) $model->amount,
+            exchange_rate: (float) $model->exchange_rate,
+            callback_data: $model->callback_data ?? [],
+            error_message: $model->error_message,
+            remark: $model->remark,
+            processed_at: $model->processed_at?->toDateTimeString(),
+            callback_at: $model->callback_at?->toDateTimeString(),
+            completed_at: $model->completed_at?->toDateTimeString(),
+            created_at: $model->created_at->toDateTimeString(),
+            updated_at: $model->updated_at->toDateTimeString(),
+        );
+    }
+
+    /**
+     * 转换为数组
+     */
+    public function toArray(): array
+    {
+        return [
+            'id' => $this->id,
+            'transfer_app_id' => $this->transfer_app_id,
+            'out_id' => $this->out_id,
+            'out_order_id' => $this->out_order_id,
+            'out_user_id' => $this->out_user_id,
+            'user_id' => $this->user_id,
+            'currency_id' => $this->currency_id,
+            'fund_id' => $this->fund_id,
+            'type' => $this->type->value,
+            'type_text' => $this->type->getDescription(),
+            'status' => $this->status->value,
+            'status_text' => $this->status->getDescription(),
+            'out_amount' => $this->out_amount,
+            'amount' => $this->amount,
+            'exchange_rate' => $this->exchange_rate,
+            'callback_data' => $this->callback_data,
+            'error_message' => $this->error_message,
+            'remark' => $this->remark,
+            'processed_at' => $this->processed_at,
+            'callback_at' => $this->callback_at,
+            'completed_at' => $this->completed_at,
+            'created_at' => $this->created_at,
+            'updated_at' => $this->updated_at,
+        ];
+    }
+
+    /**
+     * 判断是否为转入订单
+     */
+    public function isTransferIn(): bool
+    {
+        return $this->type === TransferType::IN;
+    }
+
+    /**
+     * 判断是否为转出订单
+     */
+    public function isTransferOut(): bool
+    {
+        return $this->type === TransferType::OUT;
+    }
+
+    /**
+     * 判断是否为最终状态
+     */
+    public function isFinalStatus(): bool
+    {
+        return $this->status->isFinal();
+    }
+
+    /**
+     * 判断是否可以重试
+     */
+    public function canRetry(): bool
+    {
+        return $this->status->canRetry();
+    }
+}

+ 234 - 0
app/Module/Transfer/Logics/CallbackLogic.php

@@ -0,0 +1,234 @@
+<?php
+
+namespace App\Module\Transfer\Logics;
+
+use App\Module\Transfer\Enums\TransferStatus;
+use App\Module\Transfer\Models\TransferOrder;
+use App\Module\Transfer\Services\ExternalApiService;
+use Illuminate\Support\Facades\Log;
+
+/**
+ * 回调处理逻辑
+ */
+class CallbackLogic
+{
+    /**
+     * 发送回调通知
+     * 
+     * @param TransferOrder $order 订单对象
+     * @return bool
+     */
+    public static function sendCallback(TransferOrder $order): bool
+    {
+        try {
+            $app = $order->transferApp;
+
+            // 检查是否配置了回调URL
+            if (empty($app->order_callback_url)) {
+                // 没有配置回调URL,直接完成
+                $order->updateStatus(TransferStatus::COMPLETED);
+                return true;
+            }
+
+            // 准备回调数据
+            $callbackData = [
+                'business_id' => $order->out_order_id,
+                'status' => $order->status->value,
+                'status_text' => $order->status->getDescription(),
+                'amount' => $order->out_amount,
+                'internal_amount' => $order->amount,
+                'user_id' => $order->user_id,
+                'processed_at' => $order->processed_at?->toDateTimeString(),
+                'callback_data' => $order->callback_data,
+                'timestamp' => now()->timestamp,
+            ];
+
+            // 发送回调
+            $result = ExternalApiService::sendCallback($app, $callbackData);
+
+            if ($result['success']) {
+                // 回调成功
+                $order->updateStatus(TransferStatus::CALLBACK);
+                
+                Log::info('Transfer callback sent successfully', [
+                    'order_id' => $order->id,
+                    'out_order_id' => $order->out_order_id,
+                    'callback_url' => $app->order_callback_url
+                ]);
+
+                // 检查回调响应,决定是否完成订单
+                $responseData = $result['data'] ?? [];
+                if (isset($responseData['code']) && $responseData['code'] == 0) {
+                    $order->updateStatus(TransferStatus::COMPLETED);
+                }
+
+                return true;
+            } else {
+                // 回调失败,记录错误但不更新订单状态(等待重试)
+                Log::error('Transfer callback failed', [
+                    'order_id' => $order->id,
+                    'out_order_id' => $order->out_order_id,
+                    'callback_url' => $app->order_callback_url,
+                    'error' => $result['message']
+                ]);
+
+                return false;
+            }
+
+        } catch (\Exception $e) {
+            Log::error('Transfer callback exception', [
+                'order_id' => $order->id,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+            return false;
+        }
+    }
+
+    /**
+     * 重试回调
+     * 
+     * @param TransferOrder $order 订单对象
+     * @param int $maxRetries 最大重试次数
+     * @return bool
+     */
+    public static function retryCallback(TransferOrder $order, int $maxRetries = 3): bool
+    {
+        // 检查重试次数(可以通过callback_data中的retry_count字段记录)
+        $callbackData = $order->callback_data;
+        $retryCount = $callbackData['retry_count'] ?? 0;
+
+        if ($retryCount >= $maxRetries) {
+            Log::warning('Transfer callback max retries reached', [
+                'order_id' => $order->id,
+                'retry_count' => $retryCount
+            ]);
+            return false;
+        }
+
+        // 增加重试次数
+        $callbackData['retry_count'] = $retryCount + 1;
+        $order->update(['callback_data' => $callbackData]);
+
+        // 重新发送回调
+        return self::sendCallback($order);
+    }
+
+    /**
+     * 批量处理待回调的订单
+     * 
+     * @param int $limit 处理数量限制
+     * @return int 处理的订单数量
+     */
+    public static function processPendingCallbacks(int $limit = 50): int
+    {
+        $processedCount = 0;
+
+        // 查找需要回调的订单
+        $orders = TransferOrder::where('status', TransferStatus::PROCESSING)
+            ->whereHas('transferApp', function ($query) {
+                $query->whereNotNull('order_callback_url');
+            })
+            ->where('created_at', '>', now()->subHours(24)) // 只处理24小时内的订单
+            ->limit($limit)
+            ->get();
+
+        foreach ($orders as $order) {
+            if (self::sendCallback($order)) {
+                $processedCount++;
+            }
+        }
+
+        return $processedCount;
+    }
+
+    /**
+     * 批量重试失败的回调
+     * 
+     * @param int $limit 处理数量限制
+     * @return int 处理的订单数量
+     */
+    public static function retryFailedCallbacks(int $limit = 30): int
+    {
+        $processedCount = 0;
+
+        // 查找回调失败的订单(状态为PROCESSING且创建时间超过5分钟)
+        $orders = TransferOrder::where('status', TransferStatus::PROCESSING)
+            ->whereHas('transferApp', function ($query) {
+                $query->whereNotNull('order_callback_url');
+            })
+            ->where('created_at', '<', now()->subMinutes(5))
+            ->where('created_at', '>', now()->subHours(24))
+            ->limit($limit)
+            ->get();
+
+        foreach ($orders as $order) {
+            if (self::retryCallback($order)) {
+                $processedCount++;
+            }
+        }
+
+        return $processedCount;
+    }
+
+    /**
+     * 处理接收到的回调
+     * 
+     * @param array $data 回调数据
+     * @return bool
+     */
+    public static function handleIncomingCallback(array $data): bool
+    {
+        try {
+            // 验证必要字段
+            if (!isset($data['business_id']) || !isset($data['out_id'])) {
+                Log::warning('Invalid callback data received', $data);
+                return false;
+            }
+
+            // 查找订单
+            $order = TransferOrder::where('out_order_id', $data['business_id'])
+                ->where('out_id', $data['out_id'])
+                ->first();
+
+            if (!$order) {
+                Log::warning('Callback order not found', $data);
+                return false;
+            }
+
+            // 验证订单状态
+            if ($order->isFinalStatus()) {
+                Log::info('Callback received for completed order', [
+                    'order_id' => $order->id,
+                    'current_status' => $order->status->value
+                ]);
+                return true;
+            }
+
+            // 更新订单状态
+            $success = $data['success'] ?? true;
+            $message = $data['message'] ?? null;
+
+            if ($success) {
+                $order->updateStatus(TransferStatus::COMPLETED, $message);
+            } else {
+                $order->updateStatus(TransferStatus::FAILED, $message);
+            }
+
+            Log::info('Callback processed successfully', [
+                'order_id' => $order->id,
+                'business_id' => $data['business_id'],
+                'success' => $success
+            ]);
+
+            return true;
+
+        } catch (\Exception $e) {
+            Log::error('Callback processing exception', [
+                'error' => $e->getMessage(),
+                'data' => $data
+            ]);
+            return false;
+        }
+    }
+}

+ 235 - 0
app/Module/Transfer/Logics/OrderLogic.php

@@ -0,0 +1,235 @@
+<?php
+
+namespace App\Module\Transfer\Logics;
+
+use App\Module\Transfer\Enums\TransferStatus;
+use App\Module\Transfer\Models\TransferOrder;
+use App\Module\Transfer\Services\ExternalApiService;
+use Illuminate\Support\Facades\Log;
+
+/**
+ * 订单处理逻辑
+ */
+class OrderLogic
+{
+    /**
+     * 处理转出订单
+     * 
+     * @param TransferOrder $order 订单对象
+     * @return bool
+     */
+    public static function processTransferOut(TransferOrder $order): bool
+    {
+        try {
+            $app = $order->transferApp;
+
+            // 检查是否配置了外部创建API
+            if (empty($app->order_out_create_url)) {
+                // 没有配置外部API,直接完成
+                $order->updateStatus(TransferStatus::COMPLETED);
+                return true;
+            }
+
+            // 更新状态为处理中
+            $order->updateStatus(TransferStatus::PROCESSING);
+
+            // 调用外部API创建订单
+            $apiData = [
+                'business_id' => $order->out_order_id,
+                'user_id' => $order->out_user_id,
+                'amount' => $order->out_amount,
+                'currency' => $order->currency_id,
+                'remark' => $order->remark,
+                'callback_data' => $order->callback_data,
+            ];
+
+            $result = ExternalApiService::createOutOrder($app, $apiData);
+
+            if ($result['success']) {
+                // API调用成功,等待查询结果
+                Log::info('Transfer out order created successfully', [
+                    'order_id' => $order->id,
+                    'out_order_id' => $order->out_order_id,
+                    'api_response' => $result
+                ]);
+                return true;
+            } else {
+                // API调用失败
+                $order->updateStatus(TransferStatus::FAILED, $result['message']);
+                Log::error('Transfer out order creation failed', [
+                    'order_id' => $order->id,
+                    'out_order_id' => $order->out_order_id,
+                    'error' => $result['message']
+                ]);
+                return false;
+            }
+
+        } catch (\Exception $e) {
+            $order->updateStatus(TransferStatus::FAILED, $e->getMessage());
+            Log::error('Transfer out order processing exception', [
+                'order_id' => $order->id,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+            return false;
+        }
+    }
+
+    /**
+     * 查询转出订单状态
+     * 
+     * @param TransferOrder $order 订单对象
+     * @return bool
+     */
+    public static function queryTransferOutStatus(TransferOrder $order): bool
+    {
+        try {
+            $app = $order->transferApp;
+
+            // 检查是否配置了查询API
+            if (empty($app->order_out_info_url)) {
+                // 没有配置查询API,无法查询状态
+                Log::warning('No query API configured for transfer out order', [
+                    'order_id' => $order->id,
+                    'app_id' => $app->id
+                ]);
+                return false;
+            }
+
+            // 调用外部API查询订单状态
+            $result = ExternalApiService::queryOutOrder($app, $order->out_order_id);
+
+            if ($result['success']) {
+                $status = $result['data']['status'] ?? null;
+                
+                // 根据外部状态更新内部状态
+                switch ($status) {
+                    case 'completed':
+                    case 'success':
+                        $order->updateStatus(TransferStatus::COMPLETED);
+                        break;
+                    case 'failed':
+                    case 'error':
+                        $order->updateStatus(TransferStatus::FAILED, $result['data']['message'] ?? '外部处理失败');
+                        break;
+                    case 'processing':
+                    case 'pending':
+                        // 保持处理中状态
+                        break;
+                    default:
+                        Log::warning('Unknown external order status', [
+                            'order_id' => $order->id,
+                            'external_status' => $status
+                        ]);
+                }
+
+                return true;
+            } else {
+                Log::error('Transfer out order query failed', [
+                    'order_id' => $order->id,
+                    'error' => $result['message']
+                ]);
+                return false;
+            }
+
+        } catch (\Exception $e) {
+            Log::error('Transfer out order query exception', [
+                'order_id' => $order->id,
+                'error' => $e->getMessage()
+            ]);
+            return false;
+        }
+    }
+
+    /**
+     * 查询转入订单状态
+     * 
+     * @param TransferOrder $order 订单对象
+     * @return bool
+     */
+    public static function queryTransferInStatus(TransferOrder $order): bool
+    {
+        try {
+            $app = $order->transferApp;
+
+            // 检查是否配置了查询API
+            if (empty($app->order_in_info_url)) {
+                // 没有配置查询API,无法查询状态
+                return false;
+            }
+
+            // 调用外部API查询订单状态
+            $result = ExternalApiService::queryInOrder($app, $order->out_order_id);
+
+            if ($result['success']) {
+                $status = $result['data']['status'] ?? null;
+                
+                // 根据外部状态更新内部状态
+                switch ($status) {
+                    case 'completed':
+                    case 'success':
+                        $order->updateStatus(TransferStatus::COMPLETED);
+                        break;
+                    case 'failed':
+                    case 'error':
+                        $order->updateStatus(TransferStatus::FAILED, $result['data']['message'] ?? '外部处理失败');
+                        break;
+                    case 'processing':
+                    case 'pending':
+                        // 保持处理中状态
+                        break;
+                }
+
+                return true;
+            }
+
+            return false;
+
+        } catch (\Exception $e) {
+            Log::error('Transfer in order query exception', [
+                'order_id' => $order->id,
+                'error' => $e->getMessage()
+            ]);
+            return false;
+        }
+    }
+
+    /**
+     * 批量处理待处理的订单
+     * 
+     * @param int $limit 处理数量限制
+     * @return int 处理的订单数量
+     */
+    public static function processePendingOrders(int $limit = 100): int
+    {
+        $processedCount = 0;
+
+        // 处理转出订单
+        $outOrders = TransferOrder::where('type', \App\Module\Transfer\Enums\TransferType::OUT)
+            ->where('status', TransferStatus::PROCESSING)
+            ->where('created_at', '>', now()->subHours(24)) // 只处理24小时内的订单
+            ->limit($limit)
+            ->get();
+
+        foreach ($outOrders as $order) {
+            if (self::queryTransferOutStatus($order)) {
+                $processedCount++;
+            }
+        }
+
+        // 处理转入订单
+        $inOrders = TransferOrder::where('type', \App\Module\Transfer\Enums\TransferType::IN)
+            ->where('status', TransferStatus::PROCESSING)
+            ->where('created_at', '>', now()->subHours(24))
+            ->limit($limit - $processedCount)
+            ->get();
+
+        foreach ($inOrders as $order) {
+            if (self::queryTransferInStatus($order)) {
+                $processedCount++;
+            }
+        }
+
+        return $processedCount;
+    }
+}

+ 256 - 0
app/Module/Transfer/Logics/TransferLogic.php

@@ -0,0 +1,256 @@
+<?php
+
+namespace App\Module\Transfer\Logics;
+
+use App\Module\Transfer\Enums\TransferStatus;
+use App\Module\Transfer\Enums\TransferType;
+use App\Module\Transfer\Models\TransferApp;
+use App\Module\Transfer\Models\TransferOrder;
+use App\Module\Transfer\Validations\TransferInValidation;
+use App\Module\Transfer\Validations\TransferOutValidation;
+use App\Module\Fund\Service\FundService;
+use Illuminate\Support\Str;
+
+/**
+ * Transfer模块核心业务逻辑
+ */
+class TransferLogic
+{
+    /**
+     * 创建转出订单
+     * 
+     * @param array $data 订单数据
+     * @return TransferOrder
+     * @throws \Exception
+     */
+    public static function createTransferOut(array $data): TransferOrder
+    {
+        // 验证请求数据
+        $validation = new TransferOutValidation($data);
+        $validation->validated();
+
+        // 获取应用配置
+        $app = TransferApp::findOrFail($data['transfer_app_id']);
+        if (!$app->is_enabled) {
+            throw new \Exception('应用已禁用');
+        }
+
+        // 计算金额
+        $outAmount = (string) $data['amount'];
+        $amount = bcmul($outAmount, (string) $app->exchange_rate, 10);
+
+        // 生成外部订单ID
+        $outOrderId = self::generateOutOrderId('OUT');
+
+        // 创建订单
+        $order = TransferOrder::create([
+            'transfer_app_id' => $app->id,
+            'out_id' => $app->out_id,
+            '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,
+            'callback_data' => $data['callback_data'] ?? [],
+            'remark' => $data['remark'] ?? null,
+        ]);
+
+        // 扣除用户资金
+        $fundResult = FundService::decrease(
+            $data['user_id'],
+            $app->fund_id,
+            $amount,
+            "转出到{$app->title}",
+            "transfer_out:{$order->id}"
+        );
+
+        if (!$fundResult) {
+            $order->updateStatus(TransferStatus::FAILED, '资金扣除失败');
+            throw new \Exception('余额不足或资金操作失败');
+        }
+
+        // 判断是否为农场内部模式
+        if ($app->isInternalMode()) {
+            // 内部模式直接完成
+            $order->updateStatus(TransferStatus::COMPLETED);
+        } else {
+            // 外部模式,调用外部API
+            OrderLogic::processTransferOut($order);
+        }
+
+        return $order;
+    }
+
+    /**
+     * 创建转入订单
+     * 
+     * @param array $data 订单数据
+     * @return TransferOrder
+     * @throws \Exception
+     */
+    public static function createTransferIn(array $data): TransferOrder
+    {
+        // 验证请求数据
+        $validation = new TransferInValidation($data);
+        $validation->validated();
+
+        // 获取应用配置
+        $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_id)
+            ->first();
+
+        if ($existingOrder) {
+            throw new \Exception('订单已存在');
+        }
+
+        // 计算金额
+        $outAmount = (string) $data['amount'];
+        $amount = bcdiv($outAmount, (string) $app->exchange_rate, 10);
+
+        // 创建订单
+        $order = TransferOrder::create([
+            'transfer_app_id' => $app->id,
+            'out_id' => $app->out_id,
+            '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' => $amount,
+            'exchange_rate' => $app->exchange_rate,
+            'callback_data' => $data['callback_data'] ?? [],
+            'remark' => $data['remark'] ?? null,
+        ]);
+
+        // 向用户账户转入资金
+        $fundResult = FundService::increase(
+            $data['user_id'],
+            $app->fund_id,
+            $amount,
+            "从{$app->title}转入",
+            "transfer_in:{$order->id}"
+        );
+
+        if (!$fundResult) {
+            $order->updateStatus(TransferStatus::FAILED, '资金转入失败');
+            throw new \Exception('资金操作失败');
+        }
+
+        // 判断是否为农场内部模式
+        if ($app->isInternalMode()) {
+            // 内部模式直接完成
+            $order->updateStatus(TransferStatus::COMPLETED);
+        } else {
+            // 外部模式,更新状态为处理中
+            $order->updateStatus(TransferStatus::PROCESSING);
+            
+            // 发送回调通知
+            if ($app->supportsCallback()) {
+                CallbackLogic::sendCallback($order);
+            }
+        }
+
+        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;
+    }
+}

+ 279 - 0
app/Module/Transfer/Services/ExternalApiService.php

@@ -0,0 +1,279 @@
+<?php
+
+namespace App\Module\Transfer\Services;
+
+use App\Module\Transfer\Models\TransferApp;
+use Illuminate\Support\Facades\Http;
+use Illuminate\Support\Facades\Log;
+
+/**
+ * 外部API服务
+ * 处理与外部应用的API交互
+ */
+class ExternalApiService
+{
+    /**
+     * 创建转出订单
+     * 
+     * @param TransferApp $app 应用配置
+     * @param array $data 订单数据
+     * @return array
+     */
+    public static function createOutOrder(TransferApp $app, array $data): array
+    {
+        try {
+            if (empty($app->order_out_create_url)) {
+                return ['success' => false, 'message' => '未配置转出创建API'];
+            }
+
+            // 添加签名
+            $data['timestamp'] = time();
+            $data['signature'] = self::generateSignature($data, $app);
+
+            // 发送HTTP请求
+            $response = Http::timeout(30)
+                ->post($app->order_out_create_url, $data);
+
+            if ($response->successful()) {
+                $result = $response->json();
+                
+                Log::info('External API create out order success', [
+                    'app_id' => $app->id,
+                    'url' => $app->order_out_create_url,
+                    'request' => $data,
+                    'response' => $result
+                ]);
+
+                return [
+                    'success' => true,
+                    'data' => $result,
+                    'message' => '创建成功'
+                ];
+            } else {
+                $error = "HTTP {$response->status()}: {$response->body()}";
+                
+                Log::error('External API create out order failed', [
+                    'app_id' => $app->id,
+                    'url' => $app->order_out_create_url,
+                    'error' => $error
+                ]);
+
+                return [
+                    'success' => false,
+                    'message' => $error
+                ];
+            }
+
+        } catch (\Exception $e) {
+            Log::error('External API create out order exception', [
+                'app_id' => $app->id,
+                'error' => $e->getMessage()
+            ]);
+
+            return [
+                'success' => false,
+                'message' => $e->getMessage()
+            ];
+        }
+    }
+
+    /**
+     * 查询转出订单状态
+     * 
+     * @param TransferApp $app 应用配置
+     * @param string $outOrderId 外部订单ID
+     * @return array
+     */
+    public static function queryOutOrder(TransferApp $app, string $outOrderId): array
+    {
+        try {
+            if (empty($app->order_out_info_url)) {
+                return ['success' => false, 'message' => '未配置转出查询API'];
+            }
+
+            $params = [
+                'business_id' => $outOrderId,
+                'timestamp' => time()
+            ];
+            $params['signature'] = self::generateSignature($params, $app);
+
+            $response = Http::timeout(30)
+                ->get($app->order_out_info_url, $params);
+
+            if ($response->successful()) {
+                $result = $response->json();
+                
+                return [
+                    'success' => true,
+                    'data' => $result,
+                    'message' => '查询成功'
+                ];
+            } else {
+                return [
+                    'success' => false,
+                    'message' => "HTTP {$response->status()}: {$response->body()}"
+                ];
+            }
+
+        } catch (\Exception $e) {
+            return [
+                'success' => false,
+                'message' => $e->getMessage()
+            ];
+        }
+    }
+
+    /**
+     * 查询转入订单状态
+     * 
+     * @param TransferApp $app 应用配置
+     * @param string $outOrderId 外部订单ID
+     * @return array
+     */
+    public static function queryInOrder(TransferApp $app, string $outOrderId): array
+    {
+        try {
+            if (empty($app->order_in_info_url)) {
+                return ['success' => false, 'message' => '未配置转入查询API'];
+            }
+
+            $params = [
+                'business_id' => $outOrderId,
+                'timestamp' => time()
+            ];
+            $params['signature'] = self::generateSignature($params, $app);
+
+            $response = Http::timeout(30)
+                ->get($app->order_in_info_url, $params);
+
+            if ($response->successful()) {
+                $result = $response->json();
+                
+                return [
+                    'success' => true,
+                    'data' => $result,
+                    'message' => '查询成功'
+                ];
+            } else {
+                return [
+                    'success' => false,
+                    'message' => "HTTP {$response->status()}: {$response->body()}"
+                ];
+            }
+
+        } catch (\Exception $e) {
+            return [
+                'success' => false,
+                'message' => $e->getMessage()
+            ];
+        }
+    }
+
+    /**
+     * 发送回调通知
+     * 
+     * @param TransferApp $app 应用配置
+     * @param array $data 回调数据
+     * @return array
+     */
+    public static function sendCallback(TransferApp $app, array $data): array
+    {
+        try {
+            if (empty($app->order_callback_url)) {
+                return ['success' => false, 'message' => '未配置回调URL'];
+            }
+
+            // 添加签名
+            $data['signature'] = self::generateSignature($data, $app);
+
+            $response = Http::timeout(30)
+                ->post($app->order_callback_url, $data);
+
+            if ($response->successful()) {
+                $result = $response->json();
+                
+                Log::info('External API callback success', [
+                    'app_id' => $app->id,
+                    'url' => $app->order_callback_url,
+                    'response' => $result
+                ]);
+
+                return [
+                    'success' => true,
+                    'data' => $result,
+                    'message' => '回调成功'
+                ];
+            } else {
+                $error = "HTTP {$response->status()}: {$response->body()}";
+                
+                Log::error('External API callback failed', [
+                    'app_id' => $app->id,
+                    'url' => $app->order_callback_url,
+                    'error' => $error
+                ]);
+
+                return [
+                    'success' => false,
+                    'message' => $error
+                ];
+            }
+
+        } catch (\Exception $e) {
+            Log::error('External API callback exception', [
+                'app_id' => $app->id,
+                'error' => $e->getMessage()
+            ]);
+
+            return [
+                'success' => false,
+                'message' => $e->getMessage()
+            ];
+        }
+    }
+
+    /**
+     * 生成API签名
+     * 
+     * @param array $data 数据
+     * @param TransferApp $app 应用配置
+     * @return string
+     */
+    private static function generateSignature(array $data, TransferApp $app): string
+    {
+        // 移除signature字段
+        unset($data['signature']);
+        
+        // 按key排序
+        ksort($data);
+        
+        // 拼接字符串
+        $string = '';
+        foreach ($data as $key => $value) {
+            if (is_array($value)) {
+                $value = json_encode($value, JSON_UNESCAPED_UNICODE);
+            }
+            $string .= $key . '=' . $value . '&';
+        }
+        
+        // 添加密钥(这里使用应用keyname作为密钥,实际项目中应该有专门的密钥字段)
+        $string .= 'key=' . $app->keyname;
+        
+        // 生成MD5签名
+        return md5($string);
+    }
+
+    /**
+     * 验证API签名
+     * 
+     * @param array $data 数据
+     * @param TransferApp $app 应用配置
+     * @return bool
+     */
+    public static function verifySignature(array $data, TransferApp $app): bool
+    {
+        $signature = $data['signature'] ?? '';
+        $expectedSignature = self::generateSignature($data, $app);
+        
+        return hash_equals($expectedSignature, $signature);
+    }
+}

+ 194 - 0
app/Module/Transfer/Services/TransferService.php

@@ -0,0 +1,194 @@
+<?php
+
+namespace App\Module\Transfer\Services;
+
+use App\Module\Transfer\Dtos\TransferAppDto;
+use App\Module\Transfer\Dtos\TransferOrderDto;
+use App\Module\Transfer\Logics\TransferLogic;
+use App\Module\Transfer\Models\TransferApp;
+use App\Module\Transfer\Models\TransferOrder;
+
+/**
+ * Transfer模块对外服务接口
+ * 供其他模块调用的主要服务类
+ */
+class TransferService
+{
+    /**
+     * 创建转出订单
+     * 
+     * @param array $data 订单数据
+     * @return TransferOrderDto|string 成功返回DTO,失败返回错误信息
+     */
+    public static function createTransferOut(array $data): TransferOrderDto|string
+    {
+        try {
+            $order = TransferLogic::createTransferOut($data);
+            return TransferOrderDto::fromModel($order);
+        } catch (\Exception $e) {
+            return $e->getMessage();
+        }
+    }
+
+    /**
+     * 创建转入订单
+     * 
+     * @param array $data 订单数据
+     * @return TransferOrderDto|string 成功返回DTO,失败返回错误信息
+     */
+    public static function createTransferIn(array $data): TransferOrderDto|string
+    {
+        try {
+            $order = TransferLogic::createTransferIn($data);
+            return TransferOrderDto::fromModel($order);
+        } catch (\Exception $e) {
+            return $e->getMessage();
+        }
+    }
+
+    /**
+     * 查询订单信息
+     * 
+     * @param int $orderId 订单ID
+     * @param int|null $userId 用户ID(可选,用于权限验证)
+     * @return TransferOrderDto|null
+     */
+    public static function getOrderInfo(int $orderId, ?int $userId = null): ?TransferOrderDto
+    {
+        $query = TransferOrder::where('id', $orderId);
+        
+        if ($userId !== null) {
+            $query->where('user_id', $userId);
+        }
+        
+        $order = $query->first();
+        
+        return $order ? TransferOrderDto::fromModel($order) : null;
+    }
+
+    /**
+     * 根据外部订单ID查询订单信息
+     * 
+     * @param string $outOrderId 外部订单ID
+     * @param int $outId 外部应用ID
+     * @return TransferOrderDto|null
+     */
+    public static function getOrderByOutId(string $outOrderId, int $outId): ?TransferOrderDto
+    {
+        $order = TransferOrder::where('out_order_id', $outOrderId)
+            ->where('out_id', $outId)
+            ->first();
+            
+        return $order ? TransferOrderDto::fromModel($order) : null;
+    }
+
+    /**
+     * 获取用户可用的划转应用列表
+     * 
+     * @param int $userId 用户ID
+     * @return TransferAppDto[]
+     */
+    public static function getAvailableApps(int $userId): array
+    {
+        $apps = TransferApp::where('is_enabled', true)->get();
+        
+        return $apps->map(function ($app) {
+            return TransferAppDto::fromModel($app);
+        })->toArray();
+    }
+
+    /**
+     * 获取用户订单列表
+     * 
+     * @param int $userId 用户ID
+     * @param array $filters 筛选条件
+     * @return array
+     */
+    public static function getUserOrders(int $userId, array $filters = []): array
+    {
+        $query = TransferOrder::where('user_id', $userId);
+        
+        // 应用筛选条件
+        if (isset($filters['type'])) {
+            $query->where('type', $filters['type']);
+        }
+        
+        if (isset($filters['status'])) {
+            $query->where('status', $filters['status']);
+        }
+        
+        if (isset($filters['transfer_app_id'])) {
+            $query->where('transfer_app_id', $filters['transfer_app_id']);
+        }
+        
+        if (isset($filters['start_date'])) {
+            $query->where('created_at', '>=', $filters['start_date']);
+        }
+        
+        if (isset($filters['end_date'])) {
+            $query->where('created_at', '<=', $filters['end_date']);
+        }
+        
+        // 分页参数
+        $page = $filters['page'] ?? 1;
+        $perPage = $filters['per_page'] ?? 20;
+        
+        $orders = $query->orderBy('created_at', 'desc')
+            ->paginate($perPage, ['*'], 'page', $page);
+        
+        return [
+            'data' => $orders->items(),
+            'current_page' => $orders->currentPage(),
+            'per_page' => $orders->perPage(),
+            'total' => $orders->total(),
+            'last_page' => $orders->lastPage(),
+        ];
+    }
+
+    /**
+     * 获取应用配置信息
+     * 
+     * @param int $appId 应用ID
+     * @return TransferAppDto|null
+     */
+    public static function getAppConfig(int $appId): ?TransferAppDto
+    {
+        $app = TransferApp::find($appId);
+        
+        return $app ? TransferAppDto::fromModel($app) : null;
+    }
+
+    /**
+     * 根据应用标识获取配置信息
+     * 
+     * @param string $keyname 应用标识
+     * @return TransferAppDto|null
+     */
+    public static function getAppByKeyname(string $keyname): ?TransferAppDto
+    {
+        $app = TransferApp::where('keyname', $keyname)
+            ->where('is_enabled', true)
+            ->first();
+            
+        return $app ? TransferAppDto::fromModel($app) : null;
+    }
+
+    /**
+     * 处理回调结果
+     * 
+     * @param array $callbackData 回调数据
+     * @return bool
+     */
+    public static function processCallback(array $callbackData): bool
+    {
+        try {
+            return TransferLogic::processCallback($callbackData);
+        } catch (\Exception $e) {
+            \Log::error('Transfer callback processing failed', [
+                'error' => $e->getMessage(),
+                'data' => $callbackData
+            ]);
+            return false;
+        }
+    }
+}

+ 165 - 0
app/Module/Transfer/Validations/TransferInValidation.php

@@ -0,0 +1,165 @@
+<?php
+
+namespace App\Module\Transfer\Validations;
+
+use App\Module\Transfer\Validators\TransferAppValidator;
+use App\Module\Transfer\Validators\BusinessIdValidator;
+use App\Module\Transfer\Validators\AmountValidator;
+use UCore\Validation\ValidationCore;
+
+/**
+ * 转入验证类
+ */
+class TransferInValidation extends ValidationCore
+{
+    /**
+     * 验证规则
+     */
+    protected function rules(): array
+    {
+        return [
+            'transfer_app_id' => 'required|integer|min:1',
+            'business_id' => 'required|string|max:100',
+            'out_user_id' => 'nullable|string|max:50',
+            'user_id' => 'required|integer|min:1',
+            'amount' => 'required|string',
+            'remark' => 'nullable|string|max:255',
+            'callback_data' => 'nullable|array',
+        ];
+    }
+
+    /**
+     * 验证消息
+     */
+    protected function messages(): array
+    {
+        return [
+            'transfer_app_id.required' => '应用ID不能为空',
+            'transfer_app_id.integer' => '应用ID必须为整数',
+            'transfer_app_id.min' => '应用ID必须大于0',
+            'business_id.required' => '业务订单ID不能为空',
+            'business_id.string' => '业务订单ID必须为字符串',
+            'business_id.max' => '业务订单ID长度不能超过100个字符',
+            'out_user_id.string' => '外部用户ID必须为字符串',
+            'out_user_id.max' => '外部用户ID长度不能超过50个字符',
+            'user_id.required' => '用户ID不能为空',
+            'user_id.integer' => '用户ID必须为整数',
+            'user_id.min' => '用户ID必须大于0',
+            'amount.required' => '金额不能为空',
+            'amount.string' => '金额必须为字符串格式',
+            'remark.string' => '备注必须为字符串',
+            'remark.max' => '备注长度不能超过255个字符',
+            'callback_data.array' => '回调数据必须为数组格式',
+        ];
+    }
+
+    /**
+     * 自定义验证
+     */
+    protected function customValidation(): void
+    {
+        // 验证应用配置
+        $appValidator = new TransferAppValidator($this->data['transfer_app_id'] ?? 0);
+        if (!$appValidator->validate()) {
+            $this->addError('transfer_app_id', $appValidator->getError());
+        }
+
+        // 验证业务ID唯一性
+        if (isset($this->data['business_id']) && isset($this->data['transfer_app_id'])) {
+            $businessIdValidator = new BusinessIdValidator(
+                $this->data['business_id'],
+                $this->data['transfer_app_id']
+            );
+            if (!$businessIdValidator->validate()) {
+                $this->addError('business_id', $businessIdValidator->getError());
+            }
+        }
+
+        // 验证金额格式
+        if (isset($this->data['amount'])) {
+            $amountValidator = new AmountValidator($this->data['amount']);
+            if (!$amountValidator->validate()) {
+                $this->addError('amount', $amountValidator->getError());
+            }
+        }
+
+        // 验证用户是否存在
+        if (isset($this->data['user_id'])) {
+            $userId = $this->data['user_id'];
+            // 这里可以调用用户模块的验证服务
+            // if (!UserService::exists($userId)) {
+            //     $this->addError('user_id', '用户不存在');
+            // }
+        }
+
+        // 验证回调数据格式
+        if (isset($this->data['callback_data']) && is_array($this->data['callback_data'])) {
+            $callbackData = $this->data['callback_data'];
+            
+            // 检查回调数据大小(限制为1KB)
+            $jsonSize = strlen(json_encode($callbackData, JSON_UNESCAPED_UNICODE));
+            if ($jsonSize > 1024) {
+                $this->addError('callback_data', '回调数据过大,不能超过1KB');
+            }
+
+            // 检查是否包含敏感字段
+            $forbiddenKeys = ['password', 'token', 'secret', 'key'];
+            foreach ($forbiddenKeys as $key) {
+                if (array_key_exists($key, $callbackData)) {
+                    $this->addError('callback_data', "回调数据不能包含敏感字段: {$key}");
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * 验证转入权限
+     */
+    protected function validateTransferInPermission(): void
+    {
+        if (!isset($this->data['transfer_app_id'])) {
+            return;
+        }
+
+        $appId = $this->data['transfer_app_id'];
+        
+        // 获取应用配置
+        $app = \App\Module\Transfer\Models\TransferApp::find($appId);
+        if (!$app) {
+            return;
+        }
+
+        // 检查应用是否支持转入
+        if (!$app->supportsTransferIn()) {
+            $this->addError('transfer_app_id', '该应用不支持转入操作');
+        }
+
+        // 检查应用是否启用
+        if (!$app->is_enabled) {
+            $this->addError('transfer_app_id', '应用已禁用');
+        }
+    }
+
+    /**
+     * 执行验证后的处理
+     */
+    protected function afterValidation(): void
+    {
+        // 验证转入权限
+        $this->validateTransferInPermission();
+
+        // 格式化金额(确保精度)
+        if (isset($this->data['amount'])) {
+            $this->data['amount'] = number_format((float) $this->data['amount'], 10, '.', '');
+        }
+
+        // 清理回调数据
+        if (isset($this->data['callback_data']) && is_array($this->data['callback_data'])) {
+            // 移除空值
+            $this->data['callback_data'] = array_filter($this->data['callback_data'], function ($value) {
+                return $value !== null && $value !== '';
+            });
+        }
+    }
+}