跳轉到

裝置、密與解鎖

Device read contract

Payment DTO 不含 udid、agent_id、execution_kind、WDA URL 等 ops 路由欄位。

GET /v1/devices

curl -sS "$FARM_BASE/v1/devices" \
  -H "Authorization: Bearer $FARM_API_KEY"
{
  "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, "account_numbers": []}
      ]
    }
  ]
}

GET /v1/devices/{device_id}

單筆同上 DTO。跨 tenant/不存在 → 404。

欄位 說明
online / connection 依 agent heartbeat(TTL 內)
wda_status iOS:up / down / unknown;Android:not_applicable。集合與單筆端點語意相同
locked / lock_reason / locked_at 鎖卡狀態;locked_at 即 unlock 用的 generation
banks[] bank_code、pin_configured、variant、updated_at、account_numbers(非機密字串陣列;未安裝則 [])

pin_configured:TH/GCash = 密封袋有 pin;VN = 密封袋有登入 password。Payment-facing 不回明文密。

越南 enrollment+redeem 後,單筆 GET 的 banks[] 例(帳號為 Payment 主檔副本,無密文):

{
  "device_code": "VN-XXXX",
  "device_type": "android",
  "locked": false,
  "banks": [
    {
      "bank_code": "VCB",
      "pin_configured": true,
      "variant": "standard",
      "updated_at": 0,
      "account_numbers": ["1111111111", "2222222222"]
    }
  ]
}

改密

PUT /v1/devices/{device_code}/banks/{bank_code}/secret
Authorization: Bearer $FARM_API_KEY

專門改密,不改 account_numbers(該鍵出現即 422)。省略或 "" = 不改該格。至少改一個密鍵,否則 422。未知鍵 422。

銀行 允許 pin
VN/VCB、VN/BIDV password、transfer_password、variant 禁止(422)
PH/GCASH pin(4 位)、variant 可省略(省略=不改 MPIN)
TH iOS pin(6 位)、variant 可省略(省略=不改)

TH:

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"}'

越南只改登入密:

curl -sS -X PUT "$FARM_BASE/v1/devices/VN-XXXX/banks/VCB/secret" \
  -H "Authorization: Bearer $FARM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"password":"vcb-login-pass-new"}'

只改 OTP:{"transfer_password":"654321"}。

{
  "device_code": "TH-XXXX",
  "bank_code": "SCB",
  "pin_configured": true,
  "variant": "standard",
  "updated_at": 0
}

密文

  • PIN/password/OTP 不進 log、不進 audit、不 echo 回 response
  • Payment-facing 沒有任何讀回明文密的 API
  • bank/country 必須在主資料 enabled,否則 422
  • script catalog 不可用 → 503;variant 未部署 → 422
  • 改卡號清單:DELETE 該銀行再 enrollment,不要打 PUT secret

解綁銀行

DELETE /v1/devices/{device_code}/banks/{bank_code}
結果 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(修正密)
  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
{"device_code":"TH-XXXX","was_locked":true,"locked_at":null,"lock_reason":null}

不解鎖不重開排程

Unlock 只清 lock 欄位,不碰 schedule_enabled。 Ops 刻意停用的裝置不會因上游解鎖而自動恢復排程。

Unlock 不做「PIN 是否真的修好」的前置探測;若仍錯,下一次任務會再鎖。

Timeout/callback 遺失

  • device.locked / device.unlocked 走 DB outbox,重試至 2xx。
  • 仍建議以 GET device 的 locked/locked_at 為權威。