# PiPayment integration

`@pisell/private-materials` 只保留 EFTPOS 宿主 UI 与交互适配。Headless SDK 的唯一
生产实现是独立包 `@pisell/pi-payment-sdk`，本包不再保留 SDK 副本。

- `host.ts`：从 `appHelper.utils.getPiPaymentSDK()` 获取宿主管理的 SDK。
- `paymentUI/`：承载统一的 `PiPayment` 分发入口和宿主 SDK 解析能力。
- `paymentUI/eftpos/`：承载 EFTPOS 组件、页面 schema、message resolver 与领域结果适配。

宿主应在一个登录生命周期内返回同一 SDK 实例，退出登录、切换租户或环境时
销毁并清空实例。UI 每次打开支付时都重新调用 getter，不跨登录缓存。

```ts
let piPaymentSDK: PiPaymentSDK | undefined;

appHelper.utils.getPiPaymentSDK = () => {
  if (!piPaymentSDK) {
    piPaymentSDK = new PiPaymentSDK({ environment, getToken, logger });
  }
  return piPaymentSDK;
};
```

## 公开入口

包入口导出 `PiPayment` 及其 UI 契约，以及
`resolvePiPaymentSDK` / `PiPaymentAppHelperUtils`。SDK 契约请直接从
`@pisell/pi-payment-sdk` 导入。

EFTPOS UI 的宿主事件统一为扁平的 `PiPaymentChangeEvent`。业务调用方直接读取
`paymentNumber`、`transactionNumber` 及当前事件的金额或错误字段；完整 SDK
result/error、页面、Session 和 attempt 生命周期不会暴露。不再提供 `event.params`、
`event.snapshot` 或历史 Checkout 命名字段。

每次支付 attempt 创建后，UI 会向宿主发送一次 `processing`；超时打印请求使用
`printOnTimeout`，随后按主动关闭语义发送 `closed` 并释放当前 Session。
Provider 返回的普通小票由 UI 直接派发 `print_string` DeviceTask，不再通过
`printRequested` 交给业务调用方处理。

SCI Provider 的成功小票只读取统一 `payment.receipt`，不回退
`provider_response` 中的厂商旧字段。`receipt` 按 `type` 区分 `merchant`、
`customer`、`signature` 和 `unknown`；当前成功打印忽略 `signature`，其余按
`merchant`、`customer`、`unknown` 的顺序打印。空数组表示没有可用小票。

缺少 getter、getter 返回空值或 SDK 已销毁时，UI 会拒绝启动支付并记录诊断。
`app.getPlugin('piPayment')` 和 `app.piPayment` 不再是支持的解析方式。

## EFTPOS UI 边界

`PiPayment` 通过 imperative ref 保留现有 `open(options)` / `close()` 接入方式。
宿主必须通过 `type={PiPaymentType.Eftpos}` 显式选择 UI 流程；当前只实现 `eftpos`，
未来 online 等支付 UI 将作为新的 `PiPaymentType` 判别分支接入。
内部 `usePiPaymentEftposFlow` 只负责组合展示状态，具体职责分为：

- `flowUtils`：无 React 依赖的首屏、Reader、失败参数和 attempt 参数决策。
- `usePaymentSessionController`：Session 生命周期、支付命令、action lock、任务去重和销毁。
- `useSessionEventHandler`：SDK 事件到页面状态和宿主事件的唯一映射入口。
- `useNetworkFlow`：浏览器网络监听、显式网络状态优先级和断网页恢复。

跨 hook、SDK 回调和公开动作使用稳定函数引用；动态 options、state、context 和 logger
通过当前 render 或 ref 读取，避免重渲染触发重复 Session、Reader 查询或事件监听。

UI 保持以下业务行为：固定金额跳过金额页、单 Reader 自动选择、失败重试、签名和
取消 action、打印/打印超时、人工确认、成功延迟通知、断网恢复以及关闭/卸载销毁。

Adyen POS 的签名属于交易成功后的本地人工复核，不复用普通 `signature` action：

- `processing` 且终端要求签名时继续轮询，并禁用 Abort。
- SDK 收到成功终态后释放请求资源，通过 Provider 中立的 `postSuccessReview` effect
  暂缓 UI 向宿主发送 `success`。
- 接受签名后发送 `success`；拒绝签名需要二次确认，且确认后仍发送 `success`。拒绝
  不会调用后端 action，也不会自动退款。

Checkout 只把实体 `EFTPOS_ADYENPOS` 映射到 PiPayment 的 `adyenpos` Provider。
Adyen Alipay/WeChat Scan 不属于本接入，仍沿用原流程；共享灰度开关关闭时 Adyen POS
也继续回退旧 EFTPOS 流程。

## 文案边界

- SDK 只输出 `PaymentDisplayText`：稳定 message key 或标注来源的动态 literal。
- UI 通过唯一 resolver 依次应用宿主 resolver、宿主 messages、默认 Provider/UI 文案和 fallback。
- 组件和页面不得按 Provider 判断文案，技术诊断信息不得直接展示。
- 定制文案通过 `PiPayment` 的 `messages` 或 `messageResolver` 注入，无需修改 SDK。

## 目录约束

- `PiPayment` 入口测试位于 `paymentUI/__tests__/`，EFTPOS 测试位于
  `paymentUI/eftpos/__tests__/`。
- Headless SDK 不导入 UI；UI 只通过独立包的公开类型和 Session 事件交互。
- UI 不允许深层导入 SDK 的 schema、locales 或 Provider 实现。
- provider 原始 response 和 action payload 在 SDK 内保持不透明，页面只渲染中立 schema。
