概要
- OGP 呼叫 SPay 的 7 個端點都在
/v1/agentApi/之下,以 OGP 專屬的 API key/secret 做 HMAC-SHA256 簽章;SPay 只依簽章所屬的 Agent 處理資料,請求內不需也不能指定 Agent 或幣別。 - 訂單進入終態(完成、拒絕、逾期、取消)後,SPay 以同一組 API key/secret 簽章,
POST到 OGP 設定的 callback 網址;OGP 驗章、以eventId去重後回 HTTP 200 與純文字OK。 - 同一事件的 callback 可能重複送達,也不保證依訂單或時間順序到達;自動重送有次數上限(見重送與補送),任何時候都能以查詢端點取得訂單目前狀態。
- API key/secret 由 SPay Operator 以安全管道另行交付,不會出現在本文件;secret 只能存放在 OGP 服務端。一組 key 同時用於請求與 callback,輪替時兩個方向同時生效,舊 key 立即失效。
環境
下表為各環境的 base URL;端點路徑接在 base URL 之後,例如 {baseUrl}/v1/agentApi/depositOrder/assign。每個環境使用各自的 API key/secret。
| 環境 | base URL |
|---|---|
| DEV | https://spay4api.ogp99.xyz |
| UAT | https://spay4api.ogp88.xyz |
| PRD | https://spay4api.ogp77.xyz |
請求簽章
Header
| Header | 內容 |
|---|---|
X-Api-Key | SPay 交付的 API key。 |
X-Timestamp | 送出時的 Unix epoch 秒數(十進位整數,不含小數)。與 SPay 伺服器時間相差須在 ±300 秒內(含 300 秒)。 |
X-Nonce | 每個請求新產生的小寫 UUID v4,例如 123e4567-e89b-42d3-a456-426614174000。同一組 key 用過的 nonce 會被拒絕,所有端點共用。 |
X-Signature | HMAC-SHA256 的 64 字元十六進位字串(建議小寫);不接受 Base64。 |
Content-Type | POST/PUT 必須是 application/json(charset 省略或 UTF-8)。 |
簽章字串
METHOD + "\n" + PATH + "\n" + X-Timestamp + "\n" + X-Nonce + "\n" + BODY
METHOD:大寫,與實際送出的一致(GET、POST、PUT)。PATH:實際送出的 path,含/v1/agentApi/...,保持原樣的百分比編碼,不解碼也不正規化。BODY:實際送出的 UTF-8 body bytes;GET沒有 body,簽章字串在 nonce 後的換行結束。不另做 body 雜湊,也不在 body 後補換行。- HMAC 的 key 是 secret 字串本身的 UTF-8 bytes(不做 Base64 解碼)。
- 不支援 query string:任何
?(包含空的?)都會被拒絕。body 上限 1,048,576 bytes(1 MiB)。
時間與 nonce
- 請保持服務端時鐘與 NTP 同步。超出 ±300 秒的請求會被拒絕。
- 簽章驗證通過後 nonce 即被使用,即使該請求最後因欄位錯誤失敗。每一次重送都要產生新的 timestamp、nonce 與 signature,body 則保持原樣(見冪等與重送)。
回應格式
成功時 HTTP 200,body 為:
{"code":2000,"message":"","data":{...}}
失敗時 code 為錯誤碼,message 為英文短訊,data 為欄位與原因(例如 ["amount"] 或 ["payerBankCode","unknown, inactive, or out of scope"])、空陣列或 null。認證失敗為 HTTP 401、code 40000;Withdrawal 端點會先檢查 query string、body 大小與 Content-Type,不合法時回 HTTP 400、code 40005(見錯誤碼)。Deposit 與 HOUSE 卡端點的 401 body 為 {"code":40000,"message":"no authentication"}(沒有 data),Withdrawal 端點的 401 另含 "data":null。請以 HTTP status 與 code 判斷,不要比對 message 文字。
金額一律以字串表示。回應中的金額會去掉尾端的 0,例如請求 "1000.00",回應為 "1000"。時間為 ISO-8601 UTC。
Deposit
建立:POST {baseUrl}/v1/agentApi/depositOrder/assign
建立入款訂單並取得收款帳戶。Body 為 JSON object,不接受未列出的欄位、重複欄位或型別轉換。
| 欄位 | 型別 | 必填 | 規則 |
|---|---|---|---|
agentOrderId | string | 是 | OGP 訂單編號,1 到 100 字元;不得含控制字元、前後空白或 / \ ? #;區分大小寫。 |
amount | string | 是 | 正數十進位字串,必須有小數點與 1 到 10 位小數,例如 "1000.00";不接受 JSON number、正負號、指數或前導 0。 |
payerBankCode | string | 是 | 付款人銀行代碼,1 到 50 字元;須為 SPay 對此 Agent 開放的銀行。 |
payerAccount | string | 是 | 付款人帳號,1 到 100 字元。 |
payerName | string | 是 | 付款人姓名,1 到 255 字元。 |
preferredPaymentChannelCode | string 或 null | 否 | 請省略或填 null;填入非空值會被拒絕。 |
idempotencyKey | string | 是 | 此訂單的冪等鍵,1 到 160 字元,規則同 agentOrderId。 |
回應 data:
| 欄位 | 說明 |
|---|---|
orderNo | SPay 訂單編號(32 字元十六進位)。 |
agentOrderId | 原樣回傳。 |
orderStatus | 見訂單狀態。 |
amount/actualAmount | 申請金額;實際入帳金額(完成前為 null)。 |
currencyCode | Agent 的幣別代碼。 |
expireTime | 付款期限(UTC);沒有收款帳戶時為 null。 |
paymentAccount | 付款人應轉入的帳戶 {bankCode, bankName, accountNumber, accountName, ownership},ownership 為 HOUSE 或 CARDHOLDER;沒有可用帳戶時為 null。 |
failureCode | 需人工審核時的原因代碼,例如 NO_ELIGIBLE_RECEIVER;其餘為 null。 |
requestedPaymentChannelCode、assignedPaymentChannelCode、paymentChannel、failureCategory、failureReason | 保留欄位,目前固定為 null。 |
沒有可用收款帳戶時,請求仍然成功:訂單以 PENDING_REVIEW 建立,failureCode 說明原因,paymentAccount 為 null,由 SPay 營運人員處理。
查詢:GET {baseUrl}/v1/agentApi/depositOrder/{agentOrderId}
回傳訂單目前狀態,欄位同建立回應。agentOrderId 以 UTF-8 百分比編碼放在 path,簽章使用送出的編碼後 path。找不到或不屬於此 Agent 的訂單回 HTTP 404、code 40002。
Withdrawal
建立:POST {baseUrl}/v1/agentApi/withdrawalOrder
| 欄位 | 型別 | 必填 | 規則 |
|---|---|---|---|
agentOrderId | string | 是 | OGP 訂單編號,1 到 100 字元,不得空白或含控制字元;原樣比對,不去除空白。 |
amount | string | 是 | 格式同 Deposit;並須在 SPay 設定的單筆出款上下限內(含邊界),上下限由 SPay Operator 告知。 |
receiverBankCode | string | 是 | 收款銀行代碼,1 到 50 字元;須為 SPay 對此 Agent 開放的銀行。 |
receiverAccount | string | 是 | 收款帳號,1 到 100 個英數字(A-Z a-z 0-9)。 |
receiverName | string | 是 | 收款人姓名,1 到 255 字元。 |
preferredPaymentChannelCode | string 或 null | 否 | 省略或 null 由 SPay 選擇;指定時須為 SPay 對此 Agent 開放的通道代碼,否則 HTTP 422、code 40024。 |
idempotencyKey | string | 是 | 此訂單的冪等鍵,1 到 100 字元,規則同 agentOrderId。 |
欄位格式錯誤或金額超出上下限回 HTTP 422、code 40005;銀行代碼不可用回 HTTP 400、code 40005。
回應 data:orderNo、agentOrderId、orderStatus、amount、actualAmount(完成前為 null)、currencyCode、receiverBankCode、receiverBankName、receiverAccount(遮罩,只顯示末 4 碼)、receiverName(遮罩,只顯示首字)、failureCategory、failureCode、failureReason(固定 null)。需人工處理時 failureCategory 為 ROUTING,failureCode 為原因代碼,例如 NO_ELIGIBLE_ACCOUNT、INSUFFICIENT_AVAILABLE_BALANCE、REJECT_REQUEST、FROZEN_ACCOUNT、ASSIGNMENT_EXPIRED。
沒有可用出款帳戶或可用餘額不足時,請求仍然成功:訂單以 PENDING_REVIEW 建立並附原因代碼,不會以錯誤碼回應。
查詢:GET {baseUrl}/v1/agentApi/withdrawalOrder/{agentOrderId}
agentOrderId 須以 UTF-8 標準百分比編碼放在 path(例如空白寫成 %20),且不得含 / 或 \;編碼方式不同會被當成認證失敗(HTTP 401)。
以識別碼查詢:GET {baseUrl}/v1/agentApi/withdrawalOrder/byIdentity/{token}
token 是 agentOrderId 的 UTF-8 bytes 做 Base64url、不補 =,例如 OGP-W-001 為 T0dQLVctMDAx。agentOrderId 含空白、/ 等字元時請用此端點。
兩種查詢都回傳訂單目前狀態,欄位同建立回應;找不到或不屬於此 Agent 的訂單回 HTTP 404、code 40029。Withdrawal 端點的所有回應都帶 Cache-Control: no-store。
冪等與重送
- 以
agentOrderId與idempotencyKey一起識別一筆建立請求。網路逾時或沒有收到回應時,請以相同的 body 重送建立請求,並重新產生 timestamp、nonce 與 signature。 - 相同
agentOrderId、相同idempotencyKey、相同內容:HTTP 200,回傳第一次建立時的回應,不會重複建單;要知道最新狀態請用查詢端點。 - 相同
agentOrderId、不同idempotencyKey:HTTP 409、code40046。 - 相同
idempotencyKey搭配另一個agentOrderId,或相同兩者但內容不同:HTTP 409、code40045。 agentOrderId在同一 Agent 的 Deposit 或 Withdrawal 內各自唯一;建議只使用英數字、-與_,避免編碼差異。
訂單狀態
Deposit
orderStatus | 說明 | 終態 | Callback |
|---|---|---|---|
ASSIGNED | 已指派收款帳戶,等待付款。 | 否 | |
PENDING_REVIEW | 需 SPay 營運人員處理。 | 否 | |
COMPLETED | 入款完成。 | 是 | DEPOSIT_COMPLETED |
EXPIRED | 逾期未付款。 | 是 | DEPOSIT_EXPIRED |
REJECTED | 入款被拒絕。 | 是 | DEPOSIT_REJECTED |
CANCELLED | 訂單取消。 | 是 | DEPOSIT_CANCELLED |
Withdrawal
orderStatus | 說明 | 終態 | Callback |
|---|---|---|---|
ASSIGNED | 已指派出款帳戶。 | 否 | |
PROCESSING | 出款處理中。 | 否 | |
PENDING_VERIFICATION | 已提交轉帳證明,等待確認。 | 否 | |
PENDING_REVIEW | 需 SPay 營運人員處理。 | 否 | |
COMPLETED | 出款完成。 | 是 | WITHDRAWAL_COMPLETED |
REJECTED | 拒絕,未轉帳。 | 是 | WITHDRAWAL_REJECTED |
非終態可能直接進入任一終態;請以終態 callback 或查詢結果為準,不要假設中間狀態一定出現。
Callback
請求
SPay 對 OGP 設定的 HTTPS callback 網址送出 POST(HTTP/1.1,不跟隨 redirect),body 為 JSON:
| 欄位 | 說明 |
|---|---|
eventId | 事件識別碼(UUID),同一事件的每次送達都相同;以此去重。 |
eventType | DEPOSIT_COMPLETED、DEPOSIT_EXPIRED、DEPOSIT_REJECTED、DEPOSIT_CANCELLED、WITHDRAWAL_COMPLETED、WITHDRAWAL_REJECTED。 |
occurredAt | 終態發生時間(UTC,例如 2026-10-07T10:31:00Z)。 |
orderType | DEPOSIT 或 WITHDRAWAL。 |
orderNo、agentOrderId | SPay 與 OGP 的訂單編號。 |
orderStatus | 終態,見訂單狀態。 |
amount、actualAmount | 申請金額;實際金額(非完成的終態可能為 null)。字串。 |
currency | 幣別代碼。 |
failureCategory、failureCode、failureReason | 保留欄位,目前固定為 null。 |
請以欄位名稱解析 JSON,不依賴欄位順序或空白。Header:
| Header | 內容 |
|---|---|
X-Spay-Key-Id | 目前生效的 API key(與 OGP 呼叫 SPay 用的 X-Api-Key 相同)。 |
X-Spay-Event-Id | 同 body 的 eventId。 |
X-Spay-Event-Type | 同 body 的 eventType。 |
X-Spay-Callback-Id | 此次送達的識別碼,每次送達都不同;只供排查,不用於去重。 |
X-Spay-Timestamp | 此次送達的 Unix epoch 秒數。 |
X-Spay-Nonce | 此次送達新產生的小寫 UUID v4。 |
X-Spay-Signature | HMAC-SHA256 的 64 字元小寫十六進位。 |
驗章
"SPAY-CALLBACK-V1\n" + "POST\n" + PATH + "\n" + X-Spay-Timestamp + "\n" + X-Spay-Nonce + "\n" + X-Spay-Event-Id + "\n" + BODY
PATH是 callback 網址的 path(不含 host 與 query),以百分比編碼表示,例如/ogp/spay/callback;path 為空時是/。callback 網址請勿含 query、帳密或 fragment。BODY是收到的 raw body bytes;請在解析 JSON 前以 raw bytes 驗章,並以固定時間比較簽章。- key 為 API secret 的 UTF-8 bytes。key 輪替後,下一次送達即改用新 key;輪替前已送出的簽章不會重算。
接收端參數(兩端聯測前約定):
| 參數 | 值 |
|---|---|
| Callback X-Spay-Timestamp 容許誤差 | 300 秒 |
| Callback X-Spay-Nonce 去重保留時間 | 300 秒 |
ACK
處理成功,或該 eventId 先前已處理過,都請回:
HTTP/1.1 200 OK
Content-Type: text/plain; charset=UTF-8
OK
只有 HTTP 2xx 且 body 精確等於大寫 OK(不含換行、空白或引號)才算送達。204、空 body、小寫 ok、JSON 或其他文字都視為失敗並重送。每次送達的總時限為 20 秒(含 DNS、連線、TLS 與回應);請先持久化事件,再回 ACK,不要在 ACK 前執行耗時處理。
重送與補送
- 每個事件最多送 5 次。第 1 次在訂單進入終態後約 1 分鐘內送出;每次失敗後分別再等待 1、5、30、60 分鐘重送(以分鐘為單位排程,實際時間可能再晚 1 分鐘內)。
- HTTP 408、425、429、5xx、3xx、2xx 但 ACK 不符、逾時或網路錯誤會重送;其他 4xx 視為永久拒絕,不再重送。
- 5 次都失敗或永久拒絕後,SPay 停止自動送出。SPay 營運人員確認原因後可安排補送:補送會重新進入上述最多 5 次的排程,並使用相同的
eventId與 body、新的X-Spay-Callback-Id、timestamp、nonce,以及當時生效的 key 與 callback 網址。 - 訂單結果不受 callback 是否送達影響;callback 遲遲未到時,請以查詢端點取得目前狀態。
HOUSE 卡同步
新增或更新:PUT {baseUrl}/v1/agentApi/houseCard
以 agentCardId 識別 OGP 管理的 HOUSE 卡:不存在時建立,存在時以本次內容覆蓋;內容與現況相同時不做任何變更。每次都要送完整的 8 個欄位。
| 欄位 | 型別 | 規則 |
|---|---|---|
agentCardId | string | OGP 的卡片識別碼,1 到 64 個 A-Z a-z 0-9 _ -,區分大小寫。 |
bankCode | string | 銀行代碼;建立時須為 SPay 對此 Agent 開放的銀行;建立後不可變更。 |
accountNumber | string | 帳號,可含英數字、空白、-、_;SPay 去除空白、-、_ 並轉大寫後保存(1 到 100 字元);建立後不可變更。 |
accountName | string | 戶名,1 到 100 字元,不含控制字元。 |
displayName | string | 顯示名稱,規則同 accountName。 |
depositEnabled | boolean | 是否用於入款。 |
withdrawalEnabled | boolean | 是否用於出款。 |
cardStatus | string | ACTIVE 或 INACTIVE。不提供刪除;停用請改為 INACTIVE。 |
回應 data 為該卡片:上述欄位(accountNumber 為保存後的完整帳號),另加 bankName、bankEligible(目前是否可用)與 bankEligibilityReason(不可用的原因:BANK_REFERENCE_MISSING、BANK_DELETED、BANK_INACTIVE、BANK_AGENT_MAPPING_MISSING、BANK_CURRENCY_MISMATCH,可用時為 null)。
- 同一銀行與帳號已有另一張
ACTIVE卡時,建立或啟用回 HTTP 409、code40007。 - 變更既有卡片的
bankCode或accountNumber回 HTTP 400、code40005、data為["bankCode","must not change"]或["accountNumber","must not change"];請以新的agentCardId建立新卡,並把舊卡設為INACTIVE。 - 每日限額不由此 API 設定;SPay 端的變更不會 callback 通知 OGP,請以查詢端點確認。
查詢:GET {baseUrl}/v1/agentApi/houseCard
回傳此 Agent 所有由 OGP 管理的卡片(含 INACTIVE),data 為陣列,沒有分頁或篩選。
錯誤碼
| HTTP | code | 說明 | 處理方式 |
|---|---|---|---|
| 400/422 | 40005 | 欄位格式或內容不合法,訂單未建立;data 指出欄位。Withdrawal 建立的欄位格式錯誤與金額超出上下限為 422,其餘為 400。Withdrawal 端點帶 query string、body 超過 1 MiB、Content-Type 不符或 GET 帶 body 也回 400,data 為 null。 | 修正請求後重送。 |
| 401 | 40000 | 認證失敗:header 缺漏或格式錯誤、時間超出 ±300 秒、nonce 重複、key 無效或已輪替、簽章不符,或端點不存在;Deposit 與 HOUSE 卡端點帶 query string、body 超過 1 MiB、Content-Type 不符或 GET 帶 body 也回 401。 | 檢查簽章與時鐘,以新的 nonce 重送。 |
| 401 | 40001 | Agent 目前不可使用此 API。 | 聯絡 SPay Operator。 |
| 404 | 40002 | 查無 Deposit 訂單。 | 確認 agentOrderId 與環境。 |
| 404 | 40029 | 查無 Withdrawal 訂單。 | 確認 agentOrderId 與環境。 |
| 409 | 40007 | HOUSE 卡的銀行與帳號已有另一張 ACTIVE 卡。 | 先停用另一張卡。 |
| 409 | 40045 | 冪等鍵衝突:同一 idempotencyKey 用於另一訂單,或同一訂單內容不同。 | 以原 body 重送,或改用新的訂單與冪等鍵。 |
| 409 | 40046 | 同一 agentOrderId 已以另一個 idempotencyKey 建立。 | 以查詢端點確認既有訂單。 |
| 422 | 40024 | 指定的出款通道不可用。 | 省略 preferredPaymentChannelCode 或改用可用通道。 |
| 500 | 40048 | SPay 端出款設定異常。 | 聯絡 SPay Operator。 |
| 503 | 40044 | SPay 依賴服務暫時無法使用。 | 稍後以新的 nonce 重送。 |
| 500 | 5000 | SPay 內部錯誤。 | 稍後重送;建立請求以原 body 重送不會重複建單。 |
簽章範例
以下為公開測試向量,secret 只用於核對實作,不能呼叫任何環境。範例程式 examples/sign-ogp-request.mjs(Node.js 18 以上)可執行 --self-test 核對兩組向量,也能對實際請求產生 header。
請求
| secret | ogp-public-test-only |
|---|---|
| METHOD | POST |
| PATH | /v1/agentApi/depositOrder/assign |
| X-Timestamp | 1785168000 |
| X-Nonce | 123e4567-e89b-42d3-a456-426614174000 |
| BODY | {"agentOrderId":"OGP-D-0001","amount":"1000.00","payerBankCode":"012","payerAccount":"1234567890","payerName":"王小明","idempotencyKey":"9f0c3d2e-OGP-D-0001"} |
| X-Signature | 83412ad5c23e239dc0db0797820b70f7afc074258ec8eaf4d236c123e123b5af |
Callback
| secret | ogp-public-test-only |
|---|---|
| PATH | /ogp/spay/callback |
| X-Spay-Timestamp | 1785168060 |
| X-Spay-Nonce | 0b6e2f4a-3c1d-4e5f-9a8b-7c6d5e4f3a2b |
| X-Spay-Event-Id | 019fa1f0-9a10-7c4b-8a91-1234567890ab |
| BODY | {"eventId":"019fa1f0-9a10-7c4b-8a91-1234567890ab","eventType":"DEPOSIT_COMPLETED","occurredAt":"2026-10-07T10:31:00Z","orderType":"DEPOSIT","orderNo":"0f1e2d3c4b5a69788796a5b4c3d2e1f0","agentOrderId":"OGP-D-0001","orderStatus":"COMPLETED","amount":"1000","actualAmount":"1000","currency":"TWD","failureCategory":null,"failureCode":null,"failureReason":null} |
| X-Spay-Signature | d836a8b4752e717edee67ca010e821ba93ebbdecbfebd3350b1d0396539a8a5f |
上線注意事項
- 舊單到舊系統查:切換前在舊系統建立的訂單不會搬到 SPay;在 SPay 查詢會回 404(Deposit 40002、Withdrawal 40029),請在舊系統唯讀觀察期內到舊系統查詢。
agentOrderId不得重用:SPay 只檢查自己系統內的重複;凡在舊系統用過的agentOrderId,都不得在 SPay 再使用。- 重新交付憑證:SPay 會為 OGP 重新產生 API key/secret,舊系統的憑證不能使用。
- HOUSE 卡重建:舊系統的 HOUSE 卡不會搬移,請以
PUT /v1/agentApi/houseCard重新同步。 - 先上線 callback 接收端:在 SPay 開始送 callback 前,OGP 接收端須已能對成功與重複事件都回 HTTP 200 與
OK。 - 舊系統的 callback 會在切換前送完;切換後的訂單結果只由 SPay 送出。