Class: VietnamQrPay::QRPay

Inherits:
Object
  • Object
show all
Defined in:
lib/vietnam_qr_pay/qr_pay.rb

Overview

Core object for parsing, inspecting, and building Vietnamese payment QR payloads.

The object keeps a mutable in-memory representation of the EMVCo TLV payload so callers can decode an existing string, change selected fields, and rebuild a valid payload with a regenerated CRC.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(content = nil) ⇒ QRPay

Returns a new instance of QRPay.



29
30
31
32
33
34
35
36
37
38
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 29

def initialize(content = nil)
  @valid = true
  @provider = Provider.new
  @consumer = Consumer.new
  @merchant = Merchant.new
  @additional_data = AdditionalData.new
  @evmco = {}
  @unreserved = {}
  parse(content) if content && !content.empty?
end

Instance Attribute Details

#additional_dataObject

Returns the value of attribute additional_data.



10
11
12
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 10

def additional_data
  @additional_data
end

#amountObject

Returns the value of attribute amount.



10
11
12
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 10

def amount
  @amount
end

#categoryObject

Returns the value of attribute category.



10
11
12
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 10

def category
  @category
end

#cityObject

Returns the value of attribute city.



10
11
12
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 10

def city
  @city
end

#consumerObject

Returns the value of attribute consumer.



10
11
12
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 10

def consumer
  @consumer
end

#crcObject

Returns the value of attribute crc.



10
11
12
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 10

def crc
  @crc
end

#currencyObject

Returns the value of attribute currency.



10
11
12
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 10

def currency
  @currency
end

#evmcoObject

Returns the value of attribute evmco.



10
11
12
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 10

def evmco
  @evmco
end

#init_methodObject

Returns the value of attribute init_method.



10
11
12
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 10

def init_method
  @init_method
end

#merchantObject

Returns the value of attribute merchant.



10
11
12
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 10

def merchant
  @merchant
end

#nationObject

Returns the value of attribute nation.



10
11
12
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 10

def nation
  @nation
end

#providerObject

Returns the value of attribute provider.



10
11
12
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 10

def provider
  @provider
end

#tip_and_fee_amountObject

Returns the value of attribute tip_and_fee_amount.



10
11
12
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 10

def tip_and_fee_amount
  @tip_and_fee_amount
end

#tip_and_fee_percentObject

Returns the value of attribute tip_and_fee_percent.



10
11
12
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 10

def tip_and_fee_percent
  @tip_and_fee_percent
end

#tip_and_fee_typeObject

Returns the value of attribute tip_and_fee_type.



10
11
12
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 10

def tip_and_fee_type
  @tip_and_fee_type
end

#unreservedObject

Returns the value of attribute unreserved.



10
11
12
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 10

def unreserved
  @unreserved
end

#versionObject

Returns the value of attribute version.



10
11
12
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 10

def version
  @version
end

#zip_codeObject

Returns the value of attribute zip_code.



10
11
12
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 10

def zip_code
  @zip_code
end

Class Method Details

.field_data(id, value) ⇒ Object

Serializes a single TLV field. Invalid or blank values are omitted.



194
195
196
197
198
199
200
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 194

def field_data(id, value)
  field_id = id.to_s
  field_value = value.to_s
  return "" if field_id.length != 2 || field_value.empty?

  "#{field_id}#{format('%02d', field_value.length)}#{field_value}"
end

.gen_crc_code(content) ⇒ Object

Returns the CRC value formatted as the upper-case 4-character suffix expected by EMVCo payloads.



189
190
191
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 189

def gen_crc_code(content)
  format("%04X", CRC16.crc16ccitt(content))
end

.init_viet_qr(bank_bin:, bank_number:, amount: nil, purpose: nil, service: VietQRService::BY_ACCOUNT_NUMBER) ⇒ Object

Builds a VietQR-oriented object with sensible defaults for static or dynamic transfers.



134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 134

def init_viet_qr(bank_bin:, bank_number:, amount: nil, purpose: nil, service: VietQRService::BY_ACCOUNT_NUMBER)
  qr = new
  # VietQR uses init method 11 for static QR and 12 when amount is fixed.
  qr.init_method = amount ? "12" : "11"
  qr.provider.field_id = FieldID::VIETQR
  qr.provider.guid = QRProviderGUID::VIETQR
  qr.provider.name = QRProvider::VIETQR
  qr.provider.service = service
  qr.consumer.bank_bin = bank_bin
  qr.consumer.bank_number = bank_number
  qr.amount = amount
  qr.additional_data.purpose = purpose
  qr
end

.init_vnpay_qr(merchant_id:, merchant_name:, store:, terminal:, amount: nil, purpose: nil, bill_number: nil, mobile_number: nil, loyalty_number: nil, reference: nil, customer_label: nil) ⇒ Object

Builds a VNPayQR-oriented object with the merchant block prepopulated.



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 150

def init_vnpay_qr(
  merchant_id:,
  merchant_name:,
  store:,
  terminal:,
  amount: nil,
  purpose: nil,
  bill_number: nil,
  mobile_number: nil,
  loyalty_number: nil,
  reference: nil,
  customer_label: nil
)
  qr = new
  qr.merchant.id = merchant_id
  qr.merchant.name = merchant_name
  qr.provider.field_id = FieldID::VNPAYQR
  qr.provider.guid = QRProviderGUID::VNPAY
  qr.provider.name = QRProvider::VNPAY
  qr.amount = amount
  qr.additional_data.purpose = purpose
  qr.additional_data.bill_number = bill_number
  qr.additional_data.mobile_number = mobile_number
  qr.additional_data.store = store
  qr.additional_data.terminal = terminal
  qr.additional_data.loyalty_number = loyalty_number
  qr.additional_data.reference = reference
  qr.additional_data.customer_label = customer_label
  qr
end

.verify_crc(content) ⇒ Object

Verifies the trailing CRC against the payload prefix.



182
183
184
185
186
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 182

def verify_crc(content)
  check_content = content[0...-4]
  crc_code = content[-4, 4].to_s.upcase
  crc_code == gen_crc_code(check_content)
end

Instance Method Details

#buildObject

Builds a new payload from the current in-memory structure.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 61

def build
  provider_data_content =
    case provider.guid
    when QRProviderGUID::VIETQR
      # VietQR stores bank BIN and account/card number inside a nested
      # provider template rather than directly on the root payload.
      [
        self.class.field_data(VietQRConsumerFieldID::BANK_BIN, consumer.bank_bin),
        self.class.field_data(VietQRConsumerFieldID::BANK_NUMBER, consumer.bank_number)
      ].join
    when QRProviderGUID::VNPAY
      # VNPayQR stores the merchant identifier directly in provider data.
      merchant.id.to_s
    else
      # Unknown providers are preserved so decode -> build stays lossless.
      provider.data.to_s
    end

  provider_content = [
    self.class.field_data(ProviderFieldID::GUID, provider.guid),
    self.class.field_data(ProviderFieldID::DATA, provider_data_content),
    self.class.field_data(ProviderFieldID::SERVICE, provider.service)
  ].join

  additional_data_content = [
    self.class.field_data(AdditionalDataID::BILL_NUMBER, additional_data.bill_number),
    self.class.field_data(AdditionalDataID::MOBILE_NUMBER, additional_data.mobile_number),
    self.class.field_data(AdditionalDataID::STORE_LABEL, additional_data.store),
    self.class.field_data(AdditionalDataID::LOYALTY_NUMBER, additional_data.loyalty_number),
    self.class.field_data(AdditionalDataID::REFERENCE_LABEL, additional_data.reference),
    self.class.field_data(AdditionalDataID::CUSTOMER_LABEL, additional_data.customer_label),
    self.class.field_data(AdditionalDataID::TERMINAL_LABEL, additional_data.terminal),
    self.class.field_data(AdditionalDataID::PURPOSE_OF_TRANSACTION, additional_data.purpose),
    self.class.field_data(AdditionalDataID::ADDITIONAL_CONSUMER_DATA_REQUEST, additional_data.data_request)
  ].join

  # The CRC field contains the field ID and fixed length before the value
  # is calculated over the complete payload prefix.
  content = [
    self.class.field_data(FieldID::VERSION, version || "01"),
    self.class.field_data(FieldID::INIT_METHOD, init_method || "11"),
    self.class.field_data(provider.field_id, provider_content),
    self.class.field_data(FieldID::CATEGORY, category),
    self.class.field_data(FieldID::CURRENCY, currency || "704"),
    self.class.field_data(FieldID::AMOUNT, amount),
    self.class.field_data(FieldID::TIP_AND_FEE_TYPE, tip_and_fee_type),
    self.class.field_data(FieldID::TIP_AND_FEE_AMOUNT, tip_and_fee_amount),
    self.class.field_data(FieldID::TIP_AND_FEE_PERCENT, tip_and_fee_percent),
    self.class.field_data(FieldID::NATION, nation || "VN"),
    self.class.field_data(FieldID::MERCHANT_NAME, merchant.name),
    self.class.field_data(FieldID::CITY, city),
    self.class.field_data(FieldID::ZIP_CODE, zip_code),
    self.class.field_data(FieldID::ADDITIONAL_DATA, additional_data_content),
    build_fields(evmco),
    build_fields(unreserved),
    "#{FieldID::CRC}04"
  ].join

  content + self.class.gen_crc_code(content)
end

#parse(content) ⇒ Object

Parses an existing payload.

Parsing always resets the current object first so reusing a QRPay instance cannot leak fields from a previous payload.



50
51
52
53
54
55
56
57
58
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 50

def parse(content)
  reset_parsed_state

  return invalidate! if content.nil? || content.length < 4
  return invalidate! unless self.class.verify_crc(content)

  parse_root_content(content)
  self
end

#set_evmco_field(id, value) ⇒ Object

Assigns an EMVCo extension field in the 65-79 range.



123
124
125
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 123

def set_evmco_field(id, value)
  evmco[id.to_s] = value.to_s
end

#set_unreserved_field(id, value) ⇒ Object

Assigns an unreserved extension field in the 80-99 range.



128
129
130
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 128

def set_unreserved_field(id, value)
  unreserved[id.to_s] = value.to_s
end

#valid?Boolean Also known as: is_valid

Returns:

  • (Boolean)


40
41
42
# File 'lib/vietnam_qr_pay/qr_pay.rb', line 40

def valid?
  @valid
end