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