Pārlūkot izejas kodu

Transfer模块第一阶段开发完成 - 基础架构搭建

第一阶段:基础架构搭建
- 目录结构:创建完整的模块目录结构,包含23个主要目录
- 枚举类型:创建TransferStatus、TransferType、CallbackStatus三个核心枚举
- 数据模型:创建TransferApp和TransferOrder两个核心模型,包含完整的字段定义和关联关系
- Cast转换器:创建TransferAppCast、TransferOrderCast、CallbackDataCast三个转换器
- 数据库SQL:创建建表SQL和初始化数据SQL,支持完整的表结构和示例数据
- DTO对象:创建TransferAppDto数据传输对象,支持模型转换和数组输出

技术特点:
- 遵循用户偏好的代码规范和命名约定
- 使用PHP enum语法定义枚举类型
- 模型继承UCore\ModelCore,添加field注释块
- 支持软删除、时间戳、类型转换等Laravel特性
- 完整的字段验证和业务逻辑方法

下一阶段:核心业务逻辑开发
notfff 7 mēneši atpakaļ
vecāks
revīzija
a25ca9c7

+ 54 - 0
app/Module/Transfer/Casts/CallbackDataCast.php

@@ -0,0 +1,54 @@
+<?php
+
+namespace App\Module\Transfer\Casts;
+
+use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
+use Illuminate\Database\Eloquent\Model;
+
+/**
+ * 回调数据转换器
+ */
+class CallbackDataCast implements CastsAttributes
+{
+    /**
+     * 将取出的数据进行转换
+     */
+    public function get(Model $model, string $key, mixed $value, array $attributes): mixed
+    {
+        if (is_null($value)) {
+            return [];
+        }
+
+        if (is_string($value)) {
+            $decoded = json_decode($value, true);
+            return is_array($decoded) ? $decoded : [];
+        }
+
+        return is_array($value) ? $value : [];
+    }
+
+    /**
+     * 转换成将要进行存储的值
+     */
+    public function set(Model $model, string $key, mixed $value, array $attributes): mixed
+    {
+        if (is_null($value)) {
+            return null;
+        }
+
+        if (is_array($value)) {
+            return json_encode($value, JSON_UNESCAPED_UNICODE);
+        }
+
+        if (is_string($value)) {
+            // 验证是否为有效的JSON
+            $decoded = json_decode($value, true);
+            if (json_last_error() === JSON_ERROR_NONE) {
+                return $value;
+            }
+        }
+
+        // 其他类型转换为JSON
+        return json_encode($value, JSON_UNESCAPED_UNICODE);
+    }
+}

+ 46 - 0
app/Module/Transfer/Casts/TransferAppCast.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace App\Module\Transfer\Casts;
+
+use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
+use Illuminate\Database\Eloquent\Model;
+
+/**
+ * 划转应用转换器
+ */
+class TransferAppCast implements CastsAttributes
+{
+    /**
+     * 将取出的数据进行转换
+     */
+    public function get(Model $model, string $key, mixed $value, array $attributes): mixed
+    {
+        if (is_null($value)) {
+            return null;
+        }
+
+        // 根据字段类型进行转换
+        return match ($key) {
+            'exchange_rate' => (float) $value,
+            'is_enabled' => (bool) $value,
+            default => $value,
+        };
+    }
+
+    /**
+     * 转换成将要进行存储的值
+     */
+    public function set(Model $model, string $key, mixed $value, array $attributes): mixed
+    {
+        if (is_null($value)) {
+            return null;
+        }
+
+        // 根据字段类型进行转换
+        return match ($key) {
+            'exchange_rate' => number_format((float) $value, 4, '.', ''),
+            'is_enabled' => (bool) $value,
+            default => $value,
+        };
+    }
+}

+ 46 - 0
app/Module/Transfer/Casts/TransferOrderCast.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace App\Module\Transfer\Casts;
+
+use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
+use Illuminate\Database\Eloquent\Model;
+
+/**
+ * 划转订单转换器
+ */
+class TransferOrderCast implements CastsAttributes
+{
+    /**
+     * 将取出的数据进行转换
+     */
+    public function get(Model $model, string $key, mixed $value, array $attributes): mixed
+    {
+        if (is_null($value)) {
+            return null;
+        }
+
+        // 根据字段类型进行转换
+        return match ($key) {
+            'out_amount', 'amount' => (string) $value,
+            'exchange_rate' => (float) $value,
+            default => $value,
+        };
+    }
+
+    /**
+     * 转换成将要进行存储的值
+     */
+    public function set(Model $model, string $key, mixed $value, array $attributes): mixed
+    {
+        if (is_null($value)) {
+            return null;
+        }
+
+        // 根据字段类型进行转换
+        return match ($key) {
+            'out_amount', 'amount' => (string) $value,
+            'exchange_rate' => number_format((float) $value, 4, '.', ''),
+            default => $value,
+        };
+    }
+}

+ 67 - 0
app/Module/Transfer/Databases/GenerateSql/create_tables.sql

@@ -0,0 +1,67 @@
+-- Transfer模块数据库表创建脚本
+-- 创建时间: 2025-06-15
+-- 模块: Transfer
+
+-- 1. 创建划转应用配置表
+CREATE TABLE `kku_transfer_apps` (
+  `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `keyname` varchar(50) NOT NULL COMMENT '应用标识符',
+  `title` varchar(100) NOT NULL COMMENT '应用显示名称',
+  `description` text COMMENT '应用描述信息',
+  `out_id` int NOT NULL COMMENT '外部应用ID',
+  `out_id2` int DEFAULT NULL COMMENT '外部应用ID2-开放接口',
+  `out_id3` int DEFAULT NULL COMMENT '外部应用ID3-三方平台ID',
+  `currency_id` int NOT NULL COMMENT '货币类型ID',
+  `fund_id` int NOT NULL COMMENT '资金账户类型ID',
+  `fund_to_uid` int DEFAULT NULL COMMENT '转入目标账户UID',
+  `fund_in_uid` int DEFAULT NULL COMMENT '转入来源账户UID',
+  `exchange_rate` decimal(10,4) NOT NULL DEFAULT '1.0000' COMMENT '汇率(钱包:业务)',
+  `order_callback_url` varchar(255) DEFAULT NULL COMMENT '结果通知API地址(为空则不通知)',
+  `order_in_info_url` varchar(255) DEFAULT NULL COMMENT '转入查询API地址(为空则不查询)',
+  `order_out_create_url` varchar(255) DEFAULT NULL COMMENT '转出创建API地址(为空则不创建)',
+  `order_out_info_url` varchar(255) DEFAULT NULL COMMENT '转出查询API地址(为空则不查询)',
+  `is_enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用(1=启用,0=禁用)',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '更新时间',
+  `deleted_at` timestamp NULL DEFAULT NULL COMMENT '删除时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_keyname` (`keyname`),
+  KEY `idx_out_id` (`out_id`),
+  KEY `idx_currency_id` (`currency_id`),
+  KEY `idx_enabled` (`is_enabled`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='划转应用配置表';
+
+-- 2. 创建划转订单表
+CREATE TABLE `kku_transfer_orders` (
+  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `transfer_app_id` int unsigned NOT NULL COMMENT '划转应用ID',
+  `out_id` int NOT NULL COMMENT '外部应用ID',
+  `out_order_id` varchar(100) NOT NULL COMMENT '外部订单ID',
+  `out_user_id` varchar(50) DEFAULT NULL COMMENT '外部用户ID',
+  `user_id` int unsigned NOT NULL COMMENT '内部用户ID',
+  `currency_id` int NOT NULL COMMENT '货币类型ID',
+  `fund_id` int NOT NULL COMMENT '资金账户类型ID',
+  `type` tinyint NOT NULL COMMENT '订单类型(1=转入,2=转出)',
+  `status` tinyint NOT NULL DEFAULT '1' COMMENT '订单状态',
+  `out_amount` decimal(30,10) NOT NULL COMMENT '外部金额',
+  `amount` decimal(30,10) NOT NULL COMMENT '内部金额',
+  `exchange_rate` decimal(10,4) NOT NULL COMMENT '使用汇率',
+  `callback_data` json DEFAULT NULL COMMENT '回调数据',
+  `error_message` text COMMENT '错误信息',
+  `remark` varchar(255) DEFAULT NULL COMMENT '备注信息',
+  `processed_at` timestamp NULL DEFAULT NULL COMMENT '处理时间',
+  `callback_at` timestamp NULL DEFAULT NULL COMMENT '回调时间',
+  `completed_at` timestamp NULL DEFAULT NULL COMMENT '完成时间',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '更新时间',
+  `deleted_at` timestamp NULL DEFAULT NULL COMMENT '删除时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_out_order` (`out_id`, `out_order_id`),
+  KEY `idx_transfer_app_id` (`transfer_app_id`),
+  KEY `idx_user_id` (`user_id`),
+  KEY `idx_status` (`status`),
+  KEY `idx_type` (`type`),
+  KEY `idx_created_at` (`created_at`),
+  KEY `idx_processed_at` (`processed_at`),
+  CONSTRAINT `fk_transfer_orders_app` FOREIGN KEY (`transfer_app_id`) REFERENCES `kku_transfer_apps` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='划转订单表';

+ 57 - 0
app/Module/Transfer/Databases/GenerateSql/init_data.sql

@@ -0,0 +1,57 @@
+-- Transfer模块初始化数据脚本
+-- 创建时间: 2025-06-15
+-- 模块: Transfer
+
+-- 插入示例应用配置数据
+INSERT INTO `kku_transfer_apps` (
+    `keyname`, `title`, `description`, `out_id`, `out_id2`, `out_id3`, `currency_id`, `fund_id`, 
+    `exchange_rate`, `order_callback_url`, `order_out_create_url`, `is_enabled`, `created_at`, `updated_at`
+) VALUES 
+(
+    'game_app_demo', 
+    '演示游戏应用', 
+    '用于测试的演示应用', 
+    1001, 
+    2001, 
+    3001, 
+    2, 
+    2, 
+    1.0000, 
+    'https://demo.game.com/callback', 
+    'https://demo.game.com/order/create', 
+    1, 
+    NOW(), 
+    NOW()
+),
+(
+    'game_app_a', 
+    '游戏应用A', 
+    '热门游戏应用A', 
+    1002, 
+    2002, 
+    NULL, 
+    2, 
+    2, 
+    1.0450, 
+    'https://api.game-a.com/callback', 
+    'https://api.game-a.com/order/create', 
+    1, 
+    NOW(), 
+    NOW()
+),
+(
+    'farm_internal', 
+    '农场内部应用', 
+    '仅在农场内部运转,不调用外部API', 
+    1003, 
+    NULL, 
+    NULL, 
+    1, 
+    1, 
+    1.0000, 
+    NULL, 
+    NULL, 
+    1, 
+    NOW(), 
+    NOW()
+);

+ 122 - 0
app/Module/Transfer/Dtos/TransferAppDto.php

@@ -0,0 +1,122 @@
+<?php
+
+namespace App\Module\Transfer\Dtos;
+
+/**
+ * 划转应用数据传输对象
+ */
+class TransferAppDto
+{
+    public function __construct(
+        public readonly int $id,
+        public readonly string $keyname,
+        public readonly string $title,
+        public readonly ?string $description,
+        public readonly int $out_id,
+        public readonly ?int $out_id2,
+        public readonly ?int $out_id3,
+        public readonly int $currency_id,
+        public readonly int $fund_id,
+        public readonly ?int $fund_to_uid,
+        public readonly ?int $fund_in_uid,
+        public readonly float $exchange_rate,
+        public readonly ?string $order_callback_url,
+        public readonly ?string $order_in_info_url,
+        public readonly ?string $order_out_create_url,
+        public readonly ?string $order_out_info_url,
+        public readonly bool $is_enabled,
+        public readonly string $created_at,
+        public readonly string $updated_at,
+    ) {}
+
+    /**
+     * 从模型创建DTO
+     */
+    public static function fromModel($model): self
+    {
+        return new self(
+            id: $model->id,
+            keyname: $model->keyname,
+            title: $model->title,
+            description: $model->description,
+            out_id: $model->out_id,
+            out_id2: $model->out_id2,
+            out_id3: $model->out_id3,
+            currency_id: $model->currency_id,
+            fund_id: $model->fund_id,
+            fund_to_uid: $model->fund_to_uid,
+            fund_in_uid: $model->fund_in_uid,
+            exchange_rate: (float) $model->exchange_rate,
+            order_callback_url: $model->order_callback_url,
+            order_in_info_url: $model->order_in_info_url,
+            order_out_create_url: $model->order_out_create_url,
+            order_out_info_url: $model->order_out_info_url,
+            is_enabled: $model->is_enabled,
+            created_at: $model->created_at->toDateTimeString(),
+            updated_at: $model->updated_at->toDateTimeString(),
+        );
+    }
+
+    /**
+     * 转换为数组
+     */
+    public function toArray(): array
+    {
+        return [
+            'id' => $this->id,
+            'keyname' => $this->keyname,
+            'title' => $this->title,
+            'description' => $this->description,
+            'out_id' => $this->out_id,
+            'out_id2' => $this->out_id2,
+            'out_id3' => $this->out_id3,
+            'currency_id' => $this->currency_id,
+            'fund_id' => $this->fund_id,
+            'fund_to_uid' => $this->fund_to_uid,
+            'fund_in_uid' => $this->fund_in_uid,
+            'exchange_rate' => $this->exchange_rate,
+            'order_callback_url' => $this->order_callback_url,
+            'order_in_info_url' => $this->order_in_info_url,
+            'order_out_create_url' => $this->order_out_create_url,
+            'order_out_info_url' => $this->order_out_info_url,
+            'is_enabled' => $this->is_enabled,
+            'created_at' => $this->created_at,
+            'updated_at' => $this->updated_at,
+        ];
+    }
+
+    /**
+     * 判断是否为农场内部模式
+     */
+    public function isInternalMode(): bool
+    {
+        return empty($this->order_callback_url) && 
+               empty($this->order_in_info_url) && 
+               empty($this->order_out_create_url) && 
+               empty($this->order_out_info_url);
+    }
+
+    /**
+     * 判断是否支持转入
+     */
+    public function supportsTransferIn(): bool
+    {
+        return !empty($this->order_in_info_url) || $this->isInternalMode();
+    }
+
+    /**
+     * 判断是否支持转出
+     */
+    public function supportsTransferOut(): bool
+    {
+        return !empty($this->order_out_create_url) || $this->isInternalMode();
+    }
+
+    /**
+     * 判断是否支持回调
+     */
+    public function supportsCallback(): bool
+    {
+        return !empty($this->order_callback_url);
+    }
+}

+ 69 - 0
app/Module/Transfer/Enums/CallbackStatus.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace App\Module\Transfer\Enums;
+
+use Dcore\Enum\EnumCore;
+use Dcore\Enum\EnumExpression;
+use Dcore\Enum\EnumToInt;
+
+/**
+ * 回调状态枚举
+ */
+enum CallbackStatus: int
+{
+    use EnumToInt, EnumCore, EnumExpression;
+
+    /**
+     * 待发送 - 等待发送回调
+     */
+    case PENDING = 0;
+
+    /**
+     * 已发送 - 回调已发送
+     */
+    case SENT = 1;
+
+    /**
+     * 成功 - 回调成功
+     */
+    case SUCCESS = 2;
+
+    /**
+     * 失败 - 回调失败
+     */
+    case FAILED = -1;
+
+    /**
+     * 获取状态描述
+     */
+    public function getDescription(): string
+    {
+        return match ($this) {
+            self::PENDING => '待发送',
+            self::SENT => '已发送',
+            self::SUCCESS => '成功',
+            self::FAILED => '失败',
+        };
+    }
+
+    /**
+     * 获取状态颜色
+     */
+    public function getColor(): string
+    {
+        return match ($this) {
+            self::PENDING => 'info',
+            self::SENT => 'warning',
+            self::SUCCESS => 'success',
+            self::FAILED => 'danger',
+        };
+    }
+
+    /**
+     * 判断是否需要重试
+     */
+    public function needRetry(): bool
+    {
+        return $this === self::FAILED;
+    }
+}

+ 84 - 0
app/Module/Transfer/Enums/TransferStatus.php

@@ -0,0 +1,84 @@
+<?php
+
+namespace App\Module\Transfer\Enums;
+
+use Dcore\Enum\EnumCore;
+use Dcore\Enum\EnumExpression;
+use Dcore\Enum\EnumToInt;
+
+/**
+ * 划转订单状态枚举
+ */
+enum TransferStatus: int
+{
+    use EnumToInt, EnumCore, EnumExpression;
+
+    /**
+     * 已创建 - 订单已创建,等待处理
+     */
+    case CREATED = 1;
+
+    /**
+     * 处理中 - 正在处理中,等待回调
+     */
+    case PROCESSING = 20;
+
+    /**
+     * 已回调 - 回调成功,等待最终确认
+     */
+    case CALLBACK = 30;
+
+    /**
+     * 已完成 - 订单处理完成
+     */
+    case COMPLETED = 100;
+
+    /**
+     * 失败 - 订单处理失败
+     */
+    case FAILED = -1;
+
+    /**
+     * 获取状态描述
+     */
+    public function getDescription(): string
+    {
+        return match ($this) {
+            self::CREATED => '已创建',
+            self::PROCESSING => '处理中',
+            self::CALLBACK => '已回调',
+            self::COMPLETED => '已完成',
+            self::FAILED => '失败',
+        };
+    }
+
+    /**
+     * 获取状态颜色(用于后台显示)
+     */
+    public function getColor(): string
+    {
+        return match ($this) {
+            self::CREATED => 'info',
+            self::PROCESSING => 'warning',
+            self::CALLBACK => 'primary',
+            self::COMPLETED => 'success',
+            self::FAILED => 'danger',
+        };
+    }
+
+    /**
+     * 判断是否为最终状态
+     */
+    public function isFinal(): bool
+    {
+        return in_array($this, [self::COMPLETED, self::FAILED]);
+    }
+
+    /**
+     * 判断是否可以重试
+     */
+    public function canRetry(): bool
+    {
+        return $this === self::FAILED;
+    }
+}

+ 58 - 0
app/Module/Transfer/Enums/TransferType.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace App\Module\Transfer\Enums;
+
+use Dcore\Enum\EnumCore;
+use Dcore\Enum\EnumExpression;
+use Dcore\Enum\EnumToInt;
+
+/**
+ * 划转订单类型枚举
+ */
+enum TransferType: int
+{
+    use EnumToInt, EnumCore, EnumExpression;
+
+    /**
+     * 转入 - 从外部应用向用户转入
+     */
+    case IN = 1;
+
+    /**
+     * 转出 - 从用户向外部应用转出
+     */
+    case OUT = 2;
+
+    /**
+     * 获取类型描述
+     */
+    public function getDescription(): string
+    {
+        return match ($this) {
+            self::IN => '转入',
+            self::OUT => '转出',
+        };
+    }
+
+    /**
+     * 获取类型颜色(用于后台显示)
+     */
+    public function getColor(): string
+    {
+        return match ($this) {
+            self::IN => 'success',
+            self::OUT => 'warning',
+        };
+    }
+
+    /**
+     * 获取类型图标
+     */
+    public function getIcon(): string
+    {
+        return match ($this) {
+            self::IN => 'fa-arrow-down',
+            self::OUT => 'fa-arrow-up',
+        };
+    }
+}

+ 152 - 0
app/Module/Transfer/Models/TransferApp.php

@@ -0,0 +1,152 @@
+<?php
+
+namespace App\Module\Transfer\Models;
+
+use App\Module\Transfer\Casts\TransferAppCast;
+use UCore\ModelCore;
+
+/**
+ * 划转应用配置模型
+ * 
+ * field start
+ * @property int $id 主键ID
+ * @property string $keyname 应用标识符
+ * @property string $title 应用显示名称
+ * @property string $description 应用描述信息
+ * @property int $out_id 外部应用ID
+ * @property int $out_id2 外部应用ID2-开放接口
+ * @property int $out_id3 外部应用ID3-三方平台ID
+ * @property int $currency_id 货币类型ID
+ * @property int $fund_id 资金账户类型ID
+ * @property int $fund_to_uid 转入目标账户UID
+ * @property int $fund_in_uid 转入来源账户UID
+ * @property string $exchange_rate 汇率(钱包:业务)
+ * @property string $order_callback_url 结果通知API地址(为空则不通知)
+ * @property string $order_in_info_url 转入查询API地址(为空则不查询)
+ * @property string $order_out_create_url 转出创建API地址(为空则不创建)
+ * @property string $order_out_info_url 转出查询API地址(为空则不查询)
+ * @property bool $is_enabled 是否启用
+ * @property \Carbon\Carbon $created_at 创建时间
+ * @property \Carbon\Carbon $updated_at 更新时间
+ * @property \Carbon\Carbon $deleted_at 删除时间
+ * field end
+ */
+class TransferApp extends ModelCore
+{
+    /**
+     * 数据表名
+     */
+    protected $table = 'transfer_apps';
+
+    // attrlist start
+    /**
+     * 可批量赋值的属性
+     */
+    protected $fillable = [
+        'keyname',
+        'title', 
+        'description',
+        'out_id',
+        'out_id2',
+        'out_id3',
+        'currency_id',
+        'fund_id',
+        'fund_to_uid',
+        'fund_in_uid',
+        'exchange_rate',
+        'order_callback_url',
+        'order_in_info_url',
+        'order_out_create_url',
+        'order_out_info_url',
+        'is_enabled',
+    ];
+    // attrlist end
+
+    /**
+     * 属性类型转换
+     */
+    protected $casts = [
+        'id' => 'integer',
+        'out_id' => 'integer',
+        'out_id2' => 'integer',
+        'out_id3' => 'integer',
+        'currency_id' => 'integer',
+        'fund_id' => 'integer',
+        'fund_to_uid' => 'integer',
+        'fund_in_uid' => 'integer',
+        'exchange_rate' => 'decimal:4',
+        'is_enabled' => 'boolean',
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime',
+        'deleted_at' => 'datetime',
+    ];
+
+    /**
+     * 软删除
+     */
+    protected $dates = ['deleted_at'];
+
+    /**
+     * 隐藏字段
+     */
+    protected $hidden = [];
+
+    /**
+     * 关联划转订单
+     */
+    public function orders()
+    {
+        return $this->hasMany(TransferOrder::class, 'transfer_app_id');
+    }
+
+    /**
+     * 获取启用状态文本
+     */
+    public function getEnabledTextAttribute(): string
+    {
+        return $this->is_enabled ? '启用' : '禁用';
+    }
+
+    /**
+     * 获取启用状态颜色
+     */
+    public function getEnabledColorAttribute(): string
+    {
+        return $this->is_enabled ? 'success' : 'danger';
+    }
+
+    /**
+     * 判断是否为农场内部模式
+     */
+    public function isInternalMode(): bool
+    {
+        return empty($this->order_callback_url) && 
+               empty($this->order_in_info_url) && 
+               empty($this->order_out_create_url) && 
+               empty($this->order_out_info_url);
+    }
+
+    /**
+     * 判断是否支持转入
+     */
+    public function supportsTransferIn(): bool
+    {
+        return !empty($this->order_in_info_url) || $this->isInternalMode();
+    }
+
+    /**
+     * 判断是否支持转出
+     */
+    public function supportsTransferOut(): bool
+    {
+        return !empty($this->order_out_create_url) || $this->isInternalMode();
+    }
+
+    /**
+     * 判断是否支持回调
+     */
+    public function supportsCallback(): bool
+    {
+        return !empty($this->order_callback_url);
+    }
+}

+ 205 - 0
app/Module/Transfer/Models/TransferOrder.php

@@ -0,0 +1,205 @@
+<?php
+
+namespace App\Module\Transfer\Models;
+
+use App\Module\Transfer\Casts\TransferOrderCast;
+use App\Module\Transfer\Casts\CallbackDataCast;
+use App\Module\Transfer\Enums\TransferStatus;
+use App\Module\Transfer\Enums\TransferType;
+use UCore\ModelCore;
+
+/**
+ * 划转订单模型
+ * 
+ * field start
+ * @property int $id 主键ID
+ * @property int $transfer_app_id 划转应用ID
+ * @property int $out_id 外部应用ID
+ * @property string $out_order_id 外部订单ID
+ * @property string $out_user_id 外部用户ID
+ * @property int $user_id 内部用户ID
+ * @property int $currency_id 货币类型ID
+ * @property int $fund_id 资金账户类型ID
+ * @property int $type 订单类型
+ * @property int $status 订单状态
+ * @property string $out_amount 外部金额
+ * @property string $amount 内部金额
+ * @property string $exchange_rate 使用汇率
+ * @property array $callback_data 回调数据
+ * @property string $error_message 错误信息
+ * @property string $remark 备注信息
+ * @property \Carbon\Carbon $processed_at 处理时间
+ * @property \Carbon\Carbon $callback_at 回调时间
+ * @property \Carbon\Carbon $completed_at 完成时间
+ * @property \Carbon\Carbon $created_at 创建时间
+ * @property \Carbon\Carbon $updated_at 更新时间
+ * @property \Carbon\Carbon $deleted_at 删除时间
+ * field end
+ */
+class TransferOrder extends ModelCore
+{
+    /**
+     * 数据表名
+     */
+    protected $table = 'transfer_orders';
+
+    // attrlist start
+    /**
+     * 可批量赋值的属性
+     */
+    protected $fillable = [
+        'transfer_app_id',
+        'out_id',
+        'out_order_id',
+        'out_user_id',
+        'user_id',
+        'currency_id',
+        'fund_id',
+        'type',
+        'status',
+        'out_amount',
+        'amount',
+        'exchange_rate',
+        'callback_data',
+        'error_message',
+        'remark',
+        'processed_at',
+        'callback_at',
+        'completed_at',
+    ];
+    // attrlist end
+
+    /**
+     * 属性类型转换
+     */
+    protected $casts = [
+        'id' => 'integer',
+        'transfer_app_id' => 'integer',
+        'out_id' => 'integer',
+        'user_id' => 'integer',
+        'currency_id' => 'integer',
+        'fund_id' => 'integer',
+        'type' => TransferType::class,
+        'status' => TransferStatus::class,
+        'out_amount' => 'decimal:10',
+        'amount' => 'decimal:10',
+        'exchange_rate' => 'decimal:4',
+        'callback_data' => CallbackDataCast::class,
+        'processed_at' => 'datetime',
+        'callback_at' => 'datetime',
+        'completed_at' => 'datetime',
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime',
+        'deleted_at' => 'datetime',
+    ];
+
+    /**
+     * 软删除
+     */
+    protected $dates = ['deleted_at'];
+
+    /**
+     * 隐藏字段
+     */
+    protected $hidden = [];
+
+    /**
+     * 关联划转应用
+     */
+    public function transferApp()
+    {
+        return $this->belongsTo(TransferApp::class, 'transfer_app_id');
+    }
+
+    /**
+     * 获取类型文本
+     */
+    public function getTypeTextAttribute(): string
+    {
+        return $this->type->getDescription();
+    }
+
+    /**
+     * 获取状态文本
+     */
+    public function getStatusTextAttribute(): string
+    {
+        return $this->status->getDescription();
+    }
+
+    /**
+     * 获取状态颜色
+     */
+    public function getStatusColorAttribute(): string
+    {
+        return $this->status->getColor();
+    }
+
+    /**
+     * 获取类型颜色
+     */
+    public function getTypeColorAttribute(): string
+    {
+        return $this->type->getColor();
+    }
+
+    /**
+     * 判断是否为转入订单
+     */
+    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();
+    }
+
+    /**
+     * 更新状态
+     */
+    public function updateStatus(TransferStatus $status, string $message = null): bool
+    {
+        $data = ['status' => $status];
+        
+        if ($message) {
+            $data['error_message'] = $message;
+        }
+
+        // 设置时间戳
+        switch ($status) {
+            case TransferStatus::PROCESSING:
+                $data['processed_at'] = now();
+                break;
+            case TransferStatus::CALLBACK:
+                $data['callback_at'] = now();
+                break;
+            case TransferStatus::COMPLETED:
+            case TransferStatus::FAILED:
+                $data['completed_at'] = now();
+                break;
+        }
+
+        return $this->update($data);
+    }
+}