emv_qr_code_processor 1.1.5
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|