# Tmall 退款子项 Attribute 数据解析

## 概述

Tmall 退款子项（refund_item）的 `attribute` 字段是一个编码后的键值对字符串，包含退款单的完整业务上下文：商品信息、金额构成、资金渠道、物流拦截、优惠明细等。

该字段由 `Refund::parseRefundItemAttribute(string $attribute)` 方法解析为结构化数组。

**相关代码路径：**

- 解析器：`app/Platform/Tmall/EntityParse/Refund.php`
- 测试：`test/Cases/Platform/Tmall/EntityParse/RefundAttributeParseTest.php`

---

## 1. 编码格式

### 1.1 原始格式

```
;key1:value1;key2:value2;key3:value3;
```

- 字段之间以 `;`（分号）分隔
- 键与值之间以 `:`（冒号，首个）分隔
- 字符串可能以 `;` 开头或结尾

### 1.2 字符编码规则

由于 `;` 和 `:` 被用作分隔符，**值中出现的原始 `;` 和 `:` 会被转义**：

| 编码 | 解码后 | 说明 |
|------|--------|------|
| `#3B` | `:` (冒号) | 值中的冒号被替换为 #3B |
| `#3A` | `;` (分号) | 值中的分号被替换为 #3A |

> 注意：编码标识与 ASCII 码位相反 —— `#3B` 是 `;` 的 ASCII 码，但在此编码中代表 `:`。这是 Tmall 的特定编码约定。

### 1.3 JSON 转义

attribute 中内嵌的 JSON 字符串（如 `interceptItemListResult`、`threshold_instruction`）使用 `\"` 转义双引号，解析时需先 `stripslashes` 再 `json_decode`。

### 1.4 解码示例

```
原始: bgmtc:2026-02-24 13#3B47#3B41
解码: bgmtc = 2026-02-24 13:47:41

原始: sku:5085821404607|口味#3B橘子味#3A颜色分类#3B维C 250mg
解码: sku = 5085821404607|口味:橘子味;颜色分类:维C 250mg
```

---

## 2. 字段详解

### 2.1 基本信息

| 原始 key | 解析后 key | 类型 | 说明 |
|----------|-----------|------|------|
| `bizCode` | `biz_code` | string | 业务编码。`tmall.hk.refund` = 天猫国际退款，`tmall.refund` = 天猫国内退款 |
| `sdkCode` | `sdk_code` | string | SDK 来源标识。如 `ali.china.tmall.tmallhk` |
| `shop_name` | `shop_name` | string | 店铺名称 |
| `workflowName` | `workflow_name` | string | 工作流名称，通常为 `refund` |
| `opRole` | `op_role` | string | 操作角色。`daemon` = 系统自动处理 |

### 2.2 商品信息

| 原始 key | 解析后 key | 类型 | 说明 |
|----------|-----------|------|------|
| `sku` | `sku` | object | SKU 结构化对象，见下方 SKU 解析 |
| `itemBuyAmount` | `item_buy_amount` | int | 购买数量 |
| `itemPrice` | `item_price` | int | **商品 SKU 单价（分）**，注意这是原始标价，非实际成交价 |
| `leavesCat` | `leaves_cat` | string | 叶子类目 ID |
| `rootCat` | `root_cat` | string | 根类目 ID |
| `isVirtual` | `is_virtual` | bool | 是否虚拟商品 |

**SKU 解析格式：** `SKU_ID|属性名:属性值;属性名:属性值`

```json
{
    "sku_id": "5085821404607",
    "properties": {
        "口味": "橘子味",
        "颜色分类": "维C 250mg"
    }
}
```

### 2.3 退款金额

所有金额单位均为 **分（CNY）**。

| 原始 key | 解析后 key | 类型 | 说明 |
|----------|-----------|------|------|
| `apply_init_refund_fee` | `apply_init_refund_fee` | int | 申请退款金额（消费者发起退款时的金额） |
| `EXmrf` | `ex_max_refund_fee` | int | 最大可退金额 |
| `ol_tf` | `online_refund_fee` | int | 在线退款金额 |
| `refundPostFee` | `refund_post_fee` | int | 退款运费 |
| `mainOrderPostFee` | `main_order_post_fee` | int | 主订单运费 |
| `toSellerFee` | `to_seller_fee` | int | 卖家应付金额 |

### 2.4 退款原因与类型

| 原始 key | 解析后 key | 类型 | 说明 |
|----------|-----------|------|------|
| `apply_reason_text` | `apply_reason_text` | string | 退款原因文本，如 `不喜欢/不想要` |
| `apply_text_id` | `apply_text_id` | string | 退款原因 ID，如 `175001` |
| `disputeRequest` | `dispute_request` | int | 是否有纠纷请求（1=有） |
| `disputeTradeStatus` | `dispute_trade_status` | int | 纠纷交易状态 |
| `DBT` | `dbt` | string | 退款业务类型（Dispute Business Type） |

**DBT 常见值：**

| 值 | 含义 |
|----|------|
| `InterceptUnconsignRefund` | 拦截未发货退款（仓库拦截成功后的退款） |
| `RefundBySeller` | 卖家主动退款 |
| `RefundByBuyer` | 买家申请退款 |

### 2.5 退款特征标识

| 原始 key | 解析后 key | 类型 | 说明 |
|----------|-----------|------|------|
| `clj_zero_second_refund` | `clj_zero_second_refund` | bool | **0秒闪电退款**（关键判定字段，详见第 3 节） |
| `tmgSimpleZeroRefund` | `tmg_simple_zero_refund` | bool | 简易0秒退款 |
| `warehouseRefund` | `warehouse_refund` | bool | 仓库退款 |
| `part_refund` | `part_refund` | bool | 部分退款 |
| `percent_refund` | `percent_refund` | bool | 比例退款 |
| `products` | `products` | string | 退款产品标识。`timeoutrefund^` = 超时退款 |

### 2.6 资金流 (fundFlowInfo)

资金流描述了退款涉及的各支付渠道及金额分配。

**原始格式：** `TYPE^GROUP^NAME^AMOUNT|TYPE^GROUP^NAME^AMOUNT`

解析后为数组：

```json
[
    {
        "type": "CASH",
        "group": "ALIPAY_FUND",
        "name": "支付宝",
        "amount": 15489
    },
    {
        "type": "OTHER_ASSETS",
        "group": "OTHER_ASSETS_GROUP",
        "name": "优惠",
        "amount": 2211
    }
]
```

| 字段 | 说明 |
|------|------|
| `type` | 资金类型。`CASH` = 现金，`OTHER_ASSETS` = 其他资产（优惠券、红包等） |
| `group` | 资金组。`ALIPAY_FUND` = 支付宝，`OTHER_ASSETS_GROUP` = 优惠资产组 |
| `name` | 显示名称 |
| `amount` | 金额（分） |

| 原始 key | 解析后 key | 类型 | 说明 |
|----------|-----------|------|------|
| `payMode` | `pay_mode` | string | 支付方式。如 `alipay` |
| `pay_lock` | `pay_lock` | string | 支付锁定方。`system` = 系统锁定 |

> **注意：** 并非所有退款单都包含 `fundFlowInfo`。缺失时 `fund_flow` 返回空数组 `[]`。

### 2.7 优惠信息

**pmtR 格式：** `TYPE^AMOUNT|TYPE^AMOUNT|`

```json
[
    {
        "type": "TAPP_USERCOUPON_SP",
        "amount": 2211
    }
]
```

| 优惠类型 (type) | 说明 |
|----------------|------|
| `TAPP_USERCOUPON_SP` | 平台优惠券（满减券、消费券等） |

| 原始 key | 解析后 key | 类型 | 说明 |
|----------|-----------|------|------|
| `pmtR` | `promotions` | array | 优惠明细列表 |
| `has_threshold_coupon` | `has_threshold_coupon` | bool | 是否使用门槛优惠券 |
| `threshold_instruction` | `threshold_instruction` | object | 门槛券指令详情（JSON） |

### 2.8 价保信息

**格式：** `START_TIME~END_TIME`

```json
{
    "start": "2026-02-24 13:47:45",
    "end": "2026-03-16 23:59:59"
}
```

表示该订单在此时间范围内享有价格保护，若商品降价可申请差价退款。

### 2.9 物流拦截

| 原始 key | 解析后 key | 类型 | 说明 |
|----------|-----------|------|------|
| `interceptType` | `intercept_type` | string | 拦截类型。`clj` = 菜鸟物流 |
| `interceptStatus` | `intercept_status` | int | 拦截状态。`2` = 拦截完成 |
| `interceptItemListResult` | `intercept_items` | array | 拦截结果明细（JSON），见下方 |
| `abilityTemplateCode` | `ability_template_code` | string | 能力模板。`WAREHOUSE_INTERCEPT_ABILITY^1^2` = 仓库拦截 |
| `abilitySuccessFlag` | `ability_success_flag` | bool | 拦截能力是否执行成功 |
| `sellerOpAbWarehouseIntercept` | `warehouse_intercept_op` | int | 卖家仓库拦截操作状态 |

**拦截结果示例：**

```json
[
    {
        "subBizOrderId": 5081613120736477027,
        "interceptDate": "Feb 24, 2026 1:48:02 PM",
        "logisticInterceptEnum": "INTERCEPT_SUCCESS"
    }
]
```

| logisticInterceptEnum | 含义 |
|----------------------|------|
| `INTERCEPT_SUCCESS` | 拦截成功 |
| `INTERCEPT_FAIL` | 拦截失败 |

### 2.10 卖家 / 买家信息

| 原始 key | 解析后 key | 类型 | 说明 |
|----------|-----------|------|------|
| `seller_batch` | `seller_batch` | bool | 是否批量处理 |
| `seller_audit` | `seller_audit` | int | 卖家审核状态。`0` = 未审核 |
| `hasSellerMemo` | `has_seller_memo` | bool | 是否有卖家备注 |
| `userCredit` | `user_credit` | int | 买家信用等级（1-5，数值越大信用越高） |
| `agreeSource` | `agree_source` | string | 退款同意来源。`clj` = 菜鸟系统自动同意 |

### 2.11 其他标识

| 原始 key | 解析后 key | 类型 | 说明 |
|----------|-----------|------|------|
| `chnlObn` | `channel_sub_order_id` | string | 渠道子订单编号 |
| `lastOrder` | `last_order` | bool | 是否为主单的最后一笔子单 |
| `b2c` | `b2c` | bool | 是否 B2C 交易 |
| `bgmtc` | `bgmtc` | string | 退款后台创建时间 |

---

## 3. 「未发货0秒退」判定规则

### 3.1 业务背景

「未发货0秒退」（也称闪电退款、秒退）是指买家下单后立刻反悔申请退款的场景，常见于：

- 买家下单后立即后悔
- 凑单满减后取消多余订单
- 下单后发现地址、规格错误

**核心问题：** Tmall 对此类订单不会推送原始订单数据到商家系统。因此通过标准订单 API 无法抽取到这些订单，只能通过退款单的 attribute 字段反向发现并重建订单信息。

### 3.2 判定条件

从 `parseRefundItemAttribute` 返回值中判定：

```php
$attr = $parser->parseRefundItemAttribute($attribute);

// 核心判定字段
$isInstantRefund = $attr['clj_zero_second_refund'] === true;
```

**`clj_zero_second_refund`** 是唯一的判定依据。当值为 `true` 时，该退款单对应的订单属于「未发货0秒退」。

### 3.3 辅助佐证字段

以下字段通常与 `clj_zero_second_refund = true` 同时出现，可用于交叉验证：

| 字段 | 典型值 | 含义 |
|------|--------|------|
| `tmg_simple_zero_refund` | `true` | 简易0秒退款标记 |
| `warehouse_refund` | `true` | 仓库退款（货物尚在仓库中） |
| `dbt` | `InterceptUnconsignRefund` | 拦截未发货退款 |
| `intercept_type` | `clj` | 菜鸟物流拦截 |
| `intercept_items[].logisticInterceptEnum` | `INTERCEPT_SUCCESS` | 拦截成功 |
| `agree_source` | `clj` | 菜鸟系统自动同意退款 |
| `op_role` | `daemon` | 系统自动处理（非人工） |
| `products` | `timeoutrefund^` | 超时退款产品标识 |

### 3.4 判定流程图

```
退款子项 refund_item
    │
    ▼
解析 attribute 字段
    │
    ▼
clj_zero_second_refund == true ?
    │
    ├─ YES → 「未发货0秒退」
    │         │
    │         ▼
    │     该退款对应的订单在 orders 表中可能不存在
    │         │
    │         ▼
    │     需要调用 fixInstantRefundOrders() 重建订单
    │
    └─ NO  → 正常退款流程，订单应已存在于 orders 表
```

### 3.5 与 RefundType 的关系

系统 `RefundType` 枚举中 `INSTANT_REFUND_WITHOUT_RETURN (=5)` 对应此场景：

| RefundType | 值 | 说明 | 对应 attribute 判定 |
|-----------|-----|------|-------------------|
| `RETURN_BEFORE_SHIPPING` | 1 | 未发货前退款（常规） | `good_status = BUYER_NOT_RECEIVED` |
| `RETURN_AND_REFUND` | 2 | 退货退款 | `has_good_return = true` |
| `PARTIAL_REFUND` | 3 | 退货后部分退款 | — |
| `REFUND_WITHOUT_RETURN` | 4 | 无须退货的退款 | `good_status = BUYER_RECEIVED` |
| `INSTANT_REFUND_WITHOUT_RETURN` | 5 | 闪电退款（0秒退） | `clj_zero_second_refund = true` |

> 注意：`getRefundTypeId()` 基于退款 API 的 `has_good_return` / `good_status` 字段判定退款类型。而 `clj_zero_second_refund` 来自 attribute 字段，是更细粒度的闪电退款判定。两者配合使用可精确识别退款类型。

---

## 4. 退款金额构成与还原

### 4.1 金额要素关系

退款单中涉及多个金额字段，它们的关系如下：

```
商品 SKU 标价 (item_price)
    │
    ├─ 平台优惠（官方立减等）→ 不在 attribute 中，需从订单获取
    │
    ▼
商品实际成交价（不直接存在于 attribute）
    │
    ├─ 优惠券抵扣 (promotions)
    │
    ▼
用户实际支付金额（现金部分）
```

### 4.2 attribute 中可获取的金额

| 字段 | 含义 | 示例 |
|------|------|------|
| `item_price` | SKU 标价（非成交价） | 39900 (¥399.00) |
| `apply_init_refund_fee` | 退款金额 = 成交价 - 平台优惠 | 17700 (¥177.00) |
| `ex_max_refund_fee` | 最大可退金额（通常 = 退款金额） | 17700 (¥177.00) |
| `online_refund_fee` | 在线退款金额（通常 = 退款金额） | 17700 (¥177.00) |
| `promotions[].amount` | 优惠券金额 | 2211 (¥22.11) |
| `fund_flow[].amount` | 各渠道退回金额明细 | 见下方 |

### 4.3 从 attribute 还原支付金额

以真实样本 A 为例（商品：维C 250mg 橘子味）：

**消费者视角的订单明细（来自消费者端页面）：**

```
商品总价               ¥ 893.00       ← attribute 中不存在
官方立减             - ¥ 224.00       ← attribute 中不存在
红包/优惠券          - ¥  65.89       ← promotions (部分使用)
运费                   ¥   0.00       ← main_order_post_fee
实付款                 ¥ 603.11       ← 需通过 fund_flow 计算
```

**attribute 中可以确定的等式：**

```
退款金额 (apply_init_refund_fee) = 商品成交价 - 平台补贴
                                  = 商品总价 - 官方立减
                          17700分 = ¥177.00

实际退回 = fund_flow 中各渠道金额之和
         = CASH(支付宝) + OTHER_ASSETS(优惠)
         = 15489 + 2211
         = 17700分 = ¥177.00
```

### 4.4 金额还原公式

#### 可直接计算的：

```
退款总额 = apply_init_refund_fee = ex_max_refund_fee = online_refund_fee
         = Σ fund_flow[i].amount  (当 fund_flow 存在时)

用户现金退回 = fund_flow 中 type=CASH 的 amount 之和
优惠资产退回 = fund_flow 中 type=OTHER_ASSETS 的 amount 之和
```

#### 无法直接从 attribute 计算的：

```
商品成交价 = 退款金额 + 平台补贴（官方立减等）
            → 平台补贴不在 attribute 中

用户实际支付 = 退款金额 - 优惠券抵扣
             = apply_init_refund_fee - Σ promotions[i].amount
             = 17700 - 2211
             = 15489分 = ¥154.89

SKU标价与成交价差 = item_price - (退款金额 + 平台补贴)
                   → 无法精确计算，因为平台补贴未知
```

### 4.5 fund_flow 存在时的完整还原

当 `fund_flow` 存在时，可以精确知道各渠道的退款去向：

```
退款金额 ¥177.00
    │
    ├─ CASH / ALIPAY_FUND / 支付宝     → ¥154.89  (退回支付宝余额)
    │
    └─ OTHER_ASSETS / 优惠             → ¥ 22.11  (退回优惠券/红包)

验证: ¥154.89 + ¥22.11 = ¥177.00 ✓
```

### 4.6 fund_flow 缺失时的近似推算

当 `fund_flow` 不存在时（如样本 B），只能用 promotions 近似推算：

```
退款金额 (apply_init_refund_fee)     = 66900 (¥669.00)
优惠券抵扣 (promotions[0].amount)    =  6589 (¥ 65.89)

用户现金退回 ≈ 退款金额 - 优惠券
             = 66900 - 6589
             = 60311 (¥603.11)
```

> 此为近似值。在多种优惠叠加（多张券、跨店满减等）的场景下，promotions 可能不完整。fund_flow 是唯一可靠的渠道拆分数据源。

### 4.7 金额关系总结图

```
┌─────────────────────────────────────────────────┐
│              SKU 标价 (item_price)               │
│                   ¥399.00                        │
├──────────────────┬──────────────────────────────┤
│   平台补贴(未知)  │    商品成交价(未知)            │
│                  │         ¥???                  │
│                  ├──────────────────────────────┤
│                  │ 退款金额 (apply_init_refund_fee)│
│                  │         ¥177.00                │
│                  ├────────────┬─────────────────┤
│                  │ 优惠券退回   │   现金退回        │
│                  │ ¥22.11     │   ¥154.89       │
│                  │ (OTHER_ASSETS) │  (CASH)      │
│                  │ → 退回红包   │  → 退回支付宝    │
└──────────────────┴────────────┴─────────────────┘

可从 attribute 获取: ■  退款金额、优惠券退回、现金退回
无法从 attribute 获取: □  SKU标价→成交价的差额（平台补贴）
```

---

## 5. 完整字段速查表

| # | 原始 key | 解析后 key | 类型 | 分组 |
|---|----------|-----------|------|------|
| 1 | `bizCode` | `biz_code` | string | 基本信息 |
| 2 | `sdkCode` | `sdk_code` | string | 基本信息 |
| 3 | `shop_name` | `shop_name` | string | 基本信息 |
| 4 | `workflowName` | `workflow_name` | string | 基本信息 |
| 5 | `opRole` | `op_role` | string | 基本信息 |
| 6 | `sku` | `sku` | object | 商品信息 |
| 7 | `itemBuyAmount` | `item_buy_amount` | int | 商品信息 |
| 8 | `itemPrice` | `item_price` | int(分) | 商品信息 |
| 9 | `leavesCat` | `leaves_cat` | string | 商品信息 |
| 10 | `rootCat` | `root_cat` | string | 商品信息 |
| 11 | `isVirtual` | `is_virtual` | bool | 商品信息 |
| 12 | `apply_init_refund_fee` | `apply_init_refund_fee` | int(分) | 退款金额 |
| 13 | `EXmrf` | `ex_max_refund_fee` | int(分) | 退款金额 |
| 14 | `ol_tf` | `online_refund_fee` | int(分) | 退款金额 |
| 15 | `refundPostFee` | `refund_post_fee` | int(分) | 退款金额 |
| 16 | `mainOrderPostFee` | `main_order_post_fee` | int(分) | 退款金额 |
| 17 | `toSellerFee` | `to_seller_fee` | int(分) | 退款金额 |
| 18 | `apply_reason_text` | `apply_reason_text` | string | 退款原因 |
| 19 | `apply_text_id` | `apply_text_id` | string | 退款原因 |
| 20 | `disputeRequest` | `dispute_request` | int | 退款原因 |
| 21 | `disputeTradeStatus` | `dispute_trade_status` | int | 退款原因 |
| 22 | `DBT` | `dbt` | string | 退款原因 |
| 23 | `clj_zero_second_refund` | `clj_zero_second_refund` | bool | 退款标识 |
| 24 | `tmgSimpleZeroRefund` | `tmg_simple_zero_refund` | bool | 退款标识 |
| 25 | `warehouseRefund` | `warehouse_refund` | bool | 退款标识 |
| 26 | `part_refund` | `part_refund` | bool | 退款标识 |
| 27 | `percent_refund` | `percent_refund` | bool | 退款标识 |
| 28 | `products` | `products` | string | 退款标识 |
| 29 | `fundFlowInfo` | `fund_flow` | array | 资金流 |
| 30 | `payMode` | `pay_mode` | string | 资金流 |
| 31 | `pay_lock` | `pay_lock` | string | 资金流 |
| 32 | `pmtR` | `promotions` | array | 优惠信息 |
| 33 | `has_threshold_coupon` | `has_threshold_coupon` | bool | 优惠信息 |
| 34 | `threshold_instruction` | `threshold_instruction` | object | 优惠信息 |
| 35 | `price_protection` | `price_protection` | object | 价保 |
| 36 | `interceptType` | `intercept_type` | string | 物流拦截 |
| 37 | `interceptStatus` | `intercept_status` | int | 物流拦截 |
| 38 | `interceptItemListResult` | `intercept_items` | array | 物流拦截 |
| 39 | `abilityTemplateCode` | `ability_template_code` | string | 物流拦截 |
| 40 | `abilitySuccessFlag` | `ability_success_flag` | bool | 物流拦截 |
| 41 | `sellerOpAbWarehouseIntercept` | `warehouse_intercept_op` | int | 物流拦截 |
| 42 | `seller_batch` | `seller_batch` | bool | 卖家/买家 |
| 43 | `seller_audit` | `seller_audit` | int | 卖家/买家 |
| 44 | `hasSellerMemo` | `has_seller_memo` | bool | 卖家/买家 |
| 45 | `userCredit` | `user_credit` | int | 卖家/买家 |
| 46 | `agreeSource` | `agree_source` | string | 卖家/买家 |
| 47 | `chnlObn` | `channel_sub_order_id` | string | 其他 |
| 48 | `lastOrder` | `last_order` | bool | 其他 |
| 49 | `b2c` | `b2c` | bool | 其他 |
| 50 | `bgmtc` | `bgmtc` | string | 其他 |

---

## 6. 未解析的原始字段

以下字段存在于 attribute 中但未纳入结构化解析（多为 Tmall 内部标识，业务价值较低）：

| key | 说明 |
|-----|------|
| `sgr` | 内部标识 |
| `nrp` | 新退款流程标识 |
| `rp3` | 退款 3.0 标识 |
| `sars` | 售后服务标识 (`skip` = 跳过) |
| `tos` | 超时策略 |
| `tod` | 超时时长（ms），如 `86400000` = 24小时 |
| `fps` | 内部标识 |
| `newUltron` | Ultron 版本 |
| `r_r_l` | 退款原因层级 |
| `newRefund` | 退款版本（如 `rp2`） |
| `appName` | 应用名（如 `rtee`） |
| `_F_tlmType` | 天猫类型（如 `B_taobao`） |
| `_F_uplus` | UPlus 标识 |
| `enfunddetail` | 是否启用资金明细 |
| `agrExctT` | 退款同意精确时间戳（ms） |
| `agrExctd` | 退款同意是否已执行 |
| `sellerFlag` | 卖家标记 |
| `jibuBizCode` | 极步业务编码（如 `tmall.jibu`） |
| `sync` | 同步标识 |
| `cPartRefund` | C端部分退款标识 |

如有业务需要，可在 `parseRefundItemAttribute()` 中追加解析。
