activemerchant 1.90.0 → 1.91.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 +38 -0
- data/README.md +3 -2
- data/lib/active_merchant/billing/credit_card_methods.rb +1 -1
- data/lib/active_merchant/billing/gateways/adyen.rb +26 -21
- data/lib/active_merchant/billing/gateways/bambora_apac.rb +186 -0
- data/lib/active_merchant/billing/gateways/blue_snap.rb +189 -34
- data/lib/active_merchant/billing/gateways/braintree_blue.rb +11 -9
- data/lib/active_merchant/billing/gateways/card_connect.rb +3 -0
- data/lib/active_merchant/billing/gateways/cecabank.rb +13 -2
- data/lib/active_merchant/billing/gateways/fat_zebra.rb +20 -7
- data/lib/active_merchant/billing/gateways/ipp.rb +1 -0
- data/lib/active_merchant/billing/gateways/moneris.rb +3 -4
- data/lib/active_merchant/billing/gateways/netbanx.rb +4 -0
- data/lib/active_merchant/billing/gateways/nmi.rb +6 -4
- data/lib/active_merchant/billing/gateways/openpay.rb +1 -1
- data/lib/active_merchant/billing/gateways/orbital.rb +8 -1
- data/lib/active_merchant/billing/gateways/payment_express.rb +4 -1
- data/lib/active_merchant/billing/gateways/paymentez.rb +4 -9
- data/lib/active_merchant/billing/gateways/pin.rb +19 -6
- data/lib/active_merchant/billing/gateways/pro_pay.rb +1 -1
- data/lib/active_merchant/billing/gateways/qvalent.rb +11 -0
- data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +3 -3
- data/lib/active_merchant/billing/gateways/trust_commerce.rb +1 -0
- data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +11 -3
- data/lib/active_merchant/billing/gateways/worldpay.rb +51 -2
- data/lib/active_merchant/country.rb +1 -1
- data/lib/active_merchant/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c313ba80bb5c2c04320b78d8b7248c86cb569869f9b9b25b450de62bec97dfde
|
|
4
|
+
data.tar.gz: 8fb0d7c5d581d5407cb68d7a545e8c8a52d377a232cb919804e9218fd4363d48
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d583c8da5c326c3c3e12589f4fa37ed10243d3be91098436e085bf1ccba255a6ae8e27cbe657d022d5740bbb03b0097803ae10ec8c85c327b436f7c3e33fccb4
|
|
7
|
+
data.tar.gz: cac4ea1716084ca36cd0e2a66f3940e34728c0e4731c85b5c569bf7b0aa0e56097c2b71b56e3c0fef5c1a004e8a014abdc1ed448e0ab22201c3aaca80c39cec1
|
data/CHANGELOG
CHANGED
|
@@ -2,6 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
== HEAD
|
|
4
4
|
|
|
5
|
+
== Version 1.91.0 (February 22, 2019)
|
|
6
|
+
* WorldPay: Pull CVC and AVS Result from Response [nfarve] #3106
|
|
7
|
+
* Worldpay: Add AVS and CVC Mapping [nfarve] #3107
|
|
8
|
+
* Paymentez: Fixes extra_params field [molbrown] #3108
|
|
9
|
+
* Improved support for account_type using Check class's account_type instead [lancecarlson] #3097
|
|
10
|
+
* USA Epay: Allow quantity to be passed and check custom fields [lancecarlson] #3090
|
|
11
|
+
* Fix usaepay transaction invoice [lancecarlson] #3093
|
|
12
|
+
* Adyen: Handles blank state address field [molbrown] #3113
|
|
13
|
+
* Braintree: Send all country fields [curiousepic] #3112
|
|
14
|
+
* Braintree: Account for empty string countries [curiousepic] #3115
|
|
15
|
+
* Orbital: Support for stored credentials framework [jknipp] #3117
|
|
16
|
+
* Openpay: Fix for marking successful transaction(s) as failed [jknipp] #3121
|
|
17
|
+
* Braintree: Adds support for transaction_source [molbrown] #3120
|
|
18
|
+
* Moneris: Remove redundant card on file guard clause [davidsantoso] #3123
|
|
19
|
+
* Switch order of Romania country codes [molbrown] #3125
|
|
20
|
+
* Blue Snap: Supports Level 2/3 data [molbrown] #3126
|
|
21
|
+
* Blue Snap: Support personal_identification_number [jknipp] #3128
|
|
22
|
+
* ProPay: Send 9 digit zip code without dash [molbrown] #3129
|
|
23
|
+
* Adyen: Extend AVS code mappings [therufs] #3119
|
|
24
|
+
* NMI: Add customer id to authorization on store [curiousepic] #3130
|
|
25
|
+
* Trans First Express: Don't pass blank name field [curiousepic] #3133
|
|
26
|
+
* TrustCommerce: Send full name on ACH transactions [jknipp] #3132
|
|
27
|
+
* Qvalent: Map CVV Result to responses [curiousepic] #3135
|
|
28
|
+
* Card Connect: Handle 401s as responses [curiousepic] #3137
|
|
29
|
+
* Worldpay: Introduce normalized stored credential options [davidsantoso] #3134
|
|
30
|
+
* Worldpay: Adjust use of normalized stored credentials hash [davidsantoso] #3139
|
|
31
|
+
* Adyen: Enable Dynamic 3DS [molbrown] #3138
|
|
32
|
+
* Fat Zebra: Support voids [curiousepic] #3142
|
|
33
|
+
* Blue Snap: Support ACH/ECP payments [jknipp] #3143
|
|
34
|
+
* Blue Snap: Fix Card-on-File field typo [jknipp] #3143
|
|
35
|
+
* Add Bambora gateway [InfraRuby] #3145
|
|
36
|
+
* Bambora Asia-Pacific: Updates Gateway [molbrown] #3145
|
|
37
|
+
* PaymentExpress: Support ClientInfo field [jknipp] #3131
|
|
38
|
+
* Pin Payments: Concatenate card and customer tokens when storing card [therufs] #3144
|
|
39
|
+
* Update Discover regex to allow card numbers longer than 16 digits [prashcr] #3146
|
|
40
|
+
* Merrco partial refunds fix [payfirma1] #3141
|
|
41
|
+
|
|
5
42
|
== Version 1.90.0 (January 8, 2019)
|
|
6
43
|
* Mercado Pago: Support "gateway" processing mode [curiousepic] #3087
|
|
7
44
|
* Braintree: Update gem to latest version [curiousepic] #3091
|
|
@@ -12,6 +49,7 @@
|
|
|
12
49
|
* Braintree Blue: Refactor line_items field [curiousepic] #3100
|
|
13
50
|
* TrustCommerce: Use `application_id` [nfarve] #3103
|
|
14
51
|
* Stripe: Add 3DS Support [nfarve] #3086
|
|
52
|
+
* Cecabank: Append error text to message [therufs] #3127
|
|
15
53
|
|
|
16
54
|
== Version 1.89.0 (December 17, 2018)
|
|
17
55
|
* Worldpay: handle Visa and MasterCard payouts differently [bpollack] #3068
|
data/README.md
CHANGED
|
@@ -86,12 +86,13 @@ For more in-depth documentation and tutorials, see [GettingStarted.md](GettingSt
|
|
|
86
86
|
|
|
87
87
|
## Supported Payment Gateways
|
|
88
88
|
|
|
89
|
-
The [ActiveMerchant Wiki](
|
|
89
|
+
The [ActiveMerchant Wiki](https://github.com/activemerchant/active_merchant/wikis) contains a [table of features supported by each gateway](https://github.com/activemerchant/active_merchant/wiki/Gateway-Feature-Matrix).
|
|
90
90
|
|
|
91
91
|
* [Authorize.Net CIM](http://www.authorize.net/) - US
|
|
92
92
|
* [Authorize.Net](http://www.authorize.net/) - AD, AT, AU, BE, BG, CA, CH, CY, CZ, DE, DK, ES, FI, FR, GB, GB, GI, GR, HU, IE, IT, LI, LU, MC, MT, NL, NO, PL, PT, RO, SE, SI, SK, SM, TR, US, VA
|
|
93
93
|
* [Axcess MS](http://www.axcessms.com/) - AD, AT, BE, BG, BR, CA, CH, CY, CZ, DE, DK, EE, ES, FI, FO, FR, GB, GI, GR, HR, HU, IE, IL, IM, IS, IT, LI, LT, LU, LV, MC, MT, MX, NL, NO, PL, PT, RO, RU, SE, SI, SK, TR, US, VA
|
|
94
94
|
* [Balanced](https://www.balancedpayments.com/) - US
|
|
95
|
+
* [Bambora Asia-Pacific](http://www.bambora.com/) - AU, NZ
|
|
95
96
|
* [Bank Frick](http://www.bankfrickacquiring.com/) - LI, US
|
|
96
97
|
* [Banwire](http://www.banwire.com/) - MX
|
|
97
98
|
* [Barclays ePDQ Extra Plus](http://www.barclaycard.co.uk/business/accepting-payments/epdq-ecomm/) - GB
|
|
@@ -192,7 +193,7 @@ The [ActiveMerchant Wiki](http://github.com/activemerchant/active_merchant/wikis
|
|
|
192
193
|
* [Paystation](http://paystation.co.nz) - NZ
|
|
193
194
|
* [Pay Way](http://www.payway.com.au) - AU
|
|
194
195
|
* [PayU India](https://www.payu.in/) - IN
|
|
195
|
-
* [Pin Payments](http://www.
|
|
196
|
+
* [Pin Payments](http://www.pinpayments.com/) - AU
|
|
196
197
|
* [Plug'n Pay](http://www.plugnpay.com/) - US
|
|
197
198
|
* [Psigate](http://www.psigate.com/) - CA
|
|
198
199
|
* [PSL Payment Solutions](http://www.paymentsolutionsltd.com/) - GB
|
|
@@ -5,7 +5,7 @@ module ActiveMerchant #:nodoc:
|
|
|
5
5
|
CARD_COMPANY_DETECTORS = {
|
|
6
6
|
'visa' => ->(num) { num =~ /^4\d{12}(\d{3})?(\d{3})?$/ },
|
|
7
7
|
'master' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), MASTERCARD_RANGES) },
|
|
8
|
-
'discover' => ->(num) { num =~ /^(6011|65\d{2}|64[4-9]\d)\d{12}|(62\d{14})$/ },
|
|
8
|
+
'discover' => ->(num) { num =~ /^(6011|65\d{2}|64[4-9]\d)\d{12,15}|(62\d{14,17})$/ },
|
|
9
9
|
'american_express' => ->(num) { num =~ /^3[47]\d{13}$/ },
|
|
10
10
|
'diners_club' => ->(num) { num =~ /^3(0[0-5]|[68]\d)\d{11}$/ },
|
|
11
11
|
'jcb' => ->(num) { num =~ /^35(28|29|[3-8]\d)\d{12}$/ },
|
|
@@ -33,7 +33,7 @@ module ActiveMerchant #:nodoc:
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def purchase(money, payment, options={})
|
|
36
|
-
if options[:execute_threed]
|
|
36
|
+
if options[:execute_threed] || options[:threed_dynamic]
|
|
37
37
|
authorize(money, payment, options)
|
|
38
38
|
else
|
|
39
39
|
MultiResponse.run do |r|
|
|
@@ -52,7 +52,7 @@ module ActiveMerchant #:nodoc:
|
|
|
52
52
|
add_shopper_interaction(post, payment, options)
|
|
53
53
|
add_address(post, options)
|
|
54
54
|
add_installments(post, options) if options[:installments]
|
|
55
|
-
add_3ds(post, options)
|
|
55
|
+
add_3ds(post, options)
|
|
56
56
|
commit('authorise', post)
|
|
57
57
|
end
|
|
58
58
|
|
|
@@ -110,24 +110,28 @@ module ActiveMerchant #:nodoc:
|
|
|
110
110
|
|
|
111
111
|
AVS_MAPPING = {
|
|
112
112
|
'0' => 'R', # Unknown
|
|
113
|
-
'1' => 'A',
|
|
114
|
-
'2' => 'N',
|
|
115
|
-
'3' => 'R',
|
|
116
|
-
'4' => 'E',
|
|
117
|
-
'5' => 'U',
|
|
118
|
-
'6' => 'Z',
|
|
119
|
-
'7' => 'D',
|
|
120
|
-
'8' => 'U',
|
|
121
|
-
'9' => 'B',
|
|
122
|
-
'10' => 'N',
|
|
123
|
-
'11' => 'U',
|
|
124
|
-
'12' => 'B',
|
|
125
|
-
'13' => 'U',
|
|
126
|
-
'14' => 'P',
|
|
127
|
-
'15' => 'P',
|
|
128
|
-
'16' => 'N',
|
|
113
|
+
'1' => 'A', # Address matches, postal code doesn't
|
|
114
|
+
'2' => 'N', # Neither postal code nor address match
|
|
115
|
+
'3' => 'R', # AVS unavailable
|
|
116
|
+
'4' => 'E', # AVS not supported for this card type
|
|
117
|
+
'5' => 'U', # No AVS data provided
|
|
118
|
+
'6' => 'Z', # Postal code matches, address doesn't match
|
|
119
|
+
'7' => 'D', # Both postal code and address match
|
|
120
|
+
'8' => 'U', # Address not checked, postal code unknown
|
|
121
|
+
'9' => 'B', # Address matches, postal code unknown
|
|
122
|
+
'10' => 'N', # Address doesn't match, postal code unknown
|
|
123
|
+
'11' => 'U', # Postal code not checked, address unknown
|
|
124
|
+
'12' => 'B', # Address matches, postal code not checked
|
|
125
|
+
'13' => 'U', # Address doesn't match, postal code not checked
|
|
126
|
+
'14' => 'P', # Postal code matches, address unknown
|
|
127
|
+
'15' => 'P', # Postal code matches, address not checked
|
|
128
|
+
'16' => 'N', # Postal code doesn't match, address unknown
|
|
129
129
|
'17' => 'U', # Postal code doesn't match, address not checked
|
|
130
|
-
'18' => 'I'
|
|
130
|
+
'18' => 'I', # Neither postal code nor address were checked
|
|
131
|
+
'20' => 'V', # Name, address and postal code matches.
|
|
132
|
+
'23' => 'F', # Postal code matches, name doesn't match.
|
|
133
|
+
'24' => 'H', # Both postal code and address matches, name doesn't match.
|
|
134
|
+
'25' => 'T' # Address matches, name doesn't match.
|
|
131
135
|
}
|
|
132
136
|
|
|
133
137
|
CVC_MAPPING = {
|
|
@@ -187,7 +191,7 @@ module ActiveMerchant #:nodoc:
|
|
|
187
191
|
post[:card][:billingAddress][:houseNumberOrName] = address[:address2] || 'N/A'
|
|
188
192
|
post[:card][:billingAddress][:postalCode] = address[:zip] if address[:zip]
|
|
189
193
|
post[:card][:billingAddress][:city] = address[:city] || 'N/A'
|
|
190
|
-
post[:card][:billingAddress][:stateOrProvince] = address[:state]
|
|
194
|
+
post[:card][:billingAddress][:stateOrProvince] = address[:state] || 'N/A'
|
|
191
195
|
post[:card][:billingAddress][:country] = address[:country] if address[:country]
|
|
192
196
|
end
|
|
193
197
|
end
|
|
@@ -272,8 +276,9 @@ module ActiveMerchant #:nodoc:
|
|
|
272
276
|
end
|
|
273
277
|
|
|
274
278
|
def add_3ds(post, options)
|
|
275
|
-
|
|
279
|
+
return unless options[:execute_threed] || options[:threed_dynamic]
|
|
276
280
|
post[:browserInfo] = { userAgent: options[:user_agent], acceptHeader: options[:accept_header] }
|
|
281
|
+
post[:additionalData] = { executeThreeD: 'true' } if options[:execute_threed]
|
|
277
282
|
end
|
|
278
283
|
|
|
279
284
|
def parse(body)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
require 'nokogiri'
|
|
2
|
+
|
|
3
|
+
module ActiveMerchant #:nodoc:
|
|
4
|
+
module Billing #:nodoc:
|
|
5
|
+
class BamboraApacGateway < Gateway
|
|
6
|
+
self.live_url = 'https://www.bambora.co.nz/interface/api/dts.asmx'
|
|
7
|
+
self.test_url = 'https://demo.bambora.co.nz/interface/api/dts.asmx'
|
|
8
|
+
|
|
9
|
+
self.supported_countries = ['AU', 'NZ']
|
|
10
|
+
self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb]
|
|
11
|
+
|
|
12
|
+
self.homepage_url = 'http://www.bambora.com/'
|
|
13
|
+
self.display_name = 'Bambora Asia-Pacific'
|
|
14
|
+
|
|
15
|
+
self.money_format = :cents
|
|
16
|
+
|
|
17
|
+
STANDARD_ERROR_CODE_MAPPING = {
|
|
18
|
+
'05' => STANDARD_ERROR_CODE[:card_declined],
|
|
19
|
+
'06' => STANDARD_ERROR_CODE[:processing_error],
|
|
20
|
+
'14' => STANDARD_ERROR_CODE[:invalid_number],
|
|
21
|
+
'54' => STANDARD_ERROR_CODE[:expired_card],
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def initialize(options={})
|
|
25
|
+
requires!(options, :username, :password)
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def purchase(money, payment, options={})
|
|
30
|
+
commit('SubmitSinglePayment') do |xml|
|
|
31
|
+
xml.Transaction do
|
|
32
|
+
xml.CustRef options[:order_id]
|
|
33
|
+
add_amount(xml, money)
|
|
34
|
+
xml.TrnType '1'
|
|
35
|
+
add_credit_card(xml, payment)
|
|
36
|
+
add_credentials(xml, options)
|
|
37
|
+
xml.TrnSource options[:ip]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def authorize(money, payment, options={})
|
|
43
|
+
commit('SubmitSinglePayment') do |xml|
|
|
44
|
+
xml.Transaction do
|
|
45
|
+
xml.CustRef options[:order_id]
|
|
46
|
+
add_amount(xml, money)
|
|
47
|
+
xml.TrnType '2'
|
|
48
|
+
add_credit_card(xml, payment)
|
|
49
|
+
add_credentials(xml, options)
|
|
50
|
+
xml.TrnSource options[:ip]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def capture(money, authorization, options={})
|
|
56
|
+
commit('SubmitSingleCapture') do |xml|
|
|
57
|
+
xml.Capture do
|
|
58
|
+
xml.Receipt authorization
|
|
59
|
+
add_amount(xml, money)
|
|
60
|
+
add_credentials(xml, options)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def refund(money, authorization, options={})
|
|
66
|
+
commit('SubmitSingleRefund') do |xml|
|
|
67
|
+
xml.Refund do
|
|
68
|
+
xml.Receipt authorization
|
|
69
|
+
add_amount(xml, money)
|
|
70
|
+
add_credentials(xml, options)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def void(money, authorization, options={})
|
|
76
|
+
commit('SubmitSingleVoid') do |xml|
|
|
77
|
+
xml.Void do
|
|
78
|
+
xml.Receipt authorization
|
|
79
|
+
add_amount(xml, money)
|
|
80
|
+
add_credentials(xml, options)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def supports_scrubbing?
|
|
86
|
+
true
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def scrub(transcript)
|
|
90
|
+
transcript.
|
|
91
|
+
gsub(%r((<CardNumber>)[^<]+(<))i, '\1[FILTERED]\2').
|
|
92
|
+
gsub(%r((<CVN>)[^<]+(<))i, '\1[FILTERED]\2').
|
|
93
|
+
gsub(%r((<Password>)[^<]+(<))i, '\1[FILTERED]\2')
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def add_credentials(xml, options)
|
|
99
|
+
xml.AccountNumber options[:account_number] if options[:account_number]
|
|
100
|
+
xml.Security do
|
|
101
|
+
xml.UserName @options[:username]
|
|
102
|
+
xml.Password @options[:password]
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def add_amount(xml, money)
|
|
107
|
+
xml.Amount amount(money)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def add_credit_card(xml, payment)
|
|
111
|
+
xml.CreditCard :Registered => 'False' do
|
|
112
|
+
xml.CardNumber payment.number
|
|
113
|
+
xml.ExpM format(payment.month, :two_digits)
|
|
114
|
+
xml.ExpY format(payment.year, :four_digits)
|
|
115
|
+
xml.CVN payment.verification_value
|
|
116
|
+
xml.CardHolderName payment.name
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def parse(body)
|
|
121
|
+
element = Nokogiri::XML(body).root.first_element_child.first_element_child
|
|
122
|
+
|
|
123
|
+
response = {}
|
|
124
|
+
doc = Nokogiri::XML(element)
|
|
125
|
+
doc.root.elements.each do |e|
|
|
126
|
+
response[e.name.underscore.to_sym] = e.inner_text
|
|
127
|
+
end
|
|
128
|
+
response
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def commit(action, &block)
|
|
132
|
+
headers = {
|
|
133
|
+
'Content-Type' => 'text/xml; charset=utf-8',
|
|
134
|
+
'SOAPAction' => "http://www.ippayments.com.au/interface/api/dts/#{action}",
|
|
135
|
+
}
|
|
136
|
+
response = parse(ssl_post(commit_url, new_submit_xml(action, &block), headers))
|
|
137
|
+
|
|
138
|
+
Response.new(
|
|
139
|
+
success_from(response),
|
|
140
|
+
message_from(response),
|
|
141
|
+
response,
|
|
142
|
+
authorization: authorization_from(response),
|
|
143
|
+
error_code: error_code_from(response),
|
|
144
|
+
test: test?
|
|
145
|
+
)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def new_submit_xml(action)
|
|
149
|
+
xml = Builder::XmlMarkup.new(indent: 2)
|
|
150
|
+
xml.instruct!
|
|
151
|
+
xml.soap :Envelope, 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' do
|
|
152
|
+
xml.soap :Body do
|
|
153
|
+
xml.__send__(action, 'xmlns' => 'http://www.ippayments.com.au/interface/api/dts') do
|
|
154
|
+
xml.trnXML do
|
|
155
|
+
inner_xml = Builder::XmlMarkup.new(indent: 2)
|
|
156
|
+
yield(inner_xml)
|
|
157
|
+
xml.cdata!(inner_xml.target!)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
xml.target!
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def commit_url
|
|
166
|
+
test? ? test_url : live_url
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def success_from(response)
|
|
170
|
+
response[:response_code] == '0'
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def error_code_from(response)
|
|
174
|
+
STANDARD_ERROR_CODE_MAPPING[response[:declined_code]]
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def message_from(response)
|
|
178
|
+
response[:declined_message]
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def authorization_from(response)
|
|
182
|
+
response[:receipt]
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -5,7 +5,7 @@ module ActiveMerchant
|
|
|
5
5
|
class BlueSnapGateway < Gateway
|
|
6
6
|
self.test_url = 'https://sandbox.bluesnap.com/services/2'
|
|
7
7
|
self.live_url = 'https://ws.bluesnap.com/services/2'
|
|
8
|
-
self.supported_countries = %w(US CA GB 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)
|
|
8
|
+
self.supported_countries = %w(US CA GB 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 AR BO BR BZ CL CO CR DO EC GF GP GT HN HT MF MQ MX NI PA PE PR PY SV UY VE)
|
|
9
9
|
|
|
10
10
|
self.default_currency = 'USD'
|
|
11
11
|
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro]
|
|
@@ -59,14 +59,27 @@ module ActiveMerchant
|
|
|
59
59
|
'line1: N, zip: N, name: N' => 'N',
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
BANK_ACCOUNT_TYPE_MAPPING = {
|
|
63
|
+
'personal_checking' => 'CONSUMER_CHECKING',
|
|
64
|
+
'personal_savings' => 'CONSUMER_SAVINGS',
|
|
65
|
+
'business_checking' => 'CORPORATE_CHECKING',
|
|
66
|
+
'business_savings' => 'CORPORATE_SAVINGS'
|
|
67
|
+
}
|
|
68
|
+
|
|
62
69
|
def initialize(options={})
|
|
63
70
|
requires!(options, :api_username, :api_password)
|
|
64
71
|
super
|
|
65
72
|
end
|
|
66
73
|
|
|
67
74
|
def purchase(money, payment_method, options={})
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
payment_method_details = PaymentMethodDetails.new(payment_method)
|
|
76
|
+
|
|
77
|
+
commit(:purchase, :post, payment_method_details) do |doc|
|
|
78
|
+
if payment_method_details.alt_transaction?
|
|
79
|
+
add_alt_transaction_purchase(doc, money, payment_method_details, options)
|
|
80
|
+
else
|
|
81
|
+
add_auth_purchase(doc, money, payment_method, options)
|
|
82
|
+
end
|
|
70
83
|
end
|
|
71
84
|
end
|
|
72
85
|
|
|
@@ -102,18 +115,33 @@ module ActiveMerchant
|
|
|
102
115
|
authorize(0, payment_method, options)
|
|
103
116
|
end
|
|
104
117
|
|
|
105
|
-
def store(
|
|
106
|
-
|
|
107
|
-
|
|
118
|
+
def store(payment_method, options = {})
|
|
119
|
+
payment_method_details = PaymentMethodDetails.new(payment_method)
|
|
120
|
+
|
|
121
|
+
commit(:store, :post, payment_method_details) do |doc|
|
|
122
|
+
add_personal_info(doc, payment_method, options)
|
|
123
|
+
add_echeck_company(doc, payment_method) if payment_method_details.check?
|
|
108
124
|
doc.send('payment-sources') do
|
|
109
|
-
|
|
110
|
-
add_credit_card(doc, credit_card)
|
|
111
|
-
end
|
|
125
|
+
payment_method_details.check? ? store_echeck(doc, payment_method) : store_credit_card(doc, payment_method)
|
|
112
126
|
end
|
|
113
127
|
add_order(doc, options)
|
|
114
128
|
end
|
|
115
129
|
end
|
|
116
130
|
|
|
131
|
+
def store_credit_card(doc, payment_method)
|
|
132
|
+
doc.send('credit-card-info') do
|
|
133
|
+
add_credit_card(doc, payment_method)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def store_echeck(doc, payment_method)
|
|
138
|
+
doc.send('ecp-info') do
|
|
139
|
+
doc.send('ecp') do
|
|
140
|
+
add_echeck(doc, payment_method)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
117
145
|
def verify_credentials
|
|
118
146
|
begin
|
|
119
147
|
ssl_get(url.to_s, headers)
|
|
@@ -132,7 +160,9 @@ module ActiveMerchant
|
|
|
132
160
|
transcript.
|
|
133
161
|
gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
|
|
134
162
|
gsub(%r((<card-number>).+(</card-number>)), '\1[FILTERED]\2').
|
|
135
|
-
gsub(%r((<security-code>).+(</security-code>)), '\1[FILTERED]\2')
|
|
163
|
+
gsub(%r((<security-code>).+(</security-code>)), '\1[FILTERED]\2').
|
|
164
|
+
gsub(%r((<(?:public-)?account-number>).+(</(?:public-)?account-number>)), '\1[FILTERED]\2').
|
|
165
|
+
gsub(%r((<(?:public-)?routing-number>).+(</(?:public-)?routing-number>)), '\1[FILTERED]\2')
|
|
136
166
|
end
|
|
137
167
|
|
|
138
168
|
private
|
|
@@ -140,11 +170,9 @@ module ActiveMerchant
|
|
|
140
170
|
def add_auth_purchase(doc, money, payment_method, options)
|
|
141
171
|
doc.send('recurring-transaction', options[:recurring] ? 'RECURRING' : 'ECOMMERCE')
|
|
142
172
|
add_order(doc, options)
|
|
143
|
-
doc.send('
|
|
173
|
+
doc.send('store-card', options[:store_card] || false)
|
|
144
174
|
add_amount(doc, money, options)
|
|
145
|
-
doc
|
|
146
|
-
doc.send('shopper-ip-address', options[:ip]) if options[:ip]
|
|
147
|
-
end
|
|
175
|
+
add_fraud_info(doc, options)
|
|
148
176
|
|
|
149
177
|
if payment_method.is_a?(String)
|
|
150
178
|
doc.send('vaulted-shopper-id', payment_method)
|
|
@@ -161,9 +189,10 @@ module ActiveMerchant
|
|
|
161
189
|
doc.currency(options[:currency] || currency(money))
|
|
162
190
|
end
|
|
163
191
|
|
|
164
|
-
def add_personal_info(doc,
|
|
165
|
-
doc.send('first-name',
|
|
166
|
-
doc.send('last-name',
|
|
192
|
+
def add_personal_info(doc, payment_method, options)
|
|
193
|
+
doc.send('first-name', payment_method.first_name)
|
|
194
|
+
doc.send('last-name', payment_method.last_name)
|
|
195
|
+
doc.send('personal-identification-number', options[:personal_identification_number]) if options[:personal_identification_number]
|
|
167
196
|
doc.email(options[:email]) if options[:email]
|
|
168
197
|
add_address(doc, options)
|
|
169
198
|
end
|
|
@@ -191,6 +220,7 @@ module ActiveMerchant
|
|
|
191
220
|
doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id]
|
|
192
221
|
doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor]
|
|
193
222
|
add_description(doc, options[:description]) if options[:description]
|
|
223
|
+
add_level_3_data(doc, options)
|
|
194
224
|
end
|
|
195
225
|
|
|
196
226
|
def add_address(doc, options)
|
|
@@ -204,10 +234,92 @@ module ActiveMerchant
|
|
|
204
234
|
doc.zip(address[:zip]) if address[:zip]
|
|
205
235
|
end
|
|
206
236
|
|
|
237
|
+
def add_level_3_data(doc, options)
|
|
238
|
+
return unless options[:customer_reference_number]
|
|
239
|
+
doc.send('level-3-data') do
|
|
240
|
+
send_when_present(doc, :customer_reference_number, options)
|
|
241
|
+
send_when_present(doc, :sales_tax_amount, options)
|
|
242
|
+
send_when_present(doc, :freight_amount, options)
|
|
243
|
+
send_when_present(doc, :duty_amount, options)
|
|
244
|
+
send_when_present(doc, :destination_zip_code, options)
|
|
245
|
+
send_when_present(doc, :destination_country_code, options)
|
|
246
|
+
send_when_present(doc, :ship_from_zip_code, options)
|
|
247
|
+
send_when_present(doc, :discount_amount, options)
|
|
248
|
+
send_when_present(doc, :tax_amount, options)
|
|
249
|
+
send_when_present(doc, :tax_rate, options)
|
|
250
|
+
add_level_3_data_items(doc, options[:level_3_data_items]) if options[:level_3_data_items]
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def send_when_present(doc, options_key, options, xml_element_name = nil)
|
|
255
|
+
return unless options[options_key]
|
|
256
|
+
xml_element_name ||= options_key.to_s
|
|
257
|
+
|
|
258
|
+
doc.send(xml_element_name.dasherize, options[options_key])
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def add_level_3_data_items(doc, items)
|
|
262
|
+
items.each do |item|
|
|
263
|
+
doc.send('level-3-data-item') do
|
|
264
|
+
item.each do |key, value|
|
|
265
|
+
key = key.to_s.dasherize
|
|
266
|
+
doc.send(key, value)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
207
272
|
def add_authorization(doc, authorization)
|
|
208
273
|
doc.send('transaction-id', authorization)
|
|
209
274
|
end
|
|
210
275
|
|
|
276
|
+
def add_fraud_info(doc, options)
|
|
277
|
+
doc.send('transaction-fraud-info') do
|
|
278
|
+
doc.send('shopper-ip-address', options[:ip]) if options[:ip]
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def add_alt_transaction_purchase(doc, money, payment_method_details, options)
|
|
283
|
+
doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id]
|
|
284
|
+
doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor]
|
|
285
|
+
add_amount(doc, money, options)
|
|
286
|
+
|
|
287
|
+
vaulted_shopper_id = payment_method_details.vaulted_shopper_id
|
|
288
|
+
doc.send('vaulted-shopper-id', vaulted_shopper_id) if vaulted_shopper_id
|
|
289
|
+
|
|
290
|
+
if payment_method_details.check?
|
|
291
|
+
add_echeck_transaction(doc, payment_method_details.payment_method, options, vaulted_shopper_id.present?)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
add_fraud_info(doc, options)
|
|
295
|
+
add_description(doc, options)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def add_echeck_transaction(doc, check, options, vaulted_shopper)
|
|
299
|
+
unless vaulted_shopper
|
|
300
|
+
doc.send('payer-info') do
|
|
301
|
+
add_personal_info(doc, check, options)
|
|
302
|
+
add_echeck_company(doc, check)
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
doc.send('ecp-transaction') do
|
|
307
|
+
add_echeck(doc, check) unless vaulted_shopper
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
doc.send('authorized-by-shopper', options[:authorized_by_shopper])
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def add_echeck_company(doc, check)
|
|
314
|
+
doc.send('company-name', truncate(check.name, 50)) if check.account_holder_type = 'business'
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def add_echeck(doc, check)
|
|
318
|
+
doc.send('account-number', check.account_number)
|
|
319
|
+
doc.send('routing-number', check.routing_number)
|
|
320
|
+
doc.send('account-type', BANK_ACCOUNT_TYPE_MAPPING["#{check.account_holder_type}_#{check.account_type}"])
|
|
321
|
+
end
|
|
322
|
+
|
|
211
323
|
def parse(response)
|
|
212
324
|
return bad_authentication_response if response.code.to_i == 401
|
|
213
325
|
return forbidden_response(response.body) if response.code.to_i == 403
|
|
@@ -236,15 +348,15 @@ module ActiveMerchant
|
|
|
236
348
|
end
|
|
237
349
|
end
|
|
238
350
|
|
|
239
|
-
def api_request(action, request, verb)
|
|
240
|
-
ssl_request(verb, url(action), request, headers)
|
|
351
|
+
def api_request(action, request, verb, payment_method_details)
|
|
352
|
+
ssl_request(verb, url(action, payment_method_details), request, headers)
|
|
241
353
|
rescue ResponseError => e
|
|
242
354
|
e.response
|
|
243
355
|
end
|
|
244
356
|
|
|
245
|
-
def commit(action, verb = :post)
|
|
246
|
-
request = build_xml_request(action) { |doc| yield(doc) }
|
|
247
|
-
response = api_request(action, request, verb)
|
|
357
|
+
def commit(action, verb = :post, payment_method_details = PaymentMethodDetails.new())
|
|
358
|
+
request = build_xml_request(action, payment_method_details) { |doc| yield(doc) }
|
|
359
|
+
response = api_request(action, request, verb, payment_method_details)
|
|
248
360
|
parsed = parse(response)
|
|
249
361
|
|
|
250
362
|
succeeded = success_from(action, response)
|
|
@@ -252,7 +364,7 @@ module ActiveMerchant
|
|
|
252
364
|
succeeded,
|
|
253
365
|
message_from(succeeded, parsed),
|
|
254
366
|
parsed,
|
|
255
|
-
authorization: authorization_from(action, parsed),
|
|
367
|
+
authorization: authorization_from(action, parsed, payment_method_details),
|
|
256
368
|
avs_result: avs_result(parsed),
|
|
257
369
|
cvv_result: cvv_result(parsed),
|
|
258
370
|
error_code: error_code_from(parsed),
|
|
@@ -260,9 +372,9 @@ module ActiveMerchant
|
|
|
260
372
|
)
|
|
261
373
|
end
|
|
262
374
|
|
|
263
|
-
def url(action = nil)
|
|
375
|
+
def url(action = nil, payment_method_details = PaymentMethodDetails.new())
|
|
264
376
|
base = test? ? test_url : live_url
|
|
265
|
-
resource = action == :store ? 'vaulted-shoppers' :
|
|
377
|
+
resource = action == :store ? 'vaulted-shoppers' : payment_method_details.resource_url
|
|
266
378
|
"#{base}/#{resource}"
|
|
267
379
|
end
|
|
268
380
|
|
|
@@ -287,13 +399,15 @@ module ActiveMerchant
|
|
|
287
399
|
parsed_response['description']
|
|
288
400
|
end
|
|
289
401
|
|
|
290
|
-
def authorization_from(action, parsed_response)
|
|
291
|
-
action == :store ? vaulted_shopper_id(parsed_response) : parsed_response['transaction-id']
|
|
402
|
+
def authorization_from(action, parsed_response, payment_method_details)
|
|
403
|
+
action == :store ? vaulted_shopper_id(parsed_response, payment_method_details) : parsed_response['transaction-id']
|
|
292
404
|
end
|
|
293
405
|
|
|
294
|
-
def vaulted_shopper_id(parsed_response)
|
|
406
|
+
def vaulted_shopper_id(parsed_response, payment_method_details)
|
|
295
407
|
return nil unless parsed_response['content-location-header']
|
|
296
|
-
parsed_response['content-location-header'].split('/').last
|
|
408
|
+
vaulted_shopper_id = parsed_response['content-location-header'].split('/').last
|
|
409
|
+
vaulted_shopper_id += "|#{payment_method_details.payment_method_type}" if payment_method_details.alt_transaction?
|
|
410
|
+
vaulted_shopper_id
|
|
297
411
|
end
|
|
298
412
|
|
|
299
413
|
def error_code_from(parsed_response)
|
|
@@ -306,8 +420,8 @@ module ActiveMerchant
|
|
|
306
420
|
}
|
|
307
421
|
end
|
|
308
422
|
|
|
309
|
-
def root_element(action)
|
|
310
|
-
action == :store ? 'vaulted-shopper' :
|
|
423
|
+
def root_element(action, payment_method_details)
|
|
424
|
+
action == :store ? 'vaulted-shopper' : payment_method_details.root_element
|
|
311
425
|
end
|
|
312
426
|
|
|
313
427
|
def headers
|
|
@@ -317,10 +431,10 @@ module ActiveMerchant
|
|
|
317
431
|
}
|
|
318
432
|
end
|
|
319
433
|
|
|
320
|
-
def build_xml_request(action)
|
|
434
|
+
def build_xml_request(action, payment_method_details)
|
|
321
435
|
builder = Nokogiri::XML::Builder.new
|
|
322
|
-
builder.__send__(root_element(action), root_attributes) do |doc|
|
|
323
|
-
doc.send('card-transaction-type', TRANSACTIONS[action]) if TRANSACTIONS[action]
|
|
436
|
+
builder.__send__(root_element(action, payment_method_details), root_attributes) do |doc|
|
|
437
|
+
doc.send('card-transaction-type', TRANSACTIONS[action]) if TRANSACTIONS[action] && !payment_method_details.alt_transaction?
|
|
324
438
|
yield(doc)
|
|
325
439
|
end
|
|
326
440
|
builder.doc.root.to_xml
|
|
@@ -343,5 +457,46 @@ module ActiveMerchant
|
|
|
343
457
|
{ 'description' => body }
|
|
344
458
|
end
|
|
345
459
|
end
|
|
460
|
+
|
|
461
|
+
class PaymentMethodDetails
|
|
462
|
+
attr_reader :payment_method, :vaulted_shopper_id, :payment_method_type
|
|
463
|
+
|
|
464
|
+
def initialize(payment_method = nil)
|
|
465
|
+
@payment_method = payment_method
|
|
466
|
+
@payment_method_type = nil
|
|
467
|
+
parse(payment_method)
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def check?
|
|
471
|
+
@payment_method.is_a?(Check) || @payment_method_type == 'check'
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def alt_transaction?
|
|
475
|
+
check?
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def root_element
|
|
479
|
+
alt_transaction? ? 'alt-transaction' : 'card-transaction'
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def resource_url
|
|
483
|
+
alt_transaction? ? 'alt-transactions' : 'transactions'
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
private
|
|
487
|
+
|
|
488
|
+
def parse(payment_method)
|
|
489
|
+
return unless payment_method
|
|
490
|
+
|
|
491
|
+
if payment_method.is_a?(String)
|
|
492
|
+
@vaulted_shopper_id, payment_method_type = payment_method.split('|')
|
|
493
|
+
@payment_method_type = payment_method_type if payment_method_type.present?
|
|
494
|
+
elsif payment_method.is_a?(Check)
|
|
495
|
+
@payment_method_type = payment_method.type
|
|
496
|
+
else
|
|
497
|
+
@payment_method_type = 'credit_card'
|
|
498
|
+
end
|
|
499
|
+
end
|
|
500
|
+
end
|
|
346
501
|
end
|
|
347
502
|
end
|