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.
- checksums.yaml +4 -4
- data/CHANGELOG +54 -0
- data/README.md +3 -3
- data/lib/active_merchant/billing/check.rb +3 -0
- data/lib/active_merchant/billing/credit_card.rb +7 -2
- data/lib/active_merchant/billing/credit_card_methods.rb +5 -1
- data/lib/active_merchant/billing/gateway.rb +5 -3
- data/lib/active_merchant/billing/gateways/authorize_net.rb +3 -3
- data/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +34 -5
- data/lib/active_merchant/billing/gateways/blue_pay.rb +1 -1
- data/lib/active_merchant/billing/gateways/blue_snap.rb +348 -0
- data/lib/active_merchant/billing/gateways/braintree_blue.rb +6 -3
- data/lib/active_merchant/billing/gateways/card_stream.rb +33 -15
- data/lib/active_merchant/billing/gateways/cashnet.rb +1 -0
- data/lib/active_merchant/billing/gateways/cyber_source.rb +7 -3
- data/lib/active_merchant/billing/gateways/global_collect.rb +293 -0
- data/lib/active_merchant/billing/gateways/jetpay.rb +11 -8
- data/lib/active_merchant/billing/gateways/latitude19.rb +416 -0
- data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +13 -0
- data/lib/active_merchant/billing/gateways/merchant_warrior.rb +10 -7
- data/lib/active_merchant/billing/gateways/mercury.rb +1 -1
- data/lib/active_merchant/billing/gateways/metrics_global.rb +1 -1
- data/lib/active_merchant/billing/gateways/moneris.rb +8 -1
- data/lib/active_merchant/billing/gateways/nmi.rb +25 -9
- data/lib/active_merchant/billing/gateways/openpay.rb +1 -1
- data/lib/active_merchant/billing/gateways/orbital.rb +5 -3
- data/lib/active_merchant/billing/gateways/paymill.rb +1 -1
- data/lib/active_merchant/billing/gateways/paypal_express.rb +1 -6
- data/lib/active_merchant/billing/gateways/payu_in.rb +3 -2
- data/lib/active_merchant/billing/gateways/s5.rb +8 -5
- data/lib/active_merchant/billing/gateways/sage.rb +1 -7
- data/lib/active_merchant/billing/gateways/sage_pay.rb +0 -4
- data/lib/active_merchant/billing/gateways/secure_net.rb +0 -5
- data/lib/active_merchant/billing/gateways/secure_pay.rb +1 -1
- data/lib/active_merchant/billing/gateways/securion_pay.rb +46 -17
- data/lib/active_merchant/billing/gateways/stripe.rb +5 -8
- data/lib/active_merchant/billing/gateways/tns.rb +1 -1
- data/lib/active_merchant/billing/gateways/trans_first.rb +1 -2
- data/lib/active_merchant/billing/gateways/vanco.rb +1 -1
- data/lib/active_merchant/billing/gateways/visanet_peru.rb +218 -0
- data/lib/active_merchant/billing/gateways/world_net.rb +344 -0
- data/lib/active_merchant/billing/gateways/worldpay.rb +8 -11
- data/lib/active_merchant/billing/network_tokenization_credit_card.rb +4 -0
- data/lib/active_merchant/country.rb +0 -2
- data/lib/active_merchant/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1b699a9e799907880fbd2fb3c7cc68e8f1bdaead
|
|
4
|
+
data.tar.gz: aba5081dee95a33710e5ac97bed5edd577cb1e33
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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](
|
|
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,
|
|
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
|
-
* [
|
|
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
|
|
185
|
+
# Returns or sets whether card-present EMV data has been read contactlessly.
|
|
186
186
|
#
|
|
187
187
|
# @return [true, false]
|
|
188
|
-
attr_accessor :
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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?
|
|
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.
|
|
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
|
|
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
|