# Gateway Starter Kit — Local Sandbox ชุดฝึกเชื่อมต่อฝาก–ถอนบนเครื่องเดียวกัน ใช้ Node.js 20 ขึ้นไป ไม่มี npm dependencies ไม่อ่าน production environment, ไม่ต่อฐานข้อมูล Gateway และไม่มี client สำหรับธนาคารหรือผู้ให้บริการเงินจริง ## เริ่มใช้ ดาวน์โหลด `sandbox.cjs` และ `merchant-example.cjs` ไว้ในโฟลเดอร์เดียวกัน เปิด terminal สองหน้าต่างในโฟลเดอร์นั้น: ```sh # Terminal 1 node sandbox.cjs ``` ```sh # Terminal 2 (เปิดหลัง Sandbox เริ่มทำงานแล้ว) node merchant-example.cjs ``` 1. เปิด Sandbox Console ที่ http://127.0.0.1:4545 2. เปิดเว็บร้านที่ http://127.0.0.1:4546 แล้วกด **ตรวจ API จาก backend** 3. ใน Sandbox ตั้ง Webhook URL เป็น `http://127.0.0.1:4546/webhook` แล้วบันทึก 4. กด **ส่ง Test Webhook** ดู HTTP 200 และเครื่องหมายผ่านในเว็บร้าน 5. สร้าง deposit จากเว็บร้าน ไป Sandbox กด `paid` แล้วตรวจออเดอร์/ยอดลูกค้า 6. สร้าง withdraw จากเว็บร้าน ไป Sandbox กด `paid` หรือ `rejected` แล้วตรวจยอด 7. ส่งคำขอเดิมและ webhook ซ้ำ ต้องไม่สร้างออเดอร์หรือเปลี่ยนยอดซ้ำ หากเริ่มจาก repository ใช้ `npm run dev:sandbox` และ `npm run dev:merchant-example` ใน `gateway/` ดูหน้า Guide ในเครื่องด้วย `npm run dev:starter` แล้วเปิด `http://127.0.0.1:4544/docs/starter` ## ต่อ backend ของคุณ เปิดค่าตั้งค่า Test ใน Sandbox Console: - Base URL: `http://127.0.0.1:4545` - API key: `gw_test_...` ส่งใน `X-Api-Key` เฉพาะจาก backend - Webhook secret: `whsec_test_...` ใช้ตรวจ `X-Gateway-Signature` - `Content-Type: application/json` และ `Idempotency-Key` สำหรับสร้างรายการ Endpoints ที่รองรับ: | Method | Path | ผล | |---|---|---| | GET | `/v1/balance` | ยอดร้านจำลอง | | POST | `/v1/payments` | 201 pending, replay 200 พร้อม ID เดิม | | GET | `/v1/payments/{id}` | สถานะล่าสุด | | POST | `/v1/payments/{id}/cancel` | ยกเลิก; paid คืน 409 | | POST | `/v1/withdrawals` | 202 queued; ยอดไม่พอคืน 422 rejected โดยไม่มี webhook | | GET | `/v1/withdrawals/{id}` | สถานะล่าสุด | | GET | `/v1/withdrawals?merchant_ref=...` | ค้นด้วยเลขอ้างอิง | `recipient.tax_id` และ `recipient.email` ละฟิลด์หรือส่ง `""` ได้ หากส่งค่าที่ไม่ว่างต้องเป็นเลข 13 หลักและอีเมลที่ถูกต้องตามลำดับ Sandbox ตรวจรูปแบบเหมือน Gateway; บางเส้นทาง Waylar อาจบังคับข้อมูลตอนสร้างหรือยืนยันออเดอร์ ใช้ payload ตามหน้า Starter Guide หรือสเปก Quest ฝาก 20–100,000 บาท ถอน 300–30,000 บาท ขาถอนต้องส่งข้อมูลผู้รับครบ ใช้ข้อมูลสมมติเท่านั้น Deposit key เดิมเปลี่ยนยอดคืน 409; withdraw key/ref เดิมเปลี่ยนข้อมูลคืน 409 ### ควบคุมผลจำลอง ปุ่มใน Console เรียก local control endpoints ต่อไปนี้ (ไม่ใช่ production API): - `POST /sandbox/config` body `{"webhook_url":"http://127.0.0.1:4546/webhook"}` - `POST /sandbox/fund` body `{"amount":10000}` เพิ่มเฉพาะยอดร้านจำลอง - `POST /sandbox/probe` ส่ง `sandbox.test` ตรวจการรับและลายเซ็น ไม่ลงยอด - `POST /sandbox/payments/{id}/simulate` body `{"status":"paid"}` หรือ `expired`, `cancelled` - `POST /sandbox/withdrawals/{id}/simulate` body `{"status":"paid"}` หรือ `rejected`, `unknown`, `processing` - `POST /sandbox/deliveries/{id}/replay` ส่ง delivery ID และ payload เดิมซ้ำ Local sandbox เปลี่ยนสถานะด้วย control endpoints ไม่ใช้ `test_scenario` ในคำขอสร้างรายการ รายการหมดอายุเปลี่ยนเป็น paid ได้เพื่อทดสอบ `late: true`; paid/cancelled และ paid/rejected เป็นสถานะสุดท้ายของแต่ละขา unknown/processing ยังกันยอดไว้ จนกดจำลอง paid/rejected ### Webhook HMAC-SHA256 ของ raw body พร้อม prefix `sha256=` เช่น production contract ส่ง `X-Gateway-Event`, `X-Gateway-Delivery`, `X-Gateway-Signature` ตอบ 2xx หลังบันทึกสำเร็จ ตัวอย่างร้านตรวจลายเซ็น ตรวจยอด/อ้างอิง และ GET สถานะก่อนเปลี่ยนยอด กันซ้ำด้วยสถานะธุรกิจของออเดอร์ ไม่เชื่อถือ delivery header เพียงอย่างเดียว เพราะ header นี้ไม่ถูกเซ็น Retry ทดสอบแบบเร่ง 1, 2, 5, 10 วินาที รวมสูงสุด 5 ครั้ง Timeout 5 วินาที และไม่ตาม redirect Webhook delivery HTTP 200 ไม่ได้พิสูจน์ว่าระบบร้านอื่นอัปเดตยอดถูกต้อง ต้องตรวจฐานข้อมูลของร้านด้วย ทดสอบจ่ายล่าช้าโดยสร้าง deposit จากเว็บร้าน แล้วกด `expired` → `paid` ใน Sandbox เว็บร้านจะแสดง `Webhook payment.paid: late: true` ใต้ออเดอร์ และ `✓ Late deposit` เมื่อออเดอร์ถูกเครดิตแล้ว ดู `payload.late` ในหัวข้อ **Webhook ที่ผ่านการตรวจ** ได้ ค่านี้เก็บจาก webhook ที่ตรวจลายเซ็นแล้ว ไม่อนุมานจาก GET ข้อมูลจากเวอร์ชันเก่าที่เก็บเฉพาะ digest/event/ref จะยังไม่มีหลักฐานนี้ ให้กด replay webhook เดิมใน Sandbox ระบบจะเติม payload จากข้อความที่ตรวจสอบจริง พร้อมเวลา `payload_received_at` โดยไม่ลงยอดซ้ำ ## ข้อมูลและยอด - Sandbox เก็บไฟล์ `.gateway-sandbox.json` รวม Test key, ยอดร้าน, รายการ และ webhook queue - ตัวอย่างร้านเก็บ `.gateway-merchant.json` รวมยอดลูกค้า ออเดอร์ และผลรับ webhook - ทั้งสองเริ่ม 10,000 บาทคนละยอด ค่าฝากตัวอย่าง 1.6%; ค่าถอน 0.2% ปัดค่าถอนขึ้นถึงสตางค์ - ฝาก 500: ยอดลูกค้าเพิ่ม 500, ยอดร้านเพิ่ม 492; ถอน 500: ลูกค้ากันยอด 500, ร้านกันยอด 501 - รีสตาร์ตแล้วอ่านข้อมูลเดิม และ retry delivery ที่ส่งค้างไว้ - ไฟล์ข้อมูลร้านผูกกับ Test key ของ Sandbox ชุดเดิม หากใช้ key ชุดใหม่ให้ตั้ง MERCHANT_STATE_FILE ใหม่ด้วย - รันหนึ่ง process ต่อไฟล์ข้อมูล เพื่อเริ่มชุดใหม่ให้หยุดทั้งสอง process แล้วตั้งชื่อไฟล์ใหม่ให้ทั้งคู่ ไม่ใช้ Test key เก่าร่วมกับข้อมูลชุดใหม่ - ไม่ commit ไฟล์ข้อมูล; มี Test credentials อยู่ในไฟล์ แม้ใช้กับเงินจริงไม่ได้ - จำกัดแต่ละขา 1,000 รายการ และ 3,000 deliveries ต่อชุด ให้เริ่มไฟล์ชุดใหม่เมื่อเต็ม ## ตั้ง port และชื่อไฟล์ Environment variables (ตั้งใน terminal ของแต่ละ process): | Variable | ค่าเริ่มต้น | ใช้กับ | |---|---|---| | SANDBOX_PORT | 4545 | Sandbox | | SANDBOX_STATE_FILE | `.gateway-sandbox.json` | ทั้งคู่; ร้านอ่าน Test credentials | | MERCHANT_PORT | 4546 | ร้าน | | SANDBOX_URL | `http://127.0.0.1:4545` | ร้าน | | MERCHANT_STATE_FILE | `.gateway-merchant.json` | ร้าน | | SANDBOX_API_KEY | อ่านจากไฟล์ Sandbox | ร้าน; override Test key | | SANDBOX_WEBHOOK_SECRET | อ่านจากไฟล์ Sandbox | ร้าน; override Test secret | หากเปลี่ยน port ให้เปิด URL ตาม terminal และตั้ง Webhook URL ให้ตรง ลิงก์ตัวอย่างใน Guide ใช้ port ค่าเริ่มต้น ลิงก์ **เปิด Sandbox Console** ในเว็บร้านใช้ origin ของ `SANDBOX_URL` ที่ backend ตั้งไว้ ตัวอย่างสำหรับ macOS/Linux: `SANDBOX_PORT=5545 node sandbox.cjs` PowerShell: `$env:SANDBOX_PORT='5545'; node sandbox.cjs` ## หมายเหตุสำหรับ Browser QA / E2E Sandbox และเว็บร้านปฏิเสธ request ที่มี `Sec-Fetch-Site: cross-site` ด้วย `403 local_origin_required` การเปิดผ่าน Chrome extension บางตัวอาจส่ง header นี้แม้ URL เป็น loopback ตามที่พบในการ QA ให้เปิดหน้า Guide ใน browser แล้วคลิกลิงก์ไป Sandbox/เว็บร้านบน `127.0.0.1` เดียวกัน หรือเปิดจาก address bar ตามปกติ ตรวจ navigation context ของเครื่องมือเมื่อเจอ 403 นี้ ไม่ปิด origin guard หรือแก้ header เพื่อข้ามการตรวจ การทดสอบ HTTP ระหว่าง backend ใช้ loopback client โดยตรงได้; ทดสอบกรณี cross-site แยกและคาดหวัง 403 ## ขอบเขตและ Production ชุดนี้เป็น local sandbox แบบร้านเดียว รับเฉพาะ loopback และไม่ส่ง webhook ออกไปอินเทอร์เน็ต ยังไม่ใช่ hosted sandbox สำหรับ backend ที่ deploy แล้ว และไม่มี QR ที่ชำระเงินจริงได้ ไม่มีการจำลองธนาคารจริง, provider latency, rate limits, IP allowlist, settlement หรือการยืนยันเจ้าของบัญชี สถานะไม่หมดอายุอัตโนมัติ ค่าธรรมเนียม/ยอดขั้นต่ำเป็นค่าตัวอย่าง ไม่ใช่เรท runtime ของทุกร้าน ตัวอย่างร้านใช้ไฟล์ JSON และการเขียน atomic ภายใน process เดียว เพื่อเรียนรู้ flow เท่านั้น สำหรับ backend จริงใช้ database transaction, unique constraints, durable queue, authentication ของลูกค้า และการกระทบยอดตามระบบของร้าน โดยเฉพาะเมื่อรันหลาย process/เครื่อง ตัวอย่างร้านตั้งใจไม่รองรับ live key หรือ production URL ให้ใช้ backend ของร้านเองสำหรับ Live ค่า API key/secret สำหรับ Live ต้องตั้งแยก รวมถึงฐานข้อมูลและ HTTPS webhook endpoint การผ่าน Mock ไม่ใช่การเปิดสิทธิ์ถอนหรือหลักฐานทดสอบเงินจริง หน้า Guide: `/docs/starter` · สเปก Production: `/docs/quest`