Skip to content

Adapter: Alipay Bill — 支付宝账单 Adapter

状态:v0.1 设计稿(2026-05-19)。Phase 6 待启(紧随 Phase 5 EmailAdapter)。

关联:父文档 Personal_Data_Hub_Architecture.md §12 Phase 6;姐妹文档 Adapter_Email_IMAP.md(Phase 5 前置)

目标 appcom.eg.android.AlipayGphone(Redmi 24115RA8EC 真机已装确认 ✅)

为什么 Alipay 紧随 Email:Email 已可拉到部分银行账单 + 订单确认,但支付宝内部转账 / 余额宝 / 公积金缴费 / 共享单车 / 公交地铁 / 缴费记录这些"封闭场景"邮箱看不到。Alipay 官方导出 CSV 是唯一干净结构化全量源。3 天工期,验证 UnifiedSchema 对结构化金融数据的拟合度。


1. 背景

1.1 支付宝在中国用户金融图谱的地位

场景仅支付宝有与其它源重叠
余额宝 / 理财收支
公积金 / 社保 缴费记录个税 APP 有部分
公交 / 地铁 / 共享单车高德有部分(出行)
信用卡还款部分银行账单邮件 / 银行 app
转账给好友微信支付部分(人际不同)
淘宝 / 天猫 支付电商 cookie 抓订单(金额对应)
美团 / 饿了么 支付美团 cookie 抓订单
水电气 缴费
医保 支付
政务 缴费(停车 / 罚款)
红包 / 转账给陌生人

结论:邮箱补外部世界发给你的账单 / 凭证;Alipay 补你自己付款的所有微观流水。两者拼起来 = 接近完整的"我在哪花了多少钱"档案。

维度官方 CSV 导出Cookie + web API
完整性服务器侧全量(可选最长 1 年/月)受限于 cookie 范围
稳定性协议不变API 频繁变动
风控0 触发频繁拉触发
工程解析 CSV ~50 行维护 cookie + 接口签名
自动化半自动(用户每月点导出)全自动

Trade-off:CSV 需要用户手动触发导出(每月或每季度),但稳定 + 0 风险。v1 选 CSV 模式;v2 可选加 cookie 模式做"自动每周同步近期"。


2. 目标 & 非目标

2.1 目标 (v1 in scope)

#验收
G1CSV 文件导入流程 + 自动检测 + 字段映射E2E:导入 1 个 12 月份账单 CSV → LocalVault
G2ZIP 加密附件解压(支付宝 CSV 默认 ZIP + 6 位密码)解压成功率 ≥ 99%
G3字段完整映射到 UnifiedSchema Event单测 fixture ≥ 50 条 → 全部字段非空
G4交易对方实体识别(Person.subtype=merchant / contact)商家 / 个人 二分准确 ≥ 95%
G5交易分类(消费/转账/理财/缴费/退款)→ Event.subtype准确 ≥ 90%
G6商家 → Item 映射(淘宝订单 → Item with merchant)单测
G7AI 分析接通:"上个月总支出多少" / "在淘宝总支出" / "每月平均餐饮多少"答案准
G8与 EmailAdapter 跨源去重(信用卡还款同时出现在两源)自动识别 + 标 cross-source-link
G9增量导入(同一账户多次导入不重)originalId 去重

2.2 非目标 (defer)

  • Alipay 内置自动同步 — v1 用户手动每月导出导入;v2 考虑 cookie 模式自动
  • Alipay 健康步数 / 运动数据 — v2 接(CSV 不含此)
  • Alipay 蚂蚁森林 / 公益 — v3 趣味数据
  • Alipay 信用 / 芝麻分 — v3
  • 跨境支付 / 多币种 — v2
  • 修改 / 删除导入的交易 — 中台只追加,不改源数据;用户可"标记忽略"
  • Alipay 内嵌小程序数据 — 不接入(属于第三方)

3. Open Questions

OQ-1:CSV 文件入口

A:用户手动拖文件到 ChainlessChain UI B:监测下载目录自动检测 alipay_record_*.zipC:UI 引导 + 二选一(A 和 B 共存)

推荐 C。理由:(1) A 100% 可控但麻烦;(2) B 自动检测体验好但隐私敏感(中台扫下载目录);(3) C 让用户在 UI"添加账单"时选"拖入 / 从下载目录选 / 监控"三模式。监控模式必须用户显式开启仅监控特定目录

OQ-2:ZIP 密码处理

A:每次导入弹窗输密码 B:用户首次配密码后 Keystore 存(支付宝密码通常是身份证后 6 位,多年不变) C:A + B 共存(首次配置可选记住)

推荐 C。理由:(1) 密码与 vault 主密钥独立性弱(同设备一旦失窃都泄);(2) 但每次输密码体验差;(3) C 让用户选;默认记住的话明文密码用 Keystore 加密。

OQ-3:交易对方实体识别策略

A:纯字符串匹配(同名合并) B:LLM 判定 + 用户确认(merchant vs contact 二分) C:规则白名单(known merchants)+ 不在名单走 LLM D:复用父文档 EntityResolver

推荐 D。理由:(1) 父文档 Phase 8 EntityResolver 已规划三段 pipeline;(2) Alipay 是 Phase 6 在 EntityResolver Phase 8 之前,Phase 6 用 D 的简化版:规则白名单("美团"/"淘宝"/"携程"等已知商家直标 merchant)+ 默认其它走 contact + 标记 needs-resolve 字段,待 Phase 8 EntityResolver 上线后批量重算。

OQ-4:交易子分类映射

A:直接用 Alipay 的 类型 字段("即时到账"/"商品消费"等) B:重新分类到 UnifiedSchema Event.subtype + Topic C:A + B 双轨(保留源类型,加 hub 派生分类)

推荐 C。理由:(1) Alipay 的"类型"字段对中台 KG 不直观;(2) 但完全丢源信息后用户疑问"为啥被分这类"无法追溯;(3) C:Event.subtype 用 hub 标准(payment/transfer/refund/...),Event.extra.alipay_type 保留源。

OQ-5:CSV 编码

A:固定按 GB2312 / GBK 读 B:先尝 UTF-8 失败降级 GB18030 C:用 chardet 检测

推荐 B。理由:(1) 支付宝 CSV 历史用 GBK,新版本部分用 UTF-8 BOM;(2) chardet 引依赖 + 对小文件不准;(3) 简单二段尝试覆盖 99% 情况。


4. 数据源分析

4.1 导出流程

支付宝 app → 我的 → 账单 → 右上角 ⋯ → 开具交易流水证明

选择月份范围(最长 12 个月)→ 选择用途"个人对账"

发送到邮箱(用户支付宝绑定邮箱)

邮箱收附件 alipay_record_YYYYMMDD-YYYYMMDD.zip
  密码 = 用户身份证后 6 位(默认)

注意:支付宝有时给"PDF 流水"和"CSV 账单"两种导出。本 adapter 要 CSV(结构化)。

4.2 CSV 格式样例

文件头(GBK 编码,CRLF):

支付宝交易记录明细查询
账号:[email@example.com / 13800001111]
起始日期:[2024-04-01 00:00:00]    终止日期:[2024-05-01 00:00:00]
---------------------------------交易记录明细列表------------------------------
交易号,商家订单号,交易创建时间,付款时间,最近修改时间,交易来源地,类型,交易对方,商品名称,金额(元),收/支,交易状态,服务费(元),成功退款(元),备注,资金状态
2024040122001112345678,T20240401XXXX,2024-04-01 09:23:11,2024-04-01 09:23:13,2024-04-01 09:23:13,支付宝网站,即时到账交易,美团,美团外卖订单,38.50,支出,交易成功,0.00,0.00,,已支出
...

4.3 字段语义

CSV 字段含义UnifiedSchema 目标
交易号Alipay 唯一交易号source.originalId
商家订单号商家侧订单号(淘宝订单 / 美团订单等)extra.merchantOrderNumber,与电商 adapter 跨源 link
交易创建时间创建时间extra.createdAt
付款时间实际付款Event.occurredAt
最近修改时间状态变化extra.lastModifiedAt
交易来源地"支付宝网站" / "手机网站" / "客户端" / 小程序名extra.sourceChannel
类型"即时到账"/"商品消费"/"在线支付"/"红包"/"转账"...extra.alipayType + 映射 Event.subtype
交易对方商家名 / 用户名Event.actor or Event.participants[] 派生为 Person
商品名称商品 / 描述Item.name(若有)or Event.content.title
金额(元)金额Event.content.amount.value
收/支"收入"/"支出"/"其它"Event.content.amount.direction
交易状态"交易成功"/"交易关闭"/"退款成功"/"等待付款"extra.txStatus + 过滤未完成
服务费(元)服务费extra.serviceFee
成功退款(元)已退款extra.refundedAmount
备注用户填的备注Event.content.text
资金状态"已支出"/"已收入"/"冻结"/"解冻"extra.fundStatus

4.4 子类型映射

typescript
function mapAlipayTypeToSubtype(alipayType: string, direction: string): EventSubtype {
  if (alipayType.includes("转账")) return "transfer";
  if (alipayType.includes("退款")) return "refund";
  if (alipayType.includes("理财") || alipayType.includes("余额宝")) return "investment";
  if (alipayType.includes("红包")) return "redenvelope";
  if (alipayType.includes("缴费")) return "utility";
  if (alipayType.includes("交易关闭") || alipayType.includes("交易失败")) return "cancelled";
  // 默认按方向
  return direction === "收入" ? "income" : "payment";
}

5. Adapter 实现

5.1 类结构

typescript
class AlipayBillAdapter implements PersonalDataAdapter {
  name = "alipay-bill";
  version = "0.1.0";
  capabilities = ["import:csv-zip", "parse:transactions"];

  constructor(private account: AlipayAccount) {}

  // 鉴权 = 提供 zip 解压密码(用户身份证后 6 位等)
  async authenticate(ctx: AuthContext): Promise<AuthResult>;

  // 不是 IMAP 那种 server pull,而是接收用户提供的文件路径
  async *sync(opts: SyncOptions & { csvPath: string }): AsyncIterable<RawTransaction>;

  normalize(raw: RawTransaction): NormalizedBatch;
  async healthCheck(): Promise<HealthStatus>;

  rateLimits = {};  // 本地文件,无限流
  dataDisclosure = {
    fields: [
      "alipay:txId,createdAt,paidAt,counterparty,itemName,amount,direction,status,note",
    ],
    sensitivity: "high",
  };
}

interface AlipayAccount {
  email: string;          // 支付宝绑定邮箱(用于识别多账户)
  zipPassword?: string;   // Keystore 加密存储,OQ-2 推荐 C
}

5.2 导入流程

用户拖入 alipay_record_*.zip

1. 检测文件名格式 → 提取日期范围
2. 解压:
   - 输 password(用户配或弹窗)
   - 失败 → 提示重试 / 切换 password
3. 找内部 .csv 文件
4. 读取 + 编码降级(OQ-5:UTF-8 → GBK fallback)
5. 解析头部 metadata(账号 / 起止日期)
6. 跳过文件头注释行,定位到 "交易号,商家订单号,..." header 行
7. 逐行 parse RawTransaction
8. yield RawTransaction

ingestor 调 normalize() → NormalizedBatch → LocalVault → KG ingestor

5.3 RawTransaction 结构

typescript
interface RawTransaction {
  txId: string;
  merchantOrderNumber?: string;
  createdAt: string;       // 原 string,normalize 时 parse
  paidAt: string;
  lastModifiedAt: string;
  sourceChannel: string;
  alipayType: string;
  counterparty: string;
  itemName: string;
  amount: string;          // "38.50"
  direction: "收入" | "支出" | "其它";
  status: string;
  serviceFee: string;
  refundedAmount: string;
  note: string;
  fundStatus: string;
}

5.4 normalize() 映射

typescript
function normalize(raw: RawTransaction): NormalizedBatch {
  const eventId = uuidv7();
  const counterpartyPersonId = lookupOrCreatePerson(raw.counterparty);

  const event: Event = {
    id: eventId,
    type: "event",
    subtype: mapAlipayTypeToSubtype(raw.alipayType, raw.direction),
    occurredAt: parseDateTime(raw.paidAt || raw.createdAt),
    actor: raw.direction === "支出" ? "person-self" : counterpartyPersonId,
    participants: [counterpartyPersonId, "person-self"],
    content: {
      title: raw.itemName || raw.alipayType,
      text: raw.note || undefined,
      amount: {
        value: parseFloat(raw.amount),
        currency: "CNY",
        direction: raw.direction === "收入" ? "in" : "out",
      },
    },
    source: {
      adapter: "alipay-bill",
      adapterVersion: "0.1.0",
      originalId: raw.txId,
      capturedAt: parseDateTime(raw.paidAt || raw.createdAt),
      capturedBy: "export",
    },
    extra: {
      alipayType: raw.alipayType,
      sourceChannel: raw.sourceChannel,
      merchantOrderNumber: raw.merchantOrderNumber,
      txStatus: raw.status,
      serviceFee: parseFloat(raw.serviceFee || "0"),
      refundedAmount: parseFloat(raw.refundedAmount || "0"),
      fundStatus: raw.fundStatus,
    },
  };

  const persons: Person[] = [{
    id: counterpartyPersonId,
    type: "person",
    subtype: classifyCounterparty(raw.counterparty),  // "merchant" or "contact"
    names: [raw.counterparty],
    // EntityResolver Phase 8 后回填 identifiers
  }];

  // 若有 itemName → 派生 Item
  const items: Item[] = raw.itemName ? [{
    id: uuidv7(),
    type: "item",
    subtype: "product",
    name: raw.itemName,
    price: { value: parseFloat(raw.amount), currency: "CNY" },
    merchant: counterpartyPersonId,
  }] : [];

  return { events: [event], persons, places: [], items };
}

5.5 已知商家白名单(Phase 6 简化 resolver)

typescript
const KNOWN_MERCHANTS = new Set([
  // 电商
  "淘宝", "天猫", "京东", "拼多多", "苏宁易购", "唯品会", "蘑菇街",
  // 外卖 / 团购
  "美团", "饿了么", "大众点评", "肯德基", "麦当劳", "星巴克",
  // 出行
  "滴滴", "高德", "百度地图", "12306", "携程", "去哪儿", "同程",
  // 视频音乐
  "爱奇艺", "腾讯视频", "优酷", "网易云音乐", "QQ 音乐", "酷狗",
  // 缴费
  "国家电网", "中国移动", "中国联通", "中国电信", "燃气公司", "水务局",
  // 平台
  "支付宝", "蚂蚁财富", "余额宝",
  // ... 维护增长清单(v1 ~200 项已足覆盖 80%)
]);

function classifyCounterparty(name: string): "merchant" | "contact" | "unknown" {
  for (const m of KNOWN_MERCHANTS) {
    if (name.includes(m)) return "merchant";
  }
  // 含 "公司" / "店" / "服务" → merchant
  if (/公司||服务|超市|餐厅|药房|医院|银行/.test(name)) return "merchant";
  // 看起来像人名(2-4 中文字符) → contact
  if (/^[一-龥]{2,4}$/.test(name)) return "contact";
  return "unknown";
}

6. 与 EmailAdapter 跨源去重

6.1 重叠场景

Alipay 看到Email 同时看到处理
信用卡还款(如"招行信用卡还款")招行月度账单邮件两边都存;KG 加 same-as
淘宝订单付款淘宝订单确认邮件同上;merchantOrderNumber 是 link key
12306 车票12306 出票邮件同上

6.2 实现

typescript
// AlipayBill normalize 时,extra.merchantOrderNumber 写入
// Email normalize 时,category=order 的邮件 extra.orderNumber 写入
// KG ingestor 后处理:
//   FIND events WHERE extra.merchantOrderNumber EXISTS
//   FOR EACH (alipayEvt, emailEvt) WHERE
//     alipayEvt.extra.merchantOrderNumber == emailEvt.extra.orderNumber
//   → ADD KG edge: (alipayEvt) --same-as--> (emailEvt)
//   → 标记 cross_source_link = true

分析层用法:用户问"我在淘宝今年总共下了多少单" — Cowork agent 检测到 Email + Alipay 双源后用 union,避免双计。


7. AI 分析价值

7.1 Alipay 接入后立即可问

问题数据路径
"上个月总支出多少?"sum(amount) where direction=out, 时间窗
"上个月外卖花了多少?"counterparty includes 美团/饿了么
"今年在淘宝总共下了多少单?"counterparty=淘宝 + 与 Email 跨源
"我每月共享单车花多少?"counterparty includes 哈啰/青桔/美团单车
"上个月给我妈转了多少钱?"EntityResolver 后 counterparty=mom
"我余额宝总收益?"alipayType includes 余额宝 + direction=in
"公积金这个月扣多少?"counterparty=公积金 + 缴费
"我每月平均餐饮支出?"classify 商家 → 餐饮类 → 月均
"最大一笔支出?"sort amount desc
"退过哪些款?"subtype=refund

7.2 跨源(Alipay + Email + 其它)

  • 完整消费档案:Alipay 流水 + 信用卡账单 + 现金(无法采集,仅注入估算)= 月度真实开销
  • 送礼追踪:Alipay "转账给某某" + Taobao "收件人某某" + Email 物流到某某 = 送给同一人的整条故事线
  • 出行账本:Alipay 出行支出 + 高德足迹 + 12306 车票 + 携程订单 = 一次旅行的所有钱去哪了

8. UI/UX

8.1 配置 / 导入流程

1. 设置 → 个人数据中台 → 添加支付宝账户
2. 输支付宝绑定邮箱(标识账户)+ ZIP 密码(OQ-2:选记住)
3. UI 引导:
   "去支付宝 → 我的 → 账单 → 右上角 ⋯ → 开具交易流水证明"
   "选月份范围 → 选'个人对账'用途 → 发送到邮箱"
   "下载 ZIP 后拖入这里"
4. 用户拖文件 → 解压 → 解析 → 进度条
5. 解析完成报告:
   - 共 N 笔
   - 支出 ¥X / 收入 ¥Y
   - Top 5 商家
   - 跨源 link 数:M
   - [确认导入 / 取消]

8.2 周期性提醒

可选开关:"每月 1 号提醒导出上月账单"。Workflow 定时任务发本机通知。

8.3 多账户视图

支持用户多个 Alipay 账户(个人 + 家人 ?),每个独立 vault tag,分析查询可选 scope。


9. 安全 & 隐私

9.1 ZIP 密码存储

  • Keystore 加密(与 LocalVault 独立 key namespace)
  • 用户可选"每次输"模式(不记住)

9.2 CSV 文件处理

  • 解析后 CSV 文件本身默认删除(用户可选"保留到归档目录")
  • ZIP 文件同上
  • 减少未加密的源文件暴露面

9.3 备注字段敏感

  • 用户备注可能含个人信息("借给阿姨"/"过年给爸妈")
  • 默认存 vault(已加密)
  • 用户可设"备注脱敏"开关:导入时 LLM 抽象化("给亲属生活费" 替代原文)

9.4 审计

  • 每次导入:文件 hash + 导入时间 + 行数 + 错误数 → audit_log

10. 测试计划

10.1 单测

  • CSV 解析:50 条 fixture(含中文/特殊字符/空字段/转义)
  • 编码降级(UTF-8 → GBK)
  • ZIP 解压(正确密码 / 错误密码 / 损坏文件)
  • alipayType → subtype 映射 30 条
  • classifyCounterparty 100 条(含 known 商家 / 含"公司"等关键词 / 中文人名 / 模糊)
  • normalize() 输出 schema 校验
  • 跨源 merchantOrderNumber link 后处理 10 条

10.2 集成测试

  • 端到端:fixture ZIP → 解压 → 解析 → ingest LocalVault → KG triple 完整
  • 重复导入同 ZIP → 0 重复事件(originalId 去重)
  • 与 EmailAdapter 同时跑:cross_source_link 标记正确

10.3 真机 E2E

场景验收
真实账单 12 个月 ZIP 导入≥ 99% 行解析成功;错误行进 review 队列
不同年份 CSV 编码差异UTF-8 / GBK 都能处理
密码错误友好提示重试
大量交易性能10k 笔交易 < 30s 入库
自然语言查询§7.1 10 类问题全通
跨源验证"我在淘宝今年总共下了多少单"答案与 Email 单算 / Alipay 单算 一致

11. Phase 拆分(adapter 内部)

Sub-phase内容工期
6.1ZIP 解压 + 密码处理 + CSV 编码降级0.5d
6.2CSV 解析 + RawTransaction + 头部 metadata0.5d
6.3normalize() + classifyCounterparty + 商家白名单1d
6.4跨源 merchantOrderNumber link 后处理0.5d
6.5UI 导入流程 + 进度条 + 报告0.5d
6.6E2E 真机验 + 性能0d(与父文档 Phase 6 工期 3d 一致)

:~3 天。


12. Traps & 风险

#Trap描述缓解
T1CSV 表头随版本变化字段顺序 / 命名 微调parser 按字段名(不按下标)+ 兼容旧名映射表
T2备注字段含逗号破坏 CSV 解析使用真正 CSV parser(papaparse / csv-parse)不要手切 split
T3多种 ZIP 密码(身份证 / 手机 / 老密码)用户记不清OQ-2 候选列表轮询尝试
T4类型字段不在白名单新型交易(数字人民币?)→ subtype=unknownunknown 计数 → UI alert → 人工补映射
T5同一交易号 update(最近修改时间变)重复导入旧 ZIP 看到已 refund 的交易originalId 去重时检查 lastModifiedAt → 更新现有 event
T6商家名拼写不一致"美团外卖" vs "美团点评" vs "Meituan"KNOWN_MERCHANTS 用 includes + alias map;Phase 8 EntityResolver 后端规范化
T7退款占比异常用户淘宝大量退货 → spending 统计偏高normalize 时计 effectiveAmount = amount - refundedAmount,分析层用 effective
T8交易关闭 / 失败状态包进 spending 总额错status filter:分析层默认排除"交易关闭"
T9跨年导入边界12 月底 ZIP 含跨年事件按 occurredAt 真实时间分桶,与 ZIP 文件名无关
T10用户多账户 collision两个支付宝账户都用同一商家account 字段 + originalId 复合 key 去重
T11EntityResolver 不在时商家漏识白名单不全 → unknown 太多Phase 8 EntityResolver 上线后批量 reprocess
T12用户身份证后 6 位泄漏中台明文存(Keystore 已加密但仍存)UI 警示 + 不写日志 + 仅密码尝试时解密临时使用
T13备注里有他人 PII用户备注"借给王二妈手机 138-xxxx" → 二人手机号泄漏OQ:备注脱敏开关;audit log 记录所有读备注的查询

13. 演进路线

v1(本设计稿)

  • CSV 导出导入
  • 商家白名单分类
  • 跨 Email 去重

v2

  • Cookie 模式自动同步(接 Alipay web)
  • 蚂蚁财富 / 余额宝 收益曲线
  • 健康步数

v3

  • 蚂蚁森林(趣味)
  • 芝麻信用
  • 跨境(多币种)

14. 参考

基于 MIT 许可发布