activemerchant 1.16.0 → 1.17.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.
- data.tar.gz.sig +0 -0
- data/CHANGELOG +15 -1
- data/CONTRIBUTORS +8 -0
- data/lib/active_merchant/billing/credit_card.rb +100 -18
- data/lib/active_merchant/billing/gateways/authorize_net.rb +31 -2
- data/lib/active_merchant/billing/gateways/braintree_blue.rb +46 -36
- data/lib/active_merchant/billing/gateways/card_save.rb +23 -0
- data/lib/active_merchant/billing/gateways/epay.rb +1 -1
- data/lib/active_merchant/billing/gateways/iridium.rb +3 -3
- data/lib/active_merchant/billing/gateways/optimal_payment.rb +270 -0
- data/lib/active_merchant/billing/gateways/orbital.rb +5 -5
- data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +6 -3
- data/lib/active_merchant/billing/gateways/paypal_express.rb +22 -15
- data/lib/active_merchant/billing/gateways/quickpay.rb +96 -22
- data/lib/active_merchant/billing/integrations/payflow_link.rb +21 -0
- data/lib/active_merchant/billing/integrations/payflow_link/helper.rb +58 -0
- data/lib/active_merchant/billing/integrations/payflow_link/notification.rb +78 -0
- data/lib/active_merchant/version.rb +1 -1
- metadata +23 -18
- metadata.gz.sig +0 -0
| @@ -0,0 +1,270 @@ | |
| 1 | 
            +
            module ActiveMerchant #:nodoc:
         | 
| 2 | 
            +
              module Billing #:nodoc:
         | 
| 3 | 
            +
                class OptimalPaymentGateway < Gateway
         | 
| 4 | 
            +
                  TEST_URL = 'https://webservices.test.optimalpayments.com/creditcardWS/CreditCardServlet/v1'
         | 
| 5 | 
            +
                  LIVE_URL = 'https://webservices.optimalpayments.com/creditcardWS/CreditCardServlet/v1'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  # The countries the gateway supports merchants from as 2 digit ISO country codes
         | 
| 8 | 
            +
                  self.supported_countries = ['CA', 'US', 'GB']
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  # The card types supported by the payment gateway
         | 
| 11 | 
            +
                  self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :solo] # :switch?
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  # The homepage URL of the gateway
         | 
| 14 | 
            +
                  self.homepage_url = 'http://www.optimalpayments.com/'
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  # The name of the gateway
         | 
| 17 | 
            +
                  self.display_name = 'Optimal Payments'
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def initialize(options = {})
         | 
| 20 | 
            +
                    #requires!(options, :login, :password)
         | 
| 21 | 
            +
                    @options = options
         | 
| 22 | 
            +
                    super
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def authorize(money, card_or_auth, options = {})
         | 
| 26 | 
            +
                    parse_card_or_auth(card_or_auth, options)
         | 
| 27 | 
            +
                    commit("cc#{@stored_data}Authorize", money, options)
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                  alias stored_authorize authorize # back-compat
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def purchase(money, card_or_auth, options = {})
         | 
| 32 | 
            +
                    parse_card_or_auth(card_or_auth, options)
         | 
| 33 | 
            +
                    commit("cc#{@stored_data}Purchase", money, options)
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                  alias stored_purchase purchase # back-compat
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  def refund(money, authorization, options = {})
         | 
| 38 | 
            +
                    options[:confirmationNumber] = authorization
         | 
| 39 | 
            +
                    commit('ccCredit', money, options)
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  def void(authorization, options = {})
         | 
| 43 | 
            +
                    options[:confirmationNumber] = authorization
         | 
| 44 | 
            +
                    commit('ccAuthorizeReversal', nil, options)
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  def capture(money, authorization, options = {})
         | 
| 48 | 
            +
                    options[:confirmationNumber] = authorization
         | 
| 49 | 
            +
                    commit('ccSettlement', money, options)
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                private
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  def parse_card_or_auth(card_or_auth, options)
         | 
| 55 | 
            +
                    if card_or_auth.respond_to?(:number)
         | 
| 56 | 
            +
                      @credit_card = card_or_auth
         | 
| 57 | 
            +
                      @stored_data = ""
         | 
| 58 | 
            +
                    else
         | 
| 59 | 
            +
                      options[:confirmationNumber] = card_or_auth
         | 
| 60 | 
            +
                      @stored_data = "StoredData"
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  def parse(body)
         | 
| 65 | 
            +
                    REXML::Document.new(body || '')
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  def commit(action, money, post)
         | 
| 69 | 
            +
                    post[:order_id] ||= 'order_id'
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                    xml = case action
         | 
| 72 | 
            +
                    when 'ccAuthorize', 'ccPurchase', 'ccVerification'
         | 
| 73 | 
            +
                      cc_auth_request(money, post)
         | 
| 74 | 
            +
                    when 'ccCredit', 'ccSettlement'
         | 
| 75 | 
            +
                      cc_post_auth_request(money, post)
         | 
| 76 | 
            +
                    when 'ccStoredDataAuthorize', 'ccStoredDataPurchase'
         | 
| 77 | 
            +
                      cc_stored_data_request(money, post)
         | 
| 78 | 
            +
                    when 'ccAuthorizeReversal'
         | 
| 79 | 
            +
                      cc_auth_reversal_request(post)
         | 
| 80 | 
            +
                    #when 'ccCancelSettle', 'ccCancelCredit', 'ccCancelPayment'
         | 
| 81 | 
            +
                    #  cc_cancel_request(money, post)
         | 
| 82 | 
            +
                    #when 'ccPayment'
         | 
| 83 | 
            +
                    #  cc_payment_request(money, post)
         | 
| 84 | 
            +
                    #when 'ccAuthenticate'
         | 
| 85 | 
            +
                    #  cc_authenticate_request(money, post)
         | 
| 86 | 
            +
                    else
         | 
| 87 | 
            +
                      raise 'Unknown Action'
         | 
| 88 | 
            +
                    end
         | 
| 89 | 
            +
                    txnRequest = URI.encode(xml)
         | 
| 90 | 
            +
                    response = parse(ssl_post(test? ? TEST_URL : LIVE_URL, "txnMode=#{action}&txnRequest=#{txnRequest}"))
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                    Response.new(successful?(response), message_from(response), hash_from_xml(response),
         | 
| 93 | 
            +
                      :test          => test?,
         | 
| 94 | 
            +
                      :authorization => authorization_from(response)
         | 
| 95 | 
            +
                    )
         | 
| 96 | 
            +
                  end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                  def successful?(response)
         | 
| 99 | 
            +
                    REXML::XPath.first(response, '//decision').text == 'ACCEPTED' rescue false
         | 
| 100 | 
            +
                  end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  def message_from(response)
         | 
| 103 | 
            +
                    REXML::XPath.each(response, '//detail') do |detail|
         | 
| 104 | 
            +
                      if detail.is_a?(REXML::Element) && detail.elements['tag'].text == 'InternalResponseDescription'
         | 
| 105 | 
            +
                        return detail.elements['value'].text
         | 
| 106 | 
            +
                      end
         | 
| 107 | 
            +
                    end
         | 
| 108 | 
            +
                    nil
         | 
| 109 | 
            +
                  end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  def authorization_from(response)
         | 
| 112 | 
            +
                    REXML::XPath.first(response, '//confirmationNumber').text rescue nil
         | 
| 113 | 
            +
                  end
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                  def hash_from_xml(response)
         | 
| 116 | 
            +
                    hsh = {}
         | 
| 117 | 
            +
                    %w(confirmationNumber authCode
         | 
| 118 | 
            +
                       decision code description
         | 
| 119 | 
            +
                       actionCode avsResponse cvdResponse
         | 
| 120 | 
            +
                       txnTime duplicateFound
         | 
| 121 | 
            +
                    ).each do |tag|
         | 
| 122 | 
            +
                      node = REXML::XPath.first(response, "//#{tag}")
         | 
| 123 | 
            +
                      hsh[tag] = node.text if node
         | 
| 124 | 
            +
                    end
         | 
| 125 | 
            +
                    REXML::XPath.each(response, '//detail') do |detail|
         | 
| 126 | 
            +
                      next unless detail.is_a?(REXML::Element)
         | 
| 127 | 
            +
                      tag = detail.elements['tag'].text
         | 
| 128 | 
            +
                      value = detail.elements['value'].text
         | 
| 129 | 
            +
                      hsh[tag] = value
         | 
| 130 | 
            +
                    end
         | 
| 131 | 
            +
                    hsh
         | 
| 132 | 
            +
                  end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                  def xml_document(root_tag)
         | 
| 135 | 
            +
                    xml = Builder::XmlMarkup.new :indent => 2
         | 
| 136 | 
            +
                    xml.tag!(root_tag, schema) do
         | 
| 137 | 
            +
                      yield xml
         | 
| 138 | 
            +
                    end
         | 
| 139 | 
            +
                    xml.target!
         | 
| 140 | 
            +
                  end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                  def cc_auth_request(money, opts)
         | 
| 143 | 
            +
                    xml_document('ccAuthRequestV1') do |xml|
         | 
| 144 | 
            +
                      build_merchant_account(xml, @options)
         | 
| 145 | 
            +
                      xml.merchantRefNum opts[:order_id]
         | 
| 146 | 
            +
                      xml.amount(money/100.0)
         | 
| 147 | 
            +
                      build_card(xml, opts)
         | 
| 148 | 
            +
                      build_billing_details(xml, opts)
         | 
| 149 | 
            +
                    end
         | 
| 150 | 
            +
                  end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                  def cc_auth_reversal_request(opts)
         | 
| 153 | 
            +
                    xml_document('ccAuthReversalRequestV1') do |xml|
         | 
| 154 | 
            +
                      build_merchant_account(xml, @options)
         | 
| 155 | 
            +
                      xml.confirmationNumber opts[:confirmationNumber]
         | 
| 156 | 
            +
                      xml.merchantRefNum opts[:order_id]
         | 
| 157 | 
            +
                    end
         | 
| 158 | 
            +
                  end
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                  def cc_post_auth_request(money, opts)
         | 
| 161 | 
            +
                    xml_document('ccPostAuthRequestV1') do |xml|
         | 
| 162 | 
            +
                      build_merchant_account(xml, @options)
         | 
| 163 | 
            +
                      xml.confirmationNumber opts[:confirmationNumber]
         | 
| 164 | 
            +
                      xml.merchantRefNum opts[:order_id]
         | 
| 165 | 
            +
                      xml.amount(money/100.0)
         | 
| 166 | 
            +
                    end
         | 
| 167 | 
            +
                  end
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                  def cc_stored_data_request(money, opts)
         | 
| 170 | 
            +
                    xml_document('ccStoredDataRequestV1') do |xml|
         | 
| 171 | 
            +
                      build_merchant_account(xml, @options)
         | 
| 172 | 
            +
                      xml.merchantRefNum opts[:order_id]
         | 
| 173 | 
            +
                      xml.confirmationNumber opts[:confirmationNumber]
         | 
| 174 | 
            +
                      xml.amount(money/100.0)
         | 
| 175 | 
            +
                    end
         | 
| 176 | 
            +
                  end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                  # untested
         | 
| 179 | 
            +
                  #
         | 
| 180 | 
            +
                  # def cc_cancel_request(opts)
         | 
| 181 | 
            +
                  #   xml_document('ccCancelRequestV1') do |xml|
         | 
| 182 | 
            +
                  #     build_merchant_account(xml, @options)
         | 
| 183 | 
            +
                  #     xml.confirmationNumber opts[:confirmationNumber]
         | 
| 184 | 
            +
                  #   end
         | 
| 185 | 
            +
                  # end
         | 
| 186 | 
            +
                  #
         | 
| 187 | 
            +
                  # def cc_payment_request(money, opts)
         | 
| 188 | 
            +
                  #   xml_document('ccPaymentRequestV1') do |xml|
         | 
| 189 | 
            +
                  #     build_merchant_account(xml, @options)
         | 
| 190 | 
            +
                  #     xml.merchantRefNum opts[:order_id]
         | 
| 191 | 
            +
                  #     xml.amount(money/100.0)
         | 
| 192 | 
            +
                  #     build_card(xml, opts)
         | 
| 193 | 
            +
                  #     build_billing_details(xml, opts)
         | 
| 194 | 
            +
                  #   end
         | 
| 195 | 
            +
                  # end
         | 
| 196 | 
            +
                  #
         | 
| 197 | 
            +
                  # def cc_authenticate_request(opts)
         | 
| 198 | 
            +
                  #   xml_document('ccAuthenticateRequestV1') do |xml|
         | 
| 199 | 
            +
                  #     build_merchant_account(xml, @options)
         | 
| 200 | 
            +
                  #     xml.confirmationNumber opts[:confirmationNumber]
         | 
| 201 | 
            +
                  #     xml.paymentResponse 'myPaymentResponse'
         | 
| 202 | 
            +
                  #   end
         | 
| 203 | 
            +
                  # end
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                  def schema
         | 
| 206 | 
            +
                    { 'xmlns' => 'http://www.optimalpayments.com/creditcard/xmlschema/v1',
         | 
| 207 | 
            +
                      'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
         | 
| 208 | 
            +
                      'xsi:schemaLocation' => 'http://www.optimalpayments.com/creditcard/xmlschema/v1'
         | 
| 209 | 
            +
                    }
         | 
| 210 | 
            +
                  end
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                  def build_merchant_account(xml, opts)
         | 
| 213 | 
            +
                    xml.tag! 'merchantAccount' do
         | 
| 214 | 
            +
                      xml.tag! 'accountNum' , opts[:account]
         | 
| 215 | 
            +
                      xml.tag! 'storeID'    , opts[:login]
         | 
| 216 | 
            +
                      xml.tag! 'storePwd'   , opts[:password]
         | 
| 217 | 
            +
                    end
         | 
| 218 | 
            +
                  end
         | 
| 219 | 
            +
             | 
| 220 | 
            +
                  def build_card(xml, opts)
         | 
| 221 | 
            +
                    xml.tag! 'card' do
         | 
| 222 | 
            +
                      xml.tag! 'cardNum'      , @credit_card.number
         | 
| 223 | 
            +
                      xml.tag! 'cardExpiry' do
         | 
| 224 | 
            +
                        xml.tag! 'month'      , @credit_card.month
         | 
| 225 | 
            +
                        xml.tag! 'year'       , @credit_card.year
         | 
| 226 | 
            +
                      end
         | 
| 227 | 
            +
                      if type = card_type(@credit_card.type)
         | 
| 228 | 
            +
                        xml.tag! 'cardType'     , type
         | 
| 229 | 
            +
                      end
         | 
| 230 | 
            +
                      if @credit_card.verification_value
         | 
| 231 | 
            +
                        xml.tag! 'cvdIndicator' , '1' # Value Provided
         | 
| 232 | 
            +
                        xml.tag! 'cvd'          , @credit_card.verification_value
         | 
| 233 | 
            +
                      end
         | 
| 234 | 
            +
                    end
         | 
| 235 | 
            +
                  end
         | 
| 236 | 
            +
             | 
| 237 | 
            +
                  def build_billing_details(xml, opts)
         | 
| 238 | 
            +
                    xml.tag! 'billingDetails' do
         | 
| 239 | 
            +
                      addr = opts[:billing_address]
         | 
| 240 | 
            +
                      xml.tag! 'cardPayMethod', 'WEB'
         | 
| 241 | 
            +
                      if addr[:name]
         | 
| 242 | 
            +
                        xml.tag! 'firstName', CGI.escape(addr[:name].split(' ').first) # TODO: parse properly
         | 
| 243 | 
            +
                        xml.tag! 'lastName' , CGI.escape(addr[:name].split(' ').last )
         | 
| 244 | 
            +
                      end
         | 
| 245 | 
            +
                      xml.tag! 'street' , CGI.escape(addr[:address1]) if addr[:address1] && !addr[:address1].empty?
         | 
| 246 | 
            +
                      xml.tag! 'street2', CGI.escape(addr[:address2]) if addr[:address2] && !addr[:address2].empty?
         | 
| 247 | 
            +
                      xml.tag! 'city'   , CGI.escape(addr[:city]    ) if addr[:city]     && !addr[:city].empty?
         | 
| 248 | 
            +
                      xml.tag! 'state'  , CGI.escape(addr[:state]   ) if addr[:state]    && !addr[:state].empty?
         | 
| 249 | 
            +
                      xml.tag! 'country', CGI.escape(addr[:country] ) if addr[:country]  && !addr[:country].empty?
         | 
| 250 | 
            +
                      xml.tag! 'zip'    , CGI.escape(addr[:zip]     ) # this one's actually required
         | 
| 251 | 
            +
                      xml.tag! 'phone'  , CGI.escape(addr[:phone]   ) if addr[:phone]    && !addr[:phone].empty?
         | 
| 252 | 
            +
                      #xml.tag! 'email'        , ''
         | 
| 253 | 
            +
                    end
         | 
| 254 | 
            +
                  end
         | 
| 255 | 
            +
             | 
| 256 | 
            +
                  def card_type(key)
         | 
| 257 | 
            +
                    { 'visa'            => 'VI',
         | 
| 258 | 
            +
                      'master'          => 'MC',
         | 
| 259 | 
            +
                      'american_express'=> 'AM',
         | 
| 260 | 
            +
                      'discover'        => 'DI',
         | 
| 261 | 
            +
                      'diners_club'     => 'DC',
         | 
| 262 | 
            +
                      #'switch'          => '',
         | 
| 263 | 
            +
                      'solo'            => 'SO'
         | 
| 264 | 
            +
                    }[key]
         | 
| 265 | 
            +
                  end
         | 
| 266 | 
            +
             | 
| 267 | 
            +
                end
         | 
| 268 | 
            +
              end
         | 
| 269 | 
            +
            end
         | 
| 270 | 
            +
             | 
| @@ -80,8 +80,8 @@ module ActiveMerchant #:nodoc: | |
| 80 80 | 
             
                  def initialize(options = {})
         | 
| 81 81 | 
             
                    unless options[:ip_authentication] == true
         | 
| 82 82 | 
             
                      requires!(options, :login, :password, :merchant_id)
         | 
| 83 | 
            -
                      @options = options
         | 
| 84 83 | 
             
                    end
         | 
| 84 | 
            +
                    @options = options
         | 
| 85 85 | 
             
                    super
         | 
| 86 86 | 
             
                  end
         | 
| 87 87 |  | 
| @@ -167,6 +167,7 @@ module ActiveMerchant #:nodoc: | |
| 167 167 | 
             
                    xml.tag! :CurrencyCode, currency_code(currency)
         | 
| 168 168 | 
             
                    xml.tag! :CurrencyExponent, '2' # Will need updating to support currencies such as the Yen.
         | 
| 169 169 |  | 
| 170 | 
            +
                    xml.tag! :CardSecValInd, 1 if creditcard.verification_value? && %w( visa discover ).include?(creditcard.type)
         | 
| 170 171 | 
             
                    xml.tag! :CardSecVal,  creditcard.verification_value if creditcard.verification_value?
         | 
| 171 172 | 
             
                  end
         | 
| 172 173 |  | 
| @@ -223,11 +224,11 @@ module ActiveMerchant #:nodoc: | |
| 223 224 | 
             
                  end
         | 
| 224 225 |  | 
| 225 226 | 
             
                  def success?(response)
         | 
| 226 | 
            -
                    if response[:message_type] == "R"
         | 
| 227 | 
            +
                    if response[:message_type].nil? || response[:message_type] == "R"
         | 
| 227 228 | 
             
                      response[:proc_status] == SUCCESS
         | 
| 228 229 | 
             
                    else
         | 
| 229 230 | 
             
                      response[:proc_status] == SUCCESS &&
         | 
| 230 | 
            -
             | 
| 231 | 
            +
                        response[:resp_code] == APPROVED
         | 
| 231 232 | 
             
                    end
         | 
| 232 233 | 
             
                  end
         | 
| 233 234 |  | 
| @@ -247,7 +248,7 @@ module ActiveMerchant #:nodoc: | |
| 247 248 | 
             
                      xml.tag! :NewOrder do
         | 
| 248 249 | 
             
                        xml.tag! :OrbitalConnectionUsername, @options[:login] unless ip_authentication?
         | 
| 249 250 | 
             
                        xml.tag! :OrbitalConnectionPassword, @options[:password] unless ip_authentication?
         | 
| 250 | 
            -
                        xml.tag! :IndustryType, "EC" | 
| 251 | 
            +
                        xml.tag! :IndustryType, parameters[:industry_type] || "EC"
         | 
| 251 252 | 
             
                        xml.tag! :MessageType, action
         | 
| 252 253 | 
             
                        xml.tag! :BIN, '000002' # PNS Tampa
         | 
| 253 254 | 
             
                        xml.tag! :MerchantID, @options[:merchant_id]
         | 
| @@ -289,7 +290,6 @@ module ActiveMerchant #:nodoc: | |
| 289 290 | 
             
                  end
         | 
| 290 291 |  | 
| 291 292 | 
             
                  def build_void_request_xml(money, authorization, parameters = {})
         | 
| 292 | 
            -
                    requires!(parameters, :transaction_index)
         | 
| 293 293 | 
             
                    tx_ref_num, order_id = authorization.split(';')
         | 
| 294 294 | 
             
                    xml = Builder::XmlMarkup.new(:indent => 2)
         | 
| 295 295 | 
             
                    xml.instruct!(:xml, :version => '1.0', :encoding => 'UTF-8')
         | 
| @@ -63,10 +63,13 @@ module ActiveMerchant #:nodoc: | |
| 63 63 | 
             
                  def initialize(options = {})
         | 
| 64 64 | 
             
                    requires!(options, :login, :password)
         | 
| 65 65 |  | 
| 66 | 
            +
                    headers = {'X-PP-AUTHORIZATION' => options.delete(:auth_signature), 'X-PAYPAL-MESSAGE-PROTOCOL' => 'SOAP11'} if options[:auth_signature]
         | 
| 66 67 | 
             
                    @options = {
         | 
| 67 68 | 
             
                      :pem => pem_file,
         | 
| 68 | 
            -
                      :signature => signature
         | 
| 69 | 
            +
                      :signature => signature,
         | 
| 70 | 
            +
                      :headers => headers || {}
         | 
| 69 71 | 
             
                    }.update(options)
         | 
| 72 | 
            +
             | 
| 70 73 |  | 
| 71 74 | 
             
                    if @options[:pem].blank? && @options[:signature].blank?
         | 
| 72 75 | 
             
                      raise ArgumentError, "An API Certificate or API Signature is required to make requests to PayPal" 
         | 
| @@ -280,7 +283,7 @@ module ActiveMerchant #:nodoc: | |
| 280 283 | 
             
                    xml.instruct!
         | 
| 281 284 | 
             
                    xml.tag! 'env:Envelope', ENVELOPE_NAMESPACES do
         | 
| 282 285 | 
             
                      xml.tag! 'env:Header' do
         | 
| 283 | 
            -
                        add_credentials(xml)
         | 
| 286 | 
            +
                        add_credentials(xml) unless @options[:headers] && @options[:headers]['X-PP-AUTHORIZATION']
         | 
| 284 287 | 
             
                      end
         | 
| 285 288 |  | 
| 286 289 | 
             
                      xml.tag! 'env:Body' do
         | 
| @@ -320,7 +323,7 @@ module ActiveMerchant #:nodoc: | |
| 320 323 | 
             
                  end
         | 
| 321 324 |  | 
| 322 325 | 
             
                  def commit(action, request)
         | 
| 323 | 
            -
                    response = parse(action, ssl_post(endpoint_url, build_request(request)))
         | 
| 326 | 
            +
                    response = parse(action, ssl_post(endpoint_url, build_request(request), @options[:headers]))
         | 
| 324 327 |  | 
| 325 328 | 
             
                    build_response(successful?(response), message_from(response), response,
         | 
| 326 329 | 
             
                	    :test => test?,
         | 
| @@ -80,6 +80,8 @@ module ActiveMerchant #:nodoc: | |
| 80 80 | 
             
                            xml.tag! 'n2:ButtonSource', application_id.to_s.slice(0,32) unless application_id.blank?
         | 
| 81 81 | 
             
                            xml.tag! 'n2:InvoiceID', options[:order_id]
         | 
| 82 82 | 
             
                            xml.tag! 'n2:OrderDescription', options[:description]
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                            add_items_xml(xml, options, currency_code) if options[:items]
         | 
| 83 85 | 
             
                          end
         | 
| 84 86 | 
             
                        end
         | 
| 85 87 | 
             
                      end
         | 
| @@ -115,25 +117,13 @@ module ActiveMerchant #:nodoc: | |
| 115 117 | 
             
                            xml.tag! 'n2:OrderDescription', options[:description]
         | 
| 116 118 | 
             
                            xml.tag! 'n2:InvoiceID', options[:order_id]
         | 
| 117 119 |  | 
| 118 | 
            -
                            if options[:items]
         | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 121 | 
            -
                                  xml.tag! 'n2:Name', item[:name]
         | 
| 122 | 
            -
                                  xml.tag! 'n2:Number', item[:number]
         | 
| 123 | 
            -
                                  xml.tag! 'n2:Quantity', item[:quantity]
         | 
| 124 | 
            -
                                  if item[:amount]
         | 
| 125 | 
            -
                                    xml.tag! 'n2:Amount', localized_amount(item[:amount], currency_code), 'currencyID' => currency_code
         | 
| 126 | 
            -
                                  end
         | 
| 127 | 
            -
                                  xml.tag! 'n2:Description', item[:description]
         | 
| 128 | 
            -
                                  xml.tag! 'n2:ItemURL', item[:url]
         | 
| 129 | 
            -
                                end
         | 
| 130 | 
            -
                              end
         | 
| 131 | 
            -
                            end
         | 
| 120 | 
            +
                            add_items_xml(xml, options, currency_code) if options[:items]
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                            add_address(xml, 'n2:ShipToAddress', options[:shipping_address] || options[:address])
         | 
| 132 123 |  | 
| 133 124 | 
             
                            xml.tag! 'n2:PaymentAction', action
         | 
| 134 125 | 
             
                          end
         | 
| 135 126 |  | 
| 136 | 
            -
                          add_address(xml, 'n2:Address', options[:shipping_address] || options[:address])
         | 
| 137 127 | 
             
                          xml.tag! 'n2:AddressOverride', options[:address_override] ? '1' : '0'
         | 
| 138 128 | 
             
                          xml.tag! 'n2:NoShipping', options[:no_shipping] ? '1' : '0'
         | 
| 139 129 | 
             
                          xml.tag! 'n2:ReturnURL', options[:return_url]
         | 
| @@ -172,6 +162,23 @@ module ActiveMerchant #:nodoc: | |
| 172 162 | 
             
                  def build_response(success, message, response, options = {})
         | 
| 173 163 | 
             
                    PaypalExpressResponse.new(success, message, response, options)
         | 
| 174 164 | 
             
                  end
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                  private
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                  def add_items_xml(xml, options, currency_code)
         | 
| 169 | 
            +
                    options[:items].each do |item|
         | 
| 170 | 
            +
                      xml.tag! 'n2:PaymentDetailsItem' do
         | 
| 171 | 
            +
                        xml.tag! 'n2:Name', item[:name]
         | 
| 172 | 
            +
                        xml.tag! 'n2:Number', item[:number]
         | 
| 173 | 
            +
                        xml.tag! 'n2:Quantity', item[:quantity]
         | 
| 174 | 
            +
                        if item[:amount]
         | 
| 175 | 
            +
                          xml.tag! 'n2:Amount', localized_amount(item[:amount], currency_code), 'currencyID' => currency_code
         | 
| 176 | 
            +
                        end
         | 
| 177 | 
            +
                        xml.tag! 'n2:Description', item[:description]
         | 
| 178 | 
            +
                        xml.tag! 'n2:ItemURL', item[:url]
         | 
| 179 | 
            +
                      end
         | 
| 180 | 
            +
                    end
         | 
| 181 | 
            +
                  end
         | 
| 175 182 | 
             
                end
         | 
| 176 183 | 
             
              end
         | 
| 177 184 | 
             
            end
         | 
| @@ -8,30 +8,91 @@ module ActiveMerchant #:nodoc: | |
| 8 8 |  | 
| 9 9 | 
             
                  self.default_currency = 'DKK'
         | 
| 10 10 | 
             
                  self.money_format = :cents
         | 
| 11 | 
            -
                  self.supported_cardtypes = [ | 
| 11 | 
            +
                  self.supported_cardtypes = [:dankort, :forbrugsforeningen, :visa, :master, :american_express, :diners_club, :jcb, :maestro]
         | 
| 12 12 | 
             
                  self.supported_countries = ['DK', 'SE']
         | 
| 13 13 | 
             
                  self.homepage_url = 'http://quickpay.dk/'
         | 
| 14 14 | 
             
                  self.display_name = 'Quickpay'
         | 
| 15 15 |  | 
| 16 | 
            -
                  PROTOCOL = 3
         | 
| 17 | 
            -
             | 
| 18 16 | 
             
                  MD5_CHECK_FIELDS = {
         | 
| 19 | 
            -
                     | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 17 | 
            +
                    3 => {
         | 
| 18 | 
            +
                      :authorize => %w(protocol msgtype merchant ordernumber amount
         | 
| 19 | 
            +
                                       currency autocapture cardnumber expirationdate
         | 
| 20 | 
            +
                                       cvd cardtypelock testmode),
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                      :capture   => %w(protocol msgtype merchant amount transaction),
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                      :cancel    => %w(protocol msgtype merchant transaction),
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                      :refund    => %w(protocol msgtype merchant amount transaction),
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                      :subscribe => %w(protocol msgtype merchant ordernumber cardnumber
         | 
| 29 | 
            +
                                       expirationdate cvd cardtypelock description testmode),
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                      :recurring => %w(protocol msgtype merchant ordernumber amount
         | 
| 32 | 
            +
                                       currency autocapture transaction),
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                      :status    => %w(protocol msgtype merchant transaction),
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                      :chstatus  => %w(protocol msgtype merchant)
         | 
| 37 | 
            +
                    },
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    4 => {
         | 
| 40 | 
            +
                      :authorize => %w(protocol msgtype merchant ordernumber amount
         | 
| 41 | 
            +
                                       currency autocapture cardnumber expirationdate cvd
         | 
| 42 | 
            +
                                       cardtypelock testmode fraud_remote_addr
         | 
| 43 | 
            +
                                       fraud_http_accept fraud_http_accept_language
         | 
| 44 | 
            +
                                       fraud_http_accept_encoding fraud_http_accept_charset
         | 
| 45 | 
            +
                                       fraud_http_referer fraud_http_user_agent apikey),
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                      :capture   => %w(protocol msgtype merchant amount transaction
         | 
| 48 | 
            +
                                       fraud_remote_addr fraud_http_accept
         | 
| 49 | 
            +
                                       fraud_http_accept_language fraud_http_accept_encoding
         | 
| 50 | 
            +
                                       fraud_http_accept_charset fraud_http_referer
         | 
| 51 | 
            +
                                       fraud_http_user_agent apikey),
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                      :cancel    => %w(protocol msgtype merchant transaction fraud_remote_addr
         | 
| 54 | 
            +
                                       fraud_http_accept fraud_http_accept_language
         | 
| 55 | 
            +
                                       fraud_http_accept_encoding fraud_http_accept_charset
         | 
| 56 | 
            +
                                       fraud_http_referer fraud_http_user_agent apikey),
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                      :refund    => %w(protocol msgtype merchant amount transaction
         | 
| 59 | 
            +
                                       fraud_remote_addr fraud_http_accept fraud_http_accept_language
         | 
| 60 | 
            +
                                       fraud_http_accept_encoding fraud_http_accept_charset
         | 
| 61 | 
            +
                                       fraud_http_referer fraud_http_user_agent apikey),
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                      :subscribe => %w(protocol msgtype merchant ordernumber cardnumber
         | 
| 64 | 
            +
                                       expirationdate cvd cardtypelock description testmode
         | 
| 65 | 
            +
                                       fraud_remote_addr fraud_http_accept fraud_http_accept_language
         | 
| 66 | 
            +
                                       fraud_http_accept_encoding fraud_http_accept_charset
         | 
| 67 | 
            +
                                       fraud_http_referer fraud_http_user_agent apikey),
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                      :recurring => %w(protocol msgtype merchant ordernumber amount currency
         | 
| 70 | 
            +
                                       autocapture transaction fraud_remote_addr fraud_http_accept
         | 
| 71 | 
            +
                                       fraud_http_accept_language fraud_http_accept_encoding
         | 
| 72 | 
            +
                                       fraud_http_accept_charset fraud_http_referer
         | 
| 73 | 
            +
                                       fraud_http_user_agent apikey),
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                      :status    => %w(protocol msgtype merchant transaction fraud_remote_addr
         | 
| 76 | 
            +
                                       fraud_http_accept fraud_http_accept_language
         | 
| 77 | 
            +
                                       fraud_http_accept_encoding fraud_http_accept_charset
         | 
| 78 | 
            +
                                       fraud_http_referer fraud_http_user_agent apikey),
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                      :chstatus  => %w(protocol msgtype merchant fraud_remote_addr fraud_http_accept
         | 
| 81 | 
            +
                                       fraud_http_accept_language fraud_http_accept_encoding
         | 
| 82 | 
            +
                                       fraud_http_accept_charset fraud_http_referer
         | 
| 83 | 
            +
                                       fraud_http_user_agent apikey)
         | 
| 84 | 
            +
                    }
         | 
| 27 85 | 
             
                  }
         | 
| 28 86 |  | 
| 29 87 | 
             
                  APPROVED = '000'
         | 
| 30 88 |  | 
| 31 89 | 
             
                  # The login is the QuickpayId
         | 
| 32 | 
            -
                  # The password is the md5checkword from the Quickpay  | 
| 90 | 
            +
                  # The password is the md5checkword from the Quickpay manager
         | 
| 91 | 
            +
                  # To use the API-key from the Quickpay manager, specify :api-key
         | 
| 92 | 
            +
                  # Using the API-key, requires that you use version 4. Specify :version => 4 in options.
         | 
| 33 93 | 
             
                  def initialize(options = {})
         | 
| 34 94 | 
             
                    requires!(options, :login, :password)
         | 
| 95 | 
            +
                    @protocol = options.delete(:version) || 3 # default to protocol version 3
         | 
| 35 96 | 
             
                    @options = options
         | 
| 36 97 | 
             
                    super
         | 
| 37 98 | 
             
                  end
         | 
| @@ -43,6 +104,7 @@ module ActiveMerchant #:nodoc: | |
| 43 104 | 
             
                    add_invoice(post, options)
         | 
| 44 105 | 
             
                    add_creditcard_or_reference(post, credit_card_or_reference, options)
         | 
| 45 106 | 
             
                    add_autocapture(post, false)
         | 
| 107 | 
            +
                    add_fraud_parameters(post, options)
         | 
| 46 108 | 
             
                    add_testmode(post)
         | 
| 47 109 |  | 
| 48 110 | 
             
                    commit(recurring_or_authorize(credit_card_or_reference), post)
         | 
| @@ -54,6 +116,7 @@ module ActiveMerchant #:nodoc: | |
| 54 116 | 
             
                    add_amount(post, money, options)
         | 
| 55 117 | 
             
                    add_creditcard_or_reference(post, credit_card_or_reference, options)
         | 
| 56 118 | 
             
                    add_invoice(post, options)
         | 
| 119 | 
            +
                    add_fraud_parameters(post, options)
         | 
| 57 120 | 
             
                    add_autocapture(post, true)
         | 
| 58 121 |  | 
| 59 122 | 
             
                    commit(recurring_or_authorize(credit_card_or_reference), post)
         | 
| @@ -64,6 +127,7 @@ module ActiveMerchant #:nodoc: | |
| 64 127 |  | 
| 65 128 | 
             
                    add_reference(post, authorization)
         | 
| 66 129 | 
             
                    add_amount_without_currency(post, money)
         | 
| 130 | 
            +
                    add_fraud_parameters(post, options)
         | 
| 67 131 |  | 
| 68 132 | 
             
                    commit(:capture, post)
         | 
| 69 133 | 
             
                  end
         | 
| @@ -72,6 +136,7 @@ module ActiveMerchant #:nodoc: | |
| 72 136 | 
             
                    post = {}
         | 
| 73 137 |  | 
| 74 138 | 
             
                    add_reference(post, identification)
         | 
| 139 | 
            +
                    add_fraud_parameters(post, options)
         | 
| 75 140 |  | 
| 76 141 | 
             
                    commit(:cancel, post)
         | 
| 77 142 | 
             
                  end
         | 
| @@ -81,6 +146,7 @@ module ActiveMerchant #:nodoc: | |
| 81 146 |  | 
| 82 147 | 
             
                    add_amount_without_currency(post, money)
         | 
| 83 148 | 
             
                    add_reference(post, identification)
         | 
| 149 | 
            +
                    add_fraud_parameters(post, options)
         | 
| 84 150 |  | 
| 85 151 | 
             
                    commit(:refund, post)
         | 
| 86 152 | 
             
                  end
         | 
| @@ -96,6 +162,7 @@ module ActiveMerchant #:nodoc: | |
| 96 162 | 
             
                    add_creditcard(post, creditcard, options)
         | 
| 97 163 | 
             
                    add_invoice(post, options)
         | 
| 98 164 | 
             
                    add_description(post, options)
         | 
| 165 | 
            +
                    add_fraud_parameters(post, options)
         | 
| 99 166 | 
             
                    add_testmode(post)
         | 
| 100 167 |  | 
| 101 168 | 
             
                    commit(:subscribe, post)
         | 
| @@ -150,6 +217,18 @@ module ActiveMerchant #:nodoc: | |
| 150 217 | 
             
                  def add_testmode(post)
         | 
| 151 218 | 
             
                    post[:testmode] = test? ? '1' : '0'
         | 
| 152 219 | 
             
                  end
         | 
| 220 | 
            +
                  
         | 
| 221 | 
            +
                  def add_fraud_parameters(post, options)
         | 
| 222 | 
            +
                    if @protocol == 4
         | 
| 223 | 
            +
                      post[:fraud_remote_addr] = options[:fraud_remote_addr] if options[:fraud_remote_addr]
         | 
| 224 | 
            +
                      post[:fraud_http_accept] = options[:fraud_http_accept] if options[:fraud_http_accept]
         | 
| 225 | 
            +
                      post[:fraud_http_accept_language] = options[:fraud_http_accept_language] if options[:fraud_http_accept_language]
         | 
| 226 | 
            +
                      post[:fraud_http_accept_encoding] = options[:fraud_http_accept_encoding] if options[:fraud_http_accept_encoding]
         | 
| 227 | 
            +
                      post[:fraud_http_accept_charset] = options[:fraud_http_accept_charset] if options[:fraud_http_accept_charset]
         | 
| 228 | 
            +
                      post[:fraud_http_referer] = options[:fraud_http_referer] if options[:fraud_http_referer]
         | 
| 229 | 
            +
                      post[:fraud_http_user_agent] = options[:fraud_http_user_agent] if options[:fraud_http_user_agent]
         | 
| 230 | 
            +
                    end
         | 
| 231 | 
            +
                  end
         | 
| 153 232 |  | 
| 154 233 | 
             
                  def commit(action, params)
         | 
| 155 234 | 
             
                    response = parse(ssl_post(URL, post_data(action, params)))
         | 
| @@ -177,27 +256,22 @@ module ActiveMerchant #:nodoc: | |
| 177 256 | 
             
                  end
         | 
| 178 257 |  | 
| 179 258 | 
             
                  def message_from(response)
         | 
| 180 | 
            -
                     | 
| 181 | 
            -
                    when '008'   # Error in request data
         | 
| 182 | 
            -
                      response[:qpstatmsg].to_s
         | 
| 183 | 
            -
                      #.scan(/[A-Z][a-z0-9 \/]+/).to_sentence
         | 
| 184 | 
            -
                    else
         | 
| 185 | 
            -
                      response[:qpstatmsg].to_s
         | 
| 186 | 
            -
                    end
         | 
| 259 | 
            +
                    response[:qpstatmsg].to_s
         | 
| 187 260 | 
             
                  end
         | 
| 188 261 |  | 
| 189 262 | 
             
                  def post_data(action, params = {})
         | 
| 190 | 
            -
                    params[:protocol] =  | 
| 263 | 
            +
                    params[:protocol] = @protocol
         | 
| 191 264 | 
             
                    params[:msgtype]  = action.to_s
         | 
| 192 265 | 
             
                    params[:merchant] = @options[:login]
         | 
| 266 | 
            +
                    params[:apikey] = @options[:apikey] if @options[:apikey]
         | 
| 193 267 | 
             
                    params[:md5check] = generate_check_hash(action, params)
         | 
| 194 268 |  | 
| 195 269 | 
             
                    params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
         | 
| 196 270 | 
             
                  end
         | 
| 197 271 |  | 
| 198 272 | 
             
                  def generate_check_hash(action, params)
         | 
| 199 | 
            -
                    string = MD5_CHECK_FIELDS[action].collect do |key|
         | 
| 200 | 
            -
                      params[key]
         | 
| 273 | 
            +
                    string = MD5_CHECK_FIELDS[@protocol][action].collect do |key|
         | 
| 274 | 
            +
                      params[key.to_sym]
         | 
| 201 275 | 
             
                    end.join('')
         | 
| 202 276 |  | 
| 203 277 | 
             
                    # Add the md5checkword
         |