emv_qr_code_processor 1.1.5
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 +7 -0
- data/lib/emv_qr_code_processor/decoder.rb +56 -0
- data/lib/emv_qr_code_processor/encoder.rb +41 -0
- data/lib/emv_qr_code_processor/errors.rb +7 -0
- data/lib/emv_qr_code_processor/schema.rb +86 -0
- data/lib/emv_qr_code_processor/version.rb +11 -0
- data/lib/emv_qr_code_processor.rb +10 -0
- metadata +63 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 182fc45c04647e1cc189c4af19d2094e8c0fa2e002923e1983a601e953e0d89a
|
4
|
+
data.tar.gz: ab7f560ccfec4c744976978e9505811589b0f49bf3ff2db7829fb072bcce096b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7b35c4b86f0a8593c46ceeed2b5c0377c0a120d91d31de2a0321430e38d4d541bd1502ce53a6be6c83b84f65d47612df322ecbb57ab0c5814dd879c40b6fd010
|
7
|
+
data.tar.gz: f5d72bfc2a0bc3a849af83767b79b0a7e92eb22f1e87ebb9c83f758f22315ac946927adb3247ceec068dfc4c60b60b255d629e46258bb875dc90edb74d78961b
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'digest/crc16_ccitt'
|
2
|
+
|
3
|
+
module EmvQrCodeProcessor
|
4
|
+
class Decoder
|
5
|
+
attr_reader :raw_qr_code
|
6
|
+
|
7
|
+
def initialize(raw_qr_code)
|
8
|
+
@raw_qr_code = raw_qr_code
|
9
|
+
end
|
10
|
+
|
11
|
+
def decode(validate_crc: true)
|
12
|
+
result = decode_data_objects(raw_qr_code, Schema)
|
13
|
+
validate_crc(result.fetch(:crc)) if validate_crc
|
14
|
+
|
15
|
+
result
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def decode_data_objects(raw_data, template)
|
21
|
+
result = {}
|
22
|
+
|
23
|
+
each_data_object(raw_data) do |data_object_id, value|
|
24
|
+
data_object_type = find_data_object_type(data_object_id, template)
|
25
|
+
raise Errors::InvalidQRCodePayload if data_object_type.nil?
|
26
|
+
|
27
|
+
value = decode_data_objects(value, data_object_type) unless data_object_type.primitive?
|
28
|
+
result[data_object_type.name] = value
|
29
|
+
end
|
30
|
+
|
31
|
+
result
|
32
|
+
end
|
33
|
+
|
34
|
+
def each_data_object(raw_data)
|
35
|
+
scanner = StringScanner.new(raw_data)
|
36
|
+
until scanner.eos?
|
37
|
+
data_object_id = scanner.scan(/\d{2}/)
|
38
|
+
value_length = scanner.scan(/\d{2}/).to_i
|
39
|
+
value = scanner.scan(/.{#{value_length}}/)
|
40
|
+
|
41
|
+
yield(data_object_id, value)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_data_object_type(id, template)
|
46
|
+
template.data_object_types.find { |d| d.id == id }
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate_crc(crc)
|
50
|
+
payload_without_crc = raw_qr_code[0..-9]
|
51
|
+
calculated_crc = Schema::CRC.calculate(payload_without_crc)
|
52
|
+
|
53
|
+
raise Errors::InvalidCRC if calculated_crc != crc
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'digest/crc16_ccitt'
|
2
|
+
|
3
|
+
module EmvQrCodeProcessor
|
4
|
+
class Encoder
|
5
|
+
attr_reader :payload
|
6
|
+
|
7
|
+
def initialize(payload)
|
8
|
+
@payload = payload
|
9
|
+
end
|
10
|
+
|
11
|
+
def encode
|
12
|
+
result = encode_data_objects(payload, Schema)
|
13
|
+
result << calculate_crc(result)
|
14
|
+
result
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def calculate_crc(data)
|
20
|
+
format(Schema::CRC.id, Schema::CRC.calculate(data))
|
21
|
+
end
|
22
|
+
|
23
|
+
def encode_data_objects(data, template)
|
24
|
+
sorted = data.sort_by { |name, _| find_data_object_type(name, template).id }
|
25
|
+
|
26
|
+
sorted.each_with_object("") do |(name, value), result|
|
27
|
+
data_object_type = find_data_object_type(name, template)
|
28
|
+
value = encode_data_objects(value, data_object_type) unless data_object_type.primitive?
|
29
|
+
result << format(data_object_type.id, value)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def format(id, value)
|
34
|
+
[id, value.length.to_s.rjust(2, "0"), value].join
|
35
|
+
end
|
36
|
+
|
37
|
+
def find_data_object_type(name, template)
|
38
|
+
template.data_object_types.find { |d| d.name == name }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module EmvQrCodeProcessor
|
2
|
+
module Schema
|
3
|
+
DataObjectType = Struct.new(:id, :name, keyword_init: true) do
|
4
|
+
def primitive?
|
5
|
+
true
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
TemplateType = Struct.new(:id, :name, :data_object_types, keyword_init: true) do
|
10
|
+
def primitive?
|
11
|
+
false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class CRCType < DataObjectType
|
16
|
+
LENGTH = "04".freeze
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
super(id: "63", name: :crc)
|
20
|
+
end
|
21
|
+
|
22
|
+
def calculate(data)
|
23
|
+
Digest::CRC16CCITT.new.update([data, id, LENGTH].join).to_s.upcase
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class AdditionalDataFieldType < TemplateType
|
28
|
+
PLACEHOLDER = "***".freeze
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
data_object_types = [
|
32
|
+
DataObjectType.new(id: "01", name: :bill_number),
|
33
|
+
DataObjectType.new(id: "02", name: :mobile_number),
|
34
|
+
DataObjectType.new(id: "03", name: :store_label),
|
35
|
+
DataObjectType.new(id: "04", name: :loyalty_number),
|
36
|
+
DataObjectType.new(id: "05", name: :reference_label),
|
37
|
+
DataObjectType.new(id: "06", name: :customer_label),
|
38
|
+
DataObjectType.new(id: "07", name: :terminal_label),
|
39
|
+
DataObjectType.new(id: "08", name: :transaction_purpose)
|
40
|
+
]
|
41
|
+
|
42
|
+
("09".."99").each do |id|
|
43
|
+
data_object_types << DataObjectType.new(id: id, name: :"reserved_#{id}")
|
44
|
+
end
|
45
|
+
|
46
|
+
super(
|
47
|
+
id: "62",
|
48
|
+
name: :additional_data_field,
|
49
|
+
data_object_types: data_object_types
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
CRC = CRCType.new
|
55
|
+
ADDITIONAL_DATA_FIELD = AdditionalDataFieldType.new
|
56
|
+
|
57
|
+
DATA_OBJECT_TYPES = [
|
58
|
+
DataObjectType.new(id: "00", name: :payload_format_indicator),
|
59
|
+
DataObjectType.new(id: "01", name: :point_of_initiation_method),
|
60
|
+
DataObjectType.new(id: "15", name: :"merchant_account_information.union_pay"),
|
61
|
+
DataObjectType.new(id: "52", name: :merchant_category_code),
|
62
|
+
DataObjectType.new(id: "53", name: :transaction_currency),
|
63
|
+
DataObjectType.new(id: "54", name: :transaction_amount),
|
64
|
+
DataObjectType.new(id: "55", name: :tip_or_convenience_fee_indicator),
|
65
|
+
DataObjectType.new(id: "56", name: :value_of_convenience_fee_fixed),
|
66
|
+
DataObjectType.new(id: "57", name: :value_of_convenience_fee_percentage),
|
67
|
+
DataObjectType.new(id: "58", name: :country_code),
|
68
|
+
DataObjectType.new(id: "59", name: :merchant_name),
|
69
|
+
DataObjectType.new(id: "60", name: :merchant_city),
|
70
|
+
DataObjectType.new(id: "61", name: :postal_code),
|
71
|
+
ADDITIONAL_DATA_FIELD,
|
72
|
+
CRC,
|
73
|
+
DataObjectType.new(id: "64", name: :merchant_information_language)
|
74
|
+
]
|
75
|
+
|
76
|
+
("00".."99").each do |id|
|
77
|
+
next if DATA_OBJECT_TYPES.any? { |d| d.id == id }
|
78
|
+
|
79
|
+
DATA_OBJECT_TYPES << DataObjectType.new(id: id, name: :"reserved_#{id}")
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.data_object_types
|
83
|
+
DATA_OBJECT_TYPES
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require "emv_qr_code_processor/schema"
|
2
|
+
require "emv_qr_code_processor/encoder"
|
3
|
+
require "emv_qr_code_processor/decoder"
|
4
|
+
require "emv_qr_code_processor/errors"
|
5
|
+
require "emv_qr_code_processor/version"
|
6
|
+
|
7
|
+
module EmvQrCodeProcessor
|
8
|
+
module Schema
|
9
|
+
end
|
10
|
+
end
|
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: emv_qr_code_processor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sokmesa KHIEV
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-01-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: digest-crc
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.6.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.6.3
|
27
|
+
description: 'Process to generate and read data EMV QRCode '
|
28
|
+
email:
|
29
|
+
- ksokmesa@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- lib/emv_qr_code_processor.rb
|
35
|
+
- lib/emv_qr_code_processor/decoder.rb
|
36
|
+
- lib/emv_qr_code_processor/encoder.rb
|
37
|
+
- lib/emv_qr_code_processor/errors.rb
|
38
|
+
- lib/emv_qr_code_processor/schema.rb
|
39
|
+
- lib/emv_qr_code_processor/version.rb
|
40
|
+
homepage: https://github.com/sokmesakhiev/emv_qr_code_processor
|
41
|
+
licenses:
|
42
|
+
- MIT
|
43
|
+
metadata: {}
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
requirements: []
|
59
|
+
rubygems_version: 3.0.3
|
60
|
+
signing_key:
|
61
|
+
specification_version: 4
|
62
|
+
summary: EMV QRCode reader and writer
|
63
|
+
test_files: []
|