activemerchant 1.50.0 → 1.51.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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +39 -0
  3. data/README.md +6 -5
  4. data/lib/active_merchant/billing/credit_card.rb +22 -6
  5. data/lib/active_merchant/billing/gateway.rb +15 -1
  6. data/lib/active_merchant/billing/gateways/authorize_net.rb +413 -127
  7. data/lib/active_merchant/billing/gateways/banwire.rb +2 -2
  8. data/lib/active_merchant/billing/gateways/braintree_blue.rb +4 -0
  9. data/lib/active_merchant/billing/gateways/card_stream.rb +18 -0
  10. data/lib/active_merchant/billing/gateways/cenpos.rb +6 -2
  11. data/lib/active_merchant/billing/gateways/checkout.rb +4 -6
  12. data/lib/active_merchant/billing/gateways/checkout_v2.rb +200 -0
  13. data/lib/active_merchant/billing/gateways/cyber_source.rb +19 -1
  14. data/lib/active_merchant/billing/gateways/dibs.rb +1 -1
  15. data/lib/active_merchant/billing/gateways/epay.rb +1 -1
  16. data/lib/active_merchant/billing/gateways/eway_rapid.rb +5 -5
  17. data/lib/active_merchant/billing/gateways/firstdata_e4.rb +4 -1
  18. data/lib/active_merchant/billing/gateways/garanti.rb +5 -1
  19. data/lib/active_merchant/billing/gateways/iats_payments.rb +29 -3
  20. data/lib/active_merchant/billing/gateways/litle.rb +12 -0
  21. data/lib/active_merchant/billing/gateways/paystation.rb +19 -23
  22. data/lib/active_merchant/billing/gateways/payu_in.rb +4 -0
  23. data/lib/active_merchant/billing/gateways/redsys.rb +4 -3
  24. data/lib/active_merchant/billing/gateways/s5.rb +3 -3
  25. data/lib/active_merchant/billing/gateways/sage.rb +8 -10
  26. data/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb +7 -5
  27. data/lib/active_merchant/billing/gateways/sage/sage_core.rb +3 -2
  28. data/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb +0 -5
  29. data/lib/active_merchant/billing/gateways/stripe.rb +33 -4
  30. data/lib/active_merchant/billing/gateways/wepay.rb +13 -6
  31. data/lib/active_merchant/billing/gateways/worldpay_online_payments.rb +1 -2
  32. data/lib/active_merchant/version.rb +1 -1
  33. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 42f985c77801a167c97a8fd6a09ce6516a75f645
4
- data.tar.gz: 878116909f094338c4d837cdb33315bd6bff34e7
3
+ metadata.gz: 410fa6679494b0904c1bff664722c9b23c907e06
4
+ data.tar.gz: bf3827fcd65d592b51d9e5d06b28dfeec0e05ac2
5
5
  SHA512:
6
- metadata.gz: 22674a72518b5aa6376d75366344dea6e0e4a9d021e0fa38672d8c423bda37459722f8818fa852fba4245b330deec8e7d0d7ee4d4af761c2ba204552dc1d9439
7
- data.tar.gz: 8e2a471639e936c5ec32465f691ae39df7b535490d533569a8efaf9024342219b4fd16410bd9667daf37688c3f4824b6dd41057b8a7adf3cb08a01518fa188c1
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/Shopify/active_merchant.png?branch=master)](https://travis-ci.org/Shopify/active_merchant)
3
- [![Code Climate](https://codeclimate.com/github/Shopify/active_merchant.png)](https://codeclimate.com/github/Shopify/active_merchant)
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/Shopify/active_merchant.git
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/Shopify/active_merchant/).
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/Shopify/active_merchant/wikis) contains a [table of features supported by each gateway](http://github.com/Shopify/active_merchant/wikis/gatewayfeaturematrix).
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 => 'Steve',
39
- # :last_name => 'Smith',
40
- # :month => '9',
41
- # :year => '2010',
42
- # :brand => 'visa',
43
- # :number => '4242424242424242'
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/Shopify/active_merchant/wiki/Contributing]
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
- commit("AUTH_CAPTURE") do |xml|
66
- add_order_id(xml, options)
67
- xml.transactionRequest do
68
- xml.transactionType('authCaptureTransaction')
69
- xml.amount(amount(amount))
70
-
71
- add_payment_source(xml, payment)
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
- commit("AUTH_ONLY") do |xml|
83
- add_order_id(xml, options)
84
- xml.transactionRequest do
85
- xml.transactionType('authOnlyTransaction')
86
- xml.amount(amount(amount))
87
-
88
- add_payment_source(xml, payment)
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
- commit("PRIOR_AUTH_CAPTURE") do |xml|
100
- add_order_id(xml, options)
101
- xml.transactionRequest do
102
- xml.transactionType('priorAuthCaptureTransaction')
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
- transaction_id, card_number = split_authorization(authorization)
114
- commit("CREDIT") do |xml|
115
- xml.transactionRequest do
116
- xml.transactionType('refundTransaction')
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
- commit("VOID") do |xml|
134
- add_order_id(xml, options)
135
- xml.transactionRequest do
136
- xml.transactionType('voidTransaction')
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("CREDIT") do |xml|
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 card_brand(source) == 'check'
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
- # http://developer.authorize.net/api/reference/#apple-pay-transactions
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' or card_brand(payment) == 'apple_pay'
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, billing_address, options)
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(billing_address[:company], 50)) unless empty?(billing_address[:company])
322
- xml.address(truncate(billing_address[:address1], 60))
323
- xml.city(truncate(billing_address[:city], 40))
324
- xml.state(empty?(billing_address[:state]) ? 'n/a' : truncate(billing_address[:state], 40))
325
- xml.zip(truncate((billing_address[:zip] || options[:zip]), 20))
326
- xml.country(truncate(billing_address[:country], 60))
327
- xml.phoneNumber(truncate(billing_address[:phone], 25)) unless empty?(billing_address[:phone])
328
- xml.faxNumber(truncate(billing_address[:fax], 25)) unless empty?(billing_address[:fax])
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
- xml.customerIP(options[:ip]) unless empty?(options[:ip])
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.cardholderAuthentication do
353
- xml.authenticationIndicator(options[:authentication_indicator])
354
- xml.cardholderAuthenticationValue(options[:cardholder_authentication_value])
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] || "").split
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
- url = (test? ? test_url : live_url)
380
- response = parse(action, ssl_post(url, post_data(&payload), 'Content-Type' => 'text/xml'))
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 post_data
402
- Nokogiri::XML::Builder.new do |xml|
403
- xml.createTransactionRequest('xmlns' => 'AnetApi/xml/v1/schema/AnetApiSchema.xsd') do
404
- xml.merchantAuthentication do
405
- xml.name(@options[:login])
406
- xml.transactionKey(@options[:password])
407
- end
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 parse(action, body)
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 success_from(response)
469
- (
470
- response[:response_code] == APPROVED &&
471
- TRANSACTION_ALREADY_ACTIONED.exclude?(response[:response_reason_code])
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
- [response[:transaction_id], response[:account_number]].join("#")
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
- transaction_id, card_number = authorization.split("#")
493
- [transaction_id, card_number]
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.to_s << response_reason_code.to_s]
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