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