Passed
Push — master ( 074861...06b341 )
by Monthon
01:39
created

PromptPay::crc16()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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