activemerchant 1.58.0 → 1.59.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +54 -0
  3. data/README.md +3 -3
  4. data/lib/active_merchant/billing/check.rb +3 -0
  5. data/lib/active_merchant/billing/credit_card.rb +7 -2
  6. data/lib/active_merchant/billing/credit_card_methods.rb +5 -1
  7. data/lib/active_merchant/billing/gateway.rb +5 -3
  8. data/lib/active_merchant/billing/gateways/authorize_net.rb +3 -3
  9. data/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +34 -5
  10. data/lib/active_merchant/billing/gateways/blue_pay.rb +1 -1
  11. data/lib/active_merchant/billing/gateways/blue_snap.rb +348 -0
  12. data/lib/active_merchant/billing/gateways/braintree_blue.rb +6 -3
  13. data/lib/active_merchant/billing/gateways/card_stream.rb +33 -15
  14. data/lib/active_merchant/billing/gateways/cashnet.rb +1 -0
  15. data/lib/active_merchant/billing/gateways/cyber_source.rb +7 -3
  16. data/lib/active_merchant/billing/gateways/global_collect.rb +293 -0
  17. data/lib/active_merchant/billing/gateways/jetpay.rb +11 -8
  18. data/lib/active_merchant/billing/gateways/latitude19.rb +416 -0
  19. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +13 -0
  20. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +10 -7
  21. data/lib/active_merchant/billing/gateways/mercury.rb +1 -1
  22. data/lib/active_merchant/billing/gateways/metrics_global.rb +1 -1
  23. data/lib/active_merchant/billing/gateways/moneris.rb +8 -1
  24. data/lib/active_merchant/billing/gateways/nmi.rb +25 -9
  25. data/lib/active_merchant/billing/gateways/openpay.rb +1 -1
  26. data/lib/active_merchant/billing/gateways/orbital.rb +5 -3
  27. data/lib/active_merchant/billing/gateways/paymill.rb +1 -1
  28. data/lib/active_merchant/billing/gateways/paypal_express.rb +1 -6
  29. data/lib/active_merchant/billing/gateways/payu_in.rb +3 -2
  30. data/lib/active_merchant/billing/gateways/s5.rb +8 -5
  31. data/lib/active_merchant/billing/gateways/sage.rb +1 -7
  32. data/lib/active_merchant/billing/gateways/sage_pay.rb +0 -4
  33. data/lib/active_merchant/billing/gateways/secure_net.rb +0 -5
  34. data/lib/active_merchant/billing/gateways/secure_pay.rb +1 -1
  35. data/lib/active_merchant/billing/gateways/securion_pay.rb +46 -17
  36. data/lib/active_merchant/billing/gateways/stripe.rb +5 -8
  37. data/lib/active_merchant/billing/gateways/tns.rb +1 -1
  38. data/lib/active_merchant/billing/gateways/trans_first.rb +1 -2
  39. data/lib/active_merchant/billing/gateways/vanco.rb +1 -1
  40. data/lib/active_merchant/billing/gateways/visanet_peru.rb +218 -0
  41. data/lib/active_merchant/billing/gateways/world_net.rb +344 -0
  42. data/lib/active_merchant/billing/gateways/worldpay.rb +8 -11
  43. data/lib/active_merchant/billing/network_tokenization_credit_card.rb +4 -0
  44. data/lib/active_merchant/country.rb +0 -2
  45. data/lib/active_merchant/version.rb +1 -1
  46. metadata +7 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: da3af9432d84476a6bb80c8c2ce551def7cb9aad
4
- data.tar.gz: f0fca6c6c9aea70ec6fe8ba476aee0e0087bab14
3
+ metadata.gz: 1b699a9e799907880fbd2fb3c7cc68e8f1bdaead
4
+ data.tar.gz: aba5081dee95a33710e5ac97bed5edd577cb1e33
5
5
  SHA512:
6
- metadata.gz: d2d71c5586712ab174a2aea4da55ce52e10e4df6d11207ee46d85e3ea337feb698137e5b9c80b9f764f33e7d157bd31f15e3f097e5aaa866c60df2c48960525e
7
- data.tar.gz: 831bc61882f81730b31b6f46d29e45a4d41e239c15209ff40a20abca24fe89bee9a1b04e31df0a900ccab1edd0a12ad501b46c7ae818c19dadcde21930c00da9
6
+ metadata.gz: f1ca11be38d85bcea0dcca1cb3b036090d2f37535e0987159071e40345220c14b19f2b2c314e44c81bc1652c3195306bbed61badec33a17ccefca951a21b2182
7
+ data.tar.gz: 68eddb6d812f974fe3ad0d8569bdab2374acffd012fac3bfa95c422958e61666c7994a482da845cb04f9eef9a54fc573ddebd291b08242757115ad5614bc0406
data/CHANGELOG CHANGED
@@ -1,5 +1,59 @@
1
1
  = ActiveMerchant CHANGELOG
2
2
 
3
+ == Version 1.59.0 (May 18, 2016)
4
+ * Orbital: Allow AVS parts to be sent sans country [duff]
5
+ * SecureNet: Return the right error message for declines [duff]
6
+ * Moneris: Add verify [anellis]
7
+ * Moneris: Add verify [anellis]
8
+ * Jetpay: Add support for origin field[anellis]
9
+ * Jetpay: Don't default origin field [duff]
10
+ * GlobalCollect: New gateway support [curiousepic]
11
+ * Openpay: Use strict_encode64 [duff]
12
+ * Sage: Always pass along the billing state [duff]
13
+ * VisaNet Peru: New gateway support [shasum]
14
+ * Worldpay: Allow installationId to be specified at transaction time [duff]
15
+ * SecurionPay: Support store [shasum]
16
+ * Barclaycard Smartpay: Proper AVS return codes [curiousepic]
17
+ * VisaNetPeru: Pass through CVV [duff]
18
+ * Barclaycard Smartpay: Use strict_encode64 [duff]
19
+ * VisaNetPeru: Fix error when billing address empty [shasum]
20
+ * Vanco: Update live_url [duff]
21
+ * Cardstream: Reference purchase [curiousepic]
22
+ * Paymill: Fix error handling [methodmissing]
23
+ * Latitude19: New gateway support [shasum]
24
+ * BraintreeBlue: remove invalid test assertions [prburke]
25
+ * Merchant e-Solutions: Pass order_id with capture [curiousepic]
26
+ * CyberSource: Add rescue for ResponseErrors [curiousepic]
27
+ * AuthorizeNet: Always pass recurringBilling flag if present [curiousepic]
28
+ * S5: Pass order_id to TransactionID [curiousepic]
29
+ * NMI: Set ACH sec_code from options if present [curiousepic]
30
+ * VisaNet Peru: Refactor merchant_id and purchase_number handling [shasum]
31
+ * Braintree Blue: Pass descriptor_url field [curiousepic]
32
+ * VisaNet Peru: Add merchant_define_data option [duff]
33
+ * Merchant e-Solutions: pass optional 3Dsecure params [curiousepic]
34
+ * NMI: Fix refunds and voids of echecks [duff]
35
+ * VisaNet Peru: Pass dummy email when not present [curiousepic]
36
+ * PayU India: Add Maestro as supported card [curiousepic]
37
+ * Cashnet: Don't retry [duff]
38
+ * CardStream: Make Void call Cancel instead of Refund [curiousepic]
39
+ * Remove AN and KV country codes as they're not recognized by ISO-3166-1 [apdunston]
40
+ * Worldpay: Pass unchanged amount with correct currency exponent [curiousepic]
41
+ * Improve our handling of currencies sans fractions [duff]
42
+ * Stripe: Added support for the contactless magstripe entry mode option [rbalsdon]
43
+ * VisaNet Peru: Change money format to dollars [shasum]
44
+ * BlueSnap: Add gateway [duff]
45
+ * VisaNet Peru: Select the most meaningful gateway error message [shasum]
46
+ * SecurionPay: Update country list [duff]
47
+ * Support for BIN 2 MasterCard brand detection [rbalsdon]
48
+ * CardStream: Fix signature calculation [duff]
49
+ * CyberSource: Update test and live URL [marquisong]
50
+ * AuthorizeNet: Truncate nameOnAccount field [duff]
51
+ * Tns: Fix ipAddress field [duff]
52
+ * WorldNet: New gateway support [varyonic]
53
+ * BraintreeBlue: Allow channel override [duff]
54
+ * MerchantWarrior: Use Truncated Order Id [ThereExistsX]
55
+
56
+
3
57
  == Version 1.58.0 (March 1, 2016)
4
58
  * Move Electron check out of CreditCard into CreditCardMethods [ThereExistsX]
5
59
  * CardStream: Add AED and NZD currencies [sdball]
data/README.md CHANGED
@@ -96,7 +96,7 @@ The [ActiveMerchant Wiki](http://github.com/activemerchant/active_merchant/wikis
96
96
  * [Beanstream.com](http://www.beanstream.com/) - CA, US
97
97
  * [BluePay](http://www.bluepay.com/) - US
98
98
  * [Borgun](https://www.borgun.is/) - IS
99
- * [Braintree](http://www.braintreepaymentsolutions.com) - US, CA, AU, AD, AT, BE, BG, CY, CZ, DK, EE, FI, FR, GI, DE, GR, HU, IS, IM, IE, IT, LV, LI, LT, LU, MT, MC, NL, NO, PL, PT, RO, SM, SK, SI, ES, SE, CH, TR, GB
99
+ * [Braintree](https://www.braintreepayments.com) - US, CA, AU, AD, AT, BE, BG, CY, CZ, DK, EE, FI, FR, GI, DE, GR, HU, IS, IM, IE, IT, LV, LI, LT, LU, MT, MC, NL, NO, PL, PT, RO, SM, SK, SI, ES, SE, CH, TR, GB
100
100
  * [BridgePay](http://www.bridgepaynetwork.com/) - CA, US
101
101
  * [Cardknox](https://www.cardknox.com/) - US, CA, GB
102
102
  * [CardSave](http://www.cardsave.net/) - GB
@@ -208,7 +208,7 @@ The [ActiveMerchant Wiki](http://github.com/activemerchant/active_merchant/wikis
208
208
  * [SecureNet](http://www.securenet.com/) - US
209
209
  * [SecurePay](http://www.securepay.com/) - US, CA, GB, AU
210
210
  * [SecurePayTech](http://www.securepaytech.com/) - NZ
211
- * [SecurionPay](https://securionpay.com/) - AD, AE, AF, AG, AI, AL, AM, AN, AO, AR, AS, AT, AU, AW, AX, AZ, BA, BB, BD, BE, BF, BG, BH, BI, BJ, BL, BM, BN, BO, 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, KV, 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, ST, SV, 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
211
+ * [SecurionPay](https://securionpay.com/) - AD, AE, AF, AG, AI, AL, AM, AO, AR, AS, AT, AU, AW, AX, AZ, BA, BB, BD, BE, BF, BG, BH, BI, BJ, BL, BM, BN, BO, 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, ST, SV, 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
212
212
  * [SkipJack](http://www.skipjack.com/) - US, CA
213
213
  * [SoEasyPay](http://www.soeasypay.com/) - US, CA, AT, BE, BG, HR, CY, CZ, DK, EE, FI, FR, DE, GR, HU, IE, IT, LV, LT, LU, MT, NL, PL, PT, RO, SK, SI, ES, SE, GB, IS, NO, CH
214
214
  * [Spreedly](https://spreedly.com) - AD, AE, AT, AU, BD, BE, BG, BN, CA, CH, CY, CZ, DE, DK, EE, EG, ES, FI, FR, GB, GI, GR, HK, HU, ID, IE, IL, IM, IN, IS, IT, JO, KW, LB, LI, LK, LT, LU, LV, MC, MT, MU, MV, MX, MY, NL, NO, NZ, OM, PH, PL, PT, QA, RO, SA, SE, SG, SI, SK, SM, TR, TT, UM, US, VA, VN, ZA
@@ -227,7 +227,7 @@ The [ActiveMerchant Wiki](http://github.com/activemerchant/active_merchant/wikis
227
227
  * [WePay](https://www.wepay.com/) - US
228
228
  * [Wirecard](http://www.wirecard.com) - AD, CY, GI, IM, MT, RO, CH, AT, DK, GR, IT, MC, SM, TR, BE, EE, HU, LV, NL, SK, GB, BG, FI, IS, LI, NO, SI, VA, FR, IL, LT, PL, ES, CZ, DE, IE, LU, PT, SE
229
229
  * [Worldpay Global](http://www.worldpay.com/) - HK, GB, AU, AD, BE, CH, CY, CZ, DE, DK, ES, FI, FR, GI, GR, HU, IE, IL, IT, LI, LU, MC, MT, NL, NO, NZ, PL, PT, SE, SG, SI, SM, TR, UM, VA
230
- * [Worlpay Online](http://www.worldpay.com/) - HK, US, GB, AU, AD, BE, CH, CY, CZ, DE, DK, ES, FI, FR, GI, GR, HU, IE, IL, IT, LI, LU, MC, MT, NL, NO, NZ, PL, PT, SE, SG, SI, SM, TR, UM, VA
230
+ * [Worldpay Online](https://online.worldpay.com/) - HK, US, GB, AU, AD, BE, CH, CY, CZ, DE, DK, ES, FI, FR, GI, GR, HU, IE, IL, IT, LI, LU, MC, MT, NL, NO, NZ, PL, PT, SE, SG, SI, SM, TR, UM, VA
231
231
  * [Worldpay US](http://www.worldpay.com/us) - US
232
232
 
233
233
  ## API stability policy
@@ -50,6 +50,9 @@ module ActiveMerchant #:nodoc:
50
50
  'check'
51
51
  end
52
52
 
53
+ def credit_card?
54
+ false
55
+ end
53
56
  # Routing numbers may be validated by calculating a checksum and dividing it by 10. The
54
57
  # formula is:
55
58
  # (3(d1 + d4 + d7) + 7(d2 + d5 + d8) + 1(d3 + d6 + d9))mod 10 = 0
@@ -182,10 +182,15 @@ module ActiveMerchant #:nodoc:
182
182
  # @return [String]
183
183
  attr_accessor :fallback_reason
184
184
 
185
- # Returns or sets whether card-present card data has been read contactlessly.
185
+ # Returns or sets whether card-present EMV data has been read contactlessly.
186
186
  #
187
187
  # @return [true, false]
188
- attr_accessor :contactless
188
+ attr_accessor :contactless_emv
189
+
190
+ # Returns or sets whether card-present magstripe data has been read contactlessly.
191
+ #
192
+ # @return [true, false]
193
+ attr_accessor :contactless_magstripe
189
194
 
190
195
  # Returns the ciphertext of the card's encrypted PIN.
191
196
  #
@@ -4,7 +4,7 @@ module ActiveMerchant #:nodoc:
4
4
  module CreditCardMethods
5
5
  CARD_COMPANIES = {
6
6
  'visa' => /^4\d{12}(\d{3})?(\d{3})?$/,
7
- 'master' => /^(5[1-5]\d{4}|677189)\d{10}$/,
7
+ 'master' => /^(5[1-5]\d{4}|677189|222[1-9]\d{2}|22[3-9]\d{3}|2[3-6]\d{4}|27[01]\d{3}|2720\d{2})\d{10}$/,
8
8
  'discover' => /^(6011|65\d{2}|64[4-9]\d)\d{12}|(62\d{14})$/,
9
9
  'american_express' => /^3[47]\d{13}$/,
10
10
  'diners_club' => /^3(0[0-5]|[68]\d)\d{11}$/,
@@ -45,6 +45,10 @@ module ActiveMerchant #:nodoc:
45
45
  (1..12).include?(month.to_i)
46
46
  end
47
47
 
48
+ def credit_card?
49
+ true
50
+ end
51
+
48
52
  def valid_expiry_year?(year)
49
53
  (Time.now.year..Time.now.year + 20).include?(year.to_i)
50
54
  end
@@ -57,7 +57,6 @@ module ActiveMerchant #:nodoc:
57
57
  include CreditCardFormatting
58
58
 
59
59
  DEBIT_CARDS = [ :switch, :solo ]
60
- CURRENCIES_WITHOUT_FRACTIONS = %w(BIF BYR CLP CVE DJF GNF HUF ISK JPY KMF KRW PYG RWF UGX VND VUV XAF XOF XPF)
61
60
 
62
61
  CREDIT_DEPRECATION_MESSAGE = "Support for using credit to refund existing transactions is deprecated and will be removed from a future release of ActiveMerchant. Please use the refund method instead."
63
62
  RECURRING_DEPRECATION_MESSAGE = "Recurring functionality in ActiveMerchant is deprecated and will be removed in a future version. Please contact the ActiveMerchant maintainers if you have an interest in taking ownership of a separate gem that continues support for it."
@@ -120,6 +119,9 @@ module ActiveMerchant #:nodoc:
120
119
  class_attribute :supported_cardtypes
121
120
  self.supported_cardtypes = []
122
121
 
122
+ class_attribute :currencies_without_fractions
123
+ self.currencies_without_fractions = %w(BIF BYR CLP CVE DJF GNF HUF ISK JPY KMF KRW PYG RWF UGX VND VUV XAF XOF XPF)
124
+
123
125
  class_attribute :homepage_url
124
126
  class_attribute :display_name
125
127
 
@@ -132,7 +134,7 @@ module ActiveMerchant #:nodoc:
132
134
  # The application making the calls to the gateway
133
135
  # Useful for things like the PayPal build notation (BN) id fields
134
136
  class_attribute :application_id, instance_writer: false
135
- self.application_id = 'ActiveMerchant'
137
+ self.application_id = nil
136
138
 
137
139
  attr_reader :options
138
140
 
@@ -252,7 +254,7 @@ module ActiveMerchant #:nodoc:
252
254
  end
253
255
 
254
256
  def non_fractional_currency?(currency)
255
- CURRENCIES_WITHOUT_FRACTIONS.include?(currency.to_s)
257
+ self.currencies_without_fractions.include?(currency.to_s)
256
258
  end
257
259
 
258
260
  def localized_amount(money, currency)
@@ -323,7 +323,7 @@ module ActiveMerchant #:nodoc:
323
323
 
324
324
  def add_settings(xml, source, options)
325
325
  xml.transactionSettings do
326
- if !source.is_a?(String) && card_brand(source) == "check" && options[:recurring]
326
+ if options[:recurring]
327
327
  xml.setting do
328
328
  xml.settingName("recurringBilling")
329
329
  xml.settingValue("true")
@@ -359,7 +359,7 @@ module ActiveMerchant #:nodoc:
359
359
  xml.value(currency)
360
360
  end
361
361
  end
362
- if application_id.present? && application_id != "ActiveMerchant"
362
+ if application_id.present?
363
363
  xml.userField do
364
364
  xml.name("x_solution_id")
365
365
  xml.value(application_id)
@@ -444,7 +444,7 @@ module ActiveMerchant #:nodoc:
444
444
  xml.bankAccount do
445
445
  xml.routingNumber(check.routing_number)
446
446
  xml.accountNumber(check.account_number)
447
- xml.nameOnAccount(check.name)
447
+ xml.nameOnAccount(truncate(check.name, 22))
448
448
  xml.bankName(check.bank_name)
449
449
  xml.checkNumber(check.number)
450
450
  end
@@ -75,8 +75,6 @@ module ActiveMerchant #:nodoc:
75
75
  end
76
76
 
77
77
  def store(creditcard, options = {})
78
- # ??? require :email and :customer, :order_id?
79
-
80
78
  post = store_request(options)
81
79
  post[:card] = credit_card_hash(creditcard)
82
80
  post[:recurring] = {:contract => 'RECURRING'}
@@ -97,6 +95,30 @@ module ActiveMerchant #:nodoc:
97
95
 
98
96
  private
99
97
 
98
+ # Smartpay may return AVS codes not covered by standard AVSResult codes.
99
+ # Smartpay's descriptions noted below.
100
+ AVS_MAPPING = {
101
+ '0' => 'R', # Unknown
102
+ '1' => 'A', # Address matches, postal code doesn't
103
+ '2' => 'N', # Neither postal code nor address match
104
+ '3' => 'R', # AVS unavailable
105
+ '4' => 'E', # AVS not supported for this card type
106
+ '5' => 'U', # No AVS data provided
107
+ '6' => 'Z', # Postal code matches, address doesn't match
108
+ '7' => 'D', # Both postal code and address match
109
+ '8' => 'U', # Address not checked, postal code unknown
110
+ '9' => 'B', # Address matches, postal code unknown
111
+ '10' => 'N', # Address doesn't match, postal code unknown
112
+ '11' => 'U', # Postal code not checked, address unknown
113
+ '12' => 'B', # Address matches, postal code not checked
114
+ '13' => 'U', # Address doesn't match, postal code not checked
115
+ '14' => 'P', # Postal code matches, address unknown
116
+ '15' => 'P', # Postal code matches, address not checked
117
+ '16' => 'N', # Postal code doesn't match, address unknown
118
+ '17' => 'U', # Postal code doesn't match, address not checked
119
+ '18' => 'I' # Neither postal code nor address were checked
120
+ }
121
+
100
122
  def commit(action, post)
101
123
  request = post_data(flatten_hash(post))
102
124
  raw_response = ssl_post(build_url(action), request, headers)
@@ -107,6 +129,7 @@ module ActiveMerchant #:nodoc:
107
129
  message_from(response),
108
130
  response,
109
131
  test: test?,
132
+ avs_result: AVSResult.new(:code => parse_avs_code(response)),
110
133
  authorization: response['recurringDetailReference'] || response['pspReference']
111
134
  )
112
135
 
@@ -124,6 +147,10 @@ module ActiveMerchant #:nodoc:
124
147
  raise
125
148
  end
126
149
 
150
+ def parse_avs_code(response)
151
+ AVS_MAPPING[response["avsResult"][0..1].strip] if response["avsResult"]
152
+ end
153
+
127
154
  def flatten_hash(hash, prefix = nil)
128
155
  flat_hash = {}
129
156
  hash.each_pair do |key, val|
@@ -140,7 +167,7 @@ module ActiveMerchant #:nodoc:
140
167
  def headers
141
168
  {
142
169
  'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8',
143
- 'Authorization' => 'Basic ' + Base64.encode64("ws@Company.#{@options[:company]}:#{@options[:password]}").strip
170
+ 'Authorization' => 'Basic ' + Base64.strict_encode64("ws@Company.#{@options[:company]}:#{@options[:password]}").strip
144
171
  }
145
172
  end
146
173
 
@@ -186,11 +213,13 @@ module ActiveMerchant #:nodoc:
186
213
 
187
214
  def address_hash(address)
188
215
  full_address = "#{address[:address1]} #{address[:address2]}" if address
216
+ street = address[:street] if address[:street]
217
+ house = address[:houseNumberOrName] if address[:houseNumberOrName]
189
218
 
190
219
  hash = {}
191
220
  hash[:city] = address[:city] if address[:city]
192
- hash[:street] = full_address.split(/\s+/).keep_if { |x| x !~ /\d/ }.join(' ')
193
- hash[:houseNumberOrName] = full_address.split(/\s+/).keep_if { |x| x =~ /\d/ }.join(' ')
221
+ hash[:street] = street || full_address.split(/\s+/).keep_if { |x| x !~ /\d/ }.join(' ')
222
+ hash[:houseNumberOrName] = house || full_address.split(/\s+/).keep_if { |x| x =~ /\d/ }.join(' ')
194
223
  hash[:postalCode] = address[:zip] if address[:zip]
195
224
  hash[:stateOrProvince] = address[:state] if address[:state]
196
225
  hash[:country] = address[:country] if address[:country]
@@ -472,7 +472,7 @@ module ActiveMerchant #:nodoc:
472
472
  post[:encap_char] = "$"
473
473
  post[:card_num] = '4111111111111111'
474
474
  post[:exp_date] = '1212'
475
- post[:solution_ID] = application_id if(application_id && application_id != "ActiveMerchant")
475
+ post[:solution_ID] = application_id if application_id
476
476
  post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
477
477
  end
478
478
 
@@ -0,0 +1,348 @@
1
+ require 'nokogiri'
2
+
3
+ module ActiveMerchant
4
+ module Billing
5
+ class BlueSnapGateway < Gateway
6
+ self.test_url = "https://sandbox.bluesnap.com/services/2"
7
+ self.live_url = "https://ws.bluesnap.com/services/2"
8
+ self.supported_countries = %w(US GB)
9
+
10
+ self.default_currency = 'USD'
11
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro]
12
+
13
+ self.homepage_url = 'https://home.bluesnap.com/'
14
+ self.display_name = 'BlueSnap'
15
+
16
+ TRANSACTIONS = {
17
+ purchase: "AUTH_CAPTURE",
18
+ authorize: "AUTH_ONLY",
19
+ capture: "CAPTURE",
20
+ void: "AUTH_REVERSAL",
21
+ refund: "REFUND"
22
+ }
23
+
24
+ CVC_CODE_TRANSLATOR = {
25
+ 'MA' => 'M',
26
+ 'NC' => 'U',
27
+ 'ND' => 'P',
28
+ 'NM' => 'N',
29
+ 'NP' => 'S'
30
+ }
31
+
32
+ AVS_CODE_TRANSLATOR = {
33
+ 'line1: U, zip: U, name: U' => 'I',
34
+ 'line1: U, zip: U, name: M' => 'I',
35
+ 'line1: U, zip: U, name: N' => 'I',
36
+ 'line1: U, zip: M, name: U' => 'P',
37
+ 'line1: U, zip: M, name: M' => 'P',
38
+ 'line1: U, zip: M, name: N' => 'F',
39
+ 'line1: U, zip: N, name: U' => 'O',
40
+ 'line1: U, zip: N, name: M' => 'O',
41
+ 'line1: U, zip: N, name: N' => 'O',
42
+ 'line1: M, zip: U, name: U' => 'B',
43
+ 'line1: M, zip: U, name: M' => 'B',
44
+ 'line1: M, zip: U, name: N' => 'T',
45
+ 'line1: M, zip: M, name: U' => 'M',
46
+ 'line1: M, zip: M, name: M' => 'V',
47
+ 'line1: M, zip: M, name: N' => 'H',
48
+ 'line1: M, zip: N, name: U' => 'A',
49
+ 'line1: M, zip: N, name: M' => 'O',
50
+ 'line1: M, zip: N, name: N' => 'A',
51
+ 'line1: N, zip: U, name: U' => 'C',
52
+ 'line1: N, zip: U, name: M' => 'C',
53
+ 'line1: N, zip: U, name: N' => 'C',
54
+ 'line1: N, zip: M, name: U' => 'W',
55
+ 'line1: N, zip: M, name: M' => 'L',
56
+ 'line1: N, zip: M, name: N' => 'W',
57
+ 'line1: N, zip: N, name: U' => 'N',
58
+ 'line1: N, zip: N, name: M' => 'K',
59
+ 'line1: N, zip: N, name: N' => 'N',
60
+ }
61
+
62
+ def initialize(options={})
63
+ requires!(options, :api_username, :api_password)
64
+ super
65
+ end
66
+
67
+ def purchase(money, payment_method, options={})
68
+ commit(:purchase) do |doc|
69
+ add_auth_purchase(doc, money, payment_method, options)
70
+ end
71
+ end
72
+
73
+ def authorize(money, payment_method, options={})
74
+ commit(:authorize) do |doc|
75
+ add_auth_purchase(doc, money, payment_method, options)
76
+ end
77
+ end
78
+
79
+ def capture(money, authorization, options={})
80
+ commit(:capture, :put) do |doc|
81
+ add_authorization(doc, authorization)
82
+ add_order(doc, options)
83
+ end
84
+ end
85
+
86
+ def refund(money, authorization, options={})
87
+ commit(:refund, :put) do |doc|
88
+ add_authorization(doc, authorization)
89
+ add_amount(doc, money)
90
+ add_order(doc, options)
91
+ end
92
+ end
93
+
94
+ def void(authorization, options={})
95
+ commit(:void, :put) do |doc|
96
+ add_authorization(doc, authorization)
97
+ add_order(doc, options)
98
+ end
99
+ end
100
+
101
+ def verify(payment_method, options={})
102
+ authorize(0, payment_method, options)
103
+ end
104
+
105
+ def store(credit_card, options = {})
106
+ commit(:store) do |doc|
107
+ add_personal_info(doc, credit_card, options)
108
+ doc.send("payment-sources") do
109
+ doc.send("credit-card-info") do
110
+ add_credit_card(doc, credit_card)
111
+ end
112
+ end
113
+ add_order(doc, options)
114
+ end
115
+ end
116
+
117
+ def verify_credentials
118
+ begin
119
+ ssl_get("#{url}/nonexistent", headers)
120
+ rescue ResponseError => e
121
+ return false if e.response.code.to_i == 401
122
+ end
123
+
124
+ true
125
+ end
126
+
127
+ def supports_scrubbing?
128
+ true
129
+ end
130
+
131
+ def scrub(transcript)
132
+ transcript.
133
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
134
+ gsub(%r((<card-number>).+(</card-number>)), '\1[FILTERED]\2').
135
+ gsub(%r((<security-code>).+(</security-code>)), '\1[FILTERED]\2')
136
+ end
137
+
138
+ private
139
+
140
+ def add_auth_purchase(doc, money, payment_method, options)
141
+ doc.send("recurring-transaction", options[:recurring] ? "RECURRING" : "ECOMMERCE")
142
+ add_order(doc, options)
143
+ add_amount(doc, money)
144
+ doc.send("transaction-fraud-info") do
145
+ doc.send("shopper-ip-address", options[:ip]) if options[:ip]
146
+ end
147
+
148
+ if payment_method.is_a?(String)
149
+ doc.send("vaulted-shopper-id", payment_method)
150
+ else
151
+ doc.send("card-holder-info") do
152
+ add_personal_info(doc, payment_method, options)
153
+ end
154
+ add_credit_card(doc, payment_method)
155
+ end
156
+ end
157
+
158
+ def add_amount(doc, money)
159
+ doc.amount(amount(money))
160
+ doc.currency(options[:currency] || currency(money))
161
+ end
162
+
163
+ def add_personal_info(doc, credit_card, options)
164
+ doc.send("first-name", credit_card.first_name)
165
+ doc.send("last-name", credit_card.last_name)
166
+ doc.email(options[:email]) if options[:email]
167
+ add_address(doc, options)
168
+ end
169
+
170
+ def add_credit_card(doc, card)
171
+ doc.send("credit-card") do
172
+ doc.send("card-number", card.number)
173
+ doc.send("security-code", card.verification_value)
174
+ doc.send("expiration-month", card.month)
175
+ doc.send("expiration-year", card.year)
176
+ end
177
+ end
178
+
179
+ def add_description(doc, description)
180
+ doc.send("transaction-meta-data") do
181
+ doc.send("meta-data") do
182
+ doc.send("meta-key", "description")
183
+ doc.send("meta-value", truncate(description, 500))
184
+ doc.send("meta-description", "Description")
185
+ end
186
+ end
187
+ end
188
+
189
+ def add_order(doc, options)
190
+ doc.send("merchant-transaction-id", truncate(options[:order_id], 50)) if options[:order_id]
191
+ doc.send("soft-descriptor", options[:soft_descriptor]) if options[:soft_descriptor]
192
+ add_description(doc, options[:description]) if options[:description]
193
+ end
194
+
195
+ def add_address(doc, options)
196
+ address = options[:billing_address]
197
+ return unless address
198
+
199
+ doc.country(address[:country]) if address[:country]
200
+ doc.state(address[:state]) if address[:state]
201
+ doc.address(address[:address]) if address[:address]
202
+ doc.city(address[:city]) if address[:city]
203
+ doc.zip(address[:zip]) if address[:zip]
204
+ end
205
+
206
+ def add_invoice(post, money, options)
207
+ post[:amount] = amount(money)
208
+ post[:currency] = (options[:currency] || currency(money))
209
+ end
210
+
211
+ def add_authorization(doc, authorization)
212
+ doc.send("transaction-id", authorization)
213
+ end
214
+
215
+ def parse(response)
216
+ return bad_authentication_response if response.code.to_i == 401
217
+
218
+ parsed = {}
219
+ doc = Nokogiri::XML(response.body)
220
+ doc.root.xpath('*').each do |node|
221
+ if (node.elements.empty?)
222
+ parsed[node.name.downcase] = node.text
223
+ else
224
+ node.elements.each do |childnode|
225
+ parse_element(parsed, childnode)
226
+ end
227
+ end
228
+ end
229
+
230
+ parsed["content-location-header"] = response['content-location']
231
+ parsed
232
+ end
233
+
234
+ def parse_element(parsed, node)
235
+ if !node.elements.empty?
236
+ node.elements.each {|e| parse_element(parsed, e) }
237
+ else
238
+ parsed[node.name.downcase] = node.text
239
+ end
240
+ end
241
+
242
+ def api_request(action, request, verb)
243
+ begin
244
+ ssl_request(verb, url(action), request, headers)
245
+ rescue ResponseError => e
246
+ e.response
247
+ end
248
+ end
249
+
250
+ def commit(action, verb = :post)
251
+ request = build_xml_request(action) { |doc| yield(doc) }
252
+ response = api_request(action, request, verb)
253
+ parsed = parse(response)
254
+
255
+ succeeded = success_from(action, response)
256
+ Response.new(
257
+ succeeded,
258
+ message_from(succeeded, parsed),
259
+ parsed,
260
+ authorization: authorization_from(action, parsed),
261
+ avs_result: avs_result(parsed),
262
+ cvv_result: cvv_result(parsed),
263
+ error_code: error_code_from(parsed),
264
+ test: test?,
265
+ )
266
+ end
267
+
268
+ def url(action = nil)
269
+ base = test? ? test_url : live_url
270
+ resource = (action == :store) ? "vaulted-shoppers" : "transactions"
271
+ "#{base}/#{resource}"
272
+ end
273
+
274
+ def cvv_result(parsed)
275
+ CVVResult.new(CVC_CODE_TRANSLATOR[parsed["cvv-response-code"]])
276
+ end
277
+
278
+ def avs_result(parsed)
279
+ AVSResult.new(code: AVS_CODE_TRANSLATOR[avs_lookup_key(parsed)])
280
+ end
281
+
282
+ def avs_lookup_key(p)
283
+ "line1: #{p['avs-response-code-address']}, zip: #{p['avs-response-code-zip']}, name: #{p['avs-response-code-name']}"
284
+ end
285
+
286
+ def success_from(action, response)
287
+ (200...300).include?(response.code.to_i)
288
+ end
289
+
290
+ def message_from(succeeded, parsed_response)
291
+ return "Success" if succeeded
292
+ parsed_response["description"]
293
+ end
294
+
295
+ def authorization_from(action, parsed_response)
296
+ (action == :store) ? vaulted_shopper_id(parsed_response) : parsed_response["transaction-id"]
297
+ end
298
+
299
+ def vaulted_shopper_id(parsed_response)
300
+ return nil unless parsed_response["content-location-header"]
301
+ parsed_response["content-location-header"].split("/").last
302
+ end
303
+
304
+ def error_code_from(parsed_response)
305
+ parsed_response["code"]
306
+ end
307
+
308
+ def root_attributes
309
+ {
310
+ xmlns: "http://ws.plimus.com"
311
+ }
312
+ end
313
+
314
+ def root_element(action)
315
+ (action == :store) ? "vaulted-shopper" : "card-transaction"
316
+ end
317
+
318
+ def headers
319
+ {
320
+ 'Content-Type' => 'application/xml',
321
+ 'Authorization' => ('Basic ' + Base64.strict_encode64("#{@options[:api_username]}:#{@options[:api_password]}").strip),
322
+ }
323
+ end
324
+
325
+ def build_xml_request(action)
326
+ builder = Nokogiri::XML::Builder.new
327
+ builder.__send__(root_element(action), root_attributes) do |doc|
328
+ doc.send("card-transaction-type", TRANSACTIONS[action]) if TRANSACTIONS[action]
329
+ yield(doc)
330
+ end
331
+ builder.doc.root.to_xml
332
+ end
333
+
334
+ def handle_response(response)
335
+ case response.code.to_i
336
+ when 200...300
337
+ response
338
+ else
339
+ raise ResponseError.new(response)
340
+ end
341
+ end
342
+
343
+ def bad_authentication_response
344
+ { "description" => "Unable to authenticate. Please check your credentials." }
345
+ end
346
+ end
347
+ end
348
+ end