# OMG 支付方案讨论记录

## 记录信息

- 会话 ID：`019fe942-4fb3-7701-b8d6-70f141f7774d`
- 讨论日期：2026-08-10
- 讨论主题：OMG 多店铺、夜市摊位、平台商模式、LINE Pay 扩展及店铺端收款设置
- 名称说明：早期讨论中多次写作 `OGM`，实际支付渠道名称为 `OMG`

## 当前最终决策

经过完整讨论后，当前阶段采用最简单的店铺级支付配置方案：

1. Foodie 平台不代收、不分账，款项由支付渠道直接结算给商户。
2. 支付设置直接放在店铺管理中，不额外抽象通用的支付账户和店铺绑定体系。
3. OMG 继续使用现有店铺级配置表 `pos_store_omg`；以后接入 LINE Pay 时，可增加店铺级的 `pos_store_linepay`。
4. 暂不实现 `PlatformID`、平台商、子商户、平台结算主体及二次结算逻辑。
5. 多店铺场景较少，不为少量场景预先增加复杂架构。
6. 如果少量多店铺需要共用同一个 OMG 商户：各店保存相同的 `MerchantID / HashKey / HashIV`，使用不同的 OMG `StoreID`；回调通过 `MerchantTradeNo` 定位订单和店铺。
7. `HashKey / HashIV / Channel Secret` 等敏感凭证必须加密保存，管理页面只显示掩码。

当前简化关系：

```text
店铺
  └─ 店铺收款设置
       ├─ OMG 配置
       └─ LINE Pay 配置（后续）

支付渠道
  └─ 款项直接结算给商户

Foodie
  ├─ 发起支付
  ├─ 校验支付回调
  ├─ 更新订单状态
  └─ 提供查询、退款等技术功能
```

---

## 第一轮：一个商户有多个店铺时，OMG 如何区分

### 用户问题

> ogm 支付不能再后台自己管理店铺新增多个店铺吗？我们的系统现在一个商户可能会有多个店铺，ogm怎么区分

### 分析过程

- 检查项目现有 OMG 支付实现和数据库设计。
- 确认当前代码按 `pos_store.id` 读取 OMG 凭证。
- 确认当前回调按 OMG `MerchantID` 反查店铺。
- 核对 OMG 是否允许同一 `MerchantID` 下使用多个门店标识。

### 结论

OMG 提供 `StoreID` 区分同一商户旗下的多个店铺：

- `MerchantID`：OMG 特店编号，代表收款商户或结算主体。
- `StoreID`：该特店旗下的分店代号，最长 20 位，只能使用英文字母和数字。
- 支付成功回调会原样返回 `StoreID`，可用于核对订单所属门店。

官方文档：

- [OMG 产生订单文档](https://developers-stage.omg.com.tw/payment/aio/api/01_order.html)
- [OMG 付款通知文档](https://developers-stage.omg.com.tw/payment/aio/api/03_payment_notify.html)

示例：

```text
商户 user_id = 88
OMG MerchantID = 2000132

门店 1001 → StoreID = S1001
门店 1002 → StoreID = S1002
门店 1003 → StoreID = S1003
```

发起门店 1002 的订单时：

```text
MerchantID=2000132
StoreID=S1002
MerchantTradeNo=OMGxxxxxxxxxxxxxxxxx
```

当时发现的当前实现问题：

- 下单只传 `MerchantID`，没有传 `StoreID`。
- `pos_store_omg` 按门店保存整套凭证。
- `merchant_id` 设置了唯一索引。
- 回调直接使用 `MerchantID` 反查唯一门店。

因此，当时的实现实际上强制“一店一个 MerchantID”。如果同一商户的多个门店共用一个 `MerchantID`，第二个门店会遇到唯一索引冲突。

当时建议的完整模型是“一商户一套 OMG 凭证，多门店各自使用一个 StoreID”；最终方案后来根据业务规模进一步简化为店铺级配置。

---

## 第二轮：StoreID 是否需要在 OMG 后台管理

### 用户问题

> 但是omg 后台管理没有店铺管理，传人storeId是可以自定义吗？还是需要在omg 那边管理？

### 分析过程

- 核对 OMG 官方文档中 `StoreID` 的填写与回传规则。
- 重点确认是否需要预先在 OMG 后台登记门店。
- 区分“交易归属标识”与“独立结算账户”。

### 结论

`StoreID` 可以由 Foodie 自行定义并随订单传入，不需要先在 OMG 后台新增门店。

OMG 对 `StoreID` 的限制：

- 最长 20 位。
- 只能使用大小写英文字母和数字。
- 付款回调会原样返回 `StoreID`。

官方文档没有说明需要预先登记，也没有提供新增门店 API。

建议生成方式：

```text
pos_store.id = 123
OMG StoreID = S123
```

发起支付：

```text
MerchantID=2000132
StoreID=S123
MerchantTradeNo=OMGxxxxxxxxxxxxxxxxx
```

需要注意：`StoreID` 只是交易归属标识，不是 OMG 的独立子商户账户。使用同一个 OMG 商户账户的所有店铺仍然：

- 共用 `MerchantID / HashKey / HashIV`。
- 共用结算账户和费率。
- 在 OMG 层属于同一个特店。

如果要求每个门店分别结算或绑定不同银行账户，则需要各自申请 `MerchantID`，不能只依赖 `StoreID`。

OMG 后台是否支持按 `StoreID` 筛选或导出报表，公开文档没有保证，需要向 OMG 商务确认；但支付接口本身支持使用 `StoreID` 区分门店。

---

## 第三轮：平台商和子商户模式是什么

### 用户问题

> 平台商/子商户模式又是什么

### 分析过程

- 按 OMG 对 `PlatformID` 的定义，区分平台商模式和普通的 `MerchantID + StoreID` 模式。
- 讨论代收、结算、商户审核和平台责任。
- 确认不能只根据接口字段推断平台是否具有代收或分账权限。

### 结论

平台商/子商户模式面向 Foodie 这种聚合多个独立商家的平台，不等同于同一商户的多门店。

可以理解为三级：

| 层级 | OMG 字段 | 示例 |
|---|---|---|
| Foodie 平台 | `PlatformID` | Foodie |
| 独立餐饮商户 | `MerchantID` | 王记餐饮公司 |
| 商户旗下门店 | `StoreID` | 台北店、桃园店 |

支付请求示例：

```text
PlatformID = Foodie 的平台商编号
MerchantID = 王记餐饮的 OMG 特店编号
StoreID = 王记台北店的自定义门店代号
```

字段含义：

- `PlatformID`：OMG 审核后提供给专案合作平台，不能自行生成；普通特店应留空。
- `MerchantID`：每个独立收款商户的特店编号。
- `StoreID`：商户内部自行定义的门店标识，不是独立收款账户。

示例：

```text
Foodie PlatformID = P00001

商户 A：
  MerchantID = M10001
  门店 A1 → StoreID = S101
  门店 A2 → StoreID = S102

商户 B：
  MerchantID = M10002
  门店 B1 → StoreID = S201
```

平台商模式通常需要：

- Foodie 与 OMG 签约并开通平台商资格。
- OMG 分配 `PlatformID`。
- 每个子商户按照合作方案开户、审核、绑定银行卡并取得 `MerchantID`。
- 分账、直接结算和平台抽佣能力需要 OMG 通过合同和接口明确支持。

如果只是一个商户经营多个门店，使用 `MerchantID + StoreID` 即可，不需要平台商模式。

最终业务已明确 Foodie 不代收、不分账，因此当前阶段不采用平台商/子商户模式。

---

## 第四轮：夜市每个摊位是否都需要申请商户

### 用户问题

> 现在有个问题是，夜市有很多摊位，每个摊位也要也能申请一个商户吗？开一个商户会不会门槛比较高？

### 分析过程

- 将问题从接口字段提升到实际收款主体和结算责任。
- 核对 OMG 当时公开的申请材料、审核要求和费用。
- 对比摊位独立收款、夜市统一收款以及平台子商户三种方案。

### 结论

如果每个摊位由不同老板经营，并且款项需要进入不同银行账户，那么每个摊位原则上应使用独立 OMG 商户账户。

OMG 当时公开支持自然人和公司户申请，但仍需提交收款资料并经过审核，不是注册账号后就能自动开通。

当时查询到的公开费用（截至 2026-08-10）：

- 设置费：NT$1,000/次。
- 年费：NT$5,000/年。
- 信用卡/Apple Pay：2.95%，最低 NT$5/笔。
- ATM：1%，最低 NT$15/笔。
- 超商代码：NT$38/笔。
- 30 日收款额度：最高 NT$200,000。

相关资料：

- [OMG 注册页面](https://www.funpoint.com.tw/member/register)
- [OMG 收款服务规范](https://www.funpoint.com.tw/terms/provisionlogistics)
- [OMG 费用说明](https://www.funpoint.com.tw/info/fee)

100 个独立摊位按公开价格各开一个普通商户的示例：

```text
首年固定费用：
100 × (1,000 + 5,000) = NT$600,000

以后每年：
100 × 5,000 = NT$500,000
```

因此，大量小摊位逐个开户的经济和运营门槛较高。

讨论中曾考虑：

| 夜市场景 | 方案 |
|---|---|
| 所有摊位由同一家公司经营、统一收款和报税 | 一个 `MerchantID`，不同摊位使用不同 `StoreID` |
| 每个摊位是独立老板，款项直接给各摊位 | 每摊位独立 `MerchantID` |
| 每个摊位独立，平台统一技术接入和批量开户 | 与 OMG 商谈 `PlatformID + 子商户` |
| 夜市统一代收后再给摊位结算 | 必须取得 OMG 对代收、分账方案的明确认可 |

最终业务已明确 Foodie 不参与收款。因此平台不会使用自己的 `MerchantID` 代收再结算；每个店铺或摊位应绑定最终实际收款商户的支付配置。

---

## 第五轮：为了灵活性，支付信息是否应该放在店铺管理

### 用户问题

> 现在使用哪种商户方案没不能确定，为了保持最好的灵活度，支付信息是不是还是应该放在店铺来管理？

### 分析过程

- 区分“门店操作入口”和“支付凭证归属”。
- 评估当前 `pos_store_omg` 是否会把系统限制为“一店一 MerchantID”。
- 研究通过支付账户和门店绑定关系支持多种商户方案的可能性。

### 当时结论

当时建议把数据拆为两层：

```text
OMG支付账户 pos_omg_account
  ├─ 账户模式
  ├─ PlatformID
  ├─ MerchantID
  ├─ HashKey/HashIV
  ├─ 所属收款主体
  └─ 申请、审核、环境、状态

门店绑定 pos_store_omg
  ├─ store_id
  ├─ omg_account_id
  ├─ omg_store_id
  ├─ is_enabled
  └─ enabled_payments
```

该设计可以覆盖：

```text
一店一商户：
门店A → OMG账户A

一个商户多店：
门店A ─┐
门店B ─┼→ 同一个OMG账户，不同StoreID
门店C ─┘

平台子商户：
门店A → 子商户账户A
门店B → 子商户账户B
```

当时形成的原则是：门店管理支付开关和账户绑定，支付账户管理商户身份、结算关系和敏感凭证。

### 后续取舍

在明确 Foodie 不代收且多店铺场景较少之后，决定不实施上述账户绑定抽象。当前阶段直接使用店铺级 `pos_store_omg` 配置，优先降低实现和操作复杂度。

---

## 第六轮：普通商家店铺与夜市摊位的归属差异

### 用户问题

> 但是摊位的时候，用户关联摊位的逻辑跟商家用户关联店铺的逻辑不太一样。你先看看他们之间的局别

### 分析过程

- 只分析现有关系，不修改代码。
- 对比普通商家、夜市和摊位主的用户类型、关联字段、创建流程、查询权限和订单归属。
- 核对摊位创建、摊位主管理、钱包和订单入账代码。

### 结论

普通商家和摊位是两套不同的归属模型，不能统一通过 `pos_store.user_id` 判断实际收款主体。

| 对比项 | 普通商家店铺 | 夜市摊位 |
|---|---|---|
| 用户类型 | 商家 `userType=1` | 夜市 `userType=3`、摊位主 `userType=4` |
| 店铺归属 | `PosStore.user_id = 商家ID` | `PosStore.user_id = 夜市ID` |
| 摊位标识 | `is_stall=0` | `is_stall=1` |
| 操作账号 | 商家本人管理多个店铺 | 多个摊位主共同管理一个摊位 |
| 操作账号关联 | 用户 → 多店铺 | `InfoUser.store_id → 摊位ID` |
| 钱包归属 | `UserWallet.user_id = 商家ID` | `UserWallet.store_id = 摊位ID` |
| 收入主体 | 商家用户 | 摊位本身 |

普通商家关系：

```text
InfoUser(type=1)
  └─ 1:N PosStore(user_id=商家ID)
       └─ 收入进入商家用户钱包
```

夜市摊位关系：

```text
InfoUser(type=3，夜市)
  └─ 1:N PosStore(is_stall=1，user_id=夜市ID)
       ├─ 1:N InfoUser(type=4，store_id=摊位ID)
       └─ 1:1 UserWallet(store_id=摊位ID)
```

关键结论：

> 对普通店铺，`pos_store.user_id` 是商家；对摊位，`pos_store.user_id` 是夜市管理者，不一定是实际摊位收款人。

一个摊位可以有多个 `userType=4` 账号，目前系统没有标记哪个账号是法人、OMG 申请人或结算负责人。

在当前简化方案下，支付配置仍直接挂在 `store_id`，由店铺或摊位明确填写最终实际收款商户的 OMG 凭证，不再通过 `pos_store.user_id` 自动推断支付商户身份。

---

## 第七轮：后续接入 LINE Pay 是否有足够灵活度

### 用户问题

> 还要考虑后续接入line pay，这样是否对于接入新的支付存在灵活度

### 分析过程

- 将 OMG 和后续 LINE Pay 放入统一支付域模型进行评估。
- 区分支付渠道 `provider` 与支付方式 `payment_method`。
- 确认当前代码中的 `LINEPAY` 是蓝新金流提供的付款选项，不是 LINE Pay 直连。
- 核对 LINE Pay 的 `Channel ID`、`Channel Secret`、HMAC 签名和交易流程。

### 当时结论

当时建议设计四层通用支付模型：

```text
店铺/摊位
   ↓
store_payment_binding
   ↓
payment_account
   ↓
OMG / LINE Pay 渠道凭证与适配器

订单
   ↓
payment_attempt
```

该模型可支持：

- 一个普通商户账户绑定多个店铺。
- 一个夜市账户绑定多个摊位。
- 某个摊位单独切换支付账户。
- 同一个店铺同时启用 OMG 和 LINE Pay。
- 不同渠道使用不同结算主体。
- 将来新增其他支付渠道。

LINE Pay 相关资料：

- [LINE Pay 官方凭证说明](https://developers-pay.line.me/online/prerequisites)
- [LINE Pay Online API v4](https://developers-pay.line.me/online-api-v4)
- [LINE Pay 官方变更记录](https://developers-pay.line.me/api-change-log)

### 后续取舍

由于当前已明确：

- Foodie 不代收、不分账。
- 多店铺场景较少。
- 当前首要目标是操作和实现简单。

因此暂不建设通用四层支付模型。OMG 继续使用店铺级 `pos_store_omg`；将来真正接入 LINE Pay 时新增店铺级 `pos_store_linepay`，并在出现第三个以上支付渠道或大量账户复用需求时，再评估抽象公共支付模型。

---

## 第八轮：店铺进入收款设置后如何简单操作

### 用户问题

> 我还是不够明白，通过店铺进入管理入口，应该是怎么操作的，操作起来应该简单明了

### 分析过程

- 隐藏底层账户、渠道、绑定和技术凭证概念。
- 从门店经营者角度，只展示支付方式、收款归属和开通状态。
- 将操作流程收敛为店铺列表入口和收款设置页面。

### 结论

当时形成的界面原则：

> 从用户角度，支付属于店铺；底层如何保存和调用凭证由系统处理。

店铺列表入口：

```text
店铺列表
┌──────────┬────────┬──────────┐
│ 店铺名称  │ 营业状态 │ 操作       │
├──────────┼────────┼──────────┤
│ 王记一店  │ 营业中   │ 编辑｜收款设置 │
│ 王记二店  │ 营业中   │ 编辑｜收款设置 │
└──────────┴────────┴──────────┘
```

点击“收款设置”进入当前店铺的收款页面：

```text
王记一店 · 收款设置

当前状态：正常收款
收款商户：王记餐饮有限公司
已开通：OMG
```

支付渠道卡片：

```text
┌─────────────────────────────┐
│ OMG                          │
│ 状态：已开通                  │
│ 收款商户：王记餐饮有限公司      │
│ [编辑配置] [停用]              │
└─────────────────────────────┘

┌─────────────────────────────┐
│ LINE Pay                     │
│ 状态：未开通                  │
│ [开通 LINE Pay]               │
└─────────────────────────────┘
```

普通商户不需要看到 `payment_account_id`、`holder_type` 等内部字段。`MerchantID`、`StoreID`、`Channel ID` 等可以放入“技术信息”区域或仅允许平台运营人员编辑。

页面只需让用户明确三件事：

1. 当前店铺能使用哪些支付方式。
2. 款项直接结算给哪个商户。
3. 当前渠道是否已开通。

---

## 第九轮：确认平台不参与收款后是否需要简化

### 用户问题

> 这个会不会太复杂了，现在明确平台不管收款，支付直接进入商户

### 讨论结论

确认此前包含平台商、子商户、平台结算主体和通用支付账户的设计对于当前范围过于复杂。

业务关系简化为：

```text
店铺
  ↓ 配置支付渠道
OMG / LINE Pay
  ↓ 直接结算
商户银行账户
```

Foodie 只负责技术处理，不接触结算资金：

- 发起支付。
- 接收并验证回调。
- 更新订单支付状态。
- 提供交易查询和退款能力。

因此暂不实现：

- `PlatformID`。
- 平台商/子商户模型。
- 平台代收和分账。
- 平台资金账户。
- 二次结算逻辑。

夜市摊位遵循同一原则：摊位绑定谁的支付商户账户，支付渠道就直接把钱结算给谁，Foodie 不参与后续款项再分配。

---

## 第十轮：最终选择店铺级简单配置

### 用户决定

> 我们还是简单一点直接在店铺设置就行了，多店铺的情况也不多

### 最终结论

当前阶段不建立独立的 `payment_account`、`store_payment_binding` 或通用支付域模型，直接按店铺配置。

OMG 使用：

```text
pos_store_omg
- store_id
- merchant_id
- omg_store_id
- hash_key
- hash_iv
- enabled_payments
- is_enabled
- status
```

管理入口：

```text
店铺管理 → 收款设置 → OMG
```

少量多店铺需要共用同一个 OMG 商户时：

- 每个店铺分别保存相同的 `MerchantID / HashKey / HashIV`。
- 每个店铺使用不同的 OMG `StoreID`。
- 数据库不能继续强制 `merchant_id` 全局唯一。
- 回调必须通过 `MerchantTradeNo` 找到订单和店铺，不能只使用 `MerchantID` 反查唯一店铺。

后续接入 LINE Pay 时，再增加店铺级 `pos_store_linepay` 配置；不为尚未发生的大规模支付账户复用需求提前增加抽象。

## 实施时必须保留的安全要求

即使采用简单方案，以下要求仍不能省略：

1. `HashKey / HashIV / Channel Secret` 加密保存，前端和普通管理接口只显示掩码。
2. 支付回调必须校验签名、金额、订单状态和渠道商户编号。
3. 支付流水记录当次使用的 `store_id`、`MerchantID`、`StoreID` 和渠道交易编号。
4. 回调以 `MerchantTradeNo` 或平台唯一支付流水定位订单，不能依赖 `MerchantID` 与店铺一对一。
5. 重复回调必须幂等处理，不能重复更新订单或触发重复业务动作。

## 后续重新评估架构的触发条件

只有出现以下任一情况时，再考虑抽象支付账户和店铺绑定模型：

- 大量商户需要多个店铺共用同一套支付凭证。
- 同一店铺需要同时管理多个独立结算账户。
- 接入三个以上直连支付渠道，店铺级表出现大量重复逻辑。
- Foodie 的业务模式改变，需要平台商、子商户、代收或分账。
- 凭证管理需要统一迁移到外部密钥管理服务。

