require 'dry-validation' require 'money' require 'countries' require 'iso639' module Rubyqrpay class Validator CONSUMER_DATA_REQUEST_PATTERN = /^(A?E?M|E?M?A|M?A?E|A?M?E|E?M?A|M?E?A)$/ ANS_PATTERN = /^[\w\s.:\/?#\[\]@!$&'()*+,;=\-~]*$/ MIN_MCC = 0 MAX_MCC = 10_000 MIN_FIXED = 0.01 MAX_FIXED = 9_999_999_999.99 MIN_PERCENT = 0.01 MAX_PERCENT = 99.99 def self.validate_payload(opts) schema = Dry::Validation.Schema do configure do def self.messages super.merge( en: { errors: { valid_currency: 'currency is not valid', country_code?: 'country is not valid', valid_language_reference: 'language_reference is not valid', agregator_id_presence: 'agregator must be filled', valid_merchant_account_32: 'field is too long', valid_merchant_account_33: 'field is too long', valid_additional_data: 'field is too long'} } ) end def country_code?(value) ISO3166::Country.find_country_by_alpha2(value) end end validate(valid_currency: :currency) do |currency| Money::Currency.find_by_iso_numeric(currency) end rule(valid_country: [:country]) do |country| country.filled?.then(country.country_code?) end rule(fixed_fee: [:convenience_indicator, :fixed_fee]) do |indicator, fixed| indicator.eql?(2).then(fixed.filled?) end rule(percentage_fee: [:convenience_indicator, :percentage_fee]) do |indicator, percentage| indicator.eql?(3).then(percentage.filled?) end rule(agregator_id_presence: [:agregator_id, :merchant_account_33]) do |agregator_id, merchant_account_33| merchant_account_33.filled?.then(agregator_id.filled?) end optional(:agregator_id).maybe(:str?, format?: ANS_PATTERN) required(:merchant_account_32).schema do required(:service_code_erip).filled(:str?) optional(:payer_unique_id).maybe(:str?) optional(:payer_number).maybe(:str?) optional(:amount_edit_possibility).maybe(:bool?) end optional(:merchant_account_33).schema do required(:service_producer_code).filled(:str?) optional(:service_code).maybe(:str?) optional(:outlet).maybe(:str?) optional(:order_code).maybe(:str?) end validate(valid_merchant_account_32: [:merchant_account_32]) do |merchant_account_32| (0..99).include? merchant_account_32.to_h.values.join.size end validate(valid_merchant_account_33: [:merchant_account_33]) do |merchant_account_33| (0..99).include? merchant_account_33.to_h.values.join.size end validate(valid_additional_data: [:additional_data]) do |additional_data| (0..99).include? additional_data.to_h.values.join.size end required(:currency).filled(:int?) optional(:convenience_indicator).maybe(:int?, gteq?: 1, lteq?: 3) optional(:fixed_fee).maybe(:float?, gteq?: MIN_FIXED, lteq?: MAX_FIXED) optional(:percentage_fee).maybe(:float?, gteq?: MIN_PERCENT, lteq?: MAX_PERCENT) optional(:merchant_category_code).maybe(:int?, gteq?: MIN_MCC, lt?: MAX_MCC) optional(:amount).maybe(:float?, gteq?: MIN_FIXED, lteq?: MAX_FIXED) optional(:country).maybe(:str?) optional(:merchant_name).maybe(:str?, max_size?: 25, format?: ANS_PATTERN) optional(:merchant_city).maybe(:str?, max_size?: 15, format?: ANS_PATTERN) optional(:postal_code).maybe(:str?, max_size?: 10, format?: ANS_PATTERN) optional(:additional_data).schema do optional(:bill_number).maybe(:str?, max_size?: 25, format?: ANS_PATTERN) optional(:mobile_number).maybe(:str?, max_size?: 25, format?: ANS_PATTERN) optional(:store_label).maybe(:str?, max_size?: 25, format?: ANS_PATTERN) optional(:loyalty_number).maybe(:str?, max_size?: 25, format?: ANS_PATTERN) optional(:reference_label).maybe(:str?, max_size?: 25, format?: ANS_PATTERN) optional(:customer_label).maybe(:str?, max_size?: 25, format?: ANS_PATTERN) optional(:terminal_label).maybe(:str?, max_size?: 25, format?: ANS_PATTERN) optional(:purpose_of_transaction).maybe(:str?, max_size?: 25, format?: ANS_PATTERN) optional(:consumer_data_request).maybe(format?: CONSUMER_DATA_REQUEST_PATTERN) end optional(:merchant_information_language).schema do validate(valid_language_reference: :language_reference) do |language_reference| Iso639[language_reference] end required(:language_reference).filled(:str?, max_size?: 2) required(:name_alternate).filled(:str?, max_size?: 25) optional(:city_alternate).maybe(:str?, max_size?: 15) end end result = schema.call(opts) if result.success? opts else raise ArgumentError, result.errors.first.to_s end end end end