120 lines
3.6 KiB
Ruby
120 lines
3.6 KiB
Ruby
module Girocode
|
|
class Error < StandardError; end
|
|
|
|
class Code
|
|
ATTRIBUTES = %i[bic name iban currency amount purpose creditor_reference reference bto_info]
|
|
attr_reader *ATTRIBUTES
|
|
|
|
MAX_PAYLOAD_BYTES = 331
|
|
AMOUNT_RANGE = BigDecimal('0.01')..BigDecimal('999999999.99')
|
|
|
|
def initialize(**attrs)
|
|
if keys = attrs.keys - ATTRIBUTES and not keys.empty?
|
|
raise ArgumentError, "Illegal attributes #{keys.inspect}"
|
|
end
|
|
attrs.each { |attr, value| send("#{attr}=", value) }
|
|
raise ArgumentError, "iban is required" unless iban?
|
|
raise ArgumentError, "name is required" unless name?
|
|
raise ArgumentError, 'currency is required for amount' if amount && !currency?
|
|
raise ArgumentError, "either creditor reference or reference may be set" if creditor_reference? && reference?
|
|
raise ArgumentError, "payload too long" if payload.bytesize > MAX_PAYLOAD_BYTES
|
|
end
|
|
|
|
def bic=(value)
|
|
if value.nil?
|
|
@bic = nil
|
|
elsif value.match? /\A[A-Z0-9]{4}([A-Z]{2})([A-Z0-9]{2})([A-Z0-9]{3})?\z/
|
|
@bic = value
|
|
else
|
|
raise ArgumentError, "Invalid BIC #{value.inspect}"
|
|
end
|
|
end
|
|
|
|
def name=(value)
|
|
value = value.strip
|
|
raise ArgumentError, 'name is required' unless value
|
|
raise ArgumentError, 'name too long' if value.size > 70
|
|
raise ArgumentError, 'Illegal name' if value.include?("\n") || value.include?("\r")
|
|
@name = value
|
|
end
|
|
|
|
def iban=(value)
|
|
raise ArgumentError, "Invalid IBAN #{value.inspect}" unless IBANTools::IBAN.valid?(value)
|
|
|
|
@iban = value
|
|
end
|
|
|
|
def currency=(value)
|
|
value = value.to_s.upcase
|
|
raise ArgumentError, "Invalid currency" unless value.match?(/\A[A-Z]{3}\z/)
|
|
@currency = value
|
|
end
|
|
|
|
def amount=(value)
|
|
raise ArgumentError, 'amount is required' unless value
|
|
value = BigDecimal(value, Float::DIG + 1)
|
|
raise ArgumentError, "invalid amount #{value.inspect}" unless AMOUNT_RANGE.cover?(value)
|
|
@amount = value
|
|
end
|
|
|
|
def purpose=(value)
|
|
unless value.nil?
|
|
raise ArgumentError, "invalid purpose #{value.inspect}" unless value.match?(/\A[A-z0-9]{0,4}\z/)
|
|
end
|
|
@purpose = value
|
|
end
|
|
|
|
def creditor_reference=(value)
|
|
unless value.nil?
|
|
raise ArgumentError, "invalid creditor reference #{value.inspect}" if value.include?("\n") || value.include?("\r") || value.size > 35
|
|
end
|
|
@creditor_reference = value
|
|
end
|
|
|
|
def reference=(value)
|
|
unless value.nil?
|
|
raise ArgumentError, "invalid reference #{value.inspect}" if value.include?("\n") || value.include?("\r") || value.size > 140
|
|
end
|
|
@reference = value
|
|
end
|
|
|
|
def bto_info=(value)
|
|
unless value.nil?
|
|
raise ArgumentError, "invalid bto_info #{value.inspect}" if value.include?("\n") || value.include?("\r") || value.size > 70
|
|
end
|
|
@bto_info = value
|
|
end
|
|
|
|
ATTRIBUTES.each do |attr|
|
|
define_method("#{attr}?") do
|
|
value = instance_variable_get("@#{attr}")
|
|
value.respond_to?(:empty?) ? !value.empty? : !!value
|
|
end
|
|
end
|
|
|
|
def payload
|
|
['BCD', '002', '1', 'SCT',
|
|
bic, name, iban,formatted_amount, purpose,
|
|
creditor_reference || reference, bto_info].map(&:to_s).join("\n")
|
|
end
|
|
|
|
def to_qrcode
|
|
RQRCode::QRCode.new(payload, level: :m, mode: :byte_8bit)
|
|
end
|
|
|
|
def to_ascii
|
|
to_qrcode.to_s
|
|
end
|
|
|
|
%i[png svg html ansi].each do |format|
|
|
define_method("to_#{format}") { |*args| to_qrcode.public_send("as_#{format}", *args) }
|
|
end
|
|
|
|
private
|
|
|
|
def formatted_amount
|
|
"#{currency}#{amount.round(2).to_s('F')}" if currency? && amount
|
|
end
|
|
end
|
|
end
|