|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace vansales; |
|
4
|
|
|
|
|
5
|
|
|
require_once './vendor/autoload.php'; |
|
6
|
|
|
|
|
7
|
|
|
use Endroid\QrCode\QrCode; |
|
8
|
|
|
|
|
9
|
|
|
/** |
|
10
|
|
|
* Original code logic from https://github.com/kittinan/php-promptpay-qr |
|
11
|
|
|
* More information https://www.vansalesapp.com |
|
12
|
|
|
*/ |
|
13
|
|
|
|
|
14
|
|
|
class PromptPay |
|
15
|
|
|
{ |
|
16
|
|
|
|
|
17
|
|
|
const ID_PAYLOAD_FORMAT = '00'; |
|
18
|
|
|
const ID_POI_METHOD = '01'; |
|
19
|
|
|
const ID_MERCHANT_INFORMATION_BOT = '29'; |
|
20
|
|
|
const ID_TRANSACTION_CURRENCY = '53'; |
|
21
|
|
|
const ID_TRANSACTION_AMOUNT = '54'; |
|
22
|
|
|
const ID_COUNTRY_CODE = '58'; |
|
23
|
|
|
const ID_CRC = '63'; |
|
24
|
|
|
|
|
25
|
|
|
const PAYLOAD_FORMAT_EMV_QRCPS_MERCHANT_PRESENTED_MODE = '01'; |
|
26
|
|
|
const POI_METHOD_STATIC = '11'; |
|
27
|
|
|
const POI_METHOD_DYNAMIC = '12'; |
|
28
|
|
|
const MERCHANT_INFORMATION_TEMPLATE_ID_GUID = '00'; |
|
29
|
|
|
const BOT_ID_MERCHANT_PHONE_NUMBER = '01'; |
|
30
|
|
|
const BOT_ID_MERCHANT_TAX_ID = '02'; |
|
31
|
|
|
const BOT_ID_MERCHANT_EWALLET_ID = '03'; |
|
32
|
|
|
const GUID_PROMPTPAY = 'A000000677010111'; |
|
33
|
|
|
const TRANSACTION_CURRENCY_THB = '764'; |
|
34
|
|
|
const COUNTRY_CODE_TH = 'TH'; |
|
35
|
|
|
|
|
36
|
|
|
public function generatePayload($target, $amount = null) |
|
37
|
|
|
{ |
|
38
|
|
|
|
|
39
|
|
|
$target = $this->sanitizeTarget($target); |
|
40
|
|
|
|
|
41
|
|
|
$targetType = strlen($target) >= 15 ? self::BOT_ID_MERCHANT_EWALLET_ID : (strlen($target) >= 13 ? self::BOT_ID_MERCHANT_TAX_ID : self::BOT_ID_MERCHANT_PHONE_NUMBER); |
|
42
|
|
|
|
|
43
|
|
|
$data = [ |
|
44
|
|
|
$this->f(self::ID_PAYLOAD_FORMAT, self::PAYLOAD_FORMAT_EMV_QRCPS_MERCHANT_PRESENTED_MODE), |
|
45
|
|
|
$this->f(self::ID_POI_METHOD, $amount ? self::POI_METHOD_DYNAMIC : self::POI_METHOD_STATIC), |
|
46
|
|
|
$this->f(self::ID_MERCHANT_INFORMATION_BOT, $this->serialize([ |
|
47
|
|
|
$this->f(self::MERCHANT_INFORMATION_TEMPLATE_ID_GUID, self::GUID_PROMPTPAY), |
|
48
|
|
|
$this->f($targetType, $this->formatTarget($target)) |
|
49
|
|
|
])), |
|
50
|
|
|
$this->f(self::ID_COUNTRY_CODE, self::COUNTRY_CODE_TH), |
|
51
|
|
|
$this->f(self::ID_TRANSACTION_CURRENCY, self::TRANSACTION_CURRENCY_THB), |
|
52
|
|
|
]; |
|
53
|
|
|
|
|
54
|
|
|
if ($amount !== null) { |
|
55
|
|
|
array_push($data, $this->f(self::ID_TRANSACTION_AMOUNT, $this->formatAmount($amount))); |
|
56
|
|
|
} |
|
57
|
|
|
|
|
58
|
|
|
$dataToCrc = $this->serialize($data) . self::ID_CRC . '04'; |
|
59
|
|
|
array_push($data, $this->f(self::ID_CRC, $this->CRC16HexDigest($dataToCrc))); |
|
60
|
|
|
return $this->serialize($data); |
|
61
|
|
|
} |
|
62
|
|
|
|
|
63
|
|
|
public function f($id, $value) |
|
64
|
|
|
{ |
|
65
|
|
|
return implode('', [$id, substr('00' . strlen($value), -2), $value]); |
|
66
|
|
|
} |
|
67
|
|
|
|
|
68
|
|
|
public function serialize($xs) |
|
69
|
|
|
{ |
|
70
|
|
|
return implode('', $xs); |
|
71
|
|
|
} |
|
72
|
|
|
|
|
73
|
1 |
|
public function sanitizeTarget($str) |
|
74
|
|
|
{ |
|
75
|
1 |
|
$str = preg_replace('/[^0-9]/', '', $str); |
|
76
|
1 |
|
return $str; |
|
77
|
|
|
} |
|
78
|
|
|
|
|
79
|
1 |
|
public function formatTarget($target) |
|
80
|
|
|
{ |
|
81
|
|
|
|
|
82
|
1 |
|
$str = $this->sanitizeTarget($target); |
|
83
|
1 |
|
if (strlen($str) >= 13) { |
|
84
|
1 |
|
return $str; |
|
85
|
|
|
} |
|
86
|
|
|
|
|
87
|
1 |
|
$str = preg_replace('/^0/', '66', $str); |
|
88
|
1 |
|
$str = '0000000000000' . $str; |
|
89
|
|
|
|
|
90
|
1 |
|
return substr($str, -13); |
|
91
|
|
|
} |
|
92
|
|
|
|
|
93
|
1 |
|
public function formatAmount($amount) |
|
94
|
|
|
{ |
|
95
|
1 |
|
return number_format($amount, 2, '.', ''); |
|
96
|
|
|
} |
|
97
|
|
|
|
|
98
|
1 |
|
public function crc16($data) |
|
99
|
|
|
{ |
|
100
|
1 |
|
$crc = 0xFFFF; |
|
101
|
1 |
|
for ($i = 0; $i < strlen($data); $i++) { |
|
102
|
1 |
|
$x = (($crc >> 8) ^ ord($data[(string)$i])) & 0xFF; |
|
103
|
1 |
|
$x ^= $x >> 4; |
|
104
|
1 |
|
$crc = (($crc << 8) ^ ($x << 12) ^ ($x << 5) ^ $x) & 0xFFFF; |
|
105
|
|
|
} |
|
106
|
1 |
|
return $crc; |
|
107
|
|
|
} |
|
108
|
|
|
|
|
109
|
|
|
/* |
|
110
|
|
|
* Returns CRC16 of a string as hexadecimal string |
|
111
|
|
|
*/ |
|
112
|
1 |
|
public function CRC16HexDigest($str) |
|
113
|
|
|
{ |
|
114
|
1 |
|
return sprintf('%04X', $this->crc16($str)); |
|
115
|
|
|
} |
|
116
|
|
|
|
|
117
|
|
|
|
|
118
|
|
|
public function generateQrCode($target, $amount = 0) |
|
119
|
|
|
{ |
|
120
|
|
|
|
|
121
|
|
|
$payload = $this->generatePayload($target, $amount); |
|
122
|
|
|
|
|
123
|
|
|
$qrCode = new QrCode($payload); |
|
124
|
|
|
header('Content-Type: ' . $qrCode->getContentType()); |
|
125
|
|
|
echo $qrCode->writeString(); |
|
126
|
|
|
} |
|
127
|
|
|
} |
|
128
|
|
|
|