activemerchant 1.58.0 → 1.59.0

Sign up to get free protection for your applications and to get access to all the features.
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