เวลาให้ลูกค้าโอนเงิน QR พร้อมเพย์ที่ใส่ยอดไว้ให้ช่วยได้มาก ลูกค้าสแกนแล้วยอดกับชื่อผู้รับขึ้นมาเอง ไม่ต้องพิมพ์เลขบัญชีหรือยอดเงิน ปัญหาโอนขาดหรือโอนเกินเพราะพิมพ์ผิดก็หายไปเกือบหมด ระบบของร้านสร้าง QR นี้เองได้ ไม่ต้องสมัครบริการไหนและไม่มีค่าธรรมเนียม เพราะ QR พร้อมเพย์เป็นแค่ข้อความตามมาตรฐานที่เปิดเผย
บนเว็บนี้มีเครื่องมือ สร้าง QR พร้อมเพย์สำหรับทดสอบ ที่แยก payload ให้ดูทีละฟิลด์ โพสต์นี้อธิบายว่าแต่ละฟิลด์คืออะไร และจุดไหนที่ระบบมักสร้างผิด
QR พร้อมเพย์คือข้อความหนึ่งบรรทัดตามมาตรฐาน EMVCo
ภาพ QR ที่เราเห็นเก็บข้อความไว้บรรทัดเดียว หน้าตาแบบนี้ (QR ของเบอร์ 080-123-4567 ไม่ระบุยอด)
00020101021129370016A000000677010111011300668012345675802TH530376463046197
ข้อความนี้ใช้รูปแบบของ EMVCo Merchant-Presented QR มาตรฐานกลางที่ธนาคารหลายประเทศใช้ทำ QR รับเงิน แต่ละฟิลด์เขียนเป็นสามส่วนต่อกัน คือเลขฟิลด์ 2 หลัก ความยาวของค่า 2 หลัก แล้วตามด้วยค่า เช่น 5802TH คือฟิลด์ 58 ยาว 02 ค่า TH พอรู้กติกานี้ ข้อความยาว ๆ ข้างบนก็แยกออกมาได้แบบนี้
| ส่วนของข้อความ | ฟิลด์ | ความหมาย |
|---|---|---|
000201 |
00 |
รูปแบบข้อความ เป็น 01 เสมอ |
010211 |
01 |
11 คือ QR ที่ไม่ระบุยอด ถ้าระบุยอดจะเป็น 12 |
2937 0016A000000677010111 01130066801234567 |
29 |
ข้อมูลผู้รับ ข้างในซ้อนฟิลด์ย่อยอีกชั้น คือรหัสของพร้อมเพย์ แล้วตามด้วยเบอร์ของผู้รับ |
5802TH |
58 |
ประเทศไทย |
5303764 |
53 |
สกุลเงินบาท รหัส 764 ตาม ISO 4217 |
| (ไม่มีในตัวอย่างนี้) | 54 |
ยอดเงิน ใส่เฉพาะ QR ที่ระบุยอด |
6304 6197 |
63 |
CRC สี่ตัวอักษรปิดท้าย ใช้ตรวจว่าข้อความไม่เสีย |
ในฟิลด์ 29 เลขฟิลด์ย่อยของผู้รับบอกชนิดของผู้รับ 01 คือเบอร์มือถือ 02 คือเลขประจำตัว 13 หลัก ใช้ได้ทั้งเลขบัตรประชาชนและเลขประจำตัวผู้เสียภาษีของบริษัท และ 03 คือ e-Wallet ID 15 หลัก ลำดับฟิลด์ทั้งหมดเราเรียงตาม dtinth/promptpay-qr ไลบรารีที่แอปธนาคารไทยสแกนได้มาตั้งแต่ปี 2017 จึงวาง 58 ไว้ก่อน 53
เบอร์มือถือต้องเขียนเป็น 0066 ตามด้วยเบอร์ที่ตัด 0 ออก
จุดนี้พลาดกันบ่อยที่สุด เบอร์ 080-123-4567 ต้องเขียนเป็น 0066801234567 คือรหัสประเทศ 66 นำหน้าด้วย 00 แล้วตามด้วยเบอร์ที่ตัด 0 ตัวแรกออก ผลคือ 13 ตัวอักษรพอดี เขียนเป็น +66801234567 หรือ 066801234567 แล้วแอปธนาคารจะสแกนไม่ผ่าน
ระบบที่รับเบอร์หรือเลขประจำตัวจากหน้าตั้งค่าจึงควรล้างทุกอย่างที่ไม่ใช่ตัวเลขออกก่อน แล้วดูจากจำนวนหลักว่าเป็นอะไร
- 10 หลักที่ขึ้นต้นด้วย 0 หรือ 11 หลักที่ขึ้นต้นด้วย 66 เป็นเบอร์มือถือ แปลงเป็นรูป
0066… - 13 หลัก เป็นเลขประจำตัว ใช้ตามนั้นเลย
- 15 หลัก เป็น e-Wallet ID
เลข 13 หลักเป็นได้ทั้งเลขบัตรประชาชนและเลขผู้เสียภาษีของบริษัท QR ใช้ฟิลด์ย่อยเดียวกัน จึงไม่ต้องแยก แต่เราเช็กหลักตรวจสอบตัวสุดท้ายได้ ถ้าไม่ผ่านก็น่าจะพิมพ์ผิด วิธีคำนวณอยู่ในโพสต์ ตรวจเลขบัตรประชาชน 13 หลักด้วย ThaiIdCardRule ซึ่งเลขผู้เสียภาษีของบริษัทใช้สูตรเดียวกัน
ยอดเงินเขียนเป็นทศนิยมสองตำแหน่ง และยาวไม่เกิน 13 ตัวอักษร
พอใส่ยอด ฟิลด์ 01 จะเปลี่ยนจาก 11 เป็น 12 แล้วเพิ่มฟิลด์ 54 ที่เก็บยอดเป็นตัวอักษร เช่น 4.22 ใช้จุดเป็นทศนิยม ไม่มี comma ไม่มีสัญลักษณ์เงิน และยาวรวมจุดได้ไม่เกิน 13 ตัวอักษร
เราเขียนยอดเป็นทศนิยมสองตำแหน่งเสมอ และปฏิเสธค่าที่มีทศนิยมเกินสองตำแหน่งแทนการปัดให้เอง ยอด 1.005 ที่ส่งเข้ามาน่าจะผิดตั้งแต่ต้น ถ้าระบบปัดเงียบ ๆ ลูกค้าจะเห็นยอดที่ไม่ตรงกับใบสั่งซื้อ
CRC ท้ายข้อความคำนวณจากทุกตัวอักษรข้างหน้า
สี่ตัวอักษรสุดท้ายคือ CRC-16 แบบ CCITT-FALSE (polynomial 0x1021 ค่าเริ่ม 0xFFFF) ใช้กันข้อความเสียระหว่างสแกน จุดที่ต้องระวังคือ CRC คำนวณจากทุกอย่างข้างหน้า รวม 6304 ที่เป็นเลขฟิลด์และความยาวของ CRC เอง ต้องต่อ 6304 เข้าไปก่อน แล้วค่อยคำนวณ
ทั้ง payload จึงสร้างได้จากสองเรื่องนี้ คือเขียนแต่ละฟิลด์ตามกติกาเดียวกัน แล้วปิดท้ายด้วย CRC
// ทุกฟิลด์คือ เลขฟิลด์ + ความยาว 2 หลัก + ค่า
function field(tag, value) {
return `${tag}${String(value.length).padStart(2, '0')}${value}`;
}
// ต่อ 6304 ก่อน แล้วคำนวณ CRC จากทั้งก้อน
const withCrcTag = `${fields}6304`;
return withCrcTag + crc16(withCrcTag);
crc16() เป็นลูปมาตรฐานราวสิบบรรทัด หาได้จากเอกสารของ CRC-16/CCITT-FALSE ทั่วไป ฝั่ง PHP ก็เขียนตามได้แทบบรรทัดต่อบรรทัด ถ้า CRC ผิดแม้แต่ตัวเดียว แอปธนาคารจะบอกแค่ว่า QR ไม่ถูกต้อง ไม่บอกเหตุผล เวลาหาสาเหตุจึงควรเริ่มที่ CRC ก่อน
ได้ข้อความแล้วค่อยแปลงเป็นภาพ QR ด้วยไลบรารีทั่วไป เครื่องมือบนเว็บนี้ใช้ qrcode-generator จาก npm ฝั่ง PHP ก็มีให้เลือกหลายตัว เช่น endroid/qr-code
เทียบกับข้อความที่รู้คำตอบอยู่แล้วก่อนใช้จริง
โค้ดแบบนี้ผิดนิดเดียวก็สแกนไม่ได้ทั้งใบ และเราไม่อยากเปิดแอปธนาคารสแกนทุกครั้งที่แก้โค้ด ทางที่คุ้มที่สุดคือเทียบกับคำตอบที่รู้แล้ว อย่างแรกคือค่าตรวจสอบมาตรฐานของ CRC-16/CCITT-FALSE ที่ข้อความ 123456789 ต้องได้ 29B1 อย่างที่สองคือ payload ตัวอย่างใน test ของ dtinth/promptpay-qr ที่แอปธนาคารสแกนได้จริง ถ้าระบบเรามีทั้งฝั่ง JavaScript และ PHP ก็ใช้ข้อความชุดเดียวกันเทียบได้ทั้งสองฝั่ง ผลต้องตรงกันทุกตัวอักษร ถ้าอยากตั้ง test ฝั่ง JavaScript ในโปรเจกต์ Laravel ลองดู Vitest สำหรับ React ใน Inertia
แล้วก่อนเปิดให้ลูกค้าใช้ ให้ลองสแกนจริงด้วยแอปธนาคารหนึ่งครั้ง ดูว่าชื่อผู้รับกับยอดขึ้นถูก
QR ที่สร้างเองยังไม่บอกว่าเงินเข้าแล้ว
QR แบบนี้มีแค่ผู้รับกับยอด ไม่มีเลขคำสั่งซื้อ และไม่มีใครเรียกระบบเรากลับมาตอนลูกค้าจ่ายเสร็จ เงินเข้าบัญชีของร้านตามปกติ แต่ระบบไม่รู้
ถ้าอยากให้ระบบรู้เอง มีสองทาง ทางแรกคือให้ลูกค้าส่งสลิปแล้วตรวจกับธนาคาร ทางที่สองคือใช้ QR ที่ gateway หรือธนาคารสร้างให้ ซึ่งผูกเลขอ้างอิงของรายการไว้ แล้วแจ้งผลกลับมาเมื่อจ่ายสำเร็จ สองทางนี้เทียบไว้ใน สามทางรับเงินในเว็บขายของออนไลน์
กรณีที่อาจเจอตอนใช้งานจริง
- QR ใช้ได้แต่ชื่อผู้รับไม่ใช่ร้าน แอปธนาคารแสดงชื่อที่ผูกกับเบอร์หรือเลขประจำตัวนั้นไว้ในระบบพร้อมเพย์ ถ้าใส่เบอร์ส่วนตัวของพนักงานไว้ในหน้าตั้งค่า ลูกค้าจะเห็นชื่อพนักงาน ควรใช้เลขผู้เสียภาษีของบริษัทที่ผูกกับบัญชีบริษัท
- ลูกค้าพิมพ์เบอร์เป็น +66 ระบบที่ล้างตัวอักษรอื่นออกแล้วดูจำนวนหลักจะรับได้ทั้ง
0801234567,080-123-4567และ+66 80 123 4567 - ยอดมีเศษสตางค์จากการคำนวณ ยอดที่คูณส่วนลดแล้วได้
1349.995ควรปัดตั้งแต่ตอนคำนวณใบสั่งซื้อ แล้วส่งยอดที่ปัดแล้วมาสร้าง QR ส่วนที่สร้าง QR ไม่ควรปัดเอง - QR ที่ระบบเราสร้างสแกนไม่ได้ ใส่เบอร์กับยอดเดียวกันในเครื่องมือ สร้าง QR พร้อมเพย์ แล้วเทียบข้อความกับของระบบเราทีละฟิลด์ ส่วนใหญ่จะต่างกันที่รูปแบบเบอร์ ทศนิยมของยอด หรือ CRC






