1.11 · การชำระผ่าน KBANK K-Cash
บทนี้อธิบาย integration กับธนาคารกสิกรไทย (KBANK) ผ่านระบบ K-Cash — channel หลักที่ VTRC ใช้จ่ายเงินสวัสดิการให้พนักงานเป็น batch
flow นี้เป็นการทำงานแบบ asynchronous + manual — ไม่ใช่ real-time API call แต่เป็น "export file → bank processes → import file"
แผนภาพ flow ทั้งรอบ
1. HR เลือก WDHospital หลายรายการ (status = APPROVE, bank = KBANK)
│
▼
2. createExportWDHospital(wdHospitalIds, ...)
- สร้าง WDExport row
- stamp wdExportId ในแต่ละ WDHospital
│
▼
3. HR download zip ผ่าน fetchUrlExportWdHospital
- ไฟล์: ${exportCode}_KBank_welfare.txt
- ไฟล์: ${exportCode}_Smartcredit_welfare.txt
- ไฟล์: ${exportCode}_FMIS.xlsx
│
│ (HR upload ไฟล์ KBank_welfare.txt ที่ระบบ K-Cash ของธนาคาร)
▼
4. ธนาคารกสิกรไทยประมวลผล (T+1 ถึง T+3 วันทำการ)
│
▼
5. ธนาคารส่งไฟล์ผลกลับมา
│
▼
6. HR upload ไฟล์ผลผ่าน importUpdateStatus(file)
- VTRC parse ไฟล์
- map K-Cash code → VTRC status
- update WDHospital + WDHistory
- ถ้าครบทุก row → WDExport.isImport = 1
│
▼
7. ส่ง notification ให้พนักงาน (SUCCESS หรือ FAILED — จาก statusKcash)WDExport — ติดตาม batch
vtrc-api/api/src/models/core/withdraw/export.js:1-78
| Field | หน้าที่ |
|---|---|
exportCode | รหัส batch (ใช้ในชื่อไฟล์ + ใน KBANK header) |
years | ปีงบประมาณ |
payDate | วันที่กำหนดจ่าย |
totalExport | จำนวนรายการที่ส่ง |
totalImport | จำนวนรายการที่ประมวลผลแล้ว |
totalCurrentWithdraw | ยอดเงินรวม |
isImport | 0/1 — flip เป็น 1 เมื่อ totalExport == totalImport |
isCancel | 0/1 — HR cancel batch นี้ |
cancelNote / cancelBy / cancelDate | audit ตอน cancel |
wdHospitalIds (TEXT) | list ของ id ตอนสร้าง batch |
exportBy (UUID) | userId ของ HR ที่สร้าง |
K-Cash status map — หัวใจของ integration
vtrc-api/api/src/config.js:488-519 — statusKcash map
| K-Cash code | EN | TH | VTRC status |
|---|---|---|---|
6 | ISSUED and/or STOPPAY WITH DUPLICATE | การดำเนินการและ/หรือหยุดการจ่ายจากการทำซ้ำ | FAILED |
7 | CANCELLED | ยกเลิกรายการ | FAILED |
8 | STOPPAY | หยุดการจ่าย | FAILED |
9 | PAID (label เท่านั้น) | โอนสำเร็จ | SUCCESS |
10 | UNSUCCESS CREDIT | โอนไม่สำเร็จ | FAILED |
สำคัญ — code 9 มี label: 'PAID' / labelTH: 'โอนสำเร็จ' แต่ฟิลด์ status ที่เขียนลง WDHospital คือ SUCCESS (config.js:508-511) — ไม่ใช่ document status PAID แยกจาก import
ถ้าเจอ code อื่นที่ไม่อยู่ใน map — VTRC จะไม่อัปเดต status (row นั้นค้าง) — เป็น edge case ที่ HR ต้องดูด้วยตัวเอง
Export zip — 3 ไฟล์ทำงานคนละอย่าง
vtrc-api/api/src/routes.js:776-811
| ไฟล์ | Encoding | หน้าที่ | ผู้รับ |
|---|---|---|---|
${exportCode}_KBank_welfare.txt | TIS-620 | K-Cash payroll input (หัวหลัก) | KBANK K-Cash |
${exportCode}_Smartcredit_welfare.txt | (default) | SmartCredit input (อีกระบบของ KBANK) | KBANK SmartCredit |
${exportCode}_FMIS.xlsx | binary | FMIS accounting report | ฝ่ายบัญชีสภากาชาด |
เงื่อนไข — KBank_welfare.txt สร้างเฉพาะเมื่อ length > 179 (routes.js:803) — ถ้า batch ว่างไฟล์นี้จะไม่อยู่ใน zip
การนำเข้าผล — Algorithm
lib/controllers/withdraw/hospital.js:2160-2316 — importUpdateStatus(file)
1. Parse ไฟล์ (แยกบรรทัด)
2. หา row header (header == 'h') → ระบุ exportCode
3. สำหรับ detail row แต่ละ row:
a. อ่าน element.status (K-Cash code), element.effectivePayDate,
element.totalCurrentWithdraw
b. map status ผ่าน statusKcash → ได้ VTRC status (SUCCESS หรือ FAILED)
c. validate ยอด: totalCurrentWithdraw ในไฟล์ต้องตรงกับ DB
ถ้าไม่ตรง → reject ทั้ง batch, error "ข้อมูลยอดโอน...ไม่ตรงกับไฟล์นำจ่าย"
d. update WDHospital:
- importStatus = element.status (raw K-Cash code)
- status = statusByKcash.status
- importLabel = statusByKcash.labelTH
- effectivePayDate = element.effectivePayDate
e. write WDHistory row
f. write WDHospital snapshot (isHistory = 1)
4. ถ้า wdExportData.totalExport == totalImport → WDExport.isImport = 1
5. return { status, exportCode, withdrawSuccess, withdrawFailed,
transactionSuccess, transactionFailed }ไม่มี cron — manual operator action
ข้อสำคัญ — ไม่มี cron ดึงผล K-Cash อัตโนมัติ
KBANK ไม่ expose API สำหรับดึงผล → HR ต้อง download ไฟล์ผลจากระบบ K-Cash ด้วยตนเอง แล้ว upload เข้า VTRC ผ่าน importUpdateStatus
ถ้า HR ลืม upload — WDHospital ทั้ง batch จะค้าง APPROVE ทั้งที่เงินเข้าบัญชีพนักงานแล้วจริง
Cancel batch — ก่อน import
cancelExportWDHospital (wdHospital.js:398)
HR สามารถ cancel batch export ทั้ง batch ถ้ายังไม่ import ผล
- ทุก WDHospital ใน batch reset status กลับ
APPROVE(hospital.js:2393) WDExport.isCancel = 1- บันทึก
cancelNote,cancelBy,cancelDate
หลัง import แล้ว — ไม่สามารถ cancel batch ได้ ต้องจัดการที่ละ row (เช่น mark CANCELDOC)
Channel คู่ขนาน — เงินสด
บางกรณี HR จ่ายเงินสดแทนผ่านธนาคาร — cashFlg = 1, bank = 'OTHER'
| status | หมายถึง |
|---|---|
CASH | รอจ่ายสด |
PAIDCASH | จ่ายสดแล้ว |
flow เงินสดไม่ผ่าน KBANK — HR mark ด้วยตนเองในหน้า backoffice (payCashBy, payCashDate, effectivePayDate)
ตัวอย่าง edge cases
| สถานการณ์ | ผล |
|---|---|
KBANK ส่ง code 9 (label PAID) แต่ยอดในบัญชีไม่ตรง | VTRC mark SUCCESS เพราะ map ใน statusKcash — ถ้ามีปัญหาฝั่งธนาคาร VTRC ไม่รู้ |
KBANK ส่ง code 8 (STOPPAY) | VTRC mark FAILED → HR ต้องตรวจสาเหตุแล้ว retry |
| HR upload ไฟล์ผลของ batch อื่นโดยผิด | exportCode ใน header ไม่ตรง → import ปฏิเสธ |
| HR upload ซ้ำ batch เดิม | WDHospital update ซ้ำด้วยข้อมูลเดิม — ไม่มี idempotency check |
| บัญชีพนักงานปิดไปแล้ว | KBANK return code 10 (UNSUCCESS) → VTRC mark FAILED → HR แจ้งพนักงาน |
การตรวจสอบสถานะ batch
HR ใช้ query fetchListExportWDHospital เพื่อ list batch ทั้งหมด แล้วกรองตาม
isImport = 0→ batch ที่ยังไม่ปิดisCancel = 0→ batch ที่ยัง active- วันที่สร้าง, วันที่กำหนดจ่าย
รายงานหลังจ่าย
PDF/Excel reports — lib/controllers/withdraw/hospital.template.js
| Query | Template |
|---|---|
fetchUrlFailPaymentPDF | failPaymentReportTemplate — list รายการ FAILED |
fetchUrlWDHospitalPaidPDFReport | sumWDHospitalPaidTemplate — สรุปจ่ายแล้ว |
fetchUrlAllWithdrawPDFReport | overview + variants |
รายงานเหล่านี้ HR ใช้ส่งให้ฝ่ายบัญชีและ audit — รายละเอียดใน บท 1.14
ข้อควรระวังตอน debug
| อาการ | สาเหตุ | ที่ตรวจ |
|---|---|---|
| "KBANK ไม่อัปเดต" | HR ยังไม่ upload ไฟล์ผล | ถาม HR ก่อน |
| "ยอดไม่ตรง" | batch มี row ที่ยอดในไฟล์ ≠ DB | check totalCurrentWithdraw |
| "Status ค้าง" | K-Cash code ไม่อยู่ใน map | ดู statusKcash |
| "พนักงานไม่ได้รับ notification" | FCM fail หรือ token หมดอายุ | check notification_fail table |
| "Duplicate import" | ไม่มี idempotency — ระวัง | ต้อง track เอง |