Feature Specification: 订单 ezPay 电子发票开立
Feature Branch: 不新建分支(在当前 test 分支开发)
Created: 2026-06-16
Status: Draft
Input: User description: "订单完成后客户要开发票(ezPay 电子发票开票)。基于已完成的 009-ezpay-invoice-onboarding(门店 ezPay 开通管理 + 凭证录入),复用已有 EzPay 开票工具类,在订单完成时为已开通且启用的门店开立电子发票。不创建分支,在当前 test 分支开发。"
User Scenarios & Testing (mandatory)
User Story 1 - 客户在订单完成后申请个人电子发票 (Priority: P1)
客户下单并在订单完成后,可在订单详情发起开票,选择开立个人(B2C)电子发票并填写接收邮箱;系统通过门店已配置的 ezPay 凭证即时开立电子发票,返回发票号码与发票查看凭证,客户可查看/下载。仅「已开通且启用」的门店能为客户开票;免用发票门店不提供开票入口。
Why this priority: 这是「客户能开到发票」的核心价值,也是 ezPay 开通管理(009)真正落地的出口。没有它,门店开通 ezPay 没有任何意义。
Independent Test: 对一笔已完成订单(所属门店已开通且启用 ezPay)申请个人发票 → 系统开立成功 → 订单显示发票号、客户可查看发票。
Acceptance Scenarios:
- Given 订单已完成且所属门店 ezPay 已开通且启用,When 客户在订单详情点「申请发票」、选个人发票并填接收邮箱,Then 系统调 ezPay 即时开立,订单记录发票号与防伪随机码,客户拿到发票查看凭证。
- Given 订单所属门店为「免用发票」或 ezPay 未开通/未启用,When 客户查看订单,Then 不显示开票入口(或提示该门店暂不支持开票)。
- Given 订单已成功开票,When 客户再次查看订单,Then 显示发票号与发票链接,且无法再次申请开票。
User Story 2 - 客户申请公司(B2B)电子发票并填写统一编号 (Priority: P1)
客户可选择开立公司(B2B)发票,填写公司名称与统一编号(统编,8 码);系统校验统编格式后调 ezPay 开立三联式发票,发票上印有买方统编。
Why this priority: 外卖/餐饮场景下 B2B 报账是刚需(统编为标配),与个人开票同为核心开票路径。
Independent Test: 对一笔已完成订单选公司发票、填公司名 + 合法统编 → 开立成功 → 发票记录买方统编。
Acceptance Scenarios:
- Given 订单已完成且门店可开票,When 客户选公司发票并填合法统编(8 码)与公司名,Then 开立成功,发票记录买方统编。
- Given 客户填了格式非法的统编,When 提交申请,Then 校验失败并提示,不调用开票接口。
User Story 3 - 客户选择电子发票载具 (Priority: P2)
客户开立个人发票时,可选择将发票存入载具(手机条码 / 自然人凭证 / ezPay 会员载具);不选则默认以邮箱接收。
Why this priority: 台湾电子发票的载具是常态体验,但并非「开到发票」的必要前提,属体验增强,故 P2。
Independent Test: 选手机条码载具开票 → 发票存入该载具,不另行印发邮箱 PDF。
Acceptance Scenarios:
- Given 客户开个人发票,When 选手机条码载具并填条码,Then 校验条码合法后开立,发票存入该载具。
- Given 客户开个人发票,When 选自然人凭证载具并填凭证,Then 校验后开立,发票存入该载具。
User Story 4 - 商家与平台查看订单发票、失败重试 (Priority: P2)
商家在商家端、运营在平台后台可查看订单的开票状态(未开 / 已开 / 失败 / 作废)与发票号;开票失败的订单可由运营重新触发开票。
Why this priority: 运营与商家需要掌握开票情况、处理失败单,但依赖开票主链路先打通,故 P2。
Independent Test: 一笔开票失败的订单,运营在后台点「重新开票」 → 成功后状态变已开。
Acceptance Scenarios:
- Given 订单曾开票失败,When 运营/商家查看,Then 显示失败状态与失败原因。
- Given 一笔开票失败的订单,When 运营点重新开票,Then 系统再次调 ezPay 开立,成功则更新发票号与状态。
User Story 5 - 商家作废已开立的发票 (Priority: P3)
商家/运营可对已开立的发票发起作废(受台湾法规「奇数月 14 日前可作废前两月发票」限制),作废成功后订单可重新开票。
Why this priority: 作废属售后补救,频率低且有严格时点限制,属辅助功能,故 P3。
Independent Test: 对一张本月开具的发票点作废 → 调 ezPay 作废成功 → 发票状态变作废、订单可重开。
Acceptance Scenarios:
- Given 一张开立且在可作废期限内的发票,When 商家发起作废,Then 系统调 ezPay 作废,发票标记作废。
- Given 已超过可作废期限的发票,When 商家发起作废,Then 提示不可作废并说明原因(以 ezPay 作废接口返回为准)。
Edge Cases
- 同一订单重复申请开票:已成功开票的订单不允许再次开票(除非先作废);需防止并发点击产生重复发票。
- ezPay 开票接口网络异常:不开立、订单标记失败、不产生半成品发票;提示稍后重试。
- ezPay 回应业务错误(金额不平、字轨用尽等):记录错误码与信息,订单标记失败、可重试。
- 客户邮箱/载具填错导致发票无法送达:发票在 ezPay 侧已开立成功,记录发票号,提示客户联系商家;本期不做载体自动更正。
- 订单退款/取消后已开发票:本期不自动作废,由商家按需手动作废(US5)。
- 夜市(userType=3)门店与免用发票门店:不提供开票入口(与 009 一致)。
- 门店 ezPay 被停用(009 toggleEnable 关):开票时识别为不可开票,提示门店发票功能暂停。
Requirements (mandatory)
Functional Requirements
- FR-001: 系统必须能在订单完成后,允许客户为该订单申请电子发票(仅当订单所属门店 ezPay 已开通且启用)。
- FR-002: 系统必须支持客户开立个人(B2C)电子发票,含接收邮箱,并通过 ezPay 即时开立接口完成开立。
- FR-003: 系统必须支持客户开立公司(B2B)电子发票,含买方名称与统一编号(统编,8 码),并在提交前校验统编格式。
- FR-004: 系统必须在开票成功后记录发票号码、防伪随机码等 ezPay 回应信息并关联到订单,供客户与商家查看。
- FR-005: 系统必须避免同一订单重复开票——已成功开票的订单不允许再次开票,除非该发票已作废。
- FR-006: 系统必须在门店为「免用发票」或 ezPay 未开通/未启用时,不向客户提供开票入口。
- FR-007: 开票失败(网络异常或 ezPay 业务错误)时,系统必须标记订单开票失败并记录原因,不改变订单本身状态,并允许后续重新开票。
- FR-008: 系统必须支持客户选择电子发票载具(手机条码 / 自然人凭证 / ezPay 会员载具);不选则以邮箱接收。
- FR-009: 商家端与平台后台必须能查看订单开票状态(未开 / 已开 / 失败 / 作废)与发票号;运营可对失败订单重新开票。
- FR-010: 商家/运营必须能对已开立且在可作废期限内的发票发起作废,作废成功后订单可重新开票。
- FR-011: 系统必须复用已实现的 ezPay 开票工具类(开立 / 查询 / 作废),不在本期改动其内部实现。
- FR-012: 商家端、平台后台新增的面向用户文字必须实现四语言(vi / zh / tw / en)国际化;客户端 UI 的 i18n 由客户端团队另行实现,不在本期。
Key Entities (include if feature involves data)
- 订单发票(新增,与订单 1:1):记录一笔订单的电子发票信息:发票类型(B2C/B2B)、买方名称、买方统编、接收邮箱、载具类型与号码、发票号码、防伪随机码、发票查看凭证、开票状态(未开 / 已开 / 失败 / 作废)、失败原因、申请时间、开立时间、作废时间。
- 订单(已有):开票的来源;订单完成后可发起开票;一笔订单对应一张有效发票(作废后可重开)。
- 门店 ezPay 配置(009 已有):开票时读取门店的 ezPay 凭证(商店代号 / HashKey / HashIV),并判断门店是否可开票(已开通且启用)。
- ezPay 加值服务平台(外部):实际开立 / 作废发票的外部系统,本期通过其开立 / 作废接口与之交互。
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: 客户对一笔已完成订单,能在 1 分钟内、3 次操作内完成发票申请并拿到发票查看凭证。
- SC-002: 门店为「免用发票」或 ezPay 未开通/未启用时,客户 100% 看不到开票入口(无法发起开票)。
- SC-003: 客户填错统编格式时,系统 100% 在调用开票接口前拦截并提示。
- SC-004: 同一订单不会因重复点击或并发申请产生 2 张及以上发票。
- SC-005: 开票失败(网络或业务错误)的订单,客户/运营可在不改动订单状态的前提下重新开票成功。
- SC-006: 客户、商家、运营三方都能在各自入口查看到同一订单的发票号与开票状态,且一致。
Assumptions
- 「订单完成」由现有订单状态机判定(订单到达可开票的终态),本期不改订单状态机;具体终态口径在实现阶段对齐现有逻辑。
- 开票凭证(商店代号 / HashKey / HashIV)由 009 的门店 ezPay 配置提供;仅「已开通(status=2)且启用(enabled=1)」的门店可开票。
- 复用已实现的 ezPay 工具类(EzPay / EzPayConfig / EzPayEncryptUtil),本期不改其内部。
- 餐饮主走 B2C 即时开立(Status=1);B2B 需统编;载具为 P2 体验增强。
- 发票作废受台湾法规时点限制,系统以 ezPay 作废接口返回为准判定是否可作废,不在本地做时限计算。
- 客户端前端 UI(订单详情开票入口、发票查看)不在本期实现范围:本期交付后端开票/查询 API(供客户端调用)+ 管理端(商家/平台后台)查看/重试/作废;客户端 UI 及其 i18n 由客户端团队后续对接。
- 本期不含:发票折让、字轨管理、批次开立、发票中奖通知——这些属后续独立功能。
- 捐赠码(LoveCode)不在本期范围:客户开票仅支持载具或邮箱接收,不做捐赠。
- 发票金额不含运费:运费由用户承担(付给配送方),非商家商品收入;商家发票仅就商品销售开立。订单构成
amount = 商品 − 各项优惠 + 运费,故发票金额 = amount − 运费,各类优惠已含在 amount 内无需逐项区分。
- 四语言 i18n 覆盖所有新增面向用户文字,遵循项目既有 i18n 规范。