1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace KS; |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* Inspired and code logic from https://github.com/dtinth/promptpay-qr |
7
|
|
|
* More information https://www.blognone.com/node/95133 |
8
|
|
|
*/ |
9
|
|
|
class PromptPay { |
10
|
|
|
|
11
|
|
|
const ID_PAYLOAD_FORMAT = '00'; |
12
|
|
|
const ID_POI_METHOD = '01'; |
13
|
|
|
const ID_MERCHANT_INFORMATION_BOT = '29'; |
14
|
|
|
const ID_TRANSACTION_CURRENCY = '53'; |
15
|
|
|
const ID_TRANSACTION_AMOUNT = '54'; |
16
|
|
|
const ID_COUNTRY_CODE = '58'; |
17
|
|
|
const ID_CRC = '63'; |
18
|
|
|
|
19
|
|
|
const PAYLOAD_FORMAT_EMV_QRCPS_MERCHANT_PRESENTED_MODE = '01'; |
20
|
|
|
const POI_METHOD_STATIC = '11'; |
21
|
|
|
const POI_METHOD_DYNAMIC = '12'; |
22
|
|
|
const MERCHANT_INFORMATION_TEMPLATE_ID_GUID = '00'; |
23
|
|
|
const BOT_ID_MERCHANT_PHONE_NUMBER = '01'; |
24
|
|
|
const BOT_ID_MERCHANT_TAX_ID = '02'; |
25
|
|
|
const GUID_PROMPTPAY = 'A000000677010111'; |
26
|
|
|
const TRANSACTION_CURRENCY_THB = '764'; |
27
|
|
|
const COUNTRY_CODE_TH = 'TH'; |
28
|
|
|
|
29
|
2 |
|
public function generatePayload($target, $amount = null) { |
30
|
|
|
|
31
|
2 |
|
$target = $this->sanitizeTarget($target); |
32
|
|
|
|
33
|
2 |
|
$targetType = strlen($target) >= 13 ? self::BOT_ID_MERCHANT_TAX_ID : self::BOT_ID_MERCHANT_PHONE_NUMBER; |
34
|
|
|
|
35
|
|
|
$data = [ |
36
|
2 |
|
$this->f(self::ID_PAYLOAD_FORMAT, self::PAYLOAD_FORMAT_EMV_QRCPS_MERCHANT_PRESENTED_MODE), |
37
|
2 |
|
$this->f(self::ID_POI_METHOD, $amount ? self::POI_METHOD_DYNAMIC : self::POI_METHOD_STATIC), |
38
|
2 |
|
$this->f(self::ID_MERCHANT_INFORMATION_BOT, $this->serialize([ |
39
|
2 |
|
$this->f(self::MERCHANT_INFORMATION_TEMPLATE_ID_GUID, self::GUID_PROMPTPAY), |
40
|
2 |
|
$this->f($targetType, $this->formatTarget($target)) |
41
|
|
|
])), |
42
|
2 |
|
$this->f(self::ID_COUNTRY_CODE, self::COUNTRY_CODE_TH), |
43
|
2 |
|
$this->f(self::ID_TRANSACTION_CURRENCY, self::TRANSACTION_CURRENCY_THB), |
44
|
|
|
]; |
45
|
|
|
|
46
|
2 |
|
if ($amount !== null) { |
47
|
2 |
|
array_push($data, $this->f(self::ID_TRANSACTION_AMOUNT, $this->formatAmount($amount))); |
48
|
|
|
} |
49
|
|
|
|
50
|
2 |
|
$dataToCrc = $this->serialize($data) . self::ID_CRC . '04'; |
51
|
2 |
|
array_push($data, $this->f(self::ID_CRC, $this->crc16($dataToCrc))); |
52
|
2 |
|
return $this->serialize($data); |
53
|
|
|
} |
54
|
|
|
|
55
|
3 |
|
public function f($id, $value) { |
56
|
3 |
|
return implode('', [$id, substr('00' . strlen($value), -2), $value]); |
57
|
|
|
} |
58
|
|
|
|
59
|
2 |
|
public function serialize($xs) { |
60
|
2 |
|
return implode('', $xs); |
61
|
|
|
} |
62
|
|
|
|
63
|
3 |
|
public function sanitizeTarget($str) { |
64
|
3 |
|
$str = preg_replace('/[^0-9]/', '', $str); |
65
|
3 |
|
return $str; |
66
|
|
|
} |
67
|
|
|
|
68
|
3 |
|
public function formatTarget($target) { |
69
|
|
|
|
70
|
3 |
|
$str = $this->sanitizeTarget($target); |
71
|
3 |
|
$str = preg_replace('/^0/', '66', $str); |
72
|
3 |
|
$str = '0000000000000' . $str; |
73
|
|
|
|
74
|
3 |
|
return substr($str, -13); |
75
|
|
|
} |
76
|
|
|
|
77
|
3 |
|
public function formatAmount($amount) { |
78
|
3 |
|
return number_format($amount, 2, '.', ''); |
79
|
|
|
} |
80
|
|
|
|
81
|
3 |
|
public function crc16($data) { |
82
|
3 |
|
$crc16 = new \mermshaus\CRC\CRC16CCITT(); |
83
|
3 |
|
$crc16->update($data); |
84
|
3 |
|
$checksum = $crc16->finish(); |
85
|
3 |
|
return strtoupper(bin2hex($checksum)); |
86
|
|
|
} |
87
|
|
|
|
88
|
1 |
|
public function generateQrCode($savePath, $target, $amount = null, $width = 256, $height = 256) { |
89
|
|
|
|
90
|
1 |
|
$payload = $this->generatePayload($target, $amount); |
91
|
|
|
|
92
|
1 |
|
$renderer = new \BaconQrCode\Renderer\Image\Png(); |
93
|
1 |
|
$renderer->setHeight($width); |
94
|
1 |
|
$renderer->setWidth($height); |
95
|
1 |
|
$renderer->setMargin(0); |
96
|
1 |
|
$writer = new \BaconQrCode\Writer($renderer); |
97
|
1 |
|
$writer->writeFile($payload, $savePath); |
98
|
1 |
|
} |
99
|
|
|
|
100
|
|
|
} |
101
|
|
|
|