Files
datahub/docs/Tmall退款子项Attribute数据解析.md
2026-02-25 15:29:20 +08:00

558 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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()` 中追加解析。