裝置、PIN 與解鎖¶
Device read contract¶
Payment DTO 不含 udid、agent_id、WDA URL 等 ops 路由欄位。
GET /v1/devices¶
{
"devices": [
{
"device_id": "TH-XXXX",
"device_code": "TH-XXXX",
"label": "desk-placeholder",
"device_type": "ios",
"online": true,
"connection": "usb",
"wda_status": "up",
"locked": false,
"lock_reason": null,
"locked_at": null,
"banks": [
{"bank_code": "SCB", "pin_configured": true, "variant": "standard", "updated_at": 0}
]
}
]
}
GET /v1/devices/{device_id}¶
單筆同上 DTO。跨 tenant/不存在 → 404。
| 欄位 | 說明 |
|---|---|
online / connection |
依 agent heartbeat(TTL 內) |
wda_status |
up / down / unknown |
locked / lock_reason / locked_at |
鎖卡狀態;locked_at 即 unlock 用的 generation |
banks[] |
bank_code、pin_configured、variant、updated_at |
PIN 更新¶
欄位(BankSecretIn)¶
| 欄位 | 必填 | 說明 |
|---|---|---|
pin |
✔ | 恰 6 位數字。否則 422 |
variant |
可選腳本變體(如 standard)。省略 = 不改既有綁定 |
curl -sS -X PUT "$FARM_BASE/v1/devices/TH-XXXX/banks/SCB/secret" \
-H "Authorization: Bearer $FARM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"pin":"000000","variant":"standard"}'
{
"device_code": "TH-XXXX",
"bank_code": "SCB",
"pin_configured": true,
"variant": "standard",
"updated_at": 0
}
PIN 機密
- 不進 log、不進 audit、不 echo 回 response
- Farm 沒有任何讀回明文 PIN 的 API
- bank/country 必須在主資料 enabled,否則 422
- script catalog 不可用 → 503;variant 未部署 → 422
解綁銀行¶
| 結果 | HTTP |
|---|---|
| 刪除成功 | 204(無 body) |
| 無此 binding/跨 tenant/裝置不存在 | 404 |
curl -sS -o /dev/null -w "%{http_code}\n" -X DELETE \
"$FARM_BASE/v1/devices/TH-XXXX/banks/SCB" \
-H "Authorization: Bearer $FARM_API_KEY"
解鎖 FLOW¶
sequenceDiagram
participant P as PAYMENT
participant F as Farm
P->>F: GET /v1/devices/{id}
F-->>P: locked_at generation
P->>F: PUT bank secret(修正 PIN)
P->>F: POST unlock(expected_locked_at)
alt generation 相符或已未鎖
F-->>P: 200
F-->>P: device.unlocked(outbox 重試至 2xx)
else generation 過期
F-->>P: 409 stale_lock_generation
P->>F: 重新 GET 後決定
end
POST /v1/devices/{device_code}/unlock¶
| 欄位 | 必填 | 說明 |
|---|---|---|
expected_locked_at |
✔ | 來自 GET 的 locked_at(float)。缺欄 → 422 |
note |
可選;只進 audit 與 device.unlocked 通知 |
curl -sS -X POST "$FARM_BASE/v1/devices/TH-XXXX/unlock" \
-H "Authorization: Bearer $FARM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"expected_locked_at":1754712345.6,"note":"PIN fixed placeholder"}'
Generation matrix¶
| 裝置狀態 | 結果 |
|---|---|
| 目前未鎖 | 200,was_locked=false(重送安全;意圖已達成) |
已鎖且 locked_at == expected_locked_at |
200,was_locked=true,清 lock 欄位 |
| 已鎖且 generation 不符 | 409 detail.error=stale_lock_generation,帶目前 lock_reason/locked_at;不解鎖 |
| 跨 tenant/不存在 | 404 |
不解鎖不重開排程
Unlock 只清 lock 欄位,不碰 schedule_enabled。
Ops 刻意停用的裝置不會因上游解鎖而自動恢復排程。
Unlock 不做「PIN 是否真的修好」的前置探測;若仍錯,下一次任務會再鎖。
Timeout/callback 遺失¶
device.locked/device.unlocked走 DB outbox,重試至 2xx。- 仍建議以
GET device的locked/locked_at為權威。