rocketgate-ruby 0.0.1.pre
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 +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +14 -0
- data/.travis.yml +20 -0
- data/Gemfile +3 -0
- data/Guardfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +161 -0
- data/Rakefile +14 -0
- data/lib/rocketgate.rb +52 -0
- data/lib/rocketgate/authorization.rb +31 -0
- data/lib/rocketgate/configuration.rb +53 -0
- data/lib/rocketgate/connection.rb +30 -0
- data/lib/rocketgate/credit_card.rb +42 -0
- data/lib/rocketgate/customer.rb +33 -0
- data/lib/rocketgate/hashable.rb +63 -0
- data/lib/rocketgate/request.rb +48 -0
- data/lib/rocketgate/response.rb +64 -0
- data/lib/rocketgate/response_code.rb +148 -0
- data/lib/rocketgate/transaction.rb +75 -0
- data/lib/rocketgate/validatable.rb +52 -0
- data/lib/rocketgate/version.rb +3 -0
- data/rocketgate-ruby.gemspec +34 -0
- data/spec/authorization_spec.rb +87 -0
- data/spec/configuration_spec.rb +33 -0
- data/spec/connection_spec.rb +65 -0
- data/spec/credit_card_spec.rb +73 -0
- data/spec/customer_spec.rb +57 -0
- data/spec/hashable_spec.rb +85 -0
- data/spec/integration_spec.rb +131 -0
- data/spec/request_spec.rb +92 -0
- data/spec/response_spec.rb +190 -0
- data/spec/rocketgate_spec.rb +108 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/transaction_spec.rb +250 -0
- data/spec/validatable_spec.rb +79 -0
- metadata +261 -0
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            module RocketGate
         | 
| 2 | 
            +
              class Customer
         | 
| 3 | 
            +
                include Hashable, Validatable
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                attr_accessor :id, :first_name, :last_name, :street_address, :city,
         | 
| 6 | 
            +
                              :postal_code, :state, :country, :email, :username, :ip_address
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                validatable :id, :first_name, :last_name
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                hashable({
         | 
| 11 | 
            +
                  merchantCustomerID:     :id,
         | 
| 12 | 
            +
                  customerFirstName:      :first_name,
         | 
| 13 | 
            +
                  customerLastName:       :last_name,
         | 
| 14 | 
            +
                  billingAddress:         :street_address,
         | 
| 15 | 
            +
                  billingCity:            :city,
         | 
| 16 | 
            +
                  billingState:           :state,
         | 
| 17 | 
            +
                  billingZipCode:         :postal_code,
         | 
| 18 | 
            +
                  billingCountry:         :country,
         | 
| 19 | 
            +
                  email:                  :email,
         | 
| 20 | 
            +
                  username:               :username,
         | 
| 21 | 
            +
                  ipAddress:              :ip_address
         | 
| 22 | 
            +
                })
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def initialize(*args)
         | 
| 25 | 
            +
                  @first_name, @last_name, @street_address, @city, @state, @postal_code,
         | 
| 26 | 
            +
                    @country, @email, @username, @ip_address, @id = *args
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def id
         | 
| 30 | 
            +
                  @id ||= SecureRandom.uuid
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| @@ -0,0 +1,63 @@ | |
| 1 | 
            +
            module RocketGate::Hashable
         | 
| 2 | 
            +
              module ClassMethods
         | 
| 3 | 
            +
                def hashable(mapping_hash, *mergable_items)
         | 
| 4 | 
            +
                  @mappings, @mergables = {}, []
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  mapping_hash.each do |display_name, obj|
         | 
| 7 | 
            +
                    if self.new.send(:is_callable?, obj)
         | 
| 8 | 
            +
                      @mappings[display_name] = obj
         | 
| 9 | 
            +
                    else
         | 
| 10 | 
            +
                      raise RocketGate::ValidationError.new("Invalid attribute: #{obj}")
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  mergable_items.each do |obj|
         | 
| 15 | 
            +
                    if self.new.send(:is_callable?, obj)
         | 
| 16 | 
            +
                      @mergables << obj
         | 
| 17 | 
            +
                    else
         | 
| 18 | 
            +
                      raise RocketGate::ValidationError.new("Invalid attribute: #{obj}")
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def mappings
         | 
| 24 | 
            +
                  @mappings ||= {}
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def mergables
         | 
| 28 | 
            +
                  @mergables ||= []
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              def self.included(base)
         | 
| 33 | 
            +
                base.extend(ClassMethods)
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              def to_hash
         | 
| 37 | 
            +
                hash = Hash.new.tap do |hsh|
         | 
| 38 | 
            +
                  self.class.mappings.each do |display_name, obj|
         | 
| 39 | 
            +
                    hsh[display_name] = attribute_or_proc_value(obj)
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                self.class.mergables.each do |obj|
         | 
| 44 | 
            +
                  hash.merge!(attribute_or_proc_value(obj))
         | 
| 45 | 
            +
                end if self.class.mergables.any?
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                hash
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              private
         | 
| 51 | 
            +
             | 
| 52 | 
            +
              def attribute_or_proc_value(obj)
         | 
| 53 | 
            +
                if obj.is_a?(Proc)
         | 
| 54 | 
            +
                  obj.call
         | 
| 55 | 
            +
                elsif self.respond_to?(obj)
         | 
| 56 | 
            +
                  self.send(obj)
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
              end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
              def is_callable?(obj)
         | 
| 61 | 
            +
                obj.is_a?(Proc) || self.respond_to?(obj)
         | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
            end
         | 
| @@ -0,0 +1,48 @@ | |
| 1 | 
            +
            require 'gyoku'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RocketGate
         | 
| 4 | 
            +
              class Request
         | 
| 5 | 
            +
                attr_accessor :transaction
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def initialize(transaction = nil)
         | 
| 8 | 
            +
                  @transaction = transaction
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def build_gateway_request
         | 
| 12 | 
            +
                  transaction.validate!
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  parameters = base_parameters
         | 
| 15 | 
            +
                  parameters.merge!(transaction.to_hash)
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  xml_head = '<?xml version="1.0" encoding="UTF-8"?>'
         | 
| 18 | 
            +
                  xml_body = Gyoku.xml({
         | 
| 19 | 
            +
                    gatewayRequest: parameters.select{|_,v| !v.nil? }
         | 
| 20 | 
            +
                  })
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  [xml_head, xml_body].join
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def base_parameters
         | 
| 26 | 
            +
                  config = RocketGate.configuration
         | 
| 27 | 
            +
                  config.validate!
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  params = {
         | 
| 30 | 
            +
                    version:          config.request_version,
         | 
| 31 | 
            +
                    merchantID:       config.merchant_id,
         | 
| 32 | 
            +
                    merchantPassword: config.merchant_password
         | 
| 33 | 
            +
                  }
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  params.merge!({
         | 
| 36 | 
            +
                    avsCheck:         yes_or_ignore(config.require_avs),
         | 
| 37 | 
            +
                    cvv2Check:        yes_or_ignore(config.require_cvv),
         | 
| 38 | 
            +
                    scrub:            yes_or_ignore(config.require_scrub)
         | 
| 39 | 
            +
                  }) unless transaction && transaction.is_referenced?
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  params
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def yes_or_ignore(boolean)
         | 
| 45 | 
            +
                  boolean ? 'YES' : 'IGNORE'
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
            end
         | 
| @@ -0,0 +1,64 @@ | |
| 1 | 
            +
            require 'nokogiri'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RocketGate
         | 
| 4 | 
            +
              class Response
         | 
| 5 | 
            +
                attr_accessor :authorization, :response_code, :xml_document
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def self.from_xml(xml)
         | 
| 8 | 
            +
                  response = self.new.tap do |r|
         | 
| 9 | 
            +
                    r.xml_document = Nokogiri.parse(xml)
         | 
| 10 | 
            +
                    r.build!
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def build!
         | 
| 15 | 
            +
                  @response_code = RocketGate::ResponseCode::RESPONSE[find_value('responseCode')]
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  @authorization = RocketGate::Authorization.new.tap do |auth|
         | 
| 18 | 
            +
                    auth.avs_response       = RocketGate::ResponseCode::AVS[find_value('avsResponse')]
         | 
| 19 | 
            +
                    auth.cvv_response       = RocketGate::ResponseCode::CVV[find_value('cvv2Code')]
         | 
| 20 | 
            +
                    auth.reason_code        = RocketGate::ResponseCode::REASON[find_value('reasonCode')]
         | 
| 21 | 
            +
                    auth.auth_code          = find_value('authNo')
         | 
| 22 | 
            +
                    auth.reference_id       = find_value('guidNo')
         | 
| 23 | 
            +
                    auth.approved_amount    = find_value('approvedAmount').to_f
         | 
| 24 | 
            +
                    auth.approved_currency  = find_value('approvedCurrency')
         | 
| 25 | 
            +
                    auth.card_hash          = find_value('cardHash')
         | 
| 26 | 
            +
                    auth.card_expiration    = find_value('cardExpiration')
         | 
| 27 | 
            +
                    auth.card_last_four     = find_value('cardLastFour')
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def confirm!
         | 
| 32 | 
            +
                  if success?
         | 
| 33 | 
            +
                    confirmation = RocketGate::Transaction.new.tap do |tx|
         | 
| 34 | 
            +
                      tx.reference_id = self.authorization.reference_id
         | 
| 35 | 
            +
                      tx.type = RocketGate::Transaction::TYPE[:confirmation]
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    RocketGate.send_request!(RocketGate::Request.new(confirmation))
         | 
| 39 | 
            +
                  else
         | 
| 40 | 
            +
                    raise RocketGate::AuthorizationError.new('Unable to confirm unauthorized transaction')
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def authorized?
         | 
| 45 | 
            +
                  !!(authorization && authorization.success?)
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def success?
         | 
| 49 | 
            +
                  authorized? && response_code == :success
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                private
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                def find_value(key)
         | 
| 55 | 
            +
                  nodes = xml_document.at('gatewayResponse')
         | 
| 56 | 
            +
                  if nodes && nodes.children
         | 
| 57 | 
            +
                    element = nodes % key
         | 
| 58 | 
            +
                    element.content if element
         | 
| 59 | 
            +
                  else
         | 
| 60 | 
            +
                    raise RocketGate::GatewayResponseError.new('Unexpected response format')
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
            end
         | 
| @@ -0,0 +1,148 @@ | |
| 1 | 
            +
            module RocketGate::ResponseCode
         | 
| 2 | 
            +
              AVS = {
         | 
| 3 | 
            +
                'D'             => :match,
         | 
| 4 | 
            +
                'F'             => :match,
         | 
| 5 | 
            +
                'M'             => :match,
         | 
| 6 | 
            +
                'X'             => :match,
         | 
| 7 | 
            +
                'Y'             => :match,
         | 
| 8 | 
            +
                'A'             => :address_only_match,
         | 
| 9 | 
            +
                'B'             => :address_only_match,
         | 
| 10 | 
            +
                'P'             => :zip_only_match,
         | 
| 11 | 
            +
                'T'             => :zip_only_match,
         | 
| 12 | 
            +
                'W'             => :zip_only_match,
         | 
| 13 | 
            +
                'Z'             => :zip_only_match,
         | 
| 14 | 
            +
                'C'             => :not_verified,
         | 
| 15 | 
            +
                'G'             => :not_verified,
         | 
| 16 | 
            +
                'I'             => :not_verified,
         | 
| 17 | 
            +
                'R'             => :unavailable,
         | 
| 18 | 
            +
                'U'             => :unavailable,
         | 
| 19 | 
            +
                'S'             => :unsupported,
         | 
| 20 | 
            +
                'N'             => :no_match
         | 
| 21 | 
            +
              }
         | 
| 22 | 
            +
              AVS_SUCCESSES = [ :match, :address_only_match, :zip_only_match, nil ]
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              CVV = {
         | 
| 25 | 
            +
                'M'             => :match,
         | 
| 26 | 
            +
                'N'             => :no_match,
         | 
| 27 | 
            +
                'P'             => :not_processed,
         | 
| 28 | 
            +
                'S'             => :should_have_been_present,
         | 
| 29 | 
            +
                'U'             => :issuer_could_not_process
         | 
| 30 | 
            +
              }
         | 
| 31 | 
            +
              CVV_SUCCESSES = [ :match, :not_processed, :issuer_could_not_process, nil ]
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              CARD_BRAND = {
         | 
| 34 | 
            +
                'AMEX'          => :amex,
         | 
| 35 | 
            +
                'CARTEBLANCHE'  => :carte_blanche,
         | 
| 36 | 
            +
                'DINERSCLUB'    => :diners_club,
         | 
| 37 | 
            +
                'DISCOVER'      => :discover,
         | 
| 38 | 
            +
                'JCB'           => :jcb,
         | 
| 39 | 
            +
                'MAESTRO'       => :maestro,
         | 
| 40 | 
            +
                'MC'            => :master_card,
         | 
| 41 | 
            +
                'SOLO'          => :solo,
         | 
| 42 | 
            +
                'VISA'          => :visa
         | 
| 43 | 
            +
              }
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              CARD_REGION = {
         | 
| 46 | 
            +
                '1'             => :usa,
         | 
| 47 | 
            +
                '2'             => :canada,
         | 
| 48 | 
            +
                '3'             => :europe,
         | 
| 49 | 
            +
                '4'             => :asia_pacific,
         | 
| 50 | 
            +
                '5'             => :latin_america,
         | 
| 51 | 
            +
                '6'             => :africa_middle_east
         | 
| 52 | 
            +
              }
         | 
| 53 | 
            +
             | 
| 54 | 
            +
              CARD_TYPE = {
         | 
| 55 | 
            +
                '0'             => :debit,
         | 
| 56 | 
            +
                '1'             => :credit,
         | 
| 57 | 
            +
                '2'             => :both
         | 
| 58 | 
            +
              }
         | 
| 59 | 
            +
             | 
| 60 | 
            +
              REASON = {
         | 
| 61 | 
            +
                '0'             => :success,
         | 
| 62 | 
            +
                '100'           => :no_matching_transaction,
         | 
| 63 | 
            +
                '101'           => :cannot_void,
         | 
| 64 | 
            +
                '102'           => :cannot_credit,
         | 
| 65 | 
            +
                '103'           => :cannot_ticket,
         | 
| 66 | 
            +
                '104'           => :declined_generic,
         | 
| 67 | 
            +
                '105'           => :declined_over_limit,
         | 
| 68 | 
            +
                '106'           => :declined_cvv_mismatch,
         | 
| 69 | 
            +
                '107'           => :declined_expired,
         | 
| 70 | 
            +
                '108'           => :declined_call_issuer,
         | 
| 71 | 
            +
                '109'           => :declined_pickup_card,
         | 
| 72 | 
            +
                '110'           => :declined_excessive_use,
         | 
| 73 | 
            +
                '111'           => :declined_invalid_card,
         | 
| 74 | 
            +
                '112'           => :declined_invalid_expiration,
         | 
| 75 | 
            +
                '113'           => :declined_bank_unavailable,
         | 
| 76 | 
            +
                '117'           => :declined_avs_mismatch,
         | 
| 77 | 
            +
                '123'           => :declined_by_user_request,
         | 
| 78 | 
            +
                '150'           => :declined_avs_mismatch,
         | 
| 79 | 
            +
                '151'           => :declined_cvv_mismatch,
         | 
| 80 | 
            +
                '152'           => :declined_invalid_amount,
         | 
| 81 | 
            +
                '154'           => :declined_integration_error,
         | 
| 82 | 
            +
                '156'           => :declined_unsupported_card,
         | 
| 83 | 
            +
                '157'           => :declined_processor_risk,
         | 
| 84 | 
            +
                '200'           => :declined_fraud_scrubbing,
         | 
| 85 | 
            +
                '201'           => :declined_customer_blocked,
         | 
| 86 | 
            +
                '208'           => :declined_duplicate_membership_id,
         | 
| 87 | 
            +
                '209'           => :declined_duplicate_membership_card,
         | 
| 88 | 
            +
                '210'           => :declined_duplicate_membership_email,
         | 
| 89 | 
            +
                '211'           => :declined_over_max_purchase,
         | 
| 90 | 
            +
                '212'           => :declined_duplicate_transaction,
         | 
| 91 | 
            +
                '213'           => :declined_customer_velocity,
         | 
| 92 | 
            +
                '214'           => :declined_card_velocity,
         | 
| 93 | 
            +
                '215'           => :declined_email_velocity,
         | 
| 94 | 
            +
                '216'           => :declined_iovation_generic,
         | 
| 95 | 
            +
                '217'           => :declined_iovation_email_velocity,
         | 
| 96 | 
            +
                '218'           => :declined_iovation_duplicate_membership,
         | 
| 97 | 
            +
                '219'           => :declined_different_source_ip,
         | 
| 98 | 
            +
                '220'           => :declined_too_many_cards,
         | 
| 99 | 
            +
                '221'           => :declined_affiliate_blocked,
         | 
| 100 | 
            +
                '222'           => :declined_trial_abuse,
         | 
| 101 | 
            +
                '300'           => :error_dns_failed,
         | 
| 102 | 
            +
                '301'           => :error_connection_failed,
         | 
| 103 | 
            +
                '303'           => :error_read_timeout,
         | 
| 104 | 
            +
                '311'           => :error_bank_communcations,
         | 
| 105 | 
            +
                '312'           => :error_bank_communcations,
         | 
| 106 | 
            +
                '313'           => :error_bank_communcations,
         | 
| 107 | 
            +
                '314'           => :error_bank_communcations,
         | 
| 108 | 
            +
                '315'           => :error_bank_communcations,
         | 
| 109 | 
            +
                '316'           => :error_bank_communcations,
         | 
| 110 | 
            +
                '321'           => :error_bank_communcations,
         | 
| 111 | 
            +
                '323'           => :error_bank_communcations,
         | 
| 112 | 
            +
                '325'           => :error_3d_secure,
         | 
| 113 | 
            +
                '403'           => :error_invalid_card_number,
         | 
| 114 | 
            +
                '404'           => :error_invalid_expiration,
         | 
| 115 | 
            +
                '405'           => :error_invalid_amount,
         | 
| 116 | 
            +
                '406'           => :error_invalid_merchant_id,
         | 
| 117 | 
            +
                '407'           => :error_invalid_merchant_account,
         | 
| 118 | 
            +
                '408'           => :error_incompatible_card_type,
         | 
| 119 | 
            +
                '409'           => :error_no_suitable_account,
         | 
| 120 | 
            +
                '410'           => :error_invalid_transaction_id,
         | 
| 121 | 
            +
                '411'           => :error_invalid_access_code,
         | 
| 122 | 
            +
                '412'           => :error_invalid_customer_data,
         | 
| 123 | 
            +
                '413'           => :error_invalid_external_data,
         | 
| 124 | 
            +
                '414'           => :error_invalid_customer_id,
         | 
| 125 | 
            +
                '418'           => :error_invalid_currency,
         | 
| 126 | 
            +
                '419'           => :error_incompatible_currency,
         | 
| 127 | 
            +
                '420'           => :error_invalid_rebill_arguments,
         | 
| 128 | 
            +
                '436'           => :error_incompatible_descriptor,
         | 
| 129 | 
            +
                '438'           => :error_invalid_site_id,
         | 
| 130 | 
            +
                '441'           => :error_invoice_not_found,
         | 
| 131 | 
            +
                '443'           => :error_missing_customer_id,
         | 
| 132 | 
            +
                '444'           => :error_missing_customer_name,
         | 
| 133 | 
            +
                '445'           => :error_missing_customer_address,
         | 
| 134 | 
            +
                '446'           => :error_missing_cvv,
         | 
| 135 | 
            +
                '448'           => :error_no_active_membership,
         | 
| 136 | 
            +
                '449'           => :error_invalid_cvv,
         | 
| 137 | 
            +
                '451'           => :error_invalid_clone_data,
         | 
| 138 | 
            +
                '452'           => :error_redundant_operation
         | 
| 139 | 
            +
              }
         | 
| 140 | 
            +
             | 
| 141 | 
            +
              RESPONSE = {
         | 
| 142 | 
            +
                '0'             => :success,
         | 
| 143 | 
            +
                '1'             => :bank_decline,
         | 
| 144 | 
            +
                '2'             => :rocketgate_scrubbing_decline,
         | 
| 145 | 
            +
                '3'             => :system_error,
         | 
| 146 | 
            +
                '4'             => :missing_or_invalid_fields
         | 
| 147 | 
            +
              }
         | 
| 148 | 
            +
            end
         | 
| @@ -0,0 +1,75 @@ | |
| 1 | 
            +
            module RocketGate
         | 
| 2 | 
            +
              class Transaction
         | 
| 3 | 
            +
                extend Forwardable
         | 
| 4 | 
            +
                include Hashable, Validatable
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                attr_accessor :amount, :credit_card, :customer, :currency, :type, :reference_id, :id
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                validatable :amount, :credit_card, :customer, :currency, :id
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def_delegator :credit_card, :to_hash, :credit_card_hash
         | 
| 11 | 
            +
                def_delegator :customer,    :to_hash, :customer_hash
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                hashable({
         | 
| 14 | 
            +
                    amount:             :amount,
         | 
| 15 | 
            +
                    currency:           :currency,
         | 
| 16 | 
            +
                    merchantInvoiceID:  :id,
         | 
| 17 | 
            +
                    transactionType:    :type,
         | 
| 18 | 
            +
                    referenceGUID:      :reference_id
         | 
| 19 | 
            +
                  }, :credit_card_hash, :customer_hash)
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                TYPE = {
         | 
| 22 | 
            +
                  auth:          'CC_AUTH',
         | 
| 23 | 
            +
                  confirmation:  'CC_CONFIRM',
         | 
| 24 | 
            +
                  credit:        'CC_CREDIT',
         | 
| 25 | 
            +
                  purchase:      'CC_PURCHASE',
         | 
| 26 | 
            +
                  rebill_cancel: 'REBILL_CANCEL',
         | 
| 27 | 
            +
                  rebill_update: 'REBILL_UPDATE',
         | 
| 28 | 
            +
                  scrub:         'CARDSCRUB',
         | 
| 29 | 
            +
                  ticket:        'CC_TICKET',
         | 
| 30 | 
            +
                  void:          'CC_VOID'
         | 
| 31 | 
            +
                }
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def initialize(*args)
         | 
| 34 | 
            +
                  @amount, @customer, @credit_card, @currency, @id = *args
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def id
         | 
| 38 | 
            +
                  @id ||= SecureRandom.uuid
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def currency
         | 
| 42 | 
            +
                  @currency ||= RocketGate.configuration.currency
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def to_hash
         | 
| 46 | 
            +
                  if is_referenced?
         | 
| 47 | 
            +
                    { referenceGUID: reference_id, transactionType: type }
         | 
| 48 | 
            +
                  else
         | 
| 49 | 
            +
                    super
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                def type=(new_type)
         | 
| 54 | 
            +
                  new_type = TYPE[new_type] if TYPE.keys.include?(new_type)
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  if TYPE.values.include?(new_type)
         | 
| 57 | 
            +
                    @type = new_type
         | 
| 58 | 
            +
                  else
         | 
| 59 | 
            +
                    raise ValidationError.new("Unknown transaction type: #{new_type}")
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                def type
         | 
| 64 | 
            +
                  @type ||= TYPE[:auth]
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                def is_referenced?
         | 
| 68 | 
            +
                  [ TYPE[:confirmation], TYPE[:ticket], TYPE[:void] ].include?(type) && !reference_id.nil?
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                def valid?
         | 
| 72 | 
            +
                  is_referenced? || super
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
            end
         | 
| @@ -0,0 +1,52 @@ | |
| 1 | 
            +
            module RocketGate::Validatable
         | 
| 2 | 
            +
              module ClassMethods
         | 
| 3 | 
            +
                def validatable(*args)
         | 
| 4 | 
            +
                  args.each do |attribute|
         | 
| 5 | 
            +
                    if self.new.respond_to?(attribute)
         | 
| 6 | 
            +
                      validatable_attributes << attribute
         | 
| 7 | 
            +
                    else
         | 
| 8 | 
            +
                      raise RocketGate::ValidationError.new("Invalid attribute: #{attribute}")
         | 
| 9 | 
            +
                    end
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def validatable_attributes
         | 
| 14 | 
            +
                  @validatable_attributes ||= []
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              def self.included(base)
         | 
| 19 | 
            +
                base.extend(ClassMethods)
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              def valid?
         | 
| 23 | 
            +
                self.class.validatable_attributes.all? {|v| is_object_valid?(send(v)) }
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              def validate!
         | 
| 27 | 
            +
                if valid?
         | 
| 28 | 
            +
                  self
         | 
| 29 | 
            +
                else
         | 
| 30 | 
            +
                  message = "Invalid attributes: #{invalid_attributes.join(', ')}"
         | 
| 31 | 
            +
                  raise RocketGate::ValidationError.new(message) unless valid?
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              def invalid_attributes
         | 
| 36 | 
            +
                self.class.validatable_attributes.select {|v| !is_object_valid?(send(v)) }
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              private
         | 
| 40 | 
            +
             | 
| 41 | 
            +
              def is_class_validatable?(klazz)
         | 
| 42 | 
            +
                klazz.included_modules.include?(RocketGate::Validatable)
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              def is_object_valid?(obj)
         | 
| 46 | 
            +
                if is_class_validatable?(obj.class)
         | 
| 47 | 
            +
                  obj.valid?
         | 
| 48 | 
            +
                else
         | 
| 49 | 
            +
                  !obj.nil?
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
            end
         |