BotKit
บทความ › รับเงินออนไลน์

QR พร้อมเพย์ระบุยอดทำงานยังไง: แกะโครงสร้าง EMVCo ทีละช่อง พร้อมโค้ด Python

QR พร้อมเพย์ที่สแกนจ่ายกันทุกวันไม่ได้มีอะไรลึกลับ ข้างในเป็นแค่ข้อความตัวอักษรหนึ่งบรรทัด ที่จัดรูปแบบตามมาตรฐาน EMVCo QR Code (มาตรฐานเดียวกับที่หลายประเทศใช้) แล้วแปลงเป็นรูป QR เมื่อเข้าใจโครงสร้าง เราสร้าง QR ระบุยอดเงินเองได้ทั้งหมดในโค้ดไม่กี่สิบบรรทัด ไม่ต้องส่งเบอร์และยอดเงิน ไปให้บริการภายนอก

ผมใช้วิธีนี้ในระบบร้านค้าออนไลน์ที่ออก QR ตามยอดของแต่ละออเดอร์ บทความนี้จะแกะทีละช่อง

ตัวอย่าง payload

นี่คือ payload ของ QR สำหรับโอนเข้าเบอร์ 081-234-5678 ยอด 100 บาท (เบอร์สมมติ)

00020101021229370016A0000006770101110113006681234567853037645406100.005802TH6304F142

ดูเหมือนตัวอักษรมั่ว แต่จริงๆ คือข้อมูลหลายช่องต่อกัน

รูปแบบ TLV: Tag – Length – Value

ทุกช่องเขียนแบบเดียวกันหมด

  • Tag 2 หลัก — บอกว่าช่องนี้คือข้อมูลอะไร
  • Length 2 หลัก — ค่าข้างหลังยาวกี่ตัวอักษร
  • Value — ตัวค่าจริง ยาวตาม length

เช่น 5802TH แปลว่า tag 58 (ประเทศ) ยาว 02 ตัว ค่าคือ TH

แกะทีละช่อง

ส่วนTagความหมาย
00020100Payload Format Indicator ค่าคงที่ 01
01021201ประเภท QR: 11 = ใช้ซ้ำได้ (ไม่ระบุยอด) · 12 = ใช้ครั้งเดียว (ระบุยอด)
2937…29ข้อมูลผู้รับเงินพร้อมเพย์ ยาว 37 ตัว ข้างในเป็น TLV ซ้อนอีกชั้น (ดูด้านล่าง)
530376453สกุลเงิน รหัส ISO 4217 ของเงินบาทคือ 764
5406100.0054ยอดเงิน 100.00 (ใส่เฉพาะ QR ระบุยอด)
5802TH58ประเทศ
6304F14263CRC ตัวตรวจสอบความถูกต้องของข้อความทั้งหมด

ข้างใน tag 29

0016A000000677010111   → tag 00 ยาว 16: Application ID ของพร้อมเพย์
01130066812345678      → tag 01 ยาว 13: เบอร์มือถือ

เบอร์มือถือต้องแปลงก่อน: ตัด 0 ตัวหน้าออก แล้วเติมรหัสประเทศ 0066 จาก 0812345678 จึงกลายเป็น 0066812345678 (13 หลัก) ถ้าผู้รับใช้เลขบัตรประชาชน ให้ใช้ tag 02 ใส่เลข 13 หลักตรงๆ และเลข e-Wallet 15 หลักใช้ tag 03

CRC: ช่องที่ทำให้ QR สแกนไม่ผ่านบ่อยที่สุด

4 ตัวสุดท้ายคือค่า CRC16-CCITT (polynomial 0x1021, ค่าเริ่มต้น 0xFFFF) คำนวณจากทุกตัวอักษรข้างหน้า รวมถึง 6304 ของตัวมันเองด้วย จุดที่คนพลาดบ่อยคือคำนวณ CRC ก่อนต่อ 6304 แอปธนาคารจะขึ้นว่า QR ไม่ถูกต้อง

โค้ด Python ฉบับเต็ม

import re

AID_PROMPTPAY = "A000000677010111"

def tlv(tag: str, value: str) -> str:
    return f"{tag}{len(value):02d}{value}"

def crc16(data: str) -> str:
    crc = 0xFFFF
    for ch in data.encode("ascii"):
        crc ^= ch << 8
        for _ in range(8):
            crc = ((crc << 1) ^ 0x1021) & 0xFFFF if crc & 0x8000 else (crc << 1) & 0xFFFF
    return f"{crc:04X}"

def promptpay_payload(target: str, amount: float | None = None) -> str:
    digits = re.sub(r"\D", "", target)
    if len(digits) == 10 and digits.startswith("0"):
        tag, value = "01", "0066" + digits[1:]      # เบอร์มือถือ
    elif len(digits) == 13:
        tag, value = "02", digits                   # เลขบัตรประชาชน
    else:
        raise ValueError("ต้องเป็นเบอร์มือถือ 10 หลัก หรือเลขบัตร 13 หลัก")

    parts = [
        tlv("00", "01"),
        tlv("01", "12" if amount else "11"),
        tlv("29", tlv("00", AID_PROMPTPAY) + tlv(tag, value)),
        tlv("53", "764"),
    ]
    if amount:
        parts.append(tlv("54", f"{amount:.2f}"))
    parts.append(tlv("58", "TH"))
    body = "".join(parts) + "6304"                  # ต่อ 6304 ก่อนคำนวณ
    return body + crc16(body)

print(promptpay_payload("081-234-5678", 100))

เอาสตริงที่ได้ไปทำรูป QR ด้วยไลบรารี qrcode (pip install qrcode[pil])

import qrcode
qrcode.make(promptpay_payload("0812345678", 100)).save("pay.png")

ทดสอบให้มั่นใจก่อนใช้จริง

  1. สแกนด้วยแอปธนาคารอย่างน้อย 2 ธนาคาร ต้องขึ้นชื่อผู้รับถูกและยอดถูก (สแกนดูได้โดยไม่ต้องกดยืนยันโอน)
  2. ลองยอดที่มีสตางค์ เช่น 99.50 และยอดหลักพันขึ้นไป
  3. ใช้ QR ประเภท 12 (ใช้ครั้งเดียว) กับออเดอร์ที่ระบุยอด กันลูกค้าสแกน QR เดิมจ่ายซ้ำ

QR ไม่ได้บอกว่าเงินเข้าแล้ว

สิ่งที่ต้องเข้าใจคือ QR พร้อมเพย์แบบนี้ทำแค่ขั้น "สร้างคำขอให้โอน" มันไม่ได้แจ้งกลับมาว่าลูกค้าโอนแล้ว ระบบที่หน้าจอเปลี่ยนสถานะเองเมื่อเงินเข้า ต้องมีแหล่งข้อมูลยืนยันการรับเงินอีกชั้น เช่น บริการรับชำระเงินของธนาคารหรือ payment gateway ที่ส่ง webhook มาเมื่อมีเงินเข้า หรือการตรวจสลิปผ่านบริการที่ได้รับอนุญาต อย่าเชื่อแค่ว่าลูกค้ากดปุ่ม "โอนแล้ว"

💡 เลขพร้อมเพย์และยอดเงินเป็นข้อมูลการเงิน การสร้าง payload เองบนเซิร์ฟเวอร์ของเรา ช่วยไม่ให้ข้อมูลนี้ถูกส่งไปให้บริการสร้าง QR ภายนอกโดยไม่จำเป็น
เกี่ยวกับผู้เขียน — Jark นักพัฒนา LINE Bot และระบบ AI อัตโนมัติ อยู่เชียงใหม่ ทำระบบตอบแชทและจองคิวให้ร้านค้าจริง บทความในเว็บนี้มาจากงานที่ลงมือทำเอง อ่านเพิ่ม