Module: BrazilianUtils::PixPayloadUtils

Defined in:
lib/brazilian-utils/pix-payload-utils.rb

Overview

Utilities for the Pix "BR Code" payload: the EMV-derived TLV (tag- length-value) QR code format defined by the Banco Central's Manual de Padrões para Iniciação do Pix.

Constant Summary collapse

PIX_GUI =
'br.gov.bcb.pix'

Class Method Summary collapse

Class Method Details

.generate(params) ⇒ String?

Note:

This has no acceptance test cases in the contract; it has only been self-verified (a generated payload passes is_valid and round-trips through get_info), not cross-checked against a reference implementation's own test suite.

Generates a Pix BR Code payload.

Parameters:

  • params (Hash) —

    Exactly one of :key (static) or :url (dynamic) must be given, plus :merchantName and :merchantCity, and optionally :amount, :txid and :description.

Returns:

  • (String, nil) —

    nil when the parameters are invalid or contradictory (e.g. both/neither :key and :url given).



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/brazilian-utils/pix-payload-utils.rb', line 184

def self.generate(params)
  return nil unless params.is_a?(Hash)

  key = params[:key] || params['key']
  url = params[:url] || params['url']
  return nil if key.nil? == url.nil?

  merchant_name = params[:merchantName] || params['merchantName']
  merchant_city = params[:merchantCity] || params['merchantCity']
  return nil unless merchant_name.is_a?(String) && merchant_city.is_a?(String)
  return nil if merchant_name.empty? || merchant_city.empty?

  amount = params[:amount] || params['amount']
  txid = params[:txid] || params['txid'] || '***'
  description = params[:description] || params['description']

  clean_url = nil
  key_info = nil

  if url
    return nil unless url.is_a?(String)
    return nil if amount || (params[:txid] || params['txid'])

    clean_url = url.sub(%r{\Ahttps?://}, '')
    return nil if clean_url.empty? || clean_url.length > 77
  else
    key_info = PixKeyUtils.get_info(key)
    return nil unless key_info
  end

  amount_str = nil
  if amount
    return nil if amount.to_s.match?(/\.\d{3,}/)

    amount_str = format('%.2f', amount.to_f)
    return nil unless amount_str.to_f.positive?
  end

  return nil unless txid == '***' || txid.match?(/\A[A-Za-z0-9]{1,25}\z/)

  name = TextUtils.remove_accents(merchant_name)[0, 25]
  city = TextUtils.remove_accents(merchant_city)[0, 15]

   = tlv('00', PIX_GUI) + (url ? tlv('25', clean_url) : tlv('01', key_info[:value]))

  fields = []
  fields << tlv('00', '01')
  fields << tlv('01', url ? '12' : '11')
  fields << tlv('26', )
  fields << tlv('52', '0000')
  fields << tlv('53', '986')
  fields << tlv('54', amount_str) if amount_str && !url
  fields << tlv('58', 'BR')
  fields << tlv('59', name)
  fields << tlv('60', city)

  unless url
    additional = tlv('05', txid)
    if description
      desc = TextUtils.remove_accents(description)[0, 99]
      additional += tlv('02', desc) unless desc.empty?
    end
    fields << tlv('62', additional)
  end

  payload_without_crc = fields.join + '6304'
  payload_without_crc + format('%04X', crc16(payload_without_crc))
end

.get_info(value) ⇒ Hash?

Parses a Pix BR Code payload into its fields.

Parameters:

  • value (String)

Returns:

  • (Hash, nil) —

    nil for anything is_valid rejects.



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/brazilian-utils/pix-payload-utils.rb', line 130

def self.get_info(value)
  parsed = validated_fields(value)
  return nil unless parsed

  by_id = parsed[:by_id]
  has_psp_location = !parsed[:url_entry].nil?
  point_of_initiation = (parsed[:poi] == '12' || has_psp_location) ? 'dynamic' : 'static'

  info = {
    merchantName: by_id['59'].first,
    merchantCity: by_id['60'].first,
    pointOfInitiation: point_of_initiation
  }

  if has_psp_location
    info[:url] = parsed[:url_entry][1]
  else
    info[:key] = parsed[:key_entry][1]

    unless has_psp_location
      amount = by_id['54']&.first
      info[:amount] = amount.to_f if amount

      additional = by_id['62']&.first
      if additional
        nested = parse_tlv(additional)
        txid_entry = nested&.find { |id, _| id == '05' }
        info[:txid] = txid_entry[1] if txid_entry && txid_entry[1] != '***'
      end
    end
  end

  info
end

.is_valid(value) ⇒ Boolean Also known as: valid?

Validates a Pix BR Code payload (the key itself is not checked; use PixKeyUtils.is_valid).

Parameters:

  • value (String)

Returns:

  • (Boolean)


118
119
120
# File 'lib/brazilian-utils/pix-payload-utils.rb', line 118

def self.is_valid(value)
  !validated_fields(value).nil?
end