activemerchant 1.50.0 → 1.51.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +39 -0
- data/README.md +6 -5
- data/lib/active_merchant/billing/credit_card.rb +22 -6
- data/lib/active_merchant/billing/gateway.rb +15 -1
- data/lib/active_merchant/billing/gateways/authorize_net.rb +413 -127
- data/lib/active_merchant/billing/gateways/banwire.rb +2 -2
- data/lib/active_merchant/billing/gateways/braintree_blue.rb +4 -0
- data/lib/active_merchant/billing/gateways/card_stream.rb +18 -0
- data/lib/active_merchant/billing/gateways/cenpos.rb +6 -2
- data/lib/active_merchant/billing/gateways/checkout.rb +4 -6
- data/lib/active_merchant/billing/gateways/checkout_v2.rb +200 -0
- data/lib/active_merchant/billing/gateways/cyber_source.rb +19 -1
- data/lib/active_merchant/billing/gateways/dibs.rb +1 -1
- data/lib/active_merchant/billing/gateways/epay.rb +1 -1
- data/lib/active_merchant/billing/gateways/eway_rapid.rb +5 -5
- data/lib/active_merchant/billing/gateways/firstdata_e4.rb +4 -1
- data/lib/active_merchant/billing/gateways/garanti.rb +5 -1
- data/lib/active_merchant/billing/gateways/iats_payments.rb +29 -3
- data/lib/active_merchant/billing/gateways/litle.rb +12 -0
- data/lib/active_merchant/billing/gateways/paystation.rb +19 -23
- data/lib/active_merchant/billing/gateways/payu_in.rb +4 -0
- data/lib/active_merchant/billing/gateways/redsys.rb +4 -3
- data/lib/active_merchant/billing/gateways/s5.rb +3 -3
- data/lib/active_merchant/billing/gateways/sage.rb +8 -10
- data/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb +7 -5
- data/lib/active_merchant/billing/gateways/sage/sage_core.rb +3 -2
- data/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb +0 -5
- data/lib/active_merchant/billing/gateways/stripe.rb +33 -4
- data/lib/active_merchant/billing/gateways/wepay.rb +13 -6
- data/lib/active_merchant/billing/gateways/worldpay_online_payments.rb +1 -2
- data/lib/active_merchant/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 410fa6679494b0904c1bff664722c9b23c907e06
|
4
|
+
data.tar.gz: bf3827fcd65d592b51d9e5d06b28dfeec0e05ac2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 228a238436b76648adcfab724d7807fa75c1af50c5b975057da9461a64cf9863c1b1693c65e24ceb2250e9e3bebbadca189c7d07ff4bff9b45fcd79c2ae57d23
|
7
|
+
data.tar.gz: 0922949cb3d6db73cbb74324ae57865f4eb72bcdafaba4ce4cbf468558b8d7f514dabe74cec344ca43f7a4e98906c8eff82195f604219ff851492aa5f7fadab5
|
data/CHANGELOG
CHANGED
@@ -1,5 +1,42 @@
|
|
1
1
|
= ActiveMerchant CHANGELOG
|
2
2
|
|
3
|
+
|
4
|
+
== Version 1.51.0 (July 2, 2015)
|
5
|
+
|
6
|
+
* Garanti: Illegal character '&' parsing response [masaruhoshi]
|
7
|
+
* Stripe: Revert force USD for verify [duff]
|
8
|
+
* Litle: Surface XML validation errors in the response [jasonbosco]
|
9
|
+
* Litle: Pass the credit card verification value for tokenization (#store) requests, if one is set. [jasonbosco]
|
10
|
+
* S5: Make scrubbing regex less greedy [duff]
|
11
|
+
* CardStream: Add support for verify [anellis]
|
12
|
+
* Authorize.net: UTF-8 encode requests [duff]
|
13
|
+
* Banwire: Add default email [anellis]
|
14
|
+
* PayU India: Handle bad JSON [ntalbott]
|
15
|
+
* Dibs: Pass CVC param only if there's a value [bruno]
|
16
|
+
* Sage: Credit really is credit not refund [duff]
|
17
|
+
* Sage: Add ability to refund [duff]
|
18
|
+
* Cardstream: Add scrubbing [anellis]
|
19
|
+
* Litle: Add debt_repayment_flag [duff]
|
20
|
+
* iATS: Support ACH [rwdaigle]
|
21
|
+
* CheckoutV2: Add Gateway [anellis]
|
22
|
+
* CenPOS: Fix refund amount issue [duff]
|
23
|
+
* Add error_code mapping and error_code_from to gateway generator [jnormore]
|
24
|
+
* Stripe: Parse EMV ARC from error response [bizla]
|
25
|
+
* Redsys: Add MYR currency [agseco]
|
26
|
+
* Add "contactless" flag to credit card model [davidseal]
|
27
|
+
* Stripe: Add "contactless" flag support to gateway [davidseal]
|
28
|
+
* Add encrypted_pin data to credit card model [ryanbalsdon]
|
29
|
+
* Stripe: Add encrypted_pin support to gateway [ryanbalsdon]
|
30
|
+
* Stripe: Support mapping advanced decline codes to standard codes [abecevello]
|
31
|
+
* Epay: filter out invalid characters in returned URLs [dwradcliffe]
|
32
|
+
* Redsys: Strip leading zeroes from currency codes [agseco]
|
33
|
+
* Authorize.net: Add invoice information to refund [marquisong]
|
34
|
+
* Authorize.net: Add store ability [duff]
|
35
|
+
* Paystation: Add refund [mrezentes]
|
36
|
+
* Paystation: No longer require order_id everywhere [duff]
|
37
|
+
* Checkout: Support descriptor_name and descriptor_city [duff]
|
38
|
+
* Add supports_network_tokenization? to gateways [jnormore]
|
39
|
+
|
3
40
|
== Version 1.50.0 (June 1, 2015)
|
4
41
|
|
5
42
|
* Vanco: Add gateway [duff]
|
@@ -840,6 +877,8 @@
|
|
840
877
|
* Braintree Blue gateway: Always pass CVV on update [shayfrendt]
|
841
878
|
* eWAY gateway: Update docs. Require address [juggler]
|
842
879
|
* Cybersource gateway: Add support for subscriptions [fabiokr]
|
880
|
+
* WePay: Use better endpoint for recurring with no CVV [davidsantoso]
|
881
|
+
|
843
882
|
|
844
883
|
== Version 1.24.0 (June 8, 2012)
|
845
884
|
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Active Merchant
|
2
|
-
[![Build Status](https://travis-ci.org/
|
3
|
-
[![Code Climate](https://codeclimate.com/github/
|
2
|
+
[![Build Status](https://travis-ci.org/activemerchant/active_merchant.png?branch=master)](https://travis-ci.org/activemerchant/active_merchant)
|
3
|
+
[![Code Climate](https://codeclimate.com/github/activemerchant/active_merchant.png)](https://codeclimate.com/github/activemerchant/active_merchant)
|
4
4
|
|
5
5
|
Active Merchant is an extraction from the ecommerce system [Shopify](http://www.shopify.com).
|
6
6
|
Shopify's requirements for a simple and unified API to access dozens of different payment
|
@@ -23,7 +23,7 @@ applications.
|
|
23
23
|
|
24
24
|
You can check out the latest source from git:
|
25
25
|
|
26
|
-
git clone git://github.com/
|
26
|
+
git clone git://github.com/activemerchant/active_merchant.git
|
27
27
|
|
28
28
|
### From RubyGems
|
29
29
|
|
@@ -76,11 +76,11 @@ end
|
|
76
76
|
```
|
77
77
|
|
78
78
|
For more in-depth documentation and tutorials, see [GettingStarted.md](GettingStarted.md) and the
|
79
|
-
[API documentation](http://www.rubydoc.info/github/
|
79
|
+
[API documentation](http://www.rubydoc.info/github/activemerchant/active_merchant/).
|
80
80
|
|
81
81
|
## Supported Payment Gateways
|
82
82
|
|
83
|
-
The [ActiveMerchant Wiki](http://github.com/
|
83
|
+
The [ActiveMerchant Wiki](http://github.com/activemerchant/active_merchant/wikis) contains a [table of features supported by each gateway](http://github.com/activemerchant/active_merchant/wikis/gatewayfeaturematrix).
|
84
84
|
|
85
85
|
* [App55](https://www.app55.com/) - AU, BR, CA, CH, CL, CN, CO, CZ, DK, EU, GB, HK, HU, ID, IS, JP, KE, KR, MX, MY, NO, NZ, PH, PL, TH, TW, US, VN, ZA
|
86
86
|
* [Authorize.Net CIM](http://www.authorize.net/) - US
|
@@ -156,6 +156,7 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta
|
|
156
156
|
* [NETPAY Gateway](http://www.netpay.com.mx) - MX
|
157
157
|
* [NMI](http://nmi.com/) - US
|
158
158
|
* [Ogone](http://www.ogone.com/) - BE, DE, FR, NL, AT, CH
|
159
|
+
* [Omise](https://www.omise.co/) - TH
|
159
160
|
* [Openpay](Openpay) - MX
|
160
161
|
* [Optimal Payments](http://www.optimalpayments.com/) - CA, US, GB
|
161
162
|
* [Orbital Paymentech](http://chasepaymentech.com/) - US, CA
|
@@ -35,12 +35,13 @@ module ActiveMerchant #:nodoc:
|
|
35
35
|
#
|
36
36
|
# == Example Usage
|
37
37
|
# cc = CreditCard.new(
|
38
|
-
# :first_name
|
39
|
-
# :last_name
|
40
|
-
# :month
|
41
|
-
# :year
|
42
|
-
# :brand
|
43
|
-
# :number
|
38
|
+
# :first_name => 'Steve',
|
39
|
+
# :last_name => 'Smith',
|
40
|
+
# :month => '9',
|
41
|
+
# :year => '2017',
|
42
|
+
# :brand => 'visa',
|
43
|
+
# :number => '4242424242424242',
|
44
|
+
# :verification_value => '424'
|
44
45
|
# )
|
45
46
|
#
|
46
47
|
# cc.validate # => {}
|
@@ -158,6 +159,21 @@ module ActiveMerchant #:nodoc:
|
|
158
159
|
# @return [String]
|
159
160
|
attr_accessor :fallback_reason
|
160
161
|
|
162
|
+
# Returns or sets whether card-present card data has been read contactlessly.
|
163
|
+
#
|
164
|
+
# @return [true, false]
|
165
|
+
attr_accessor :contactless
|
166
|
+
|
167
|
+
# Returns the ciphertext of the card's encrypted PIN.
|
168
|
+
#
|
169
|
+
# @return [String]
|
170
|
+
attr_accessor :encrypted_pin_cryptogram
|
171
|
+
|
172
|
+
# Returns the Key Serial Number (KSN) of the card's encrypted PIN.
|
173
|
+
#
|
174
|
+
# @return [String]
|
175
|
+
attr_accessor :encrypted_pin_ksn
|
176
|
+
|
161
177
|
def type
|
162
178
|
ActiveMerchant.deprecated "CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead."
|
163
179
|
brand
|
@@ -50,7 +50,7 @@ module ActiveMerchant #:nodoc:
|
|
50
50
|
#
|
51
51
|
# == Implmenting new gateways
|
52
52
|
#
|
53
|
-
# See the {ActiveMerchant Guide to Contributing}[https://github.com/
|
53
|
+
# See the {ActiveMerchant Guide to Contributing}[https://github.com/activemerchant/active_merchant/wiki/Contributing]
|
54
54
|
#
|
55
55
|
class Gateway
|
56
56
|
include PostsData
|
@@ -190,6 +190,10 @@ module ActiveMerchant #:nodoc:
|
|
190
190
|
raise RuntimeError.new("This gateway does not support scrubbing.")
|
191
191
|
end
|
192
192
|
|
193
|
+
def supports_network_tokenization?
|
194
|
+
false
|
195
|
+
end
|
196
|
+
|
193
197
|
protected # :nodoc: all
|
194
198
|
|
195
199
|
def normalize(field)
|
@@ -212,6 +216,16 @@ module ActiveMerchant #:nodoc:
|
|
212
216
|
})
|
213
217
|
end
|
214
218
|
|
219
|
+
def strip_invalid_xml_chars(xml)
|
220
|
+
begin
|
221
|
+
REXML::Document.new(xml)
|
222
|
+
rescue REXML::ParseException
|
223
|
+
xml = xml.gsub(/&(?!(?:[a-z]+|#[0-9]+|x[a-zA-Z0-9]+);)/, '&')
|
224
|
+
end
|
225
|
+
|
226
|
+
xml
|
227
|
+
end
|
228
|
+
|
215
229
|
private # :nodoc: all
|
216
230
|
|
217
231
|
def name
|
@@ -56,88 +56,58 @@ module ActiveMerchant #:nodoc:
|
|
56
56
|
|
57
57
|
APPLE_PAY_DATA_DESCRIPTOR = "COMMON.APPLE.INAPP.PAYMENT"
|
58
58
|
|
59
|
+
PAYMENT_METHOD_NOT_SUPPORTED_ERROR = "155"
|
60
|
+
|
59
61
|
def initialize(options={})
|
60
62
|
requires!(options, :login, :password)
|
61
63
|
super
|
62
64
|
end
|
63
65
|
|
64
66
|
def purchase(amount, payment, options = {})
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
add_invoice(xml, options)
|
73
|
-
add_customer_data(xml, payment, options)
|
74
|
-
add_market_type(xml, payment)
|
75
|
-
add_settings(xml, payment, options)
|
76
|
-
add_user_fields(xml, amount, options)
|
67
|
+
if payment.is_a?(String)
|
68
|
+
commit(:cim_purchase) do |xml|
|
69
|
+
add_cim_auth_purchase(xml, "profileTransAuthCapture", amount, payment, options)
|
70
|
+
end
|
71
|
+
else
|
72
|
+
commit(:purchase) do |xml|
|
73
|
+
add_auth_purchase(xml, "authCaptureTransaction", amount, payment, options)
|
77
74
|
end
|
78
75
|
end
|
79
76
|
end
|
80
77
|
|
81
78
|
def authorize(amount, payment, options={})
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
add_invoice(xml, options)
|
90
|
-
add_customer_data(xml, payment, options)
|
91
|
-
add_market_type(xml, payment)
|
92
|
-
add_settings(xml, payment, options)
|
93
|
-
add_user_fields(xml, amount, options)
|
79
|
+
if payment.is_a?(String)
|
80
|
+
commit(:cim_authorize) do |xml|
|
81
|
+
add_cim_auth_purchase(xml, "profileTransAuthOnly", amount, payment, options)
|
82
|
+
end
|
83
|
+
else
|
84
|
+
commit(:authorize) do |xml|
|
85
|
+
add_auth_purchase(xml, "authOnlyTransaction", amount, payment, options)
|
94
86
|
end
|
95
87
|
end
|
96
88
|
end
|
97
89
|
|
98
90
|
def capture(amount, authorization, options={})
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
xml.amount(amount(amount))
|
104
|
-
xml.refTransId(split_authorization(authorization)[0])
|
105
|
-
|
106
|
-
add_invoice(xml, options)
|
107
|
-
add_user_fields(xml, amount, options)
|
108
|
-
end
|
91
|
+
if auth_was_for_cim?(authorization)
|
92
|
+
cim_capture(amount, authorization, options)
|
93
|
+
else
|
94
|
+
normal_capture(amount, authorization, options)
|
109
95
|
end
|
110
96
|
end
|
111
97
|
|
112
98
|
def refund(amount, authorization, options={})
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
xml.amount(amount.nil? ? 0 : amount(amount))
|
118
|
-
xml.payment do
|
119
|
-
xml.creditCard do
|
120
|
-
xml.cardNumber(card_number || options[:card_number])
|
121
|
-
xml.expirationDate('XXXX')
|
122
|
-
end
|
123
|
-
end
|
124
|
-
xml.refTransId(transaction_id)
|
125
|
-
|
126
|
-
add_customer_data(xml, nil, options)
|
127
|
-
add_user_fields(xml, amount, options)
|
128
|
-
end
|
99
|
+
if auth_was_for_cim?(authorization)
|
100
|
+
cim_refund(amount, authorization, options)
|
101
|
+
else
|
102
|
+
normal_refund(amount, authorization, options)
|
129
103
|
end
|
130
104
|
end
|
131
105
|
|
132
106
|
def void(authorization, options={})
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
xml.refTransId(split_authorization(authorization)[0])
|
138
|
-
|
139
|
-
add_user_fields(xml, nil, options)
|
140
|
-
end
|
107
|
+
if auth_was_for_cim?(authorization)
|
108
|
+
cim_void(authorization, options)
|
109
|
+
else
|
110
|
+
normal_void(authorization, options)
|
141
111
|
end
|
142
112
|
end
|
143
113
|
|
@@ -146,7 +116,7 @@ module ActiveMerchant #:nodoc:
|
|
146
116
|
raise ArgumentError, "Reference credits are not supported. Please supply the original credit card or use the #refund method."
|
147
117
|
end
|
148
118
|
|
149
|
-
commit(
|
119
|
+
commit(:credit) do |xml|
|
150
120
|
add_order_id(xml, options)
|
151
121
|
xml.transactionRequest do
|
152
122
|
xml.transactionType('refundTransaction')
|
@@ -168,21 +138,179 @@ module ActiveMerchant #:nodoc:
|
|
168
138
|
end
|
169
139
|
end
|
170
140
|
|
141
|
+
def store(credit_card, options = {})
|
142
|
+
commit(:cim_store) do |xml|
|
143
|
+
xml.profile do
|
144
|
+
xml.merchantCustomerId(truncate(options[:merchant_customer_id], 20) || SecureRandom.hex(10))
|
145
|
+
xml.description(truncate(options[:description], 255)) unless empty?(options[:description])
|
146
|
+
xml.email(options[:email]) unless empty?(options[:email])
|
147
|
+
|
148
|
+
xml.paymentProfiles do
|
149
|
+
xml.customerType("individual")
|
150
|
+
add_billing_address(xml, credit_card, options)
|
151
|
+
add_shipping_address(xml, options, "shipToList")
|
152
|
+
xml.payment do
|
153
|
+
xml.creditCard do
|
154
|
+
xml.cardNumber(truncate(credit_card.number, 16))
|
155
|
+
xml.expirationDate(format(credit_card.year, :four_digits) + '-' + format(credit_card.month, :two_digits))
|
156
|
+
xml.cardCode(credit_card.verification_value) if credit_card.verification_value
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
171
164
|
def supports_scrubbing?
|
172
165
|
true
|
173
166
|
end
|
174
167
|
|
175
168
|
def scrub(transcript)
|
176
169
|
transcript.
|
170
|
+
gsub(%r((<transactionKey>).+(</transactionKey>)), '\1[FILTERED]\2').
|
177
171
|
gsub(%r((<cardNumber>).+(</cardNumber>)), '\1[FILTERED]\2').
|
178
|
-
gsub(%r((<cardCode>).+(</cardCode>)), '\1[FILTERED]\2')
|
172
|
+
gsub(%r((<cardCode>).+(</cardCode>)), '\1[FILTERED]\2').
|
173
|
+
gsub(%r((<track1>).+(</track1>)), '\1[FILTERED]\2').
|
174
|
+
gsub(%r((<track2>).+(</track2>)), '\1[FILTERED]\2').
|
175
|
+
gsub(%r((<cryptogram>).+(</cryptogram>)), '\1[FILTERED]\2')
|
176
|
+
end
|
177
|
+
|
178
|
+
def supports_network_tokenization?
|
179
|
+
card = Billing::NetworkTokenizationCreditCard.new({
|
180
|
+
:number => "4111111111111111",
|
181
|
+
:month => 12,
|
182
|
+
:year => 20,
|
183
|
+
:first_name => 'John',
|
184
|
+
:last_name => 'Smith',
|
185
|
+
:brand => 'visa',
|
186
|
+
:payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
|
187
|
+
})
|
188
|
+
|
189
|
+
request = post_data(:authorize) do |xml|
|
190
|
+
add_auth_purchase(xml, "authOnlyTransaction", 1, card, {})
|
191
|
+
end
|
192
|
+
raw_response = ssl_post(url, request, headers)
|
193
|
+
response = parse(:authorize, raw_response)
|
194
|
+
response[:response_reason_code].to_s != PAYMENT_METHOD_NOT_SUPPORTED_ERROR
|
179
195
|
end
|
180
196
|
|
181
197
|
private
|
182
198
|
|
199
|
+
def add_auth_purchase(xml, transaction_type, amount, payment, options)
|
200
|
+
add_order_id(xml, options)
|
201
|
+
xml.transactionRequest do
|
202
|
+
xml.transactionType(transaction_type)
|
203
|
+
xml.amount(amount(amount))
|
204
|
+
add_payment_source(xml, payment)
|
205
|
+
add_invoice(xml, options)
|
206
|
+
add_customer_data(xml, payment, options)
|
207
|
+
add_market_type(xml, payment)
|
208
|
+
add_settings(xml, payment, options)
|
209
|
+
add_user_fields(xml, amount, options)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def add_cim_auth_purchase(xml, transaction_type, amount, payment, options)
|
214
|
+
add_order_id(xml, options)
|
215
|
+
xml.transaction do
|
216
|
+
xml.send(transaction_type) do
|
217
|
+
xml.amount(amount(amount))
|
218
|
+
add_payment_source(xml, payment)
|
219
|
+
add_invoice(xml, options)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def cim_capture(amount, authorization, options)
|
225
|
+
commit(:cim_capture) do |xml|
|
226
|
+
add_order_id(xml, options)
|
227
|
+
xml.transaction do
|
228
|
+
xml.profileTransPriorAuthCapture do
|
229
|
+
xml.amount(amount(amount))
|
230
|
+
xml.transId(transaction_id_from(authorization))
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def normal_capture(amount, authorization, options)
|
237
|
+
commit(:capture) do |xml|
|
238
|
+
add_order_id(xml, options)
|
239
|
+
xml.transactionRequest do
|
240
|
+
xml.transactionType('priorAuthCaptureTransaction')
|
241
|
+
xml.amount(amount(amount))
|
242
|
+
xml.refTransId(transaction_id_from(authorization))
|
243
|
+
add_invoice(xml, options)
|
244
|
+
add_user_fields(xml, amount, options)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def cim_refund(amount, authorization, options)
|
250
|
+
transaction_id, card_number, _ = split_authorization(authorization)
|
251
|
+
|
252
|
+
commit(:cim_refund) do |xml|
|
253
|
+
add_order_id(xml, options)
|
254
|
+
xml.transaction do
|
255
|
+
xml.profileTransRefund do
|
256
|
+
xml.amount(amount(amount))
|
257
|
+
xml.creditCardNumberMasked(card_number)
|
258
|
+
add_invoice(xml, options)
|
259
|
+
xml.transId(transaction_id)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def normal_refund(amount, authorization, options)
|
266
|
+
transaction_id, card_number, _ = split_authorization(authorization)
|
267
|
+
|
268
|
+
commit(:refund) do |xml|
|
269
|
+
xml.transactionRequest do
|
270
|
+
xml.transactionType('refundTransaction')
|
271
|
+
xml.amount(amount.nil? ? 0 : amount(amount))
|
272
|
+
xml.payment do
|
273
|
+
xml.creditCard do
|
274
|
+
xml.cardNumber(card_number || options[:card_number])
|
275
|
+
xml.expirationDate('XXXX')
|
276
|
+
end
|
277
|
+
end
|
278
|
+
xml.refTransId(transaction_id)
|
279
|
+
|
280
|
+
add_invoice(xml, options)
|
281
|
+
add_customer_data(xml, nil, options)
|
282
|
+
add_user_fields(xml, amount, options)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def cim_void(authorization, options)
|
288
|
+
commit(:cim_void) do |xml|
|
289
|
+
add_order_id(xml, options)
|
290
|
+
xml.transaction do
|
291
|
+
xml.profileTransVoid do
|
292
|
+
xml.transId(transaction_id_from(authorization))
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
def normal_void(authorization, options)
|
299
|
+
commit(:void) do |xml|
|
300
|
+
add_order_id(xml, options)
|
301
|
+
xml.transactionRequest do
|
302
|
+
xml.transactionType('voidTransaction')
|
303
|
+
xml.refTransId(transaction_id_from(authorization))
|
304
|
+
add_user_fields(xml, nil, options)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
183
309
|
def add_payment_source(xml, source)
|
184
310
|
return unless source
|
185
|
-
if
|
311
|
+
if source.is_a?(String)
|
312
|
+
add_token_payment_method(xml, source)
|
313
|
+
elsif card_brand(source) == 'check'
|
186
314
|
add_check(xml, source)
|
187
315
|
elsif card_brand(source) == 'apple_pay'
|
188
316
|
add_apple_pay_payment_token(xml, source)
|
@@ -193,7 +321,7 @@ module ActiveMerchant #:nodoc:
|
|
193
321
|
|
194
322
|
def add_settings(xml, source, options)
|
195
323
|
xml.transactionSettings do
|
196
|
-
if card_brand(source) == "check" && options[:recurring]
|
324
|
+
if !source.is_a?(String) && card_brand(source) == "check" && options[:recurring]
|
197
325
|
xml.setting do
|
198
326
|
xml.settingName("recurringBilling")
|
199
327
|
xml.settingValue("true")
|
@@ -264,7 +392,12 @@ module ActiveMerchant #:nodoc:
|
|
264
392
|
end
|
265
393
|
end
|
266
394
|
|
267
|
-
|
395
|
+
def add_token_payment_method(xml, token)
|
396
|
+
customer_profile_id, customer_payment_profile_id, _ = split_authorization(token)
|
397
|
+
xml.customerProfileId(customer_profile_id)
|
398
|
+
xml.customerPaymentProfileId(customer_payment_profile_id)
|
399
|
+
end
|
400
|
+
|
268
401
|
def add_apple_pay_payment_token(xml, apple_pay_payment_token)
|
269
402
|
xml.payment do
|
270
403
|
xml.opaqueData do
|
@@ -275,7 +408,7 @@ module ActiveMerchant #:nodoc:
|
|
275
408
|
end
|
276
409
|
|
277
410
|
def add_market_type(xml, payment)
|
278
|
-
return if card_brand(payment) == 'check'
|
411
|
+
return if payment.is_a?(String) || card_brand(payment) == 'check' || card_brand(payment) == 'apple_pay'
|
279
412
|
if valid_track_data
|
280
413
|
xml.retail do
|
281
414
|
xml.marketType(MARKET_TYPE[:retail])
|
@@ -305,54 +438,63 @@ module ActiveMerchant #:nodoc:
|
|
305
438
|
end
|
306
439
|
|
307
440
|
def add_customer_data(xml, payment_source, options)
|
308
|
-
billing_address = options[:billing_address] || options[:address] || {}
|
309
|
-
shipping_address = options[:shipping_address] || options[:address] || {}
|
310
|
-
|
311
441
|
xml.customer do
|
312
442
|
xml.id(options[:customer]) unless empty?(options[:customer]) || options[:customer] !~ /^\d+$/
|
313
443
|
xml.email(options[:email]) unless empty?(options[:email])
|
314
444
|
end
|
315
445
|
|
446
|
+
add_billing_address(xml, payment_source, options)
|
447
|
+
add_shipping_address(xml, options)
|
448
|
+
|
449
|
+
xml.customerIP(options[:ip]) unless empty?(options[:ip])
|
450
|
+
|
451
|
+
xml.cardholderAuthentication do
|
452
|
+
xml.authenticationIndicator(options[:authentication_indicator])
|
453
|
+
xml.cardholderAuthenticationValue(options[:cardholder_authentication_value])
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
def add_billing_address(xml, payment_source, options)
|
458
|
+
address = options[:billing_address] || options[:address] || {}
|
459
|
+
|
316
460
|
xml.billTo do
|
317
|
-
first_name, last_name = names_from(payment_source,
|
461
|
+
first_name, last_name = names_from(payment_source, address, options)
|
318
462
|
xml.firstName(truncate(first_name, 50)) unless empty?(first_name)
|
319
463
|
xml.lastName(truncate(last_name, 50)) unless empty?(last_name)
|
320
464
|
|
321
|
-
xml.company(truncate(
|
322
|
-
xml.address(truncate(
|
323
|
-
xml.city(truncate(
|
324
|
-
xml.state(empty?(
|
325
|
-
xml.zip(truncate((
|
326
|
-
xml.country(truncate(
|
327
|
-
xml.phoneNumber(truncate(
|
328
|
-
xml.faxNumber(truncate(
|
329
|
-
end
|
330
|
-
|
331
|
-
unless shipping_address.blank?
|
332
|
-
xml.shipTo do
|
333
|
-
(first_name, last_name) = if shipping_address[:name]
|
334
|
-
shipping_address[:name].split
|
335
|
-
else
|
336
|
-
[shipping_address[:first_name], shipping_address[:last_name]]
|
337
|
-
end
|
338
|
-
xml.firstName(truncate(first_name, 50)) unless empty?(first_name)
|
339
|
-
xml.lastName(truncate(last_name, 50)) unless empty?(last_name)
|
340
|
-
|
341
|
-
xml.company(truncate(shipping_address[:company], 50)) unless empty?(shipping_address[:company])
|
342
|
-
xml.address(truncate(shipping_address[:address1], 60))
|
343
|
-
xml.city(truncate(shipping_address[:city], 40))
|
344
|
-
xml.state(truncate(shipping_address[:state], 40))
|
345
|
-
xml.zip(truncate(shipping_address[:zip], 20))
|
346
|
-
xml.country(truncate(shipping_address[:country], 60))
|
347
|
-
end
|
465
|
+
xml.company(truncate(address[:company], 50)) unless empty?(address[:company])
|
466
|
+
xml.address(truncate(address[:address1], 60))
|
467
|
+
xml.city(truncate(address[:city], 40))
|
468
|
+
xml.state(empty?(address[:state]) ? 'n/a' : truncate(address[:state], 40))
|
469
|
+
xml.zip(truncate((address[:zip] || options[:zip]), 20))
|
470
|
+
xml.country(truncate(address[:country], 60))
|
471
|
+
xml.phoneNumber(truncate(address[:phone], 25)) unless empty?(address[:phone])
|
472
|
+
xml.faxNumber(truncate(address[:fax], 25)) unless empty?(address[:fax])
|
348
473
|
end
|
474
|
+
end
|
349
475
|
|
350
|
-
|
476
|
+
def add_shipping_address(xml, options, root_node="shipTo")
|
477
|
+
address = options[:shipping_address] || options[:address]
|
478
|
+
return unless address
|
351
479
|
|
352
|
-
xml.
|
353
|
-
|
354
|
-
|
480
|
+
xml.send(root_node) do
|
481
|
+
first_name, last_name = if address[:name]
|
482
|
+
split_names(address[:name])
|
483
|
+
else
|
484
|
+
[address[:first_name], address[:last_name]]
|
485
|
+
end
|
486
|
+
|
487
|
+
xml.firstName(truncate(first_name, 50)) unless empty?(first_name)
|
488
|
+
xml.lastName(truncate(last_name, 50)) unless empty?(last_name)
|
489
|
+
|
490
|
+
xml.company(truncate(address[:company], 50)) unless empty?(address[:company])
|
491
|
+
xml.address(truncate(address[:address1], 60))
|
492
|
+
xml.city(truncate(address[:city], 40))
|
493
|
+
xml.state(truncate(address[:state], 40))
|
494
|
+
xml.zip(truncate(address[:zip], 20))
|
495
|
+
xml.country(truncate(address[:country], 60))
|
355
496
|
end
|
497
|
+
|
356
498
|
end
|
357
499
|
|
358
500
|
def add_order_id(xml, options)
|
@@ -367,17 +509,40 @@ module ActiveMerchant #:nodoc:
|
|
367
509
|
end
|
368
510
|
|
369
511
|
def names_from(payment_source, address, options)
|
370
|
-
if payment_source && !payment_source.is_a?(PaymentToken)
|
371
|
-
first_name, last_name = (address[:name]
|
512
|
+
if payment_source && !payment_source.is_a?(PaymentToken) && !payment_source.is_a?(String)
|
513
|
+
first_name, last_name = split_names(address[:name])
|
372
514
|
[(payment_source.first_name || first_name), (payment_source.last_name || last_name)]
|
373
515
|
else
|
374
516
|
[options[:first_name], options[:last_name]]
|
375
517
|
end
|
376
518
|
end
|
377
519
|
|
520
|
+
def split_names(full_name)
|
521
|
+
names = (full_name || "").split
|
522
|
+
last_name = names.pop
|
523
|
+
first_name = names.join(" ")
|
524
|
+
[first_name, last_name]
|
525
|
+
end
|
526
|
+
|
527
|
+
def headers
|
528
|
+
{ 'Content-Type' => 'text/xml' }
|
529
|
+
end
|
530
|
+
|
531
|
+
def url
|
532
|
+
test? ? test_url : live_url
|
533
|
+
end
|
534
|
+
|
535
|
+
def parse(action, raw_response)
|
536
|
+
if is_cim_action?(action)
|
537
|
+
parse_cim(raw_response)
|
538
|
+
else
|
539
|
+
parse_normal(action, raw_response)
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
378
543
|
def commit(action, &payload)
|
379
|
-
|
380
|
-
response = parse(action,
|
544
|
+
raw_response = ssl_post(url, post_data(action, &payload), headers)
|
545
|
+
response = parse(action, raw_response)
|
381
546
|
|
382
547
|
avs_result = AVSResult.new(code: response[:avs_result_code])
|
383
548
|
cvv_result = CVVResult.new(response[:card_code])
|
@@ -385,10 +550,10 @@ module ActiveMerchant #:nodoc:
|
|
385
550
|
Response.new(false, "Using a live Authorize.net account in Test Mode is not permitted.")
|
386
551
|
else
|
387
552
|
Response.new(
|
388
|
-
success_from(response),
|
389
|
-
message_from(response, avs_result, cvv_result),
|
553
|
+
success_from(action, response),
|
554
|
+
message_from(action, response, avs_result, cvv_result),
|
390
555
|
response,
|
391
|
-
authorization: authorization_from(response),
|
556
|
+
authorization: authorization_from(action, response),
|
392
557
|
test: test?,
|
393
558
|
avs_result: avs_result,
|
394
559
|
cvv_result: cvv_result,
|
@@ -398,19 +563,37 @@ module ActiveMerchant #:nodoc:
|
|
398
563
|
end
|
399
564
|
end
|
400
565
|
|
401
|
-
def
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
566
|
+
def is_cim_action?(action)
|
567
|
+
action.to_s.start_with?("cim")
|
568
|
+
end
|
569
|
+
|
570
|
+
def post_data(action)
|
571
|
+
Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
572
|
+
xml.send(root_for(action), 'xmlns' => 'AnetApi/xml/v1/schema/AnetApiSchema.xsd') do
|
573
|
+
add_authentication(xml)
|
408
574
|
yield(xml)
|
409
575
|
end
|
410
576
|
end.to_xml(indent: 0)
|
411
577
|
end
|
412
578
|
|
413
|
-
def
|
579
|
+
def root_for(action)
|
580
|
+
if action == :cim_store
|
581
|
+
"createCustomerProfileRequest"
|
582
|
+
elsif is_cim_action?(action)
|
583
|
+
"createCustomerProfileTransactionRequest"
|
584
|
+
else
|
585
|
+
"createTransactionRequest"
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
def add_authentication(xml)
|
590
|
+
xml.merchantAuthentication do
|
591
|
+
xml.name(@options[:login])
|
592
|
+
xml.transactionKey(@options[:password])
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
def parse_normal(action, body)
|
414
597
|
doc = Nokogiri::XML(body)
|
415
598
|
doc.remove_namespaces!
|
416
599
|
|
@@ -465,14 +648,50 @@ module ActiveMerchant #:nodoc:
|
|
465
648
|
response
|
466
649
|
end
|
467
650
|
|
468
|
-
def
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
651
|
+
def parse_cim(body)
|
652
|
+
response = {}
|
653
|
+
|
654
|
+
doc = Nokogiri::XML(body).remove_namespaces!
|
655
|
+
|
656
|
+
if (element = doc.at_xpath("//messages/message"))
|
657
|
+
response[:message_code] = element.at_xpath("code").content[/0*(\d+)$/, 1]
|
658
|
+
response[:message_text] = element.at_xpath("text").content.chomp('.')
|
659
|
+
end
|
660
|
+
|
661
|
+
response[:result_code] = if(element = doc.at_xpath("//messages/resultCode"))
|
662
|
+
(empty?(element.content) ? nil : element.content)
|
663
|
+
end
|
664
|
+
|
665
|
+
response[:test_request] = if(element = doc.at_xpath("//testRequest"))
|
666
|
+
(empty?(element.content) ? nil : element.content)
|
667
|
+
end
|
668
|
+
|
669
|
+
response[:customer_profile_id] = if(element = doc.at_xpath("//customerProfileId"))
|
670
|
+
(empty?(element.content) ? nil : element.content)
|
671
|
+
end
|
672
|
+
|
673
|
+
response[:customer_payment_profile_id] = if(element = doc.at_xpath("//customerPaymentProfileIdList/numericString"))
|
674
|
+
(empty?(element.content) ? nil : element.content)
|
675
|
+
end
|
676
|
+
|
677
|
+
response[:direct_response] = if(element = doc.at_xpath("//directResponse"))
|
678
|
+
(empty?(element.content) ? nil : element.content)
|
679
|
+
end
|
680
|
+
|
681
|
+
response.merge!(parse_direct_response_elements(response))
|
682
|
+
|
683
|
+
response
|
684
|
+
end
|
685
|
+
|
686
|
+
def success_from(action, response)
|
687
|
+
if action == :cim_store
|
688
|
+
response[:result_code] == "Ok"
|
689
|
+
else
|
690
|
+
response[:response_code] == APPROVED && TRANSACTION_ALREADY_ACTIONED.exclude?(response[:response_reason_code])
|
691
|
+
end
|
473
692
|
end
|
474
693
|
|
475
|
-
def message_from(response, avs_result, cvv_result)
|
694
|
+
def message_from(action, response, avs_result, cvv_result)
|
476
695
|
if response[:response_code] == DECLINED
|
477
696
|
if CARD_CODE_ERRORS.include?(cvv_result.code)
|
478
697
|
return cvv_result.message
|
@@ -481,30 +700,97 @@ module ActiveMerchant #:nodoc:
|
|
481
700
|
end
|
482
701
|
end
|
483
702
|
|
484
|
-
response[:response_reason_text]
|
703
|
+
response[:response_reason_text] || response[:message_text]
|
485
704
|
end
|
486
705
|
|
487
|
-
def authorization_from(response)
|
488
|
-
|
706
|
+
def authorization_from(action, response)
|
707
|
+
if action == :cim_store
|
708
|
+
[response[:customer_profile_id], response[:customer_payment_profile_id], action].join("#")
|
709
|
+
else
|
710
|
+
[response[:transaction_id], response[:account_number], action].join("#")
|
711
|
+
end
|
489
712
|
end
|
490
713
|
|
491
714
|
def split_authorization(authorization)
|
492
|
-
|
493
|
-
|
715
|
+
authorization.split("#")
|
716
|
+
end
|
717
|
+
|
718
|
+
def transaction_id_from(authorization)
|
719
|
+
transaction_id, _, _ = split_authorization(authorization)
|
720
|
+
transaction_id
|
494
721
|
end
|
495
722
|
|
496
723
|
def fraud_review?(response)
|
497
724
|
(response[:response_code] == FRAUD_REVIEW)
|
498
725
|
end
|
499
726
|
|
500
|
-
|
501
727
|
def using_live_gateway_in_test_mode?(response)
|
502
728
|
!test? && response[:test_request] == "1"
|
503
729
|
end
|
504
730
|
|
505
731
|
def map_error_code(response_code, response_reason_code)
|
506
|
-
STANDARD_ERROR_CODE_MAPPING[response_code
|
732
|
+
STANDARD_ERROR_CODE_MAPPING["#{response_code}#{response_reason_code}"]
|
733
|
+
end
|
734
|
+
|
735
|
+
def auth_was_for_cim?(authorization)
|
736
|
+
_, _, action = split_authorization(authorization)
|
737
|
+
action && is_cim_action?(action)
|
738
|
+
end
|
739
|
+
|
740
|
+
def parse_direct_response_elements(response)
|
741
|
+
params = response[:direct_response]
|
742
|
+
return {} unless params
|
743
|
+
|
744
|
+
parts = params.split(',')
|
745
|
+
{
|
746
|
+
response_code: parts[0].to_i,
|
747
|
+
response_subcode: parts[1],
|
748
|
+
response_reason_code: parts[2],
|
749
|
+
response_reason_text: parts[3],
|
750
|
+
approval_code: parts[4],
|
751
|
+
avs_result_code: parts[5],
|
752
|
+
transaction_id: parts[6],
|
753
|
+
invoice_number: parts[7],
|
754
|
+
order_description: parts[8],
|
755
|
+
amount: parts[9],
|
756
|
+
method: parts[10],
|
757
|
+
transaction_type: parts[11],
|
758
|
+
customer_id: parts[12],
|
759
|
+
first_name: parts[13],
|
760
|
+
last_name: parts[14],
|
761
|
+
company: parts[15],
|
762
|
+
address: parts[16],
|
763
|
+
city: parts[17],
|
764
|
+
state: parts[18],
|
765
|
+
zip_code: parts[19],
|
766
|
+
country: parts[20],
|
767
|
+
phone: parts[21],
|
768
|
+
fax: parts[22],
|
769
|
+
email_address: parts[23],
|
770
|
+
ship_to_first_name: parts[24],
|
771
|
+
ship_to_last_name: parts[25],
|
772
|
+
ship_to_company: parts[26],
|
773
|
+
ship_to_address: parts[27],
|
774
|
+
ship_to_city: parts[28],
|
775
|
+
ship_to_state: parts[29],
|
776
|
+
ship_to_zip_code: parts[30],
|
777
|
+
ship_to_country: parts[31],
|
778
|
+
tax: parts[32],
|
779
|
+
duty: parts[33],
|
780
|
+
freight: parts[34],
|
781
|
+
tax_exempt: parts[35],
|
782
|
+
purchase_order_number: parts[36],
|
783
|
+
md5_hash: parts[37],
|
784
|
+
card_code: parts[38],
|
785
|
+
cardholder_authentication_verification_response: parts[39],
|
786
|
+
account_number: parts[50] || '',
|
787
|
+
card_type: parts[51] || '',
|
788
|
+
split_tender_id: parts[52] || '',
|
789
|
+
requested_amount: parts[53] || '',
|
790
|
+
balance_on_card: parts[54] || '',
|
791
|
+
}
|
507
792
|
end
|
793
|
+
|
508
794
|
end
|
509
795
|
end
|
510
796
|
end
|