# wallet Specification ## Purpose TBD - created by archiving change add-wallet-transfer-tag-models. Update Purpose after archive. ## Requirements ### Requirement: 钱包实体定义 系统 SHALL 定义钱包(Wallet)实体,统一管理用户钱包和代理钱包,支持余额管理、充值、扣款等操作。 **核心概念**: - **用户钱包**:普通用户和企业用户的钱包,用于购买套餐 - **代理钱包**:代理商的钱包,支持预充值,可用成本价购买套餐 **实体字段**: - `id`:钱包 ID(主键,BIGINT) - `user_id`:用户 ID(BIGINT,关联 tb_account.id) - `wallet_type`:钱包类型(VARCHAR(20),枚举值:"user"-用户钱包 | "agent"-代理钱包) - `balance`:余额(BIGINT,单位:分,默认 0) - `frozen_balance`:冻结余额(BIGINT,单位:分,默认 0,用于订单待支付、提现申请中等场景) - `currency`:币种(VARCHAR(10),默认 "CNY") - `status`:钱包状态(INT,1-正常 2-冻结 3-关闭) - `version`:版本号(INT,默认 0,乐观锁字段,用于防止并发扣款) - `creator`:创建人 ID(BIGINT) - `updater`:更新人 ID(BIGINT) - `created_at`:创建时间(TIMESTAMP,自动填充) - `updated_at`:更新时间(TIMESTAMP,自动填充) - `deleted_at`:删除时间(TIMESTAMP,可空,软删除) **唯一约束**:`(user_id, wallet_type, currency)` 在 `deleted_at IS NULL` 条件下唯一 **可用余额计算**:可用余额 = balance - frozen_balance #### Scenario: 创建用户钱包 - **WHEN** 用户(ID 为 2001)首次充值 - **THEN** 系统创建钱包记录,`user_id` 为 2001,`wallet_type` 为 "user",`balance` 为 0,`status` 为 1(正常) #### Scenario: 创建代理钱包 - **WHEN** 代理商(ID 为 123)首次充值 - **THEN** 系统创建钱包记录,`user_id` 为 123,`wallet_type` 为 "agent",`balance` 为 0,`status` 为 1(正常) #### Scenario: 计算可用余额 - **WHEN** 用户钱包余额为 10000 分(100 元),冻结余额为 3000 分(30 元) - **THEN** 系统计算可用余额为 7000 分(70 元) --- ### Requirement: 钱包明细记录 系统 SHALL 记录所有钱包余额变动,包括充值、扣款、退款、分佣、提现等操作,确保完整的审计追踪。 **实体字段**: - `id`:明细 ID(主键,BIGINT) - `wallet_id`:钱包 ID(BIGINT,关联 tb_wallet.id) - `user_id`:用户 ID(BIGINT,关联 tb_account.id) - `transaction_type`:交易类型(VARCHAR(20),枚举值:"recharge"-充值 | "deduct"-扣款 | "refund"-退款 | "commission"-分佣 | "withdrawal"-提现) - `amount`:变动金额(BIGINT,单位:分,正数为增加,负数为减少) - `balance_before`:变动前余额(BIGINT,单位:分) - `balance_after`:变动后余额(BIGINT,单位:分) - `status`:交易状态(INT,1-成功 2-失败 3-处理中) - `reference_type`:关联业务类型(VARCHAR(50),如 "order" | "commission" | "withdrawal" | "topup") - `reference_id`:关联业务 ID(BIGINT) - `remark`:备注(TEXT) - `metadata`:扩展信息(JSONB,如手续费、支付方式等) - `creator`:创建人 ID(BIGINT) - `created_at`:创建时间(TIMESTAMP,自动填充) - `updated_at`:更新时间(TIMESTAMP,自动填充) - `deleted_at`:删除时间(TIMESTAMP,可空,软删除) #### Scenario: 充值创建明细记录 - **WHEN** 用户(ID 为 2001)充值 10000 分(100 元) - **THEN** 系统创建钱包明细记录,`transaction_type` 为 "recharge",`amount` 为 10000,`balance_before` 为 0,`balance_after` 为 10000,`status` 为 1(成功) #### Scenario: 购买套餐扣款创建明细记录 - **WHEN** 用户(ID 为 2001)使用钱包支付购买套餐,金额 3000 分(30 元) - **THEN** 系统创建钱包明细记录,`transaction_type` 为 "deduct",`amount` 为 -3000,`balance_before` 为 10000,`balance_after` 为 7000,`reference_type` 为 "order",`reference_id` 为订单 ID #### Scenario: 分佣发放创建明细记录 - **WHEN** 代理(ID 为 123)的分佣 5000 分(50 元)审批通过并发放 - **THEN** 系统创建钱包明细记录,`transaction_type` 为 "commission",`amount` 为 5000,`balance_before` 为 20000,`balance_after` 为 25000,`reference_type` 为 "commission",`reference_id` 为分佣记录 ID --- ### Requirement: 充值记录管理 系统 SHALL 记录所有充值操作,包括充值订单号、金额、支付方式、支付状态等信息。 **实体字段**: - `id`:充值记录 ID(主键,BIGINT) - `user_id`:用户 ID(BIGINT,关联 tb_account.id) - `wallet_id`:钱包 ID(BIGINT,关联 tb_wallet.id) - `recharge_no`:充值订单号(VARCHAR(50),唯一) - `amount`:充值金额(BIGINT,单位:分) - `payment_method`:支付方式(VARCHAR(20),枚举值:"alipay"-支付宝 | "wechat"-微信 | "bank"-银行转账 | "offline"-线下) - `payment_channel`:支付渠道(VARCHAR(50)) - `payment_transaction_id`:第三方支付交易号(VARCHAR(100)) - `status`:充值状态(INT,1-待支付 2-已支付 3-已完成 4-已关闭 5-已退款) - `paid_at`:支付时间(TIMESTAMP,可空) - `completed_at`:完成时间(TIMESTAMP,可空) - `creator`:创建人 ID(BIGINT) - `updater`:更新人 ID(BIGINT) - `created_at`:创建时间(TIMESTAMP,自动填充) - `updated_at`:更新时间(TIMESTAMP,自动填充) - `deleted_at`:删除时间(TIMESTAMP,可空,软删除) #### Scenario: 创建充值订单 - **WHEN** 用户(ID 为 2001)发起充值 10000 分(100 元),选择支付宝支付 - **THEN** 系统创建充值记录,生成唯一的 `recharge_no`,`amount` 为 10000,`payment_method` 为 "alipay",`status` 为 1(待支付) #### Scenario: 充值支付完成 - **WHEN** 用户完成支付宝支付 - **THEN** 系统将充值记录状态从 1(待支付)变更为 2(已支付),记录 `paid_at` 时间和 `payment_transaction_id` #### Scenario: 充值到账 - **WHEN** 充值记录状态为 2(已支付),系统处理充值到账 - **THEN** 系统将钱包余额增加 10000 分,创建钱包明细记录,将充值记录状态变更为 3(已完成),记录 `completed_at` 时间 --- ### Requirement: 钱包余额操作 系统 SHALL 支持钱包余额的充值、扣款、退款、冻结、解冻等操作,使用乐观锁防止并发问题。 **操作类型**: - **充值**:增加钱包余额 - **扣款**:减少钱包余额(如购买套餐) - **退款**:增加钱包余额(如订单退款) - **冻结**:将部分余额转为冻结状态(如订单待支付) - **解冻**:将冻结余额转回可用余额(如订单取消) **并发控制**: - 使用 `version` 字段实现乐观锁 - 每次更新余额时,检查 `version` 是否匹配 - 如果 `version` 不匹配,说明有并发更新,操作失败并重试 #### Scenario: 钱包充值 - **WHEN** 用户钱包当前余额为 10000 分,充值 5000 分 - **THEN** 系统将钱包余额更新为 15000 分,`version` 从 1 变更为 2,创建钱包明细记录 #### Scenario: 钱包扣款 - **WHEN** 用户钱包当前余额为 15000 分,购买套餐扣款 3000 分 - **THEN** 系统检查可用余额(15000 - 0 = 15000)≥ 3000,将钱包余额更新为 12000 分,`version` 从 2 变更为 3,创建钱包明细记录 #### Scenario: 余额不足扣款失败 - **WHEN** 用户钱包当前余额为 2000 分,购买套餐需要扣款 3000 分 - **THEN** 系统检查可用余额(2000 - 0 = 2000)< 3000,拒绝扣款,返回错误信息"余额不足" #### Scenario: 并发扣款乐观锁生效 - **WHEN** 用户钱包当前余额为 10000 分,version 为 1,两个并发请求同时扣款 3000 分和 5000 分 - **THEN** 第一个请求成功,余额变为 7000 分,version 变为 2;第二个请求因 version 不匹配失败,需重新读取最新余额(7000 分)后重试 #### Scenario: 冻结余额 - **WHEN** 用户创建订单 10001,订单金额 3000 分,选择钱包支付 - **THEN** 系统将钱包的 `frozen_balance` 增加 3000 分,可用余额减少 3000 分 #### Scenario: 解冻余额 - **WHEN** 用户取消订单 10001,订单金额 3000 分 - **THEN** 系统将钱包的 `frozen_balance` 减少 3000 分,可用余额增加 3000 分 --- ### Requirement: 钱包数据校验 系统 SHALL 对钱包数据进行校验,确保数据完整性和一致性。 **校验规则**: - `user_id`:必填,≥ 1 - `wallet_type`:必填,枚举值 "user" | "agent" - `balance`:必填,≥ 0 - `frozen_balance`:必填,≥ 0,≤ balance - `currency`:必填,长度 1-10 字符 - `status`:必填,枚举值 1-3 - `version`:必填,≥ 0 #### Scenario: 创建钱包时 user_id 无效 - **WHEN** 创建钱包,`user_id` 为 0 - **THEN** 系统拒绝创建,返回错误信息"用户 ID 无效" #### Scenario: 创建钱包时 wallet_type 无效 - **WHEN** 创建钱包,`wallet_type` 为 "invalid" - **THEN** 系统拒绝创建,返回错误信息"钱包类型无效" #### Scenario: 冻结余额超过总余额 - **WHEN** 钱包余额为 10000 分,尝试冻结 15000 分 - **THEN** 系统拒绝操作,返回错误信息"冻结余额不能超过总余额"