activemerchant 1.64.0 → 1.65.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/CHANGELOG +27 -0
- data/README.md +0 -1
- data/lib/active_merchant/billing/gateways/adyen.rb +228 -0
- data/lib/active_merchant/billing/gateways/authorize_net.rb +9 -1
- data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +68 -2
- data/lib/active_merchant/billing/gateways/braintree_blue.rb +10 -2
- data/lib/active_merchant/billing/gateways/checkout_v2.rb +2 -2
- data/lib/active_merchant/billing/gateways/cyber_source.rb +2 -0
- data/lib/active_merchant/billing/gateways/global_collect.rb +17 -5
- data/lib/active_merchant/billing/gateways/jetpay.rb +12 -9
- data/lib/active_merchant/billing/gateways/openpay.rb +1 -0
- data/lib/active_merchant/billing/gateways/orbital.rb +5 -3
- data/lib/active_merchant/billing/gateways/payeezy.rb +7 -0
- data/lib/active_merchant/billing/gateways/payu_latam.rb +1 -1
- data/lib/active_merchant/billing/gateways/pin.rb +5 -0
- data/lib/active_merchant/billing/gateways/quickpay.rb +3 -3
- data/lib/active_merchant/billing/gateways/qvalent.rb +44 -1
- data/lib/active_merchant/billing/gateways/safe_charge.rb +211 -0
- data/lib/active_merchant/billing/gateways/sage_pay.rb +8 -5
- data/lib/active_merchant/billing/gateways/stripe.rb +6 -1
- data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +45 -11
- data/lib/active_merchant/billing/gateways/wepay.rb +1 -0
- data/lib/active_merchant/billing/gateways/worldpay.rb +8 -3
- data/lib/active_merchant/posts_data.rb +1 -1
- data/lib/active_merchant/version.rb +1 -1
- metadata +4 -3
- data/lib/active_merchant/billing/gateways/barclays_epdq.rb +0 -314
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 6a6b4a889b1f046f131622178e728d71cf0e1a41
         | 
| 4 | 
            +
              data.tar.gz: 5e6eba2ad428363e4024c789e32ce348fef7b4b6
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: e5221e56fe77524519e0cfe91ddf61603f97a2826b1773f3b65dd1c7048ca531311cb8b6f3fc3c918e2d54cdbe4d3ff1c8a662595c1bc24adc958acf9b6a12e3
         | 
| 7 | 
            +
              data.tar.gz: 6bd1d7b0d877748404c56f3fe62b0e806997e3c17161f72e79f11a769564e7028c1356b969a960912d290fbf7f6faabb5a686efcd07bfd707ab0a196eb78f939
         | 
    
        data/CHANGELOG
    CHANGED
    
    | @@ -2,6 +2,33 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            == HEAD
         | 
| 4 4 |  | 
| 5 | 
            +
            == Version 1.65.0 (April 26, 2017)
         | 
| 6 | 
            +
            * Adyen: Add Adyen v18 gateway [adyenpayments] #2272
         | 
| 7 | 
            +
            * Authorize.Net: Force refund of unsettled payments via void [bizla] #2399
         | 
| 8 | 
            +
            * Barclays ePDQ: removed because it has been replaced by a new API [bdewater] #2331
         | 
| 9 | 
            +
            * Beanstream: Map ISO province codes for US and CA [shasum] #2396
         | 
| 10 | 
            +
            * Braintree Blue: Change :full_refund option to :force_full_refund_if_unsettled [bizla] #2403
         | 
| 11 | 
            +
            * Braintree Blue: Force refund of unsettled payments via void [bizla] #2398
         | 
| 12 | 
            +
            * Checkout V2: Fix sandbox URL [nicolas-maalouf-cko] #2391
         | 
| 13 | 
            +
            * Checkout V2: Fix success_from not properly checking two possible success codes [davidsantoso]
         | 
| 14 | 
            +
            * Cybersource: Rescue XML parse exception [shasum] #2380
         | 
| 15 | 
            +
            * GlobalCollect: Make message and error reporting more robust [curiousepic] #2370
         | 
| 16 | 
            +
            * GlobalCollect: Set REJECTED refunds as unsuccessful transactions [davidsantoso] #2365
         | 
| 17 | 
            +
            * GlobalCollect: Truncate firstName field to 15 characters [davidsantoso]
         | 
| 18 | 
            +
            * JetPay: Pass down authorization payment method token to refund a capture [davidsantoso]
         | 
| 19 | 
            +
            * Openpay: Support card points [shasum] #2401
         | 
| 20 | 
            +
            * Orbital: Don't send CVV indicator if CVV is not present [curiousepic] #2368
         | 
| 21 | 
            +
            * PayU LATAM: Fix incorrect capture method definition [davidsantoso]
         | 
| 22 | 
            +
            * Payeezy: Support dynamic soft descriptors [shasum] #2384
         | 
| 23 | 
            +
            * Pin: Add metadata optional field [shasum] #2363
         | 
| 24 | 
            +
            * Qvalent: Add soft descriptor fields. Add authorize, capture, and void [davidsantoso]
         | 
| 25 | 
            +
            * SafeCharge: Add gateway [davidsantoso]
         | 
| 26 | 
            +
            * SagePay: Support Repeat transactions [curiousepic] #2395
         | 
| 27 | 
            +
            * Stripe: Support custom application in X-Stripe-Client-User-Agent header [davidsantoso]
         | 
| 28 | 
            +
            * TransFirst Transaction Express: Support ACH [curiousepic] #2389
         | 
| 29 | 
            +
            * WePay: Support unique_id for idempotent transactions [shasum] #2367
         | 
| 30 | 
            +
            * Worldpay: Force refund of unsettled payments via void [bizla] #2402
         | 
| 31 | 
            +
             | 
| 5 32 | 
             
            == Version 1.64.0 (March 6, 2017)
         | 
| 6 33 | 
             
            * Authorize.net: Allow settings to be passed for CIM purchases [fwilkins] #2300
         | 
| 7 34 | 
             
            * Authorize.net: Use new `unsupported_feature` standard error code [jasonwebster] #2322
         | 
    
        data/README.md
    CHANGED
    
    | @@ -91,7 +91,6 @@ The [ActiveMerchant Wiki](http://github.com/activemerchant/active_merchant/wikis | |
| 91 91 | 
             
            * [Bank Frick](http://www.bankfrickacquiring.com/) - LI, US
         | 
| 92 92 | 
             
            * [Banwire](http://www.banwire.com/) - MX
         | 
| 93 93 | 
             
            * [Barclays ePDQ Extra Plus](http://www.barclaycard.co.uk/business/accepting-payments/epdq-ecomm/) - GB
         | 
| 94 | 
            -
            * [Barclays ePDQ MPI](http://www.barclaycard.co.uk/business/accepting-payments/epdq-mpi/) - GB
         | 
| 95 94 | 
             
            * [Be2Bill](http://www.be2bill.com/) - FR
         | 
| 96 95 | 
             
            * [Beanstream.com](http://www.beanstream.com/) - CA, US
         | 
| 97 96 | 
             
            * [BluePay](http://www.bluepay.com/) - US
         | 
| @@ -0,0 +1,228 @@ | |
| 1 | 
            +
            module ActiveMerchant #:nodoc:
         | 
| 2 | 
            +
              module Billing #:nodoc:
         | 
| 3 | 
            +
                class AdyenGateway < Gateway
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  # we recommend setting up merchant-specific endpoints.
         | 
| 6 | 
            +
                  # https://docs.adyen.com/developers/api-manual#apiendpoints
         | 
| 7 | 
            +
                  self.test_url = 'https://pal-test.adyen.com/pal/servlet/Payment/v18'
         | 
| 8 | 
            +
                  self.live_url = 'https://pal-live.adyen.com/pal/servlet/Payment/v18'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  self.supported_countries = ['AD','AE','AF','AG','AI','AL','AM','AO','AQ','AR','AS','AT','AU','AW','AX','AZ','BA','BB','BD','BE','BF','BG','BH','BI','BJ','BL','BM','BN','BO','BQ','BR','BS','BT','BV','BW','BY','BZ','CA','CC','CD','CF','CG','CH','CI','CK','CL','CM','CN','CO','CR','CU','CV','CW','CX','CY','CZ','DE','DJ','DK','DM','DO','DZ','EC','EE','EG','EH','ER','ES','ET','FI','FJ','FK','FM','FO','FR','GA','GB','GD','GE','GF','GG','GH','GI','GL','GM','GN','GP','GQ','GR','GS','GT','GU','GW','GY','HK','HM','HN','HR','HT','HU','ID','IE','IL','IM','IN','IO','IQ','IR','IS','IT','JE','JM','JO','JP','KE','KG','KH','KI','KM','KN','KP','KR','KW','KY','KZ','LA','LB','LC','LI','LK','LR','LS','LT','LU','LV','LY','MA','MC','MD','ME','MF','MG','MH','MK','ML','MM','MN','MO','MP','MQ','MR','MS','MT','MU','MV','MW','MX','MY','MZ','NA','NC','NE','NF','NG','NI','NL','NO','NP','NR','NU','NZ','OM','PA','PE','PF','PG','PH','PK','PL','PM','PN','PR','PS','PT','PW','PY','QA','RE','RO','RS','RU','RW','SA','SB','SC','SD','SE','SG','SH','SI','SJ','SK','SL','SM','SN','SO','SR','SS','ST','SV','SX','SY','SZ','TC','TD','TF','TG','TH','TJ','TK','TL','TM','TN','TO','TR','TT','TV','TW','TZ','UA','UG','UM','US','UY','UZ','VA','VC','VE','VG','VI','VN','VU','WF','WS','YE','YT','ZA','ZM','ZW']
         | 
| 11 | 
            +
                  self.default_currency = 'USD'
         | 
| 12 | 
            +
                  self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb, :dankort, :maestro,  :discover]
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  self.money_format = :cents
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  self.homepage_url = 'https://www.adyen.com/'
         | 
| 17 | 
            +
                  self.display_name = 'Adyen'
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  STANDARD_ERROR_CODE_MAPPING = {
         | 
| 20 | 
            +
                    '101' => STANDARD_ERROR_CODE[:incorrect_number],
         | 
| 21 | 
            +
                    '103' => STANDARD_ERROR_CODE[:invalid_cvc],
         | 
| 22 | 
            +
                    '131' => STANDARD_ERROR_CODE[:incorrect_address],
         | 
| 23 | 
            +
                    '132' => STANDARD_ERROR_CODE[:incorrect_address],
         | 
| 24 | 
            +
                    '133' => STANDARD_ERROR_CODE[:incorrect_address],
         | 
| 25 | 
            +
                    '134' => STANDARD_ERROR_CODE[:incorrect_address],
         | 
| 26 | 
            +
                    '135' => STANDARD_ERROR_CODE[:incorrect_address],
         | 
| 27 | 
            +
                  }
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def initialize(options={})
         | 
| 30 | 
            +
                    requires!(options, :username, :password, :merchant_account)
         | 
| 31 | 
            +
                    @username, @password, @merchant_account = options.values_at(:username, :password, :merchant_account)
         | 
| 32 | 
            +
                    super
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def purchase(money, payment, options={})
         | 
| 36 | 
            +
                    MultiResponse.run do |r|
         | 
| 37 | 
            +
                      r.process{authorize(money, payment, options)}
         | 
| 38 | 
            +
                      r.process{capture(money, r.authorization, options)}
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  def authorize(money, payment, options={})
         | 
| 43 | 
            +
                    requires!(options, :reference)
         | 
| 44 | 
            +
                    post = init_post(options)
         | 
| 45 | 
            +
                    add_invoice(post, money, options)
         | 
| 46 | 
            +
                    add_payment(post, payment)
         | 
| 47 | 
            +
                    add_extra_data(post, options)
         | 
| 48 | 
            +
                    add_address(post, options)
         | 
| 49 | 
            +
                    commit('authorise', post)
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  def capture(money, authorization, options={})
         | 
| 53 | 
            +
                    post = init_post(options)
         | 
| 54 | 
            +
                    add_invoice_for_modification(post, money, authorization, options)
         | 
| 55 | 
            +
                    add_references(post, authorization, options)
         | 
| 56 | 
            +
                    commit('capture', post)
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  def refund(money, authorization, options={})
         | 
| 60 | 
            +
                    post = init_post(options)
         | 
| 61 | 
            +
                    add_invoice_for_modification(post, money, authorization, options)
         | 
| 62 | 
            +
                    add_references(post, authorization, options)
         | 
| 63 | 
            +
                    commit('refund', post)
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  def void(authorization, options={})
         | 
| 67 | 
            +
                    post = init_post(options)
         | 
| 68 | 
            +
                    add_references(post, authorization, options)
         | 
| 69 | 
            +
                    commit('cancel', post)
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  def verify(credit_card, options={})
         | 
| 73 | 
            +
                    MultiResponse.run(:use_first_response) do |r|
         | 
| 74 | 
            +
                      r.process { authorize(100, credit_card, options) }
         | 
| 75 | 
            +
                      r.process(:ignore_result) { void(r.authorization, options) }
         | 
| 76 | 
            +
                    end
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                  def supports_scrubbing?
         | 
| 80 | 
            +
                    true
         | 
| 81 | 
            +
                  end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                  def scrub(transcript)
         | 
| 84 | 
            +
                    transcript.
         | 
| 85 | 
            +
                      gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
         | 
| 86 | 
            +
                      gsub(%r(("number\\?":\\?")[^"]*)i, '\1[FILTERED]').
         | 
| 87 | 
            +
                      gsub(%r(("cvc\\?":\\?")[^"]*)i, '\1[FILTERED]')
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  private
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                  def add_extra_data(post, options)
         | 
| 93 | 
            +
                    post[:shopperEmail] = options[:shopper_email] if options[:shopper_email]
         | 
| 94 | 
            +
                    post[:shopperIP] = options[:shopper_ip] if options[:shopper_ip]
         | 
| 95 | 
            +
                    post[:shopperReference] = options[:shopper_reference] if options[:shopper_reference]
         | 
| 96 | 
            +
                    post[:fraudOffset] = options[:fraud_offset] if options[:fraud_offset]
         | 
| 97 | 
            +
                    post[:selectedBrand] = options[:selected_brand] if options[:selected_brand]
         | 
| 98 | 
            +
                    post[:deliveryDate] = options[:delivery_date] if options[:delivery_date]
         | 
| 99 | 
            +
                    post[:merchantOrderReference] = options[:merchant_order_reference] if options[:merchant_order_reference]
         | 
| 100 | 
            +
                    post[:shopperInteraction] = options[:shopper_interaction] if options[:shopper_interaction]
         | 
| 101 | 
            +
                  end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  def add_address(post, options)
         | 
| 104 | 
            +
                    return unless post[:card] && post[:card].kind_of?(Hash)
         | 
| 105 | 
            +
                    if address = options[:billing_address] || options[:address]
         | 
| 106 | 
            +
                      post[:card][:billingAddress] = {}
         | 
| 107 | 
            +
                      post[:card][:billingAddress][:street] = address[:address1] if address[:address1]
         | 
| 108 | 
            +
                      post[:card][:billingAddress][:houseNumberOrName] = address[:address2] if address[:address2]
         | 
| 109 | 
            +
                      post[:card][:billingAddress][:postalCode] = address[:zip] if address[:zip]
         | 
| 110 | 
            +
                      post[:card][:billingAddress][:city] = address[:city] if address[:city]
         | 
| 111 | 
            +
                      post[:card][:billingAddress][:stateOrProvince] = address[:state] if address[:state]
         | 
| 112 | 
            +
                      post[:card][:billingAddress][:country] = address[:country] if address[:country]
         | 
| 113 | 
            +
                    end
         | 
| 114 | 
            +
                  end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                  def add_invoice(post, money, options)
         | 
| 117 | 
            +
                    amount = {
         | 
| 118 | 
            +
                      value: amount(money),
         | 
| 119 | 
            +
                      currency: options[:currency] || currency(money)
         | 
| 120 | 
            +
                    }
         | 
| 121 | 
            +
                    post[:reference] = options[:reference]
         | 
| 122 | 
            +
                    post[:amount] = amount
         | 
| 123 | 
            +
                  end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                  def add_invoice_for_modification(post, money, authorization, options)
         | 
| 126 | 
            +
                    amount = {
         | 
| 127 | 
            +
                      value: amount(money),
         | 
| 128 | 
            +
                      currency: options[:currency] || currency(money)
         | 
| 129 | 
            +
                    }
         | 
| 130 | 
            +
                    post[:modificationAmount] = amount
         | 
| 131 | 
            +
                  end
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                  def add_payment(post, payment)
         | 
| 134 | 
            +
                    card = {
         | 
| 135 | 
            +
                      expiryMonth: payment.month,
         | 
| 136 | 
            +
                      expiryYear: payment.year,
         | 
| 137 | 
            +
                      holderName: payment.name,
         | 
| 138 | 
            +
                      number: payment.number,
         | 
| 139 | 
            +
                      cvc: payment.verification_value
         | 
| 140 | 
            +
                    }
         | 
| 141 | 
            +
                    card.delete_if{|k,v| v.blank? }
         | 
| 142 | 
            +
                    requires!(card, :expiryMonth, :expiryYear, :holderName, :number, :cvc)
         | 
| 143 | 
            +
                    post[:card] = card
         | 
| 144 | 
            +
                  end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                  def add_references(post, authorization, options = {})
         | 
| 147 | 
            +
                    post[:originalReference] = authorization
         | 
| 148 | 
            +
                    post[:reference] = options[:reference]
         | 
| 149 | 
            +
                  end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                  def parse(body)
         | 
| 152 | 
            +
                    return {} if body.blank?
         | 
| 153 | 
            +
                    JSON.parse(body)
         | 
| 154 | 
            +
                  end
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                  def commit(action, parameters)
         | 
| 157 | 
            +
                    url = (test? ? test_url : live_url)
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                    begin
         | 
| 160 | 
            +
                      raw_response = ssl_post("#{url}/#{action.to_s}", post_data(action, parameters), request_headers)
         | 
| 161 | 
            +
                      response = parse(raw_response)
         | 
| 162 | 
            +
                    rescue ResponseError => e
         | 
| 163 | 
            +
                      raw_response = e.response.body
         | 
| 164 | 
            +
                      response = parse(raw_response)
         | 
| 165 | 
            +
                    end
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                    success = success_from(action, response)
         | 
| 168 | 
            +
                    Response.new(
         | 
| 169 | 
            +
                      success,
         | 
| 170 | 
            +
                      message_from(action, response),
         | 
| 171 | 
            +
                      response,
         | 
| 172 | 
            +
                      authorization: authorization_from(response),
         | 
| 173 | 
            +
                      test: test?,
         | 
| 174 | 
            +
                      error_code: success ? nil : error_code_from(response)
         | 
| 175 | 
            +
                    )
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                  end
         | 
| 178 | 
            +
             | 
| 179 | 
            +
                  def basic_auth
         | 
| 180 | 
            +
                    Base64.strict_encode64("#{@username}:#{@password}")
         | 
| 181 | 
            +
                  end
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                  def request_headers
         | 
| 184 | 
            +
                    {
         | 
| 185 | 
            +
                      "Content-Type" => "application/json",
         | 
| 186 | 
            +
                      "Authorization" => "Basic #{basic_auth}"
         | 
| 187 | 
            +
                    }
         | 
| 188 | 
            +
                  end
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                  def success_from(action, response)
         | 
| 191 | 
            +
                    case action.to_s
         | 
| 192 | 
            +
                    when 'authorise'
         | 
| 193 | 
            +
                      ['Authorised', 'Received', 'RedirectShopper'].include?(response['resultCode'])
         | 
| 194 | 
            +
                    when 'capture', 'refund', 'cancel'
         | 
| 195 | 
            +
                      response['response'] == "[#{action}-received]"
         | 
| 196 | 
            +
                    else
         | 
| 197 | 
            +
                      false
         | 
| 198 | 
            +
                    end
         | 
| 199 | 
            +
                  end
         | 
| 200 | 
            +
             | 
| 201 | 
            +
                  def message_from(action, response)
         | 
| 202 | 
            +
                    case action.to_s
         | 
| 203 | 
            +
                    when 'authorise'
         | 
| 204 | 
            +
                      response['refusalReason'] || response['resultCode'] || response['message']
         | 
| 205 | 
            +
                    when 'capture', 'refund', 'cancel'
         | 
| 206 | 
            +
                      response['response'] || response['message']
         | 
| 207 | 
            +
                    end
         | 
| 208 | 
            +
                  end
         | 
| 209 | 
            +
             | 
| 210 | 
            +
                  def authorization_from(response)
         | 
| 211 | 
            +
                    response['pspReference']
         | 
| 212 | 
            +
                  end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                  def init_post(options = {})
         | 
| 215 | 
            +
                    {merchantAccount: options[:merchant_account] || @merchant_account}
         | 
| 216 | 
            +
                  end
         | 
| 217 | 
            +
             | 
| 218 | 
            +
                  def post_data(action, parameters = {})
         | 
| 219 | 
            +
                    JSON.generate(parameters)
         | 
| 220 | 
            +
                  end
         | 
| 221 | 
            +
             | 
| 222 | 
            +
                  def error_code_from(response)
         | 
| 223 | 
            +
                    STANDARD_ERROR_CODE_MAPPING[response['errorCode']]
         | 
| 224 | 
            +
                  end
         | 
| 225 | 
            +
             | 
| 226 | 
            +
                end
         | 
| 227 | 
            +
              end
         | 
| 228 | 
            +
            end
         | 
| @@ -92,6 +92,7 @@ module ActiveMerchant | |
| 92 92 | 
             
                  APPLE_PAY_DATA_DESCRIPTOR = "COMMON.APPLE.INAPP.PAYMENT"
         | 
| 93 93 |  | 
| 94 94 | 
             
                  PAYMENT_METHOD_NOT_SUPPORTED_ERROR = "155"
         | 
| 95 | 
            +
                  INELIGIBLE_FOR_ISSUING_CREDIT_ERROR = "54"
         | 
| 95 96 |  | 
| 96 97 | 
             
                  def initialize(options={})
         | 
| 97 98 | 
             
                    requires!(options, :login, :password)
         | 
| @@ -131,11 +132,18 @@ module ActiveMerchant | |
| 131 132 | 
             
                  end
         | 
| 132 133 |  | 
| 133 134 | 
             
                  def refund(amount, authorization, options={})
         | 
| 134 | 
            -
                    if auth_was_for_cim?(authorization)
         | 
| 135 | 
            +
                    response = if auth_was_for_cim?(authorization)
         | 
| 135 136 | 
             
                      cim_refund(amount, authorization, options)
         | 
| 136 137 | 
             
                    else
         | 
| 137 138 | 
             
                      normal_refund(amount, authorization, options)
         | 
| 138 139 | 
             
                    end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                    return response if response.success?
         | 
| 142 | 
            +
                    return response unless options[:force_full_refund_if_unsettled]
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                    if response.params["response_reason_code"] == INELIGIBLE_FOR_ISSUING_CREDIT_ERROR
         | 
| 145 | 
            +
                      void(authorization, options)
         | 
| 146 | 
            +
                    end
         | 
| 139 147 | 
             
                  end
         | 
| 140 148 |  | 
| 141 149 | 
             
                  def void(authorization, options={})
         | 
| @@ -59,6 +59,72 @@ module ActiveMerchant #:nodoc: | |
| 59 59 | 
             
                    :cancel => 'C'
         | 
| 60 60 | 
             
                  }
         | 
| 61 61 |  | 
| 62 | 
            +
                  STATES = {
         | 
| 63 | 
            +
                    "ALBERTA" => "AB",
         | 
| 64 | 
            +
                    "BRITISH COLUMBIA" => "BC",
         | 
| 65 | 
            +
                    "MANITOBA" => "MB",
         | 
| 66 | 
            +
                    "NEW BRUNSWICK" => "NB",
         | 
| 67 | 
            +
                    "NEWFOUNDLAND AND LABRADOR" => "NL",
         | 
| 68 | 
            +
                    "NOVA SCOTIA" => "NS",
         | 
| 69 | 
            +
                    "ONTARIO" => "ON",
         | 
| 70 | 
            +
                    "PRINCE EDWARD ISLAND" => "PE",
         | 
| 71 | 
            +
                    "QUEBEC" => "QC",
         | 
| 72 | 
            +
                    "SASKATCHEWAN" => "SK",
         | 
| 73 | 
            +
                    "NORTHWEST TERRITORIES" => "NT",
         | 
| 74 | 
            +
                    "NUNAVUT" => "NU",
         | 
| 75 | 
            +
                    "YUKON" => "YT",
         | 
| 76 | 
            +
                    "ALABAMA" => "AL",
         | 
| 77 | 
            +
                    "ALASKA" => "AK",
         | 
| 78 | 
            +
                    "ARIZONA" => "AZ",
         | 
| 79 | 
            +
                    "ARKANSAS" => "AR",
         | 
| 80 | 
            +
                    "CALIFORNIA" => "CA",
         | 
| 81 | 
            +
                    "COLORADO" => "CO",
         | 
| 82 | 
            +
                    "CONNECTICUT" => "CT",
         | 
| 83 | 
            +
                    "DELAWARE" => "DE",
         | 
| 84 | 
            +
                    "FLORIDA" => "FL",
         | 
| 85 | 
            +
                    "GEORGIA" => "GA",
         | 
| 86 | 
            +
                    "HAWAII" => "HI",
         | 
| 87 | 
            +
                    "IDAHO" => "ID",
         | 
| 88 | 
            +
                    "ILLINOIS" => "IL",
         | 
| 89 | 
            +
                    "INDIANA" => "IN",
         | 
| 90 | 
            +
                    "IOWA" => "IA",
         | 
| 91 | 
            +
                    "KANSAS" => "KS",
         | 
| 92 | 
            +
                    "KENTUCKY" => "KY",
         | 
| 93 | 
            +
                    "LOUISIANA" => "LA",
         | 
| 94 | 
            +
                    "MAINE" => "ME",
         | 
| 95 | 
            +
                    "MARYLAND" => "MD",
         | 
| 96 | 
            +
                    "MASSACHUSETTS" => "MA",
         | 
| 97 | 
            +
                    "MICHIGAN" => "MI",
         | 
| 98 | 
            +
                    "MINNESOTA" => "MN",
         | 
| 99 | 
            +
                    "MISSISSIPPI" => "MS",
         | 
| 100 | 
            +
                    "MISSOURI" => "MO",
         | 
| 101 | 
            +
                    "MONTANA" => "MT",
         | 
| 102 | 
            +
                    "NEBRASKA" => "NE",
         | 
| 103 | 
            +
                    "NEVADA" => "NV",
         | 
| 104 | 
            +
                    "NEW HAMPSHIRE" => "NH",
         | 
| 105 | 
            +
                    "NEW JERSEY" => "NJ",
         | 
| 106 | 
            +
                    "NEW MEXICO" => "NM",
         | 
| 107 | 
            +
                    "NEW YORK" => "NY",
         | 
| 108 | 
            +
                    "NORTH CAROLINA" => "NC",
         | 
| 109 | 
            +
                    "NORTH DAKOTA" => "ND",
         | 
| 110 | 
            +
                    "OHIO" => "OH",
         | 
| 111 | 
            +
                    "OKLAHOMA" => "OK",
         | 
| 112 | 
            +
                    "OREGON" => "OR",
         | 
| 113 | 
            +
                    "PENNSYLVANIA" => "PA",
         | 
| 114 | 
            +
                    "RHODE ISLAND" => "RI",
         | 
| 115 | 
            +
                    "SOUTH CAROLINA" => "SC",
         | 
| 116 | 
            +
                    "SOUTH DAKOTA" => "SD",
         | 
| 117 | 
            +
                    "TENNESSEE" => "TN",
         | 
| 118 | 
            +
                    "TEXAS" => "TX",
         | 
| 119 | 
            +
                    "UTAH" => "UT",
         | 
| 120 | 
            +
                    "VERMONT" => "VT",
         | 
| 121 | 
            +
                    "VIRGINIA" => "VA",
         | 
| 122 | 
            +
                    "WASHINGTON" => "WA",
         | 
| 123 | 
            +
                    "WEST VIRGINIA" => "WV",
         | 
| 124 | 
            +
                    "WISCONSIN" => "WI",
         | 
| 125 | 
            +
                    "WYOMING" => "WY"
         | 
| 126 | 
            +
                  }
         | 
| 127 | 
            +
             | 
| 62 128 | 
             
                  def self.included(base)
         | 
| 63 129 | 
             
                    base.default_currency = 'CAD'
         | 
| 64 130 |  | 
| @@ -161,7 +227,7 @@ module ActiveMerchant #:nodoc: | |
| 161 227 | 
             
                      post[:ordAddress1]      = billing_address[:address1]
         | 
| 162 228 | 
             
                      post[:ordAddress2]      = billing_address[:address2]
         | 
| 163 229 | 
             
                      post[:ordCity]          = billing_address[:city]
         | 
| 164 | 
            -
                      post[:ordProvince]      = billing_address[:state]
         | 
| 230 | 
            +
                      post[:ordProvince]      = STATES[billing_address[:state].upcase] || billing_address[:state] if billing_address[:state]
         | 
| 165 231 | 
             
                      post[:ordPostalCode]    = billing_address[:zip]
         | 
| 166 232 | 
             
                      post[:ordCountry]       = billing_address[:country]
         | 
| 167 233 | 
             
                    end
         | 
| @@ -172,7 +238,7 @@ module ActiveMerchant #:nodoc: | |
| 172 238 | 
             
                      post[:shipAddress1]     = shipping_address[:address1]
         | 
| 173 239 | 
             
                      post[:shipAddress2]     = shipping_address[:address2]
         | 
| 174 240 | 
             
                      post[:shipCity]         = shipping_address[:city]
         | 
| 175 | 
            -
                      post[:shipProvince]     = shipping_address[:state]
         | 
| 241 | 
            +
                      post[:shipProvince]     = STATES[shipping_address[:state].upcase] || shipping_address[:state] if shipping_address[:state]
         | 
| 176 242 | 
             
                      post[:shipPostalCode]   = shipping_address[:zip]
         | 
| 177 243 | 
             
                      post[:shipCountry]      = shipping_address[:country]
         | 
| 178 244 | 
             
                      post[:shippingMethod]   = shipping_address[:shipping_method]
         | 
| @@ -42,6 +42,10 @@ module ActiveMerchant #:nodoc: | |
| 42 42 |  | 
| 43 43 | 
             
                  self.display_name = 'Braintree (Blue Platform)'
         | 
| 44 44 |  | 
| 45 | 
            +
                  ERROR_CODES = {
         | 
| 46 | 
            +
                    cannot_refund_if_unsettled: 91506
         | 
| 47 | 
            +
                  }
         | 
| 48 | 
            +
             | 
| 45 49 | 
             
                  def initialize(options = {})
         | 
| 46 50 | 
             
                    requires!(options, :merchant_id, :public_key, :private_key)
         | 
| 47 51 | 
             
                    @merchant_account_id = options[:merchant_account_id]
         | 
| @@ -90,11 +94,15 @@ module ActiveMerchant #:nodoc: | |
| 90 94 | 
             
                  def refund(*args)
         | 
| 91 95 | 
             
                    # legacy signature: #refund(transaction_id, options = {})
         | 
| 92 96 | 
             
                    # new signature: #refund(money, transaction_id, options = {})
         | 
| 93 | 
            -
                    money, transaction_id,  | 
| 97 | 
            +
                    money, transaction_id, options = extract_refund_args(args)
         | 
| 94 98 | 
             
                    money = amount(money).to_s if money
         | 
| 95 99 |  | 
| 96 100 | 
             
                    commit do
         | 
| 97 | 
            -
                      response_from_result(@braintree_gateway.transaction.refund(transaction_id, money))
         | 
| 101 | 
            +
                      response = response_from_result(@braintree_gateway.transaction.refund(transaction_id, money))
         | 
| 102 | 
            +
                      return response if response.success?
         | 
| 103 | 
            +
                      return response unless options[:force_full_refund_if_unsettled]
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                      void(transaction_id) if response.message =~ /#{ERROR_CODES[:cannot_refund_if_unsettled]}/
         | 
| 98 106 | 
             
                    end
         | 
| 99 107 | 
             
                  end
         | 
| 100 108 |  | 
| @@ -4,7 +4,7 @@ module ActiveMerchant #:nodoc: | |
| 4 4 | 
             
                  self.display_name = "Checkout.com V2 Gateway"
         | 
| 5 5 | 
             
                  self.homepage_url = "https://www.checkout.com/"
         | 
| 6 6 | 
             
                  self.live_url = "https://api2.checkout.com/v2"
         | 
| 7 | 
            -
                  self.test_url = " | 
| 7 | 
            +
                  self.test_url = "https://sandbox.checkout.com/api2/v2"
         | 
| 8 8 |  | 
| 9 9 | 
             
                  self.supported_countries = ['AD', 'AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FO', 'FI', 'FR', 'GB', 'GI', 'GL', 'GR', 'HR', 'HU', 'IE', 'IS', 'IL', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SM', 'SK', 'SJ', 'TR', 'VA']
         | 
| 10 10 | 
             
                  self.default_currency = "USD"
         | 
| @@ -165,7 +165,7 @@ module ActiveMerchant #:nodoc: | |
| 165 165 | 
             
                  end
         | 
| 166 166 |  | 
| 167 167 | 
             
                  def success_from(response)
         | 
| 168 | 
            -
                    response["responseCode"] ==  | 
| 168 | 
            +
                    response["responseCode"] == "10000" || response["responseCode"] == "10100"
         | 
| 169 169 | 
             
                  end
         | 
| 170 170 |  | 
| 171 171 | 
             
                  def message_from(succeeded, response)
         |