python promptpay

หากเราสแกน QR ที่ใช้จ่ายเงินตอนนี้ด้วยแอปพลิเคชั่นสแกน QR โดยเฉพาะ ไม่ใช่แอปของธนาคาร จะเห็นตัวเลขและข้อความยาวๆ เป็นชุดเดียว เช่นตัวอย่าง "00020101021129370016A000000677010111011300660000000005802TH530376463048956" (ตัวอย่างนี้ใช้ข้อมูลหลอก ด้วยหมายเลขโทรศัพท์ 000-000-0000)

แนวทางการอ่านข้อมูลของมาตรฐาน EMVco คือ หมายเลขประจำฟิลด์ข้อมูล (เป็น 00-99), ความยาวของข้อมูลในฟิลด์นั้น (เป็น 01-99), และตัวข้อมูลจริงๆ การอ่านข้อมูลตัวอย่างจะทำเป็นขั้นได้ดังนี้

    • หมายเลขเวอร์ชั่น ฟิลด์ 00 ความยาว 02 ข้อมูลตอนนี้คือ 01 เสมอ ดังนั้นข้อมูลชุดแรกคือ "000201"

    • ประเภทของ QR ฟิลด์ 01 ความยาว 02 ข้อมูล "11" แปลว่า QR นี้สร้างขึ้นเพื่อใช้สำหรับการขายหลายครั้ง เช่นระบุผู้ขาย หรือระบุราคาสินค้า ถ้าเป็น 12 คือ QR สร้างขึ้นเพื่อใช้ครั้งเดียว เช่น เป็นการจ่ายสำหรับใบเสร็จใบเดียว

    • ข้อมูลผู้ขาย (merchant account information) ฟิลด์ 29 ความยาว 37 ข้อมูล "0016A00000067701011101130066000000000" โดยข้อมูลนี้แบ่งออกเป็นสองฟิลด์ย่อย

        • หมายเลขแอปพลิเคชั่น (application ID - AID) เป็นหมายเลขที่ปกติแล้วใช้อ้างอิงประเภทบัตรสมาร์ตการ์ดแบบต่างๆ ตั้งแต่บัตรประชาชนไปจนถึงบัตรเครดิตทั้งหลาย ในกรณีนี้มีการนำหมายเลขนี้มาใช้ใน QR เพื่อระบุว่า QR นี้เป็น PromptPay หมายเลขฟิลด์ย่อย 00 ความยาว 16 ข้อมูล "A000000677010111"

        • หมายเลขบัญชี เป็นหมายเลข PromptPay โดยตรง ตอนนี้มีหมายเลขฟิลด์ เช่น

        • 01 หมายเลขโทรศัพท์ ความยาว 13 นำหน้าด้วย 00 แล้วตามด้วยรหัสประเทศ 66 แล้วจึงเป็นหมายเลขโทรศัพท์ตัดศูนย์นำหน้าออก 00-000-0000

        • 02 หมายเลขบัตรประชาชนไม่มีขีดคั่น

    • ประเทศ ฟิลด์ 58 ความยาว 02 ข้อมูล "TH" หมายถึงประเทศไทย

    • สกุลเงินที่ใช้งาน ฟิลด์ 53 ความยาว 03 ข้อมูล "764" โดยหมายเลข 764 เป็นหมายเลขประจำค่าเงินบาทตาม ISO 4217

    • ค่า check sum ฟิลด์ 63 ความยาว 04 ข้อมูล "8956" ข้อมูลนี้ต้องอยู่ท้ายสุดเสมอ โดยค่า check sum เป็นการคำนวณจากข้อมูลทั้งหมด รวมถึงหมายเลขฟิลด์ของ check sum และความยาวของฟิลด์ check sum เอง กระบวนการหาค่า check sum ใช้ CRC-16 และ ค่าคงที่ polynomial 0x1021 (XMODEM) พร้อมกับค่าเริ่มต้น 0xFFFF (อันนี้ต้องระวังเพราะไลบรารีส่วนมากมักใส่ค่าเริ่มต้นเป็น 0x0000) ตัวไลบรารีสามารถใช้ pycrc16 ได้โดยตรง

ตัวอย่างการหาค่า check sum ด้วย crc16pure.py เช่น

In [7]: hex(crc16pure.crc16xmodem("00020101021129370016A000000677010111011300660000000005802TH53037646304",0xffff)) Out[7]: '0x8956'

สังเกตว่าหมายเลขฟิลด์กระโดดจาก 01 ซึ่งเป็นฟิลด์บังคับ ไปยัง 29 ทันที เพราะหมายเลข 02-25 นั้นสงวนไว้สำหรับผู้ให้บริการบัตรเครดิตโดยเฉพาะ เช่น 02-03 สำหรับ VISA, 04-05 สำหรับ Mastercard, 06-08, 11-12 สำหรับ Amex, 13-14 สำหรับ JCB, และ 15-16 สำหรับ UnionPay ที่เหลือทาง EMVco ก็ให้เว้นไว้สำหรับการใช้งานในอนาคต

เราได้ข้อมูลหลายอย่างจากการดูว่ามีข้อมูลอยู่ใน QR เช่น

    • ตัว QR ยังคงแสดงข้อมูลหมายเลข PromptPay เช่นเดิม ซึ่งอาจจะหมายถึงหมายเลขโทรศัพท์หรือหมายเลขบัตรประชาชน หากใครมีความไม่สะดวกใจที่จะให้หมายเลขทั้งสองกับผู้ซื้อก็อาจจะต้องระวัง

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

    • มีความเป็นไปได้ที่จะสร้าง QR เหล่านี้โดยอัตโนมัติ ในเร็วๆ นี้เราคงได้เห็นไลบรารีโอเพนซอร์ส หรือบริการต่างๆ เพื่อสร้าง QR ทั้งระบุและไม่ระบุจำนวนเงิน ความเป็นไปได้อื่นๆ ของบริการนี้คงมีอีกมาก เช่นร้านค้าออนไลน์อาจจะเริ่มรับเงินผ่าน PromptPay กันมากขึ้นเพราะสามารถส่ง QR ระบุจำนวนเงินไปในแชตหรือเว็บอีคอมเมิร์ชได้โดยตรง

หลังจากที่ทางธนาคารแห่งประเทศไทยได้เปิดตัว PromptPay QR ออกมาแล้ว และยังได้เปิดเอกสารมาตรฐาน "EMVCo QR Code Specification for Payment Systems: Merchant-Presented Mode" ออกมาด้วย ผมจึงนำข้อมูลดังกล่าวและจาก https://www.blognone.com/node/95133 มาสร้างเป็นโค้ดสำหรับสร้าง QR code ของ PromptPay กับ Python ครับ

อ่านคำอธิบายสเปค PromptPay QR ได้จาก https://www.blognone.com/node/95133

อธิบายเพิ่มเติมที่เนื้อหาใน Blognone ไม่มี

สำหรับการกำหนดจำนวนเงินที่ต้องการจ่าย

จำนวนเงินที่ต้องการจ่าย ฟิลด์ 54 ความยาวข้อมูล 01-09 ข้อมูล "จำนวนเงินที่มีทศนิยม 2 ตำแหน่ง"

เช่น 50 บาท ได้เป็น 540450.00

การเช็คค่า check sum ต้องเอาไว้ข้างหลังท้ายข้อมูลเท่านั้น

ในการเช็คค่า check sum ผมแนะนำให้ใช้ libscrc เป็นโมดูลสำหรับคำนวณ CRC8/CRC16/CRC32/CRC64 ใช้ MIT License และรองรับทั้ง Python 2 , Python 3

ติดตั้งได้ด้วยคำสั่ง pip install libscrc

มาลงมือโค้ดกัน

import libscrc

def text_qr(account,one_time=True,country="TH",money="",currency="THB"):

"""

text_qr(account,one_time=True,country="TH",money="",currency="THB")

account is phone number or identification number.

one_time : if you use once than it's True.

country : TH

money : money (if have)

currency : THB

"""

Version = "0002"+"01" # เวชั่นของ PromptPay

if one_time==True: # one_time คือ ต้องการให้โค้ดนี้ครั้งเดียวหรือไม่

one_time="010212" # 12 ใช้ครั้งเดียว

else:

one_time="010211" # 11 ใช้ได้้หลายครั้ง

merchant_account_information="2937" # ข้อมูลผู้ขาย

merchant_account_information+="0016"+"A000000677010111" # หมายเลขแอปพลิเคชั่น PromptPay

if len(account)!=13: # ใช้บัญชีใช้เป็นเบอร์มือถือหรือไม่ ถ้าใช่ จำนวนจะไม่เท่ากับ 13

account=list(account)

merchant_account_information+="011300" # 01 หมายเลขโทรศัพท์ ความยาว 13 ขึ้นต้น 00

if country=="TH":

merchant_account_information+="66" # รหัสประเทศ 66 คือประเทศไทย

del account[0] # ตัดเลข 0 หน้าเบอร์ออก

merchant_account_information+=''.join(account)

else:

merchant_account_information+="02"+account.replace('-','') # กรณีที่ไม่รับมือถือ แสดงว่าเป็นเลขบัตรประชาชน

country="5802"+country # ประเทศ

if currency=="THB":

currency="5303"+"764" # "764" คือเงินบาทไทย ตาม https://en.wikipedia.org/wiki/ISO_4217

if money!="": # กรณีกำหนดเงิน

check_money=money.split('.') # แยกจาก .

if len(check_money)==1 or len(check_money[1])==1: # กรณีที่ไม่มี . หรือ มีทศนิยมแค่หลักเดียว

money="54"+"0"+str(len(str(float(money)))+1)+str(float(money))+"0"

else:

money="54"+"0"+str(len(str(float(money))))+str(float(money)) # กรณีที่มีทศนิยมครบ

check_sum=Version+one_time+merchant_account_information+country+currency+money+"6304" # เช็คค่า check sum

check_sum1=hex(libscrc.ccitt(check_sum.encode("ascii"),0xffff)).replace('0x','')

if len(check_sum1)<4: # # แก้ไขข้อมูล check_sum ไม่ครบ 4 หลัก

check_sum1=("0"*(4-len(check_sum1)))+check_sum1

check_sum+=check_sum1

return check_sum.upper() # upper ใช้คืนค่าสตริงเป็นตัวพิมพ์ใหญ่

pypromptpay.py hosted with ❤ by GitHub เมื่อลองเรียกใช้ ใส่เบอร์ 0000000000 ลงไป กำหนด 50.56 บาท ใช้ได้ครั้งเดียว

data=text_qr("0000000000",one_time=True,money="50.56")

print(data)

ผลลัพธ์

00020101021229370016A000000677010111011300660000000005802TH5303764540550.566304013E

สามารถนำไปสร้าง QR code ได้โดยใช้โมดูล qrcode อ่านได้จาก https://python3.wannaphong.com/2015/04/สร้าง-qr-code-บน-python.html/

ทำการสร้าง QR code

import qrcode

img = qrcode.make(data)

imgload = open('img-data.png','wb') #สร้างไฟล์ไบต์ใหม่ขึ้นมา กำหนดสิทธิ์เขียนไฟล์ได้

img.save(imgload, 'PNG') #บันทึกค่า QR Code เข้าไปยังไฟล์

imgload.close() #ปิดไฟล์

หากผู้อ่านไม่ต้องการลงมือโค้ดหรือ set ค่าอะไรให้ยุ่งวาย ผมได้ทำโมดูล pypromptpay ไว้แล้ว สามารถใช้ได้ง่าย ๆ โดยติดตั้งด้วยคำสั่ง pip install pypromptpay

การใช้งาน

from pypromptpay import qr_code

qr_code("0000000000",one_time=True,money="50.56",path_qr_code="b.png")

อ่านเอกสารการใช้งาน pypromptpay ได้ที่ https://github.com/wannaphong/pypromptpay