activemerchant 1.50.0 → 1.51.0

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