裝置註冊與綁定¶
PAYMENT 先建立短效 enrollment token;現場人員在 FarmAgent 輸入 token 完成綁定。
Token 明文只在 POST /v1/enrollments 回應出現一次;不得當 URL 熵值依賴。
新裝置註冊(device_create)¶
sequenceDiagram
participant P as PAYMENT
participant F as Farm
participant A as FarmAgent/Field
P->>F: POST /v1/enrollments(不帶 device_code)
F-->>P: 201 enrollment_id + device_code + token(只一次)
Note over P: 保存 device_code;token 交現場
A->>F: POST /v1/enrollments/redeem(token 即憑證)
F-->>A: 200 bound
F-->>P: webhook device.bound(best-effort)
P->>F: GET /v1/enrollments/{id}(callback 遺失時)
F-->>P: state=bound, device_code
Note over P: 權威保存 device_code
既有裝置加銀行(bank_add)¶
sequenceDiagram
participant P as PAYMENT
participant F as Farm
participant A as FarmAgent/Field
P->>F: POST /v1/enrollments(帶既有 device_code)
F-->>P: 201 token + mode=bank_add
A->>F: POST /v1/enrollments/redeem
F-->>A: 200 bound(sealed PIN 寫入 bank binding)
F-->>P: webhook bank.bound(best-effort)
P->>F: GET enrollment + GET device
F-->>P: bank + pin_configured 確認
POST /v1/enrollments¶
完整 request 欄位(EnrollIn)¶
| 欄位 | 必填 | 說明 |
|---|---|---|
region |
✔ | 與 bank_code 須命中 banks 主資料且 enabled;正規化為大寫。缺 → 422 |
bank_code |
✔ | 銀行代碼(如 SCB)。空 → 422 |
bank_payload |
object,預設 {}。典型含 pin(六位);密文暫存,永不出現在 callback |
|
device_code |
省略 → 新機 mode=device_create(Farm 配置 device_code);有值 → 既有機 mode=bank_add |
|
label |
可選標籤 | |
allowed_apps |
字串陣列,預設 [] |
|
ttl_seconds |
預設 1800;合法範圍 1–7200。超出 → 400 |
|
device_type |
可選 ios / android(小寫)。僅 device_create 寫入 devices,之後不可改。非法 → 422 |
最小 request(新機)¶
curl -sS -X POST "$FARM_BASE/v1/enrollments" \
-H "Authorization: Bearer $FARM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"region": "TH",
"bank_code": "SCB",
"bank_payload": {"pin": "000000"},
"label": "desk-placeholder",
"ttl_seconds": 1800
}'
最小 request(既有機加銀行)¶
成功 response(201)¶
{
"enrollment_id": "enr_…",
"device_code": "TH-XXXX",
"device_id": "TH-XXXX",
"token": "A3K9MP",
"mode": "device_create",
"region": "TH",
"bank_code": "SCB",
"status": "pending",
"expires_at": 0
}
Token 規則: 恰 6 字元,字集 23456789ABCDEFGHJKMNPQRSTUVWXYZ(無 0/O/1/I/L)。
Redeem 會正規化為大寫。安全依賴短 TTL + 單次使用 + 現場實體配對,不是 URL 熵。
常見錯誤¶
| 狀況 | HTTP |
|---|---|
ttl_seconds 超出 1..7200 |
400 |
| region/bank 未啟用 | 422 |
device_type 非 ios/android |
422 |
bank_add 但 device_code 非本 tenant |
404 |
| secrets key 未設定 | 503 |
GET /v1/enrollments/{enrollment_id}¶
Callback 遺失時的權威狀態。跨 tenant → 404。
{
"enrollment_id": "enr_…",
"tenant_id": "…",
"device_id": "TH-XXXX",
"device_code": "TH-XXXX",
"mode": "device_create",
"region": "TH",
"bank_code": "SCB",
"device_type": "ios",
"state": "bound",
"udid_bound": true,
"label": "desk-placeholder",
"allowed_apps": [],
"created_at": 0,
"expires_at": 0
}
POST /v1/enrollments/redeem(Field/FarmAgent)¶
PAYMENT 不直接呼叫
此端點以 token 為憑證(無 Bearer)。文件僅說明完整 FLOW。 正式現場路徑經 FarmAgent UI/job;勿在 PAYMENT 後端持有 redeem 邏輯或假造 Bearer 範例。
請求形狀(供理解 FLOW):
成功 → status=bound,並觸發 device.bound 或 bank.bound。
無效/過期 token → 404;無可用裝置 → 409;猜測過頻 → 429。
Webhook:device.bound / bank.bound¶
{
"event": "device.bound",
"idempotency_key": "enr_…",
"enrollment_id": "enr_…",
"device_id": "TH-XXXX",
"device_code": "TH-XXXX",
"bank_code": "SCB",
"region": "TH",
"tenant_id": "…",
"label": "desk-placeholder",
"allowed_apps": []
}
mode=bank_add時event為bank.bound。- 永不帶 PIN。
- 投遞:best-effort,無持久化重送。失敗時以
GET /v1/enrollments/{id}恢復。 - 接收端去重鍵:
enrollment_id(同idempotency_key)。
PAYMENT 必須保存¶
| 欄位 | 用途 |
|---|---|
enrollment_id |
GET fallback、與 bound event 對帳 |
device_code |
後續 devices/tasks 唯一公開裝置鍵 |
mode / bank_code |
區分新機 vs 加銀行 |
是否可重試¶
| 情況 | 決策 |
|---|---|
POST enrollments 網路超時、未拿到 body |
可重送新 enrollment(會發新 token/可能新 device_code);或先依業務側記錄查 |
已拿到 enrollment_id、等 bound |
不要重發 enrollment;輪詢 GET enrollment |
| redeem 由現場處理 | PAYMENT 不重試 redeem |