รายชื่อธนาคาร
GET /v1/pay/banks คือแหล่งเดียวของรายชื่อธนาคารที่ใช้ในฟอร์มบัญชีผู้โอน
อย่า hardcode รายชื่อธนาคาร
ธนาคารถูกเปิด/ปิดรับรายการใหม่ได้ตลอดเวลา (ปิดปรับปรุงระบบ · ปัญหาชั่วคราว · นโยบายความเสี่ยง) · ถ้าคุณ hardcode ลิสต์ไว้ ผู้ใช้จะเลือกธนาคารที่ปิดอยู่ กรอกเลขบัญชีจนครบ แล้วไปเจอ 409 sender_bank_unavailable ที่ปุ่มยืนยัน
นั่นคือจุดที่คนทิ้งรายการมากที่สุดในหน้าเติมเงิน — เพราะเขาเสียเวลาไปแล้วทั้งหมด
Request
curl https://api.example-wl.com/v1/pay/banks \
-H "Authorization: Bearer sk_live_xxx" \
-H "PG-Timestamp: 1755749400" \
-H "PG-Nonce: 01JQA5TG4T6V8X0Z2B4D6F8H0K" \
-H "PG-Version: 2026-08-01" \
-H "PG-Signature: v1=0d5e...71fa"Response
{
"data": [
{ "bank_code": "002", "name_th": "ธนาคารกรุงเทพ", "name_en": "Bangkok Bank", "available": true, "side": ["sender", "receiver"] },
{ "bank_code": "004", "name_th": "ธนาคารกสิกรไทย", "name_en": "Kasikornbank", "available": true, "side": ["sender", "receiver"] },
{ "bank_code": "006", "name_th": "ธนาคารกรุงไทย", "name_en": "Krungthai Bank", "available": true, "side": ["sender", "receiver"] },
{ "bank_code": "011", "name_th": "ธนาคารทหารไทยธนชาต", "name_en": "TMBThanachart Bank", "available": true, "side": ["sender", "receiver"] },
{ "bank_code": "014", "name_th": "ธนาคารไทยพาณิชย์", "name_en": "Siam Commercial Bank", "available": true, "side": ["sender", "receiver"] },
{ "bank_code": "025", "name_th": "ธนาคารกรุงศรีอยุธยา", "name_en": "Bank of Ayudhya", "available": false, "message_th": "ธนาคารปิดปรับปรุงระบบชั่วคราว กรุณาเลือกธนาคารอื่น", "side": ["sender", "receiver"] },
{ "bank_code": "030", "name_th": "ธนาคารออมสิน", "name_en": "Government Savings Bank", "available": true, "side": ["sender"] },
{ "bank_code": "034", "name_th": "ธนาคารเพื่อการเกษตรและสหกรณ์การเกษตร", "name_en": "Bank for Agriculture and Agricultural Cooperatives", "available": true, "side": ["sender"] },
{ "bank_code": "069", "name_th": "ธนาคารเกียรตินาคินภัทร", "name_en": "Kiatnakin Phatra Bank", "available": true, "side": ["sender", "receiver"] },
{ "bank_code": "022", "name_th": "ธนาคารซีไอเอ็มบี ไทย", "name_en": "CIMB Thai Bank", "available": false, "message_th": "ธนาคารนี้ปิดรับรายการใหม่ชั่วคราว", "side": ["sender"] }
]
}(รายการด้านบนเป็นตัวอย่างรูปแบบ ค่าจริงต้องดึงจาก endpoint เสมอ)
| field | ชนิด | มีเสมอ | ความหมาย |
|---|---|---|---|
bank_code | string | ✅ | รหัส 3 หลัก · ค่านี้คือค่าที่ส่งเข้า sender.bank_code ตอนสร้างรายการ |
name_th | string | ✅ | ชื่อไทยสำหรับแสดงผล |
name_en | string | ✅ | ชื่ออังกฤษสำหรับแสดงผล |
available | boolean | ✅ | 🔴 false = ปิดรับรายการใหม่ ต้องเทาปุ่ม |
message_th | string | ❌ | เหตุผลที่แสดงให้ผู้ใช้เห็นได้ · optional แปลว่าอาจไม่มี key นี้เลย ไม่ใช่ null — โค้ดต้องรองรับกรณีไม่มี key |
side | string[] | ✅ | ธนาคารนี้ใช้ได้ในฝั่งไหน — "sender" = ใช้เป็นบัญชีผู้โอนได้ · "receiver" = เป็นบัญชีปลายทางได้ |
message_th เป็น optional จริง ๆ
// ❌ พังเมื่อไม่มี key
const reason = bank.message_th.trim()
// ✅
const reason = bank.message_th ?? 'ธนาคารนี้ปิดรับรายการใหม่ชั่วคราว'เอาไปใช้ยังไง
1. กรองด้วย side ก่อน
ฟอร์มบัญชีผู้โอนต้องแสดงเฉพาะธนาคารที่มี "sender" ใน side · ธนาคารที่ไม่มีจะใช้เป็นบัญชีผู้โอนไม่ได้ตั้งแต่ต้น อย่าเอามาแสดงเลย
2. เทาปุ่มด้วย available — อย่าซ่อน
เทา ไม่ใช่ซ่อน
ถ้าซ่อนธนาคารที่ปิดออกจากลิสต์ ผู้ใช้ที่มีบัญชีอยู่แค่ธนาคารนั้นจะหาไม่เจอแล้วคิดว่าระบบพัง แล้วโทรหา support
ถ้าเทาไว้พร้อมข้อความ เขาจะรู้ทันทีว่าเกิดอะไรขึ้นและเลือกทางอื่นเอง
คุณจะโอนจากบัญชีไหน
( ) ธนาคารกรุงเทพ
( ) ธนาคารกสิกรไทย
( ) ธนาคารกรุงไทย
( ) ธนาคารไทยพาณิชย์
(·) ธนาคารกรุงศรีอยุธยา ← เทา กดไม่ได้
ธนาคารปิดปรับปรุงระบบชั่วคราว กรุณาเลือกธนาคารอื่น3. cache ได้ แต่ต้อง refresh
รายชื่อนี้ cache ได้ และควร cache — refresh ทุก ~5 นาที ก็เพียงพอ · bucket ของ endpoint นี้กว้างมาก (3,000 ครั้ง/นาที ต่อ key) แต่ไม่มีเหตุผลที่จะยิงทุก request
// Node — cache 5 นาที พร้อม fallback เป็นค่าเดิมถ้า refresh พลาด
let cache = { at: 0, banks: [] }
const TTL_MS = 5 * 60 * 1000
async function getBanks() {
if (Date.now() - cache.at < TTL_MS && cache.banks.length) return cache.banks
try {
const res = await fetch('https://api.example-wl.com/v1/pay/banks', {
headers: sign('GET', '/v1/pay/banks', '', '', null),
})
const { data } = await res.json()
cache = { at: Date.now(), banks: data }
} catch (err) {
// 🔴 refresh พลาดแล้วยังมีของเก่า ให้ใช้ของเก่าต่อ ดีกว่าโชว์ฟอร์มเปล่า
if (!cache.banks.length) throw err
}
return cache.banks
}
async function senderBankOptions() {
const banks = await getBanks()
return banks
.filter((b) => b.side.includes('sender'))
.map((b) => ({
value: b.bank_code,
label: b.name_th,
disabled: b.available !== true,
hint: b.available ? undefined : (b.message_th ?? 'ธนาคารนี้ปิดรับรายการใหม่ชั่วคราว'),
}))
}<?php
// PHP — cache ลงไฟล์/APCu แล้วกรองเฉพาะฝั่งผู้โอน
function senderBankOptions(array $headers): array
{
$banks = apcu_fetch('pg_banks');
if ($banks === false) {
$res = httpGet('https://api.example-wl.com/v1/pay/banks', $headers);
$banks = $res['data'];
apcu_store('pg_banks', $banks, 300); // 5 นาที
}
$out = [];
foreach ($banks as $b) {
if (!in_array('sender', $b['side'], true)) {
continue;
}
$out[] = [
'value' => $b['bank_code'],
'label' => $b['name_th'],
'disabled' => ($b['available'] !== true),
'hint' => $b['available']
? null
: ($b['message_th'] ?? 'ธนาคารนี้ปิดรับรายการใหม่ชั่วคราว'),
];
}
return $out;
}ธนาคารถูกปิดแล้วเกิดอะไรขึ้นบ้าง
การปิดธนาคารกระทบแค่ 2 อย่าง
POST /v1/pay/depositsที่ใช้ธนาคารนั้นเป็นบัญชีผู้โอน →409 sender_bank_unavailable- ค่า
availableในGET /v1/pay/banksเปลี่ยนเป็นfalse
🔴 รายการที่เปิดอยู่แล้วไม่ขยับแม้แต่ใบเดียว — ไม่มี event ไม่มี version ใหม่ ไม่มีการยกเลิกอัตโนมัติ · และ สลิปของรายการเดิมยังส่งได้เสมอ ต่อให้ธนาคารต้นทางเพิ่งถูกปิดไปเมื่อกี้
⇒ อย่าเขียน job ที่ไล่ยกเลิกรายการค้างเมื่อเห็นธนาคารถูกปิด นั่นคือการปิดทางผู้ใช้ที่กำลังจะโอน
รับมือกับ 409 sender_bank_unavailable
{
"error": {
"code": "sender_bank_unavailable",
"type": "invalid_request_error",
"message": "The sending bank is not accepting new deposits right now.",
"message_th": "ธนาคารต้นทางปิดรับรายการใหม่ชั่วคราว",
"retryable": true,
"retry_after_seconds": null,
"request_id": "req_01JQA5TH6U8W0Y2A4C6E8G0J2L",
"details": {
"bank_code": "025",
"retry_after_hint_seconds": 1800,
"public_message_th": "ธนาคารกรุงศรีอยุธยาปิดปรับปรุงระบบชั่วคราว กรุณาเลือกธนาคารอื่น",
"alternative_banks": ["002", "004", "014"]
}
}
}details | เอาไปทำอะไร |
|---|---|
public_message_th | แสดงให้ผู้ใช้เห็นตรง ๆ ได้เลย ไม่ต้องเขียนเอง |
alternative_banks[] | เลื่อนธนาคารเหล่านี้ขึ้นบนสุดของลิสต์ให้ผู้ใช้เลือกต่อทันที |
retry_after_hint_seconds | คาดว่าอีกนานเท่าไหร่จะกลับมา — ใช้ตัดสินใจว่าจะบอกให้ "ลองใหม่ภายหลัง" หรือ "เลือกธนาคารอื่น" |
// Node — พาผู้ใช้ไปต่อแทนการโชว์ error เปล่า
catch (err) {
if (err.code === 'sender_bank_unavailable') {
await refreshBankCache() // ค่าใน cache เก่าไปแล้ว
return ui.pickAnotherBank({
message: err.details.public_message_th,
highlight: err.details.alternative_banks, // เลื่อนขึ้นบนสุด
})
}
throw err
}available: false กับ BANK_DISABLED คนละเรื่องกัน
สองอย่างนี้ชื่อคล้ายกันจนสับสน แต่เกิดคนละจุดและต้องรับมือคนละแบบ
available: false | reasons: ['BANK_DISABLED'] | |
|---|---|---|
| เห็นที่ไหน | GET /v1/pay/banks | deposit.under_review หลังอัปสลิป |
| เกิดตอนไหน | ก่อนสร้างรายการ | หลังผู้ใช้โอนและอัปสลิปแล้ว |
| ผลกับรายการ | สร้างไม่ได้ → 409 | รายการเข้าคิวเจ้าหน้าที่ แล้วเจ้าหน้าที่ตรวจด้วยตนเอง |
| เงินเป็นยังไง | ยังไม่มีเงินเกี่ยวข้อง | โอนไปแล้ว 🔴 ไม่หาย และไม่ถูกปฏิเสธอัตโนมัติ |
| คุณต้องทำ | เทาปุ่มในฟอร์ม | แสดง review.reasons[0].display_th · ห้ามมีปุ่ม "โอนใหม่" |
⇒ ธนาคารที่ available: true ก็ยังทำให้รายการเข้าคิว BANK_DISABLED ได้ เพราะการตรวจสลิปอัตโนมัติของธนาคารนั้นอาจถูกปิดชั่วคราวแยกจากการเปิดรับรายการใหม่ · นี่ไม่ใช่ error และไม่ใช่การปฏิเสธเงิน
เช็กก่อนขึ้น production
- [ ] ฟอร์มบัญชีผู้โอนดึงลิสต์จาก
GET /v1/pay/banksไม่ใช่จากค่าคงที่ในโค้ด - [ ] กรองด้วย
sideให้เหลือเฉพาะธนาคารที่ใช้เป็นบัญชีผู้โอนได้ - [ ]
available: false→ เทา ไม่ใช่ซ่อน · แสดงmessage_thถ้ามี - [ ] โค้ดรองรับกรณีที่ ไม่มี key
message_th(ไม่ใช่แค่กรณีnull) - [ ] cache 5 นาที และ fallback เป็นค่าเดิมเมื่อ refresh พลาด
- [ ]
409 sender_bank_unavailableพาผู้ใช้ไปเลือกจากalternative_banks[]ไม่ใช่ขึ้น error เปล่า - [ ] ไม่มี job ที่ยกเลิกรายการค้างเมื่อธนาคารถูกปิด
ขั้นต่อไป
- บัญชีผู้โอน — ฟอร์มที่ใช้ลิสต์นี้ และวิธีลดการเข้าคิวเจ้าหน้าที่
- สร้างรายการฝาก —
409 sender_bank_unavailableและ error อื่นทั้งหมด - อัปโหลดสลิป — จุดที่
BANK_DISABLEDโผล่มา - สถานะรายการฝาก —
under_reviewต้องแสดงยังไงให้ผู้ใช้ไม่โอนซ้ำ
