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 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,7 @@
1
+ module EmvQrCodeProcessor
2
+ module Errors
3
+ class EMVQRCodeError < StandardError; end
4
+ class InvalidCRC < EMVQRCodeError; end
5
+ class InvalidQRCodePayload < EMVQRCodeError; end
6
+ end
7
+ 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,11 @@
1
+ module EmvQrCodeProcessor
2
+ module VERSION
3
+ # Semantic versioning
4
+ MAJOR = 1
5
+ MINOR = 1
6
+ TINY = 5
7
+
8
+ # Full version number
9
+ STRING = [MAJOR, MINOR, TINY].compact.join(".")
10
+ end
11
+ 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: []