paycertify 0.0.7 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/paycertify.rb +2 -0
- data/lib/paycertify/confirmation.rb +94 -0
- data/lib/paycertify/gateway.rb +17 -17
- data/lib/paycertify/gateway/attribute_mapping.rb +49 -6
- data/lib/paycertify/gateway/base.rb +8 -0
- data/lib/paycertify/gateway/base/resource.rb +87 -0
- data/lib/paycertify/gateway/base/validation.rb +105 -0
- data/lib/paycertify/gateway/charge.rb +58 -0
- data/lib/paycertify/gateway/client.rb +11 -1
- data/lib/paycertify/gateway/credit_card.rb +65 -0
- data/lib/paycertify/gateway/customer.rb +60 -0
- data/lib/paycertify/gateway/response.rb +18 -15
- data/lib/paycertify/gateway/transaction.rb +97 -35
- data/lib/paycertify/insurance.rb +118 -0
- data/lib/paycertify/three_ds.rb +1 -1
- data/lib/paycertify/three_ds/callback.rb +8 -0
- data/lib/paycertify/three_ds/client.rb +5 -0
- data/lib/paycertify/three_ds/form.rb +24 -26
- metadata +23 -2
- data/lib/paycertify/gateway/validation.rb +0 -174
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 4853d8c8ab842f2530d63b7c7880e51b0131c6cb
         | 
| 4 | 
            +
              data.tar.gz: 65aa6d612b3c3c7154595a7d59cb5f31278f4fb9
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: accec71a0359c602880883d965b315fd65f2bb9189719ad9003e33145f3ec079c78983e6d864f6ead27f453edb42dfb4ef34c998ca416093601f3e44b7a1241e
         | 
| 7 | 
            +
              data.tar.gz: df636c0cd6d851677d7fc2e4428be57c5b9b426c2ad1de1f86122daf6756fd9368628b3f890ca1ae4dcd6be22aa63316e0fa3a77a5976cc360e6f985bb6618dd
         | 
    
        data/lib/paycertify.rb
    CHANGED
    
    
| @@ -0,0 +1,94 @@ | |
| 1 | 
            +
            module PayCertify
         | 
| 2 | 
            +
              class Confirmation
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                class NoCredentialsError < StandardError; end
         | 
| 5 | 
            +
                
         | 
| 6 | 
            +
                API_ENDPOINT = 'https://www.paycertify.com/'
         | 
| 7 | 
            +
                
         | 
| 8 | 
            +
                MANDATORY_FIELDS = [
         | 
| 9 | 
            +
                  :transaction_id, :cc_last_four_digits, :name, :email, 
         | 
| 10 | 
            +
                  :phone, :amount, :currency, :payment_gateway
         | 
| 11 | 
            +
                ]
         | 
| 12 | 
            +
                
         | 
| 13 | 
            +
                OPTIONAL_FIELDS = [
         | 
| 14 | 
            +
                  :status, :transaction_date, :order_description, :card_type, :name_on_card, 
         | 
| 15 | 
            +
                  :address, :city, :zip, :state, :country, :confirmation_type, :fraud_score_processing, :scheduled_messages, 
         | 
| 16 | 
            +
                  :thank_you_page_url, :metadata
         | 
| 17 | 
            +
                ]
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                attr_accessor :attributes, :errors, :response
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                delegate :api_key, to: :class
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def initialize(attributes)
         | 
| 24 | 
            +
                  raise NoCredentialsError, 'No api key provided.' unless api_key.present?
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  self.attributes = HashWithIndifferentAccess.new(attributes)
         | 
| 27 | 
            +
                  self.errors = {}
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  validate!
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def success?
         | 
| 33 | 
            +
                  response.success?
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def start!
         | 
| 37 | 
            +
                  data = attributes.slice *[MANDATORY_FIELDS + OPTIONAL_FIELDS].flatten
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  api_response = connection.post do |request|
         | 
| 40 | 
            +
                    request.url path_for('merchant/transactions')
         | 
| 41 | 
            +
                    request.headers['Content-Type'] = 'application/json'
         | 
| 42 | 
            +
                    request.headers['PAYCERTIFYKEY'] = api_key
         | 
| 43 | 
            +
                    request.body = JSON.generate(data)
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  self.response = Response.new(api_response)
         | 
| 47 | 
            +
                  self.errors = errors.merge(response) unless response.success?
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  response
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                class << self
         | 
| 53 | 
            +
                  attr_accessor :api_key
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  def configure(&block)
         | 
| 56 | 
            +
                    yield self if block_given?
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                class Response < HashWithIndifferentAccess
         | 
| 61 | 
            +
                  attr_accessor :original_response
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  def initialize(response)
         | 
| 64 | 
            +
                    self.original_response = response
         | 
| 65 | 
            +
                    super JSON.parse(response.body)
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  def success?
         | 
| 69 | 
            +
                    original_response.status < 400
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                private
         | 
| 74 | 
            +
                  def validate!
         | 
| 75 | 
            +
                    MANDATORY_FIELDS.each do |field|
         | 
| 76 | 
            +
                      unless attributes[field].present?
         | 
| 77 | 
            +
                        self.errors[field] = "Required attribute not present"
         | 
| 78 | 
            +
                      end
         | 
| 79 | 
            +
                    end
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                  def connection
         | 
| 83 | 
            +
                    @connection ||= Faraday.new(url: API_ENDPOINT, ssl: {verify: false}) do |faraday|
         | 
| 84 | 
            +
                      faraday.request :url_encoded
         | 
| 85 | 
            +
                      faraday.response :logger
         | 
| 86 | 
            +
                      faraday.adapter  Faraday.default_adapter
         | 
| 87 | 
            +
                    end
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  def path_for(path)
         | 
| 91 | 
            +
                    return "api/v1/" + path
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
              end
         | 
| 94 | 
            +
            end
         | 
    
        data/lib/paycertify/gateway.rb
    CHANGED
    
    | @@ -1,34 +1,34 @@ | |
| 1 1 | 
             
            require_relative './gateway/attribute_mapping'
         | 
| 2 | 
            +
            require_relative './gateway/base'
         | 
| 3 | 
            +
            require_relative './gateway/charge'
         | 
| 2 4 | 
             
            require_relative './gateway/client'
         | 
| 5 | 
            +
            require_relative './gateway/credit_card'
         | 
| 6 | 
            +
            require_relative './gateway/customer'
         | 
| 3 7 | 
             
            require_relative './gateway/response'
         | 
| 4 8 | 
             
            require_relative './gateway/transaction'
         | 
| 5 | 
            -
            require_relative './gateway/validation'
         | 
| 6 9 |  | 
| 7 10 | 
             
            require 'json'
         | 
| 8 11 |  | 
| 9 12 | 
             
            module PayCertify
         | 
| 10 13 | 
             
              class Gateway
         | 
| 11 | 
            -
             | 
| 12 | 
            -
                attr_accessor :client, :attributes
         | 
| 13 | 
            -
             | 
| 14 | 
            -
                delegate :api_key, :mode, to: :class
         | 
| 15 | 
            -
             | 
| 16 | 
            -
                def initialize(attributes)
         | 
| 17 | 
            -
                  self.attributes = attributes
         | 
| 18 | 
            -
                  self.client = PayCertify::Gateway::Client.new(api_key: api_key, mode: mode)
         | 
| 19 | 
            -
                end
         | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
                def process_transaction!
         | 
| 23 | 
            -
                  transaction = PayCertify::Gateway::Transaction.new(client, attributes)
         | 
| 24 | 
            -
                  transaction.process
         | 
| 25 | 
            -
                end
         | 
| 14 | 
            +
                CREDENTIALS_PATH = '/ws/encgateway2.asmx/GetCredential'
         | 
| 26 15 |  | 
| 27 16 | 
             
                class << self
         | 
| 28 | 
            -
                   | 
| 17 | 
            +
                  attr_accessor :api_key, :mode, :vendor
         | 
| 29 18 |  | 
| 30 19 | 
             
                  def configure(&block)
         | 
| 31 20 | 
             
                    yield self if block_given?
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    client = PayCertify::Gateway::Client.new(api_key: api_key, mode: mode)
         | 
| 23 | 
            +
                    response = client.get(path: CREDENTIALS_PATH)
         | 
| 24 | 
            +
                    
         | 
| 25 | 
            +
                    self.vendor = response['response']['vendor']
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    return {
         | 
| 28 | 
            +
                      api_key: api_key,
         | 
| 29 | 
            +
                      mode: mode,
         | 
| 30 | 
            +
                      vendor: vendor
         | 
| 31 | 
            +
                    }
         | 
| 32 32 | 
             
                  end
         | 
| 33 33 | 
             
                end
         | 
| 34 34 | 
             
              end
         | 
| @@ -2,7 +2,7 @@ module PayCertify | |
| 2 2 | 
             
              class Gateway
         | 
| 3 3 | 
             
                module AttributeMapping
         | 
| 4 4 | 
             
                  module_function
         | 
| 5 | 
            -
                    def  | 
| 5 | 
            +
                    def transaction
         | 
| 6 6 | 
             
                      {
         | 
| 7 7 | 
             
                        'Amount' => :amount,
         | 
| 8 8 | 
             
                        'Currency' => :currency,
         | 
| @@ -25,8 +25,40 @@ module PayCertify | |
| 25 25 | 
             
                        'Email' => :email,
         | 
| 26 26 | 
             
                        'Description' => :order_description,
         | 
| 27 27 | 
             
                        'CustomerID' => :customer_id,
         | 
| 28 | 
            -
                        'ServerID' => :ip | 
| 29 | 
            -
             | 
| 28 | 
            +
                        'ServerID' => :ip
         | 
| 29 | 
            +
                      }
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    def customer
         | 
| 33 | 
            +
                      {
         | 
| 34 | 
            +
                        'CustomerID' => :app_customer_id,
         | 
| 35 | 
            +
                        'CustomerKey' => :customer_id,
         | 
| 36 | 
            +
                        'CustomerName' => :name,
         | 
| 37 | 
            +
                        'Street1' => :address,
         | 
| 38 | 
            +
                        'City' => :city,
         | 
| 39 | 
            +
                        'StateID' => :state,
         | 
| 40 | 
            +
                        'Zip' => :zip,
         | 
| 41 | 
            +
                        'MobilePhone' => :phone,
         | 
| 42 | 
            +
                        'Fax' => :fax,
         | 
| 43 | 
            +
                        'Email' => :email,
         | 
| 44 | 
            +
                        'Status' => :status
         | 
| 45 | 
            +
                      }
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    def credit_card
         | 
| 49 | 
            +
                      {
         | 
| 50 | 
            +
                        'CardNum' => :card_number,
         | 
| 51 | 
            +
                        'NameOnCard' => :name_on_card,
         | 
| 52 | 
            +
                        'CustomerKey' => :customer_id,
         | 
| 53 | 
            +
                        'PostalCode' => :zip
         | 
| 54 | 
            +
                      }
         | 
| 55 | 
            +
                    end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                    def charge
         | 
| 58 | 
            +
                      {
         | 
| 59 | 
            +
                        'CardToken' => :credit_card_id,
         | 
| 60 | 
            +
                        'Amount' => :amount,
         | 
| 61 | 
            +
                        'PNRef' => :transaction_id
         | 
| 30 62 | 
             
                      }
         | 
| 31 63 | 
             
                    end
         | 
| 32 64 |  | 
| @@ -34,9 +66,20 @@ module PayCertify | |
| 34 66 | 
             
                      { 'ExpDate' => [transaction.expiration_month, transaction.expiration_year].join }
         | 
| 35 67 | 
             
                    end
         | 
| 36 68 |  | 
| 37 | 
            -
                    def  | 
| 38 | 
            -
                       | 
| 69 | 
            +
                    def type(object)
         | 
| 70 | 
            +
                      case object.class.name
         | 
| 71 | 
            +
                      when /.*Transaction.*/, /.*Charge.*/
         | 
| 72 | 
            +
                        { 'TransType' => object.type.to_s.capitalize }
         | 
| 73 | 
            +
                      when /.*Customer.*/
         | 
| 74 | 
            +
                        { 'TransType' => object.type.to_s.upcase }
         | 
| 75 | 
            +
                      else
         | 
| 76 | 
            +
                        {}
         | 
| 77 | 
            +
                      end
         | 
| 78 | 
            +
                    end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                    def status(customer)
         | 
| 81 | 
            +
                      object.status.to_s.upcase
         | 
| 39 82 | 
             
                    end
         | 
| 40 83 | 
             
                end
         | 
| 41 84 | 
             
              end
         | 
| 42 | 
            -
            end
         | 
| 85 | 
            +
            end
         | 
| @@ -0,0 +1,87 @@ | |
| 1 | 
            +
            module PayCertify
         | 
| 2 | 
            +
              class Gateway
         | 
| 3 | 
            +
                module Base
         | 
| 4 | 
            +
                  class Resource
         | 
| 5 | 
            +
                    attr_accessor :client, :original_attributes, :response, :errors
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                    delegate :api_key, :mode, to: PayCertify::Gateway
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                    def initialize(attributes)
         | 
| 10 | 
            +
                      self.original_attributes = attributes
         | 
| 11 | 
            +
                      self.client = PayCertify::Gateway::Client.new(api_key: api_key, mode: mode)
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                      if validatable?
         | 
| 14 | 
            +
                        # Validate + attribute assignment
         | 
| 15 | 
            +
                        validation.attributes.each do |key, value|
         | 
| 16 | 
            +
                          self.send("#{key}=", value)
         | 
| 17 | 
            +
                        end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                        self.errors = validation.errors
         | 
| 20 | 
            +
                      else
         | 
| 21 | 
            +
                        # Attribute assignment only
         | 
| 22 | 
            +
                        self.class.const_get('ATTRIBUTES').each do |key|
         | 
| 23 | 
            +
                          self.send("#{key}=", attributes[key])
         | 
| 24 | 
            +
                        end
         | 
| 25 | 
            +
                      end
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    def success?
         | 
| 29 | 
            +
                      errors.empty?
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    def validatable?
         | 
| 33 | 
            +
                      self.class.const_get('Validation')
         | 
| 34 | 
            +
                      true
         | 
| 35 | 
            +
                    rescue NameError
         | 
| 36 | 
            +
                      false
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    def validation
         | 
| 40 | 
            +
                      @validation ||= self.class.const_get('Validation').new(original_attributes)
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    def attributes
         | 
| 44 | 
            +
                      {}.tap do |attributes|
         | 
| 45 | 
            +
                        self.class.const_get('ATTRIBUTES').each do |attribute|
         | 
| 46 | 
            +
                          value = self.send(attribute)
         | 
| 47 | 
            +
                          attributes[attribute] = value if value.present?
         | 
| 48 | 
            +
                        end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                        if response.present?
         | 
| 51 | 
            +
                          attributes['gateway_response'] = response
         | 
| 52 | 
            +
                        end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                        attributes
         | 
| 55 | 
            +
                      end
         | 
| 56 | 
            +
                    end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                    def to_json
         | 
| 59 | 
            +
                      JSON.generate(attributes)
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                    def save!
         | 
| 63 | 
            +
                      self.response = client.post(
         | 
| 64 | 
            +
                        path: self.class.const_get('API_ENDPOINT'),
         | 
| 65 | 
            +
                        data: attributes_to_gateway_format
         | 
| 66 | 
            +
                      )
         | 
| 67 | 
            +
                    end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                    def attributes_to_gateway_format
         | 
| 70 | 
            +
                      {}.tap do |formatted|
         | 
| 71 | 
            +
                        attribute_mapping = PayCertify::Gateway::AttributeMapping
         | 
| 72 | 
            +
                        mapping_name = self.class.name.underscore.split('/').last
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                        attribute_mapping.send(mapping_name).each do |key, value|
         | 
| 75 | 
            +
                          [value].flatten.tap do |method_chain|
         | 
| 76 | 
            +
                            new_value = method_chain.map { |method_name| self.send(method_name) }.join
         | 
| 77 | 
            +
                            formatted[key] = new_value
         | 
| 78 | 
            +
                          end
         | 
| 79 | 
            +
                        end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                        formatted
         | 
| 82 | 
            +
                      end
         | 
| 83 | 
            +
                    end
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
              end
         | 
| 87 | 
            +
            end
         | 
| @@ -0,0 +1,105 @@ | |
| 1 | 
            +
            module PayCertify
         | 
| 2 | 
            +
              class Gateway
         | 
| 3 | 
            +
                module Base
         | 
| 4 | 
            +
                  class Validation
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                    EMAIL_REGEX = /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
         | 
| 7 | 
            +
                    CREDIT_CARD_REGEX = /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/i
         | 
| 8 | 
            +
                    
         | 
| 9 | 
            +
                    attr_accessor :attributes, :errors
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    def initialize(attributes)
         | 
| 12 | 
            +
                      self.attributes = attributes
         | 
| 13 | 
            +
                      self.errors = {}
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    def no_validation(_); end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    def presence_validation(attribute)
         | 
| 19 | 
            +
                      if value_for(attribute).blank?
         | 
| 20 | 
            +
                        add_error(attribute, "Required attribute not present")
         | 
| 21 | 
            +
                      end
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    def email_validation(attribute)
         | 
| 25 | 
            +
                      unless value_for(attribute) =~ EMAIL_REGEX
         | 
| 26 | 
            +
                        add_error(attribute, "Doesn't validate as an email.")
         | 
| 27 | 
            +
                      end
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    def zip_validation(attribute)
         | 
| 31 | 
            +
                      set_attribute(attribute, Integer(value_for(attribute)).to_s)
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                      unless value_for(attribute).length == 5
         | 
| 34 | 
            +
                        add_error(attribute, "Must be a 5-digit string that can evaluate to a number.")
         | 
| 35 | 
            +
                      end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    rescue
         | 
| 38 | 
            +
                      add_error(attribute, "Must be a 5-digit string that can evaluate to a number.")
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    def card_number_validation(attribute)
         | 
| 42 | 
            +
                      # Non decimal numbers should be stripped to match.
         | 
| 43 | 
            +
                      set_attribute(attribute, value_for(attribute).gsub(/\D/, ''))
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                      unless value_for(attribute) =~ CREDIT_CARD_REGEX
         | 
| 46 | 
            +
                        add_error(attribute, "Doesn't validate as a credit card.")
         | 
| 47 | 
            +
                      end
         | 
| 48 | 
            +
                    end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    def expiration_month_validation(attribute)
         | 
| 51 | 
            +
                      # if a string, check if length = 2 and smaller than int 12
         | 
| 52 | 
            +
                      # if int, transform into string with zero pad and check if smaller than int 12
         | 
| 53 | 
            +
                      integer = Integer(value_for(attribute))
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                      if integer > 12
         | 
| 56 | 
            +
                        add_error(attribute, "Must be smaller than 12.")
         | 
| 57 | 
            +
                      end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                      set_attribute(attribute, integer.to_s.rjust(2, '0'))
         | 
| 60 | 
            +
                      
         | 
| 61 | 
            +
                    rescue ArgumentError
         | 
| 62 | 
            +
                      add_error(attribute, "Must be an integer.")
         | 
| 63 | 
            +
                    end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                    def expiration_year_validation(attribute)
         | 
| 66 | 
            +
                      # if length = 4, strip to 2;
         | 
| 67 | 
            +
                      # if a string, check if length = 2 and smaller than int 12
         | 
| 68 | 
            +
                      # if int, transform into string with zero pad and check if smaller than int 12
         | 
| 69 | 
            +
             | 
| 70 | 
            +
             | 
| 71 | 
            +
                      is_four_digit = if value_for(attribute).is_a?(String)
         | 
| 72 | 
            +
                        value_for(attribute).length == 4
         | 
| 73 | 
            +
                      else
         | 
| 74 | 
            +
                        value_for(attribute) > 999
         | 
| 75 | 
            +
                      end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                      integer_value = Integer(value_for(attribute))
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                      set_attribute(attribute, integer_value.to_s.last(2))
         | 
| 80 | 
            +
                    rescue
         | 
| 81 | 
            +
                      add_error(attribute, "Must be a 2 to 4-digit string.")
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                    def amount_validation(attribute)
         | 
| 85 | 
            +
                      set_attribute(attribute, Float(value_for(attribute)))
         | 
| 86 | 
            +
                    rescue ArgumentError
         | 
| 87 | 
            +
                      add_error(attribute, "Must be a float, integer or decimal")
         | 
| 88 | 
            +
                    end
         | 
| 89 | 
            +
                    
         | 
| 90 | 
            +
                    def value_for(attribute)
         | 
| 91 | 
            +
                      attributes[attribute[:name]]
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                    def set_attribute(attribute, value)
         | 
| 95 | 
            +
                      self.attributes[attribute[:name]] = value
         | 
| 96 | 
            +
                    end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                    def add_error(attribute, message)
         | 
| 99 | 
            +
                      self.errors[attribute[:name]] ||= []
         | 
| 100 | 
            +
                      self.errors[attribute[:name]] << message
         | 
| 101 | 
            +
                    end
         | 
| 102 | 
            +
                  end
         | 
| 103 | 
            +
                end
         | 
| 104 | 
            +
              end
         | 
| 105 | 
            +
            end
         | 
| @@ -0,0 +1,58 @@ | |
| 1 | 
            +
            module PayCertify
         | 
| 2 | 
            +
              class Gateway
         | 
| 3 | 
            +
                class Charge < PayCertify::Gateway::Base::Resource
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  API_ENDPOINT = '/ws/cardsafe.asmx/ProcessStoredCard'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  ATTRIBUTES = [
         | 
| 8 | 
            +
                    :transaction_id, :app_transaction_id, :type, :amount, 
         | 
| 9 | 
            +
                    :credit_card_id, :gateway_response
         | 
| 10 | 
            +
                  ]
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  attr_accessor *ATTRIBUTES
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  alias :execute! :save!
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def type
         | 
| 17 | 
            +
                    'sale'
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def success?
         | 
| 21 | 
            +
                    response['response']['transaction_result']['result'] == '0'
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def save!
         | 
| 25 | 
            +
                    super
         | 
| 26 | 
            +
                    self.transaction_id = response['response']['transaction_result']['pn_ref']
         | 
| 27 | 
            +
                    self
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def attributes_to_gateway_format
         | 
| 31 | 
            +
                    formatted = super
         | 
| 32 | 
            +
                    attribute_mapping = PayCertify::Gateway::AttributeMapping
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    formatted.merge! attribute_mapping.type(self)
         | 
| 35 | 
            +
                    formatted.merge!({'TokenMode' => 'DEFAULT'})
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    formatted
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  class Validation < PayCertify::Gateway::Base::Validation
         | 
| 41 | 
            +
                    ALL_VALIDATIONS = [
         | 
| 42 | 
            +
                      { name: :credit_card_id, validation: :no_validation, required: true },
         | 
| 43 | 
            +
                      { name: :amount, validation: :amount_validation, required: true },
         | 
| 44 | 
            +
                      { name: :transaction_id, validation: :no_validation, required: true },
         | 
| 45 | 
            +
                    ]
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    def initialize(attributes={})
         | 
| 48 | 
            +
                      super(attributes)
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                      ALL_VALIDATIONS.each do |attribute|
         | 
| 51 | 
            +
                        presence_validation(attribute) if attribute[:required]
         | 
| 52 | 
            +
                        send(attribute[:validation], attribute) if value_for(attribute).present?
         | 
| 53 | 
            +
                      end
         | 
| 54 | 
            +
                    end
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
            end
         | 
| @@ -19,8 +19,14 @@ module PayCertify | |
| 19 19 | 
             
                    @api_endpoint ||= 'https://'+ (live?? 'gateway' : 'demo') +'.paycertify.net'
         | 
| 20 20 | 
             
                  end
         | 
| 21 21 |  | 
| 22 | 
            +
                  def get(path:, data: {})
         | 
| 23 | 
            +
                    data.merge!(token_payload)
         | 
| 24 | 
            +
                    response = connection.get(path, data)
         | 
| 25 | 
            +
                    respond_with response
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 22 28 | 
             
                  def post(path:, data:)
         | 
| 23 | 
            -
                    body = data.merge( | 
| 29 | 
            +
                    body = data.merge(token_payload)
         | 
| 24 30 |  | 
| 25 31 | 
             
                    response = connection.post do |request|
         | 
| 26 32 | 
             
                      request.url path
         | 
| @@ -47,6 +53,10 @@ module PayCertify | |
| 47 53 | 
             
                      end
         | 
| 48 54 | 
             
                    end
         | 
| 49 55 |  | 
| 56 | 
            +
                    def token_payload
         | 
| 57 | 
            +
                      { 'ApiToken' => api_key }
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
             | 
| 50 60 | 
             
                    def respond_with(response)
         | 
| 51 61 | 
             
                      self.response = PayCertify::Gateway::Response.new(response)
         | 
| 52 62 | 
             
                    end
         | 
| @@ -0,0 +1,65 @@ | |
| 1 | 
            +
            module PayCertify
         | 
| 2 | 
            +
              class Gateway
         | 
| 3 | 
            +
                class CreditCard < PayCertify::Gateway::Base::Resource
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  API_ENDPOINT = '/ws/cardsafe.asmx/StoreCard'
         | 
| 6 | 
            +
                  SAFE_CARD_REGEX = /\<CardSafeToken>(.*)<\/CardSafeToken>/
         | 
| 7 | 
            +
                  
         | 
| 8 | 
            +
                  ATTRIBUTES = [
         | 
| 9 | 
            +
                    :credit_card_id, :card_number, :expiration_month, :expiration_year, 
         | 
| 10 | 
            +
                    :customer_id, :name_on_card, :zip
         | 
| 11 | 
            +
                  ]
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  attr_accessor *ATTRIBUTES
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def success?
         | 
| 16 | 
            +
                    super && response['response']['result'] == '0'
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def save!
         | 
| 20 | 
            +
                    super
         | 
| 21 | 
            +
                    self.credit_card_id = get_credit_card_id
         | 
| 22 | 
            +
                    self
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def attributes_to_gateway_format
         | 
| 26 | 
            +
                    formatted = super
         | 
| 27 | 
            +
                    attribute_mapping = PayCertify::Gateway::AttributeMapping
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    formatted.merge! attribute_mapping.expiration_date(self)
         | 
| 30 | 
            +
                    formatted.merge!({'TokenMode' => 'DEFAULT'})
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    formatted
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def get_credit_card_id
         | 
| 36 | 
            +
                    response['response']['ext_data'].match(SAFE_CARD_REGEX)
         | 
| 37 | 
            +
                    credit_card_id = $1
         | 
| 38 | 
            +
                    credit_card_id.presence || response['response']['ext_data']['safe_card_token']
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  class Validation < PayCertify::Gateway::Base::Validation
         | 
| 42 | 
            +
                    ALL_VALIDATIONS = [
         | 
| 43 | 
            +
                      # Mandatory fields
         | 
| 44 | 
            +
                      { name: :card_number, validation: :card_number_validation, required: true },
         | 
| 45 | 
            +
                      { name: :expiration_month, validation: :expiration_month_validation, required: true },
         | 
| 46 | 
            +
                      { name: :expiration_year, validation: :expiration_year_validation, required: true },
         | 
| 47 | 
            +
                      { name: :name_on_card, validation: :no_validation, required: true },
         | 
| 48 | 
            +
                      { name: :customer_id, validation: :no_validation, required: true },
         | 
| 49 | 
            +
                      
         | 
| 50 | 
            +
                      # Optional fields
         | 
| 51 | 
            +
                      { name: :zip, validation: :zip_validation, required: false }
         | 
| 52 | 
            +
                    ]
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                    def initialize(attributes={})
         | 
| 55 | 
            +
                      super(attributes)
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                      ALL_VALIDATIONS.each do |attribute|
         | 
| 58 | 
            +
                        presence_validation(attribute) if attribute[:required]
         | 
| 59 | 
            +
                        send(attribute[:validation], attribute) if value_for(attribute).present?
         | 
| 60 | 
            +
                      end
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
            end
         | 
| @@ -0,0 +1,60 @@ | |
| 1 | 
            +
            module PayCertify
         | 
| 2 | 
            +
              class Gateway
         | 
| 3 | 
            +
                class Customer < PayCertify::Gateway::Base::Resource
         | 
| 4 | 
            +
                  
         | 
| 5 | 
            +
                  API_ENDPOINT = '/ws/recurring.asmx/ManageCustomer'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  ATTRIBUTES = [
         | 
| 8 | 
            +
                    :app_customer_id, :name, :customer_id, :type, :address, :city, 
         | 
| 9 | 
            +
                    :state, :zip, :phone, :fax, :email, :status
         | 
| 10 | 
            +
                  ]
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  attr_accessor *ATTRIBUTES
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def save!
         | 
| 15 | 
            +
                    super
         | 
| 16 | 
            +
                    self.customer_id = self.response['response']['customer_key']
         | 
| 17 | 
            +
                    self
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def attributes_to_gateway_format
         | 
| 21 | 
            +
                    formatted = super
         | 
| 22 | 
            +
                    attribute_mapping = PayCertify::Gateway::AttributeMapping
         | 
| 23 | 
            +
                    formatted.merge! attribute_mapping.type(self)
         | 
| 24 | 
            +
                    formatted
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  class Validation < PayCertify::Gateway::Base::Validation
         | 
| 28 | 
            +
                    ALL_VALIDATIONS = [
         | 
| 29 | 
            +
                      # Mandatory fields
         | 
| 30 | 
            +
                      { name: :app_customer_id, validation: :no_validation, required: true },
         | 
| 31 | 
            +
                      { name: :type, validation: :type_validation, required: true },
         | 
| 32 | 
            +
                      { name: :name, validation: :no_validation, required: true },
         | 
| 33 | 
            +
                      
         | 
| 34 | 
            +
                      # Optional fields
         | 
| 35 | 
            +
                      { name: :zip, validation: :zip_validation, required: false },
         | 
| 36 | 
            +
                      { name: :email, validation: :email_validation, required: false },
         | 
| 37 | 
            +
                      { name: :status, validation: :status_validation, required: false }
         | 
| 38 | 
            +
                    ]
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                    ALLOWED_TYPES = %w(add update delete)
         | 
| 41 | 
            +
                    ALLOWED_STATUSES = %w(active inactive pending closed)
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    def initialize(attributes={})
         | 
| 44 | 
            +
                      super(attributes)
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                      ALL_VALIDATIONS.each do |attribute|
         | 
| 47 | 
            +
                        presence_validation(attribute) if attribute[:required]
         | 
| 48 | 
            +
                        send(attribute[:validation], attribute) if value_for(attribute).present?
         | 
| 49 | 
            +
                      end
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    def type_validation(attribute)
         | 
| 53 | 
            +
                      unless value_for(attribute).try(:to_s).in?(ALLOWED_TYPES)
         | 
| 54 | 
            +
                        add_error(attribute, "Must be one of #{ALLOWED_TYPES.join(', ')}")
         | 
| 55 | 
            +
                      end
         | 
| 56 | 
            +
                    end
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
            end
         | 
| @@ -8,24 +8,27 @@ module PayCertify | |
| 8 8 |  | 
| 9 9 | 
             
                  def initialize(response)
         | 
| 10 10 | 
             
                    self.status = response.status
         | 
| 11 | 
            -
                    self.original_body = Hash.from_xml(response.body) | 
| 11 | 
            +
                    self.original_body = Hash.from_xml(response.body)
         | 
| 12 12 |  | 
| 13 | 
            -
                    super(
         | 
| 14 | 
            -
                      id: original_body['PNRef'],
         | 
| 15 | 
            -
                      status: response.status,
         | 
| 16 | 
            -
                      status_string: original_body['Result'] == APPROVED ? 'approved' : 'declined',
         | 
| 17 | 
            -
                      message: [original_body['Message'], original_body['Message1'], original_body['Message2']].compact.join(' / '),
         | 
| 18 | 
            -
                      amount: original_body['Amount'].to_d,
         | 
| 19 | 
            -
                      auth_code: original_body['AuthCode'],
         | 
| 20 | 
            -
                      payment_type: original_body['PaymentType'],
         | 
| 21 | 
            -
                      card_number: original_body['Account'],
         | 
| 22 | 
            -
                      created_at: DateTime.strptime([original_body['TransDate'], original_body['TransTime']].join, '%m%d%Y%H%M%s')
         | 
| 23 | 
            -
                    )
         | 
| 13 | 
            +
                    super(convert_hash_keys(original_body))
         | 
| 24 14 | 
             
                  end
         | 
| 25 15 |  | 
| 26 | 
            -
                   | 
| 27 | 
            -
                     | 
| 28 | 
            -
             | 
| 16 | 
            +
                  private
         | 
| 17 | 
            +
                    def underscore_key(k)
         | 
| 18 | 
            +
                      k.to_s.underscore.to_sym
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    def convert_hash_keys(value)
         | 
| 22 | 
            +
                      case value
         | 
| 23 | 
            +
                        when Array
         | 
| 24 | 
            +
                          value.map { |v| convert_hash_keys(v) }
         | 
| 25 | 
            +
                          # or `value.map(&method(:convert_hash_keys))`
         | 
| 26 | 
            +
                        when Hash
         | 
| 27 | 
            +
                          Hash[value.map { |k, v| [underscore_key(k), convert_hash_keys(v)] }]
         | 
| 28 | 
            +
                        else
         | 
| 29 | 
            +
                          value
         | 
| 30 | 
            +
                       end
         | 
| 31 | 
            +
                    end
         | 
| 29 32 | 
             
                end
         | 
| 30 33 | 
             
              end
         | 
| 31 34 | 
             
            end
         | 
| @@ -1,54 +1,116 @@ | |
| 1 | 
            +
            require 'ipaddr'
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module PayCertify
         | 
| 2 4 | 
             
              class Gateway
         | 
| 3 | 
            -
                class Transaction
         | 
| 4 | 
            -
             | 
| 5 | 
            -
                  TRANSACTION_PATH = '/ws/encgateway2.asmx/ProcessCreditCard'
         | 
| 6 | 
            -
             | 
| 7 | 
            -
                  attr_accessor :client, :original_attributes
         | 
| 5 | 
            +
                class Transaction < PayCertify::Gateway::Base::Resource
         | 
| 8 6 |  | 
| 9 | 
            -
                   | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
                    : | 
| 7 | 
            +
                  API_ENDPOINT = '/ws/encgateway2.asmx/ProcessCreditCard'
         | 
| 8 | 
            +
                  
         | 
| 9 | 
            +
                  ATTRIBUTES = [
         | 
| 10 | 
            +
                    :transaction_id, :type, :amount, :currency, :card_number, :expiration_month, :expiration_year, 
         | 
| 11 | 
            +
                    :name_on_card, :cvv, :billing_address, :billing_city, :billing_state, :billing_country, 
         | 
| 12 | 
            +
                    :billing_zip, :shipping_address, :shipping_city, :shipping_state, :shipping_country,  :shipping_zip, 
         | 
| 13 | 
            +
                    :email, :phone, :ip, :order_description, :customer_id
         | 
| 14 | 
            +
                  ]
         | 
| 13 15 |  | 
| 14 | 
            -
                   | 
| 15 | 
            -
                    self.original_attributes = attributes
         | 
| 16 | 
            -
                    self.client = client
         | 
| 16 | 
            +
                  attr_accessor *ATTRIBUTES
         | 
| 17 17 |  | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
                     | 
| 18 | 
            +
                  def save!
         | 
| 19 | 
            +
                    super
         | 
| 20 | 
            +
                    self.transaction_id = response['response']['pn_ref']
         | 
| 21 | 
            +
                    self
         | 
| 21 22 | 
             
                  end
         | 
| 22 23 |  | 
| 23 | 
            -
                  def  | 
| 24 | 
            -
                     | 
| 24 | 
            +
                  def success?
         | 
| 25 | 
            +
                    super && response['response']['result'] == '0'
         | 
| 25 26 | 
             
                  end
         | 
| 26 27 |  | 
| 27 | 
            -
                  def  | 
| 28 | 
            -
                     | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
                    )
         | 
| 28 | 
            +
                  def attributes_to_gateway_format
         | 
| 29 | 
            +
                    formatted = super
         | 
| 30 | 
            +
                    attribute_mapping = PayCertify::Gateway::AttributeMapping
         | 
| 31 | 
            +
                    formatted.merge! attribute_mapping.expiration_date(self)
         | 
| 32 | 
            +
                    formatted.merge! attribute_mapping.type(self)
         | 
| 33 | 
            +
                    formatted
         | 
| 32 34 | 
             
                  end
         | 
| 33 35 |  | 
| 34 | 
            -
                   | 
| 35 | 
            -
                     | 
| 36 | 
            -
                       | 
| 37 | 
            -
             | 
| 36 | 
            +
                  class Validation < PayCertify::Gateway::Base::Validation
         | 
| 37 | 
            +
                    ALL_VALIDATIONS = [
         | 
| 38 | 
            +
                      # Mandatory fields
         | 
| 39 | 
            +
                      { name: :type, validation: :type_validation, required: true },
         | 
| 40 | 
            +
                      { name: :amount, validation: :amount_validation, required: true },
         | 
| 41 | 
            +
                      { name: :currency, validation: :currency_validation, required: true },
         | 
| 42 | 
            +
                      { name: :card_number, validation: :card_number_validation, required: true },
         | 
| 43 | 
            +
                      { name: :expiration_month, validation: :expiration_month_validation, required: true },
         | 
| 44 | 
            +
                      { name: :expiration_year, validation: :expiration_year_validation, required: true },
         | 
| 45 | 
            +
                      { name: :name_on_card, validation: :no_validation, required: true },
         | 
| 46 | 
            +
                      { name: :cvv, validation: :no_validation, required: true },
         | 
| 47 | 
            +
                      { name: :transaction_id, validation: :no_validation, required: true },
         | 
| 48 | 
            +
                      { name: :billing_city, validation: :no_validation, required: true },
         | 
| 49 | 
            +
                      { name: :billing_state, validation: :no_validation, required: true },
         | 
| 50 | 
            +
                      { name: :billing_country, validation: :no_validation, required: true },
         | 
| 51 | 
            +
                      { name: :billing_zip, validation: :zip_validation, required: true },
         | 
| 52 | 
            +
                      
         | 
| 53 | 
            +
                      # Optional fields
         | 
| 54 | 
            +
                      { name: :shipping_zip, validation: :zip_validation, required: false },
         | 
| 55 | 
            +
                      { name: :email, validation: :email_validation, required: false },
         | 
| 56 | 
            +
                      { name: :ip, validation: :ip_validation, required: false }
         | 
| 57 | 
            +
                    ]
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    CAPTURE_VALIDATIONS = [
         | 
| 60 | 
            +
                      { name: :type, validation: :type_validation, required: true },
         | 
| 61 | 
            +
                      { name: :amount, validation: :amount_validation, required: true },
         | 
| 62 | 
            +
                      { name: :transaction_id, validation: :no_validation, required: true }
         | 
| 63 | 
            +
                    ]
         | 
| 38 64 |  | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
                        end
         | 
| 65 | 
            +
                    VOID_VALIDATIONS = [
         | 
| 66 | 
            +
                      { name: :type, validation: :type_validation, required: true },
         | 
| 67 | 
            +
                      # { name: :amount, validation: :amount_validation, required: true },
         | 
| 68 | 
            +
                      { name: :transaction_id, validation: :no_validation, required: true }
         | 
| 69 | 
            +
                    ]
         | 
| 45 70 |  | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 71 | 
            +
                    ALLOWED_TYPES = %w(sale auth return void force recurring)
         | 
| 72 | 
            +
                    ALLOWED_CURRENCIES = %w(USD EUR)
         | 
| 48 73 |  | 
| 49 | 
            -
             | 
| 74 | 
            +
                    def initialize(attributes={})
         | 
| 75 | 
            +
                      super(attributes)
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                      validations.each do |attribute|
         | 
| 78 | 
            +
                        presence_validation(attribute) if attribute[:required]
         | 
| 79 | 
            +
                        send(attribute[:validation], attribute) if value_for(attribute).present?
         | 
| 50 80 | 
             
                      end
         | 
| 51 81 | 
             
                    end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                    def validations
         | 
| 84 | 
            +
                      case attributes[:type]
         | 
| 85 | 
            +
                      when 'force'
         | 
| 86 | 
            +
                        CAPTURE_VALIDATIONS
         | 
| 87 | 
            +
                      when 'void', 'return'
         | 
| 88 | 
            +
                        VOID_VALIDATIONS
         | 
| 89 | 
            +
                      else
         | 
| 90 | 
            +
                        ALL_VALIDATIONS
         | 
| 91 | 
            +
                      end
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                    def type_validation(attribute)
         | 
| 95 | 
            +
                      unless value_for(attribute).try(:to_s).in?(ALLOWED_TYPES)
         | 
| 96 | 
            +
                        add_error(attribute, "Must be one of #{ALLOWED_TYPES.join(', ')}")
         | 
| 97 | 
            +
                      end
         | 
| 98 | 
            +
                    end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                    def currency_validation(attribute)
         | 
| 101 | 
            +
                      set_attribute(attribute, value_for(attribute).upcase)
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                      unless value_for(attribute).try(:to_s).in?(ALLOWED_CURRENCIES)
         | 
| 104 | 
            +
                        add_error(attribute, "Must be one of #{ALLOWED_CURRENCIES.join(', ')}")
         | 
| 105 | 
            +
                      end
         | 
| 106 | 
            +
                    end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                    def ip_validation(attribute)
         | 
| 109 | 
            +
                      IPAddr.new(value_for(attribute))
         | 
| 110 | 
            +
                    rescue IPAddr::InvalidAddressError
         | 
| 111 | 
            +
                      add_error(attribute, "Doesn't validate as an IP.")
         | 
| 112 | 
            +
                    end
         | 
| 113 | 
            +
                  end
         | 
| 52 114 | 
             
                end
         | 
| 53 115 | 
             
              end
         | 
| 54 116 | 
             
            end
         | 
| @@ -0,0 +1,118 @@ | |
| 1 | 
            +
            module PayCertify
         | 
| 2 | 
            +
              class Insurance
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                class NoCredentialsError < StandardError; end
         | 
| 5 | 
            +
                
         | 
| 6 | 
            +
                API_ENDPOINT = 'https://connect.paycertify.com/'
         | 
| 7 | 
            +
                
         | 
| 8 | 
            +
                MANDATORY_FIELDS = [
         | 
| 9 | 
            +
                  :firstname, :lastname, :email, :order_number, :items_ordered, :charge_amount, 
         | 
| 10 | 
            +
                  :billing_address, :billing_address2, :billing_city, :billing_state, :billing_country, :billing_zip_code
         | 
| 11 | 
            +
                ]
         | 
| 12 | 
            +
                
         | 
| 13 | 
            +
                OPTIONAL_FIELDS = [
         | 
| 14 | 
            +
                  :phone, :shipping_address, :shipping_address2, :shipping_city, 
         | 
| 15 | 
            +
                  :shipping_state, :shipping_country, :shipping_zip_code, :shipping_carrier, :tracking_number
         | 
| 16 | 
            +
                ]
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                attr_accessor :attributes, :errors, :response
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                delegate :client_id, :token, to: :class
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def initialize(attributes)
         | 
| 23 | 
            +
                  raise NoCredentialsError, 'No token found for api_client/secret/client_id combination.' unless token.present?
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  self.attributes = HashWithIndifferentAccess.new(attributes)
         | 
| 26 | 
            +
                  self.errors = {}
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  validate!
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def success?
         | 
| 32 | 
            +
                  response.success?
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def save!
         | 
| 36 | 
            +
                  data = attributes.slice *[MANDATORY_FIELDS + OPTIONAL_FIELDS].flatten
         | 
| 37 | 
            +
                  data[:ship_to_billing_addr] = true unless data[:shipping_address].present?
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  api_response = connection.post do |request|
         | 
| 40 | 
            +
                    request.url path_for('orders')
         | 
| 41 | 
            +
                    request.headers['Content-Type'] = 'application/json'
         | 
| 42 | 
            +
                    request.headers['Authorization'] = "JWT #{token}"
         | 
| 43 | 
            +
                    request.body = JSON.generate(data)
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  self.response = Response.new(api_response)
         | 
| 47 | 
            +
                  self.errors = errors.merge(response) unless response.success?
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  response
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                class << self
         | 
| 53 | 
            +
                  attr_accessor :api_public_key, :api_secret_key, :client_id, :token
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  def configure(&block)
         | 
| 56 | 
            +
                    yield self if block_given?
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                    connection = Faraday.new(url: API_ENDPOINT, ssl: {verify: false}) do |faraday|
         | 
| 59 | 
            +
                      faraday.request :url_encoded
         | 
| 60 | 
            +
                      faraday.response :logger
         | 
| 61 | 
            +
                      faraday.adapter  Faraday.default_adapter
         | 
| 62 | 
            +
                    end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                    response = connection.get do |request|
         | 
| 65 | 
            +
                      request.url 'api/v1/token'
         | 
| 66 | 
            +
                      request.headers['api-public-key'] = api_public_key
         | 
| 67 | 
            +
                      request.headers['api-secret-key'] = api_secret_key
         | 
| 68 | 
            +
                      request.headers['api-client-id']  = client_id
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                    json = JSON.parse(response.body)
         | 
| 72 | 
            +
                    
         | 
| 73 | 
            +
                    self.token = json['jwt']
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                    return {
         | 
| 76 | 
            +
                      api_public_key: api_public_key,
         | 
| 77 | 
            +
                      api_secret_key: api_secret_key,
         | 
| 78 | 
            +
                      client_id: client_id,
         | 
| 79 | 
            +
                      token: token
         | 
| 80 | 
            +
                    }
         | 
| 81 | 
            +
                  end
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                class Response < HashWithIndifferentAccess
         | 
| 85 | 
            +
                  attr_accessor :original_response
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                  def initialize(response)
         | 
| 88 | 
            +
                    self.original_response = response
         | 
| 89 | 
            +
                    super JSON.parse(response.body)
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                  def success?
         | 
| 93 | 
            +
                    original_response.status < 400
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                private
         | 
| 98 | 
            +
                  def validate!
         | 
| 99 | 
            +
                    MANDATORY_FIELDS.each do |field|
         | 
| 100 | 
            +
                      unless attributes[field].present?
         | 
| 101 | 
            +
                        self.errors[field] = "Required attribute not present"
         | 
| 102 | 
            +
                      end
         | 
| 103 | 
            +
                    end
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                  def connection
         | 
| 107 | 
            +
                    @connection ||= Faraday.new(url: API_ENDPOINT, ssl: {verify: false}) do |faraday|
         | 
| 108 | 
            +
                      faraday.request :url_encoded
         | 
| 109 | 
            +
                      faraday.response :logger
         | 
| 110 | 
            +
                      faraday.adapter  Faraday.default_adapter
         | 
| 111 | 
            +
                    end
         | 
| 112 | 
            +
                  end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                  def path_for(path)
         | 
| 115 | 
            +
                    return "api/v1/#{client_id}/#{path}/"
         | 
| 116 | 
            +
                  end
         | 
| 117 | 
            +
              end
         | 
| 118 | 
            +
            end
         | 
    
        data/lib/paycertify/three_ds.rb
    CHANGED
    
    
| @@ -19,6 +19,14 @@ module PayCertify | |
| 19 19 | 
             
                    self['_frictionless_3ds_callback'].present?
         | 
| 20 20 | 
             
                  end
         | 
| 21 21 |  | 
| 22 | 
            +
                  def handshake
         | 
| 23 | 
            +
                    self.slice('cavv', 'eci', 'cavv_algorithm', 'xid')
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def redirect_to(location)
         | 
| 27 | 
            +
                    "<script>window.location.href = '#{location}'</script>"
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 22 30 | 
             
                  def authenticate!
         | 
| 23 31 | 
             
                    PayCertify::ThreeDS.authenticate!(settings: session, callback_params: params)
         | 
| 24 32 | 
             
                  end
         | 
| @@ -1,4 +1,5 @@ | |
| 1 1 | 
             
            require 'faraday'
         | 
| 2 | 
            +
            require 'faraday_curl'
         | 
| 2 3 |  | 
| 3 4 | 
             
            module PayCertify
         | 
| 4 5 | 
             
              class ThreeDS
         | 
| @@ -56,12 +57,16 @@ module PayCertify | |
| 56 57 | 
             
                    def connection
         | 
| 57 58 | 
             
                      @connection ||= Faraday.new(url: API_ENDPOINT, ssl: {verify: false}) do |faraday|
         | 
| 58 59 | 
             
                        faraday.request :url_encoded
         | 
| 60 | 
            +
                        faraday.request :curl, Logger.new(STDOUT), :warn
         | 
| 59 61 | 
             
                        faraday.response :logger
         | 
| 60 62 | 
             
                        faraday.adapter  Faraday.default_adapter
         | 
| 61 63 | 
             
                      end
         | 
| 62 64 | 
             
                    end
         | 
| 63 65 |  | 
| 64 66 | 
             
                    def signature(path, data)
         | 
| 67 | 
            +
                      10.times{puts}
         | 
| 68 | 
            +
                      puts "#{api_key}#{path_for(path)}#{data}#{api_secret}"
         | 
| 69 | 
            +
                      10.times{puts}
         | 
| 65 70 | 
             
                      Digest::SHA256.hexdigest "#{api_key}#{path_for(path)}#{data}#{api_secret}"
         | 
| 66 71 | 
             
                    end
         | 
| 67 72 |  | 
| @@ -46,7 +46,7 @@ module PayCertify | |
| 46 46 | 
             
                      </script>
         | 
| 47 47 | 
             
                    HTML
         | 
| 48 48 | 
             
                  end
         | 
| 49 | 
            -
             | 
| 49 | 
            +
             | 
| 50 50 | 
             
                  def frictionless
         | 
| 51 51 | 
             
                    html = <<-HTML.squish
         | 
| 52 52 | 
             
                      <style> #frame { display: none; } </style>
         | 
| @@ -69,42 +69,40 @@ module PayCertify | |
| 69 69 | 
             
                          var frame = document.getElementById('frame');
         | 
| 70 70 | 
             
                          var form = document.getElementById('callback-form');
         | 
| 71 71 | 
             
                          var interval = 500;
         | 
| 72 | 
            -
                          var timeout = interval *  | 
| 73 | 
            -
                          var counter = 0;
         | 
| 72 | 
            +
                          var timeout = interval * 15;
         | 
| 74 73 |  | 
| 75 74 | 
             
                          frame.contentDocument.write('#{form}');
         | 
| 76 75 | 
             
                          frame.contentDocument.form3ds.submit();
         | 
| 77 76 |  | 
| 78 | 
            -
                          setInterval(function() {
         | 
| 79 | 
            -
                             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
                              try {
         | 
| 83 | 
            -
                                var frameContent = frame.contentDocument;
         | 
| 84 | 
            -
                                var frameDoc = frameContent.documentElement;
         | 
| 77 | 
            +
                          var interval = setInterval(function() {
         | 
| 78 | 
            +
                            try {
         | 
| 79 | 
            +
                              var frameContent = frame.contentDocument;
         | 
| 80 | 
            +
                              var frameDoc = frameContent.documentElement;
         | 
| 85 81 |  | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 88 | 
            -
                                
         | 
| 89 | 
            -
                                var input;
         | 
| 82 | 
            +
                              var text = frameContent.body.innerHTML || frameDoc.textContent || frameDoc.innerText;
         | 
| 83 | 
            +
                              var json = JSON.parse(text);
         | 
| 90 84 |  | 
| 91 | 
            -
             | 
| 92 | 
            -
                                  input = document.createElement('input');
         | 
| 93 | 
            -
                                  input.type = 'hidden';
         | 
| 94 | 
            -
                                  input.name = key;
         | 
| 95 | 
            -
                                  input.value = json[key];
         | 
| 85 | 
            +
                              var input;
         | 
| 96 86 |  | 
| 97 | 
            -
             | 
| 98 | 
            -
                                 | 
| 87 | 
            +
                              for(key in json) {
         | 
| 88 | 
            +
                                input = document.createElement('input');
         | 
| 89 | 
            +
                                input.type = 'hidden';
         | 
| 90 | 
            +
                                input.name = key;
         | 
| 91 | 
            +
                                input.value = json[key];
         | 
| 99 92 |  | 
| 100 | 
            -
                                form. | 
| 101 | 
            -
                              } catch(e) {
         | 
| 102 | 
            -
                                return false;
         | 
| 93 | 
            +
                                form.appendChild(input);
         | 
| 103 94 | 
             
                              };
         | 
| 104 | 
            -
             | 
| 95 | 
            +
             | 
| 96 | 
            +
                              clearInterval(interval);
         | 
| 105 97 | 
             
                              form.submit();
         | 
| 106 | 
            -
                            }
         | 
| 98 | 
            +
                            } catch(e) {
         | 
| 99 | 
            +
                              return false;
         | 
| 100 | 
            +
                            };
         | 
| 107 101 | 
             
                          }, interval);
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                          setTimeout(function() {
         | 
| 104 | 
            +
                            form.submit();
         | 
| 105 | 
            +
                          }, timeout);
         | 
| 108 106 | 
             
                        })();
         | 
| 109 107 | 
             
                      </script>
         | 
| 110 108 | 
             
                    HTML
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: paycertify
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.0 | 
| 4 | 
            +
              version: 0.1.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - PayCertify Engineering Team
         | 
| @@ -38,6 +38,20 @@ dependencies: | |
| 38 38 | 
             
                - - ">="
         | 
| 39 39 | 
             
                  - !ruby/object:Gem::Version
         | 
| 40 40 | 
             
                    version: '0'
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: faraday_curl
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - ">="
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: '0'
         | 
| 48 | 
            +
              type: :runtime
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - ">="
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '0'
         | 
| 41 55 | 
             
            description: Interact with the Gateway, 3DS, Kount, and FraudPortal
         | 
| 42 56 | 
             
            email: engineering@paycertify.com
         | 
| 43 57 | 
             
            executables: []
         | 
| @@ -45,12 +59,19 @@ extensions: [] | |
| 45 59 | 
             
            extra_rdoc_files: []
         | 
| 46 60 | 
             
            files:
         | 
| 47 61 | 
             
            - lib/paycertify.rb
         | 
| 62 | 
            +
            - lib/paycertify/confirmation.rb
         | 
| 48 63 | 
             
            - lib/paycertify/gateway.rb
         | 
| 49 64 | 
             
            - lib/paycertify/gateway/attribute_mapping.rb
         | 
| 65 | 
            +
            - lib/paycertify/gateway/base.rb
         | 
| 66 | 
            +
            - lib/paycertify/gateway/base/resource.rb
         | 
| 67 | 
            +
            - lib/paycertify/gateway/base/validation.rb
         | 
| 68 | 
            +
            - lib/paycertify/gateway/charge.rb
         | 
| 50 69 | 
             
            - lib/paycertify/gateway/client.rb
         | 
| 70 | 
            +
            - lib/paycertify/gateway/credit_card.rb
         | 
| 71 | 
            +
            - lib/paycertify/gateway/customer.rb
         | 
| 51 72 | 
             
            - lib/paycertify/gateway/response.rb
         | 
| 52 73 | 
             
            - lib/paycertify/gateway/transaction.rb
         | 
| 53 | 
            -
            - lib/paycertify/ | 
| 74 | 
            +
            - lib/paycertify/insurance.rb
         | 
| 54 75 | 
             
            - lib/paycertify/three_ds.rb
         | 
| 55 76 | 
             
            - lib/paycertify/three_ds/callback.rb
         | 
| 56 77 | 
             
            - lib/paycertify/three_ds/client.rb
         | 
| @@ -1,174 +0,0 @@ | |
| 1 | 
            -
            require 'ipaddr'
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            module PayCertify
         | 
| 4 | 
            -
              class Gateway
         | 
| 5 | 
            -
                class Validation
         | 
| 6 | 
            -
             | 
| 7 | 
            -
                  ATTRIBUTES = [
         | 
| 8 | 
            -
                    # Mandatory fields
         | 
| 9 | 
            -
                    { name: :type, validation: :type_validation, required: true },
         | 
| 10 | 
            -
                    { name: :amount, validation: :amount_validation, required: true },
         | 
| 11 | 
            -
                    { name: :currency, validation: :currency_validation, required: true },
         | 
| 12 | 
            -
                    { name: :card_number, validation: :card_number_validation, required: true },
         | 
| 13 | 
            -
                    { name: :expiration_month, validation: :expiration_month_validation, required: true },
         | 
| 14 | 
            -
                    { name: :expiration_year, validation: :expiration_year_validation, required: true },
         | 
| 15 | 
            -
                    { name: :name_on_card, validation: :no_validation, required: true },
         | 
| 16 | 
            -
                    { name: :cvv, validation: :no_validation, required: true },
         | 
| 17 | 
            -
                    { name: :transaction_id, validation: :no_validation, required: true },
         | 
| 18 | 
            -
                    { name: :billing_city, validation: :no_validation, required: true },
         | 
| 19 | 
            -
                    { name: :billing_state, validation: :no_validation, required: true },
         | 
| 20 | 
            -
                    { name: :billing_country, validation: :no_validation, required: true },
         | 
| 21 | 
            -
                    { name: :billing_zip, validation: :zip_validation, required: true },
         | 
| 22 | 
            -
                    
         | 
| 23 | 
            -
                    # Optional fields
         | 
| 24 | 
            -
                    { name: :billing_address, validation: :no_validation, required: false },
         | 
| 25 | 
            -
                    { name: :shipping_address, validation: :no_validation, required: false },
         | 
| 26 | 
            -
                    { name: :shipping_city, validation: :no_validation, required: false },
         | 
| 27 | 
            -
                    { name: :shipping_state, validation: :no_validation, required: false },
         | 
| 28 | 
            -
                    { name: :shipping_country, validation: :no_validation, required: false },
         | 
| 29 | 
            -
                    { name: :shipping_zip, validation: :zip_validation, required: false },
         | 
| 30 | 
            -
                    { name: :email, validation: :email_validation, required: false },
         | 
| 31 | 
            -
                    { name: :phone, validation: :no_validation, required: false },
         | 
| 32 | 
            -
                    { name: :ip, validation: :ip_validation, required: false },
         | 
| 33 | 
            -
                    { name: :order_description, validation: :no_validation, required: false },
         | 
| 34 | 
            -
                    { name: :customer_id, validation: :no_validation, required: false },
         | 
| 35 | 
            -
                    { name: :metadata, validation: :no_validation, required: false }
         | 
| 36 | 
            -
                  ]
         | 
| 37 | 
            -
             | 
| 38 | 
            -
                  ALLOWED_TYPES = %w(sale auth refund void force recurring)
         | 
| 39 | 
            -
                  ALLOWED_CURRENCIES = %w(USD EUR)
         | 
| 40 | 
            -
                  CREDIT_CARD_REGEX = /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/i
         | 
| 41 | 
            -
                  EMAIL_REGEX = /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
         | 
| 42 | 
            -
             | 
| 43 | 
            -
                  class ValidationError < StandardError; end
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                  attr_accessor :attributes, :errors
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                  def initialize(attributes={})
         | 
| 48 | 
            -
                    self.attributes = attributes
         | 
| 49 | 
            -
             | 
| 50 | 
            -
                    ATTRIBUTES.each do |attribute|
         | 
| 51 | 
            -
                      presence_validation(attribute) if attribute[:required]
         | 
| 52 | 
            -
                      send(attribute[:validation], attribute) if value_for(attribute).present?
         | 
| 53 | 
            -
                    end
         | 
| 54 | 
            -
                  end
         | 
| 55 | 
            -
             | 
| 56 | 
            -
                  protected
         | 
| 57 | 
            -
                    def presence_validation(attribute)
         | 
| 58 | 
            -
                      if value_for(attribute).blank?
         | 
| 59 | 
            -
                        raise ValidationError, "Required attribute not present: #{attribute[:name]}."
         | 
| 60 | 
            -
                      end
         | 
| 61 | 
            -
                    end
         | 
| 62 | 
            -
             | 
| 63 | 
            -
                    def no_validation(_); end
         | 
| 64 | 
            -
             | 
| 65 | 
            -
                    def type_validation(attribute)
         | 
| 66 | 
            -
                      unless value_for(attribute).try(:to_s).in?(ALLOWED_TYPES)
         | 
| 67 | 
            -
                        raise ValidationError, error_message(attribute, "Must be one of #{ALLOWED_TYPES.join(', ')}")
         | 
| 68 | 
            -
                      end
         | 
| 69 | 
            -
                    end
         | 
| 70 | 
            -
             | 
| 71 | 
            -
                    def amount_validation(attribute)
         | 
| 72 | 
            -
                      set_attribute(attribute, Float(value_for(attribute)))
         | 
| 73 | 
            -
                    rescue ArgumentError
         | 
| 74 | 
            -
                      raise ValidationError, error_message(attribute, "Must be a float, integer or decimal")
         | 
| 75 | 
            -
                    end
         | 
| 76 | 
            -
             | 
| 77 | 
            -
                    def currency_validation(attribute)
         | 
| 78 | 
            -
                      set_attribute(attribute, value_for(attribute).upcase)
         | 
| 79 | 
            -
             | 
| 80 | 
            -
                      unless value_for(attribute).try(:to_s).in?(ALLOWED_CURRENCIES)
         | 
| 81 | 
            -
                        raise ValidationError, error_message(attribute, "Must be one of #{ALLOWED_CURRENCIES.join(', ')}")
         | 
| 82 | 
            -
                      end
         | 
| 83 | 
            -
                    end
         | 
| 84 | 
            -
             | 
| 85 | 
            -
                    def card_number_validation(attribute)
         | 
| 86 | 
            -
                      # Non decimal numbers should be stripped to match.
         | 
| 87 | 
            -
                      set_attribute(attribute, value_for(attribute).gsub(/\D/, ''))
         | 
| 88 | 
            -
             | 
| 89 | 
            -
                      unless value_for(attribute) =~ CREDIT_CARD_REGEX
         | 
| 90 | 
            -
                        raise ValidationError, error_message(attribute, "Doesn't validate as a credit card.")
         | 
| 91 | 
            -
                      end
         | 
| 92 | 
            -
                    end
         | 
| 93 | 
            -
             | 
| 94 | 
            -
                    def zip_validation
         | 
| 95 | 
            -
                      # Check if it's a number
         | 
| 96 | 
            -
                      set_attribute(attribute, Integer(value_for(attribute)).to_s)
         | 
| 97 | 
            -
             | 
| 98 | 
            -
                      if value_for(attribute).length > 5
         | 
| 99 | 
            -
                        # Raise validation error
         | 
| 100 | 
            -
                      end
         | 
| 101 | 
            -
                    end
         | 
| 102 | 
            -
             | 
| 103 | 
            -
                    def expiration_month_validation(attribute)
         | 
| 104 | 
            -
                      # if a string, check if length = 2 and smaller than int 12
         | 
| 105 | 
            -
                      # if int, transform into string with zero pad and check if smaller than int 12
         | 
| 106 | 
            -
                      integer = Integer(value_for(attribute))
         | 
| 107 | 
            -
             | 
| 108 | 
            -
                      if integer > 12
         | 
| 109 | 
            -
                        raise ValidationError, error_message(attribute, "Must be smaller than 12.")
         | 
| 110 | 
            -
                      end
         | 
| 111 | 
            -
             | 
| 112 | 
            -
                      set_attribute(attribute, integer.to_s.rjust(2, '0'))
         | 
| 113 | 
            -
                      
         | 
| 114 | 
            -
                    rescue ArgumentError
         | 
| 115 | 
            -
                      raise ValidationError, error_message(attribute, "Must be an integer.")
         | 
| 116 | 
            -
                    end
         | 
| 117 | 
            -
             | 
| 118 | 
            -
                    def expiration_year_validation(attribute)
         | 
| 119 | 
            -
                      # if length = 4, strip to 2;
         | 
| 120 | 
            -
                      # if a string, check if length = 2 and smaller than int 12
         | 
| 121 | 
            -
                      # if int, transform into string with zero pad and check if smaller than int 12
         | 
| 122 | 
            -
             | 
| 123 | 
            -
             | 
| 124 | 
            -
                      is_four_digit = if value_for(attribute).is_a?(String)
         | 
| 125 | 
            -
                        value_for(attribute).length == 4
         | 
| 126 | 
            -
                      else
         | 
| 127 | 
            -
                        value_for(attribute) > 999
         | 
| 128 | 
            -
                      end
         | 
| 129 | 
            -
             | 
| 130 | 
            -
                      integer_value = Integer(value_for(attribute))
         | 
| 131 | 
            -
             | 
| 132 | 
            -
                      set_attribute(attribute, integer_value.to_s.last(2))
         | 
| 133 | 
            -
                    rescue
         | 
| 134 | 
            -
                      raise ValidationError, error_message(attribute, "Must be a 2 to 4-digit string.")
         | 
| 135 | 
            -
                    end
         | 
| 136 | 
            -
             | 
| 137 | 
            -
                    def zip_validation(attribute)
         | 
| 138 | 
            -
                      set_attribute(attribute, Integer(value_for(attribute)).to_s)
         | 
| 139 | 
            -
             | 
| 140 | 
            -
                      unless value_for(attribute).length == 5
         | 
| 141 | 
            -
                        raise ValidationError, error_message(attribute, "Must be a 5-digit string that can evaluate to a number.")
         | 
| 142 | 
            -
                      end
         | 
| 143 | 
            -
             | 
| 144 | 
            -
                    rescue
         | 
| 145 | 
            -
                      raise ValidationError, error_message(attribute, "Must be a 5-digit string that can evaluate to a number.")
         | 
| 146 | 
            -
                    end
         | 
| 147 | 
            -
             | 
| 148 | 
            -
                    def email_validation(attribute)
         | 
| 149 | 
            -
                      unless value_for(attribute) =~ EMAIL_REGEX
         | 
| 150 | 
            -
                        raise ValidationError, error_message(attribute, "Doesn't validate as an email.")
         | 
| 151 | 
            -
                      end
         | 
| 152 | 
            -
                    end
         | 
| 153 | 
            -
             | 
| 154 | 
            -
                    def ip_validation(attribute)
         | 
| 155 | 
            -
                      IPAddr.new(value_for(attribute))
         | 
| 156 | 
            -
                    rescue IPAddr::InvalidAddressError
         | 
| 157 | 
            -
                      raise ValidationError, error_message(attribute, "Doesn't validate as an IP.")
         | 
| 158 | 
            -
                    end
         | 
| 159 | 
            -
             | 
| 160 | 
            -
                  private
         | 
| 161 | 
            -
                    def value_for(attribute)
         | 
| 162 | 
            -
                      attributes[attribute[:name]]
         | 
| 163 | 
            -
                    end
         | 
| 164 | 
            -
             | 
| 165 | 
            -
                    def set_attribute(attribute, value)
         | 
| 166 | 
            -
                      self.attributes[attribute[:name]] = value
         | 
| 167 | 
            -
                    end
         | 
| 168 | 
            -
             | 
| 169 | 
            -
                    def error_message(attribute, message)
         | 
| 170 | 
            -
                      "Attribute #{attribute[:name]} passed as: #{value_for(attribute)}. #{message}"
         | 
| 171 | 
            -
                    end
         | 
| 172 | 
            -
                end
         | 
| 173 | 
            -
              end
         | 
| 174 | 
            -
            end
         |