thai_qr_pay 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/thai_qr_pay/generate/promptpay/any_id.rb +2 -2
- data/lib/thai_qr_pay/generate/slip_verify.rb +25 -0
- data/lib/thai_qr_pay/generate/true_money.rb +45 -0
- data/lib/thai_qr_pay/generate/true_money_slip_verify.rb +25 -0
- data/lib/thai_qr_pay/version.rb +1 -1
- data/spec/generate/slip_verify_spec.rb +36 -0
- data/spec/generate/true_money_slip_verify_spec.rb +33 -0
- data/spec/generate/true_money_spec.rb +79 -0
- data/spec/parser_spec.rb +1 -1
- data/spec/thai_qr_pay_spec.rb +1 -1
- metadata +7 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9e83575512379820fcca739aa2ab4573f63f78c354aded6b6537e3fc70c4b0a
|
4
|
+
data.tar.gz: 19caeb222f1a43ef42d2cc6438f928f6b519e5f7df0b04647e3f79ae2546f416
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fde0294a52a30ab085e58dbbe24bd2bcf1535b14b2196b2af37651133d2625a0ecb66cf7e0047c56424f23f10ef548796e29d5cc7d39a342d90ec8b6ec651c98
|
7
|
+
data.tar.gz: 1e4dcc046453c564d9a77c176ef3cd8a662ae832351888887c13085349df84408d00c891223d3c8f86f8864c69a7e029fd5c46a0f86ad947b57493d8c215bb65
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thai_qr_pay/tlv'
|
4
|
+
require 'thai_qr_pay/crc16'
|
5
|
+
|
6
|
+
module ThaiQrPay
|
7
|
+
module Generate
|
8
|
+
# Generates an EMVCo “slip verify” (mini-QR) payload
|
9
|
+
# Tag 00 = sub-TLV [000001, sendingBank, transRef], Tag 51 = 'TH', CRC tag = '91'
|
10
|
+
class SlipVerify
|
11
|
+
def self.payload(sending_bank:, trans_ref:)
|
12
|
+
inner = [
|
13
|
+
TLV.tag('00', '000001'),
|
14
|
+
TLV.tag('01', sending_bank),
|
15
|
+
TLV.tag('02', trans_ref)
|
16
|
+
]
|
17
|
+
payload = [
|
18
|
+
TLV.tag('00', TLV.encode(inner)),
|
19
|
+
TLV.tag('51', 'TH')
|
20
|
+
]
|
21
|
+
CRC16.with_crc(TLV.encode(payload), '91')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thai_qr_pay/tlv'
|
4
|
+
require 'thai_qr_pay/crc16'
|
5
|
+
|
6
|
+
module ThaiQrPay
|
7
|
+
module Generate
|
8
|
+
# Generates a TrueMoney “wallet” PromptPay-compatible QR
|
9
|
+
# Tag 29 = sub-TLV [A000000677010111, '03'+mobileNo], optional Tag 54 & Tag 81, CRC tag = '63'
|
10
|
+
class TrueMoney
|
11
|
+
def self.payload(mobile_no:, amount: nil, message: nil)
|
12
|
+
tag29 = TLV.encode([
|
13
|
+
TLV.tag('00', 'A000000677010111'),
|
14
|
+
TLV.tag('03', "14000#{mobile_no}")
|
15
|
+
])
|
16
|
+
|
17
|
+
main = [
|
18
|
+
TLV.tag('00', '01'),
|
19
|
+
TLV.tag('01', amount ? '12' : '11'),
|
20
|
+
TLV.tag('29', tag29),
|
21
|
+
TLV.tag('53', '764'),
|
22
|
+
TLV.tag('58', 'TH')
|
23
|
+
]
|
24
|
+
main << TLV.tag('54', format('%.2f', amount)) if amount
|
25
|
+
|
26
|
+
if message
|
27
|
+
# Tag 81 is UCS-2 BE hex of the message
|
28
|
+
encoded = encode_tag81(message)
|
29
|
+
main << TLV.tag('81', encoded)
|
30
|
+
end
|
31
|
+
|
32
|
+
CRC16.with_crc(TLV.encode(main), '63')
|
33
|
+
end
|
34
|
+
|
35
|
+
# @api private
|
36
|
+
def self.encode_tag81(message)
|
37
|
+
# TS: Buffer.from(msg,'utf16le').swap16().toString('hex').toUpperCase()
|
38
|
+
utf16le = message.encode('utf-16le')
|
39
|
+
# swap each pair of bytes
|
40
|
+
be_bytes = utf16le.bytes.each_slice(2).flat_map { |lo, hi| [hi, lo] }
|
41
|
+
be_bytes.pack('C*').unpack1('H*').upcase
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thai_qr_pay/tlv'
|
4
|
+
require 'thai_qr_pay/crc16'
|
5
|
+
|
6
|
+
module ThaiQrPay
|
7
|
+
module Generate
|
8
|
+
# Generates a TrueMoney-specific slip-verify QR
|
9
|
+
# Tag 00 = sub-TLV [01,01,eventType,transactionId,date], CRC tag = '91'
|
10
|
+
class TrueMoneySlipVerify
|
11
|
+
def self.payload(event_type:, transaction_id:, date:)
|
12
|
+
inner = [
|
13
|
+
TLV.tag('00', '01'),
|
14
|
+
TLV.tag('01', '01'),
|
15
|
+
TLV.tag('02', event_type),
|
16
|
+
TLV.tag('03', transaction_id),
|
17
|
+
TLV.tag('04', date)
|
18
|
+
]
|
19
|
+
payload = [TLV.tag('00', TLV.encode(inner))]
|
20
|
+
# per TS, CRC-tag '91', case-sensitive (we output uppercase)
|
21
|
+
CRC16.with_crc(TLV.encode(payload), '91')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/thai_qr_pay/version.rb
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thai_qr_pay/generate/slip_verify'
|
4
|
+
require 'thai_qr_pay/tlv'
|
5
|
+
require 'thai_qr_pay/crc16'
|
6
|
+
|
7
|
+
RSpec.describe ThaiQrPay::Generate::SlipVerify do
|
8
|
+
let(:bank) { 'KBank' }
|
9
|
+
let(:ref) { 'ABC123' }
|
10
|
+
|
11
|
+
subject(:payload) { described_class.payload(sending_bank: bank, trans_ref: ref) }
|
12
|
+
|
13
|
+
it 'returns a string' do
|
14
|
+
expect(payload).to be_a(String)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'contains Tag 00, Tag 51, and CRC Tag 91' do
|
18
|
+
tags = ThaiQrPay::TLV.decode(payload)
|
19
|
+
expect(tags.map(&:id)).to include('00', '51', '91')
|
20
|
+
tag51 = tags.find { |t| t.id == '51' }
|
21
|
+
expect(tag51.value).to eq('TH')
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'embeds sending_bank and trans_ref as subtags of Tag 00' do
|
25
|
+
tag00 = ThaiQrPay::TLV.decode(payload).find { |t| t.id == '00' }
|
26
|
+
subtags = ThaiQrPay::TLV.decode(tag00.value)
|
27
|
+
expect(subtags.find { |st| st.id == '01' }.value).to eq(bank)
|
28
|
+
expect(subtags.find { |st| st.id == '02' }.value).to eq(ref)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'appends correct CRC using tag 91' do
|
32
|
+
body = payload[0..-5] # everything before the CRC hex
|
33
|
+
expected = ThaiQrPay::CRC16.checksum(body)
|
34
|
+
expect(payload).to end_with("9104#{expected}")
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thai_qr_pay/generate/true_money_slip_verify'
|
4
|
+
require 'thai_qr_pay/tlv'
|
5
|
+
require 'thai_qr_pay/crc16'
|
6
|
+
|
7
|
+
RSpec.describe ThaiQrPay::Generate::TrueMoneySlipVerify do
|
8
|
+
let(:event) { 'PAYMENT' }
|
9
|
+
let(:txid) { 'TX123456' }
|
10
|
+
let(:date) { '20250615' }
|
11
|
+
|
12
|
+
subject(:payload) { described_class.payload(event_type: event, transaction_id: txid, date: date) }
|
13
|
+
|
14
|
+
it 'produces exactly two tags: 00 and 91' do
|
15
|
+
tags = ThaiQrPay::TLV.decode(payload)
|
16
|
+
expect(tags.map(&:id)).to eq(%w[00 91])
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'nests subtags 00..04 correctly under Tag 00' do
|
20
|
+
inner = ThaiQrPay::TLV.decode(payload).find { |t| t.id == '00' }
|
21
|
+
subtags = ThaiQrPay::TLV.decode(inner.value)
|
22
|
+
expect(subtags.map(&:id)).to eq(%w[00 01 02 03 04])
|
23
|
+
expect(subtags.find { |st| st.id == '02' }.value).to eq(event)
|
24
|
+
expect(subtags.find { |st| st.id == '03' }.value).to eq(txid)
|
25
|
+
expect(subtags.find { |st| st.id == '04' }.value).to eq(date)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'appends correct CRC using Tag 91' do
|
29
|
+
body = payload[0..-5]
|
30
|
+
expected = ThaiQrPay::CRC16.checksum(body)
|
31
|
+
expect(payload).to end_with("9104#{expected}")
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thai_qr_pay/generate/true_money'
|
4
|
+
require 'thai_qr_pay/tlv'
|
5
|
+
require 'thai_qr_pay/crc16'
|
6
|
+
|
7
|
+
RSpec.describe ThaiQrPay::Generate::TrueMoney do
|
8
|
+
let(:mobile) { '0812345678' }
|
9
|
+
let(:amount) { 123.45 }
|
10
|
+
let(:message) { 'Hello' }
|
11
|
+
|
12
|
+
context 'without amount or message' do
|
13
|
+
subject(:payload) { described_class.payload(mobile_no: mobile) }
|
14
|
+
|
15
|
+
it 'uses point-of-initiation 11' do
|
16
|
+
tag01 = ThaiQrPay::TLV.decode(payload).find { |t| t.id == '01' }
|
17
|
+
expect(tag01.value).to eq('11')
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'includes correct sub-tags in Tag 29' do
|
21
|
+
sub = ThaiQrPay::TLV.decode(payload).find { |t| t.id == '29' }
|
22
|
+
subtags = ThaiQrPay::TLV.decode(sub.value)
|
23
|
+
expect(subtags.find { |st| st.id == '00' }.value).to eq('A000000677010111')
|
24
|
+
expect(subtags.find { |st| st.id == '03' }.value).to eq("14000#{mobile}")
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'does not include Tag 54 nor Tag 81' do
|
28
|
+
tags = ThaiQrPay::TLV.decode(payload)
|
29
|
+
ids = tags.map(&:id)
|
30
|
+
expect(ids).not_to include('54')
|
31
|
+
expect(ids).not_to include('81')
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'ends with CRC Tag 63' do
|
35
|
+
expect(payload).to match(/63..[0-9A-F]{4}\z/)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'with amount only' do
|
40
|
+
subject(:payload) { described_class.payload(mobile_no: mobile, amount: amount) }
|
41
|
+
|
42
|
+
it 'uses point-of-initiation 12' do
|
43
|
+
tag01 = ThaiQrPay::TLV.decode(payload).find { |t| t.id == '01' }
|
44
|
+
expect(tag01.value).to eq('12')
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'includes Tag 54 formatted to two decimals' do
|
48
|
+
tag54 = ThaiQrPay::TLV.decode(payload).find { |t| t.id == '54' }
|
49
|
+
expect(tag54.value).to eq('123.45')
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'does not include Tag 81' do
|
53
|
+
ids = ThaiQrPay::TLV.decode(payload).map(&:id)
|
54
|
+
expect(ids).not_to include('81')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'with message only' do
|
59
|
+
subject(:payload) { described_class.payload(mobile_no: mobile, message: message) }
|
60
|
+
|
61
|
+
it 'includes Tag 81 with UCS-2 BE hex value' do
|
62
|
+
tag81 = ThaiQrPay::TLV.decode(payload).find { |t| t.id == '81' }
|
63
|
+
# "Hello" in UCS-2 BE hex:
|
64
|
+
expect(tag81.value).to eq('00480065006C006C006F')
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'does not include Tag 54' do
|
68
|
+
expect(payload).not_to include('54')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'with both amount and message' do
|
73
|
+
subject(:payload) { described_class.payload(mobile_no: mobile, amount: amount, message: message) }
|
74
|
+
|
75
|
+
it 'includes both Tag 54 and Tag 81' do
|
76
|
+
expect(payload).to include('54', '81')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/spec/parser_spec.rb
CHANGED
@@ -43,7 +43,7 @@ RSpec.describe ThaiQrPay::Parser do
|
|
43
43
|
|
44
44
|
it 'fetches nested sub-tag value' do
|
45
45
|
# Tag '29' contains sub-tags; '01' inside gives the MSISDN with 66 prefix
|
46
|
-
expected_msisdn = mobile.sub(/^0/, '66')
|
46
|
+
expected_msisdn = mobile.sub(/^0/, '66').rjust(13, '0')
|
47
47
|
expect(parser.get_tag_value('29', '01')).to eq(expected_msisdn)
|
48
48
|
end
|
49
49
|
|
data/spec/thai_qr_pay_spec.rb
CHANGED
@@ -25,7 +25,7 @@ RSpec.describe ThaiQrPay do
|
|
25
25
|
expect(parser.get_tag_value('01')).to eq('12') # Point-of-Initiation Method
|
26
26
|
|
27
27
|
# Sub-tag 29.01 contains the converted mobile number (no padding)
|
28
|
-
expected_msisdn = mobile.sub(/^0/, '66')
|
28
|
+
expected_msisdn = mobile.sub(/^0/, '66').rjust(13, '0')
|
29
29
|
expect(parser.get_tag_value('29', '01')).to eq(expected_msisdn)
|
30
30
|
|
31
31
|
expect(parser.valid_crc?).to be true
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: thai_qr_pay
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chayut Orapinpatipat
|
@@ -61,12 +61,18 @@ files:
|
|
61
61
|
- lib/thai_qr_pay/crc16.rb
|
62
62
|
- lib/thai_qr_pay/generate/promptpay/any_id.rb
|
63
63
|
- lib/thai_qr_pay/generate/promptpay/bill_payment.rb
|
64
|
+
- lib/thai_qr_pay/generate/slip_verify.rb
|
65
|
+
- lib/thai_qr_pay/generate/true_money.rb
|
66
|
+
- lib/thai_qr_pay/generate/true_money_slip_verify.rb
|
64
67
|
- lib/thai_qr_pay/parser.rb
|
65
68
|
- lib/thai_qr_pay/tlv.rb
|
66
69
|
- lib/thai_qr_pay/validate/slip_verify.rb
|
67
70
|
- lib/thai_qr_pay/validate/true_money_slip_verify.rb
|
68
71
|
- lib/thai_qr_pay/version.rb
|
69
72
|
- spec/crc16_spec.rb
|
73
|
+
- spec/generate/slip_verify_spec.rb
|
74
|
+
- spec/generate/true_money_slip_verify_spec.rb
|
75
|
+
- spec/generate/true_money_spec.rb
|
70
76
|
- spec/parser_spec.rb
|
71
77
|
- spec/thai_qr_pay_spec.rb
|
72
78
|
- spec/tlv_spec.rb
|