activemerchant 1.44.1 → 1.45.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 (58) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +1 -3
  3. data.tar.gz.sig +0 -0
  4. data/CHANGELOG +48 -0
  5. data/CONTRIBUTORS +12 -0
  6. data/README.md +15 -5
  7. data/lib/active_merchant/billing.rb +2 -0
  8. data/lib/active_merchant/billing/apple_pay_payment_token.rb +22 -0
  9. data/lib/active_merchant/billing/gateway.rb +36 -4
  10. data/lib/active_merchant/billing/gateways/adyen.rb +6 -2
  11. data/lib/active_merchant/billing/gateways/authorize_net.rb +332 -255
  12. data/lib/active_merchant/billing/gateways/bank_frick.rb +225 -0
  13. data/lib/active_merchant/billing/gateways/bogus.rb +9 -9
  14. data/lib/active_merchant/billing/gateways/borgun.rb +0 -1
  15. data/lib/active_merchant/billing/gateways/braintree_blue.rb +8 -0
  16. data/lib/active_merchant/billing/gateways/cashnet.rb +17 -10
  17. data/lib/active_merchant/billing/gateways/checkout.rb +213 -0
  18. data/lib/active_merchant/billing/gateways/conekta.rb +1 -1
  19. data/lib/active_merchant/billing/gateways/cyber_source.rb +1 -1
  20. data/lib/active_merchant/billing/gateways/elavon.rb +3 -3
  21. data/lib/active_merchant/billing/gateways/eway_rapid.rb +114 -13
  22. data/lib/active_merchant/billing/gateways/finansbank.rb +1 -1
  23. data/lib/active_merchant/billing/gateways/global_transport.rb +183 -0
  24. data/lib/active_merchant/billing/gateways/hps.rb +27 -20
  25. data/lib/active_merchant/billing/gateways/iats_payments.rb +68 -35
  26. data/lib/active_merchant/billing/gateways/litle.rb +36 -1
  27. data/lib/active_merchant/billing/gateways/merchant_one.rb +0 -1
  28. data/lib/active_merchant/billing/gateways/merchant_ware.rb +8 -4
  29. data/lib/active_merchant/billing/gateways/mercury.rb +17 -10
  30. data/lib/active_merchant/billing/gateways/moneris.rb +11 -6
  31. data/lib/active_merchant/billing/gateways/moneris_us.rb +126 -33
  32. data/lib/active_merchant/billing/gateways/money_movers.rb +0 -1
  33. data/lib/active_merchant/billing/gateways/net_registry.rb +6 -1
  34. data/lib/active_merchant/billing/gateways/network_merchants.rb +5 -5
  35. data/lib/active_merchant/billing/gateways/nmi.rb +241 -5
  36. data/lib/active_merchant/billing/gateways/openpay.rb +1 -0
  37. data/lib/active_merchant/billing/gateways/optimal_payment.rb +6 -1
  38. data/lib/active_merchant/billing/gateways/orbital.rb +6 -4
  39. data/lib/active_merchant/billing/gateways/pay_junction.rb +9 -5
  40. data/lib/active_merchant/billing/gateways/payex.rb +19 -9
  41. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +2 -2
  42. data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +4 -0
  43. data/lib/active_merchant/billing/gateways/payscout.rb +0 -2
  44. data/lib/active_merchant/billing/gateways/pin.rb +1 -1
  45. data/lib/active_merchant/billing/gateways/psigate.rb +1 -2
  46. data/lib/active_merchant/billing/gateways/redsys.rb +37 -40
  47. data/lib/active_merchant/billing/gateways/secure_pay.rb +181 -9
  48. data/lib/active_merchant/billing/gateways/stripe.rb +106 -31
  49. data/lib/active_merchant/billing/gateways/tns.rb +227 -0
  50. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +38 -10
  51. data/lib/active_merchant/billing/gateways/webpay.rb +14 -0
  52. data/lib/active_merchant/billing/payment_token.rb +21 -0
  53. data/lib/active_merchant/billing/response.rb +2 -1
  54. data/lib/active_merchant/country.rb +6 -1
  55. data/lib/active_merchant/version.rb +1 -1
  56. metadata +8 -3
  57. metadata.gz.sig +0 -0
  58. data/lib/active_merchant/billing/gateways/samurai.rb +0 -130
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eca92a2d6a93b9c3de6bcacaf0ca4ff928826124
4
- data.tar.gz: 24ed6b72ce5bed57175e5b23808e3471ed3a15f0
3
+ metadata.gz: 705744a332bbe4db27dc6e0dbbefd63712e94c28
4
+ data.tar.gz: 452924bd903988716baa84b34969a9248abb6480
5
5
  SHA512:
6
- metadata.gz: 9a51d9b4a6a140298e8c1af1952f5b2c8f320b4f722510c3c57e8fa43887335ba9e53c6a80a00e086e718dc9f1872459331d478dd5c3a5e961012d802733ff46
7
- data.tar.gz: 21f734179dede5a752de2f42d6d9f1269b66f8d2274abdde3486345ad2740999f4e1414b266a247958f3909bb7573054402c578000f3c7a4e920f07d3534ffc4
6
+ metadata.gz: bea4d6dd57b28e0e6b852e05630ace0b08d51e6d3403f5754ba060b8651842ab26871e601833e966b293d2d3d28cc49a52aa6b54352b7fd774c4cb324c84d1fa
7
+ data.tar.gz: 11dab3ab3cb2b85a54b31283f2127c6adfe0f1e2dbcdeb73a40e9d2096d45fa258474845f62ee73d6a6ecb6564bd04974941b85c2450cb403773bc739c41d3fa
@@ -1,3 +1 @@
1
- [hbu0Jo��F�������ԴUO��T�}�G&C����xFYhGa0
2
- >�7�IE�;���2G2U���=+I��1'��;����p�@��t
3
- Tw�ц!�nQ9He���2��u�]3.�������-yR�t�L �據0�rvJ�0���˗�=�+ ��4ІgqKA��ࢂ� Y��2PKK�[�����!M���׺O;�����l�7yP\��{P�9���?vW�4$Wq��o��j%
1
+ ��j#D>j(QVr�B��<���N�ז���zV�&��ҴLC�N ��O�,��+�"G�&�j*��R�|���c�Li3�t��IsK[���������sz>����Շ���Tkw�%��/�J`ą���Ճ&<����.�)im6r�^5��~/��8q�D<If ���͈����p��iL�}Iٺ_x5e�*8��>>���w*�^h�G\A#���g����9�);T Z+i
data.tar.gz.sig CHANGED
Binary file
data/CHANGELOG CHANGED
@@ -1,5 +1,53 @@
1
1
  = ActiveMerchant CHANGELOG
2
2
 
3
+ == Version 1.45.0 (December 1, 2014)
4
+
5
+ * HPS: Always pass CardHolderData element [SecureSubmit, ntalbott]
6
+ * PayJunction: Include 'track' parameter if provided [hron]
7
+ * WebPay: Fix API calls [tomykaira]
8
+ * Moneris US: Add store, unstore, and update [AntoineInsa]
9
+ * Moneris US: Add CVV and AVS [AntoineInsa]
10
+ * Stripe: Add support for statement_description [markabe]
11
+ * CASHNet: Add support for fname and lname [markabe]
12
+ * Orbital: Fix customer profile auth/purchase [denis]
13
+ * Payex: Fix expiry month to allow 4 digit year [duff]
14
+ * Cashnet: Allow overriding custcode [hoenth]
15
+ * Cashnet: Fix overridding item_code per transaction [ntalbott]
16
+ * Add Checkout.com gateway [ravish-ramrakha-cko]
17
+ * HPS: Add verify support [SecureSubmit]
18
+ * Add Bank Frick gateway [varyonic]
19
+ * Add Global Transport gateway [duff]
20
+ * iATS: Add #store and #unstore [duff]
21
+ * Authorize.Net: Fix amount formatting [ntalbott]
22
+ * Authorize.Net: Truncate order_id to 20 characters [ntalbott]
23
+ * Authorize.Net: Truncate more fields [duff]
24
+ * Authorize.Net: Truncate invoiceNumber [ntalbott]
25
+ * Adyen: Add support for verify operation [duff]
26
+ * USAePay: Add track_data support [louiskearns]
27
+ * Payex: Use the right url for the purchase call [duff]
28
+ * Braintree: Allow dynamic descriptors [duff]
29
+ * Openpay: Add support for device session id [guillermo-delucio, ismaelem]
30
+ * Redsys: Add support for verify [duff]
31
+ * Redsys: Handle unknown currencies [duff]
32
+ * Stripe: Make #unstore signature consistent [duff]
33
+ * Remove defunct Samurai gateway [ntalbott]
34
+ * eWay Rapid: Tweak authorization support [duff]
35
+ * Litle: Add support for dynamic descriptors [duff]
36
+ * Add TNS gateway [markabe]
37
+ * TNS: Update countries and supported card types [markabe]
38
+ * Conekta: Add AMEX as a supported card type [MauricioMurga]
39
+ * Checkout: pass through currency type [markabe]
40
+ * Bogus: return standard error codes [jpcaissy]
41
+ * Add PaymentToken and ApplePayPaymentToken objects for token-based transactions [bizla]
42
+ * Authorize.Net: Add ApplePay in-app transaction support [bizla]
43
+ * Stripe: Add ApplePay in-app transaction support [bizla]
44
+ * eWAY Rapid: Add PartnerID param [j-mutter]
45
+ * GlobalTransport: Truncate order_id [duff]
46
+ * Redsys: Allow a description to be specified [duff]
47
+ * NetworkMerchants: Fix currency [j-mutter]
48
+ * Redsys: Improve handling of order_id [duff]
49
+ * Checkout: Add support for void, refund, and verify [markabe]
50
+
3
51
  == Version 1.44.1 (Aug 28, 2014)
4
52
 
5
53
  * Allow SSLv3 for PsiGate [mutemule]
@@ -497,3 +497,15 @@ Commercegate (June 2014)
497
497
  Worldpay US (August 2014)
498
498
 
499
499
  * Mark Bennett (markabe)
500
+
501
+ Checkout.com (September 2014)
502
+
503
+ * (ravish-ramrakha-cko)
504
+
505
+ Bank Frick (September 2014)
506
+
507
+ * Piers Chambers (varyonic)
508
+
509
+ Global Transport (September 2014)
510
+
511
+ * Duff O'Melia (duff)
data/README.md CHANGED
@@ -87,6 +87,7 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta
87
87
  * [Authorize.Net CIM](http://www.authorize.net/) - US
88
88
  * [Authorize.Net](http://www.authorize.net/) - AD, AT, AU, BE, BG, CA, CH, CY, CZ, DE, DK, ES, FI, FR, GB, GB, GI, GR, HU, IE, IT, LI, LU, MC, MT, NL, NO, PL, PT, RO, SE, SI, SK, SM, TR, US, VA
89
89
  * [Balanced](https://www.balancedpayments.com/) - US
90
+ * [Bank Frick](http://www.bankfrickacquiring.com/) - LI, US
90
91
  * [Banwire](http://www.banwire.com/) - MX
91
92
  * [Barclays ePDQ Extra Plus](http://www.barclaycard.co.uk/business/accepting-payments/epdq-ecomm/) - GB
92
93
  * [Barclays ePDQ MPI](http://www.barclaycard.co.uk/business/accepting-payments/epdq-mpi/) - GB
@@ -101,6 +102,7 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta
101
102
  * [Cashnet](http://www.higherone.com/) - US
102
103
  * [Cecabank](http://www.ceca.es/es/) - ES
103
104
  * [CertoDirect](http://www.certodirect.com/) - BE, BG, CZ, DK, DE, EE, IE, EL, ES, FR, IT, CY, LV, LT, LU, HU, MT, NL, AT, PL, PT, RO, SI, SK, FI, SE, GB
105
+ * [Checkout.com](https://www.checkout.com/) - AT, BE, BG, CY, CZ, DE, DK, EE, ES, FI, FR, GR, HR, HU, IE, IS, IT, LI, LT, LU, LV, MT, MU, NL, NO, PL, PT, RO, SE, SI, SK, US
104
106
  * [Commercegate](http://www.commercegate.com/) - AD, AT, AX, BE, BG, CH, CY, CZ, DE, DK, ES, FI, FR, GB, GG, GI, GR, HR, HU, IE, IM, IS, IT, JE, LI, LT, LU, LV, MC, MT, NL, NO, PL, PT, RO, SE, SI, SK, VA
105
107
  * [Conekta](https://conekta.io) - MX
106
108
  * [CyberSource](http://www.cybersource.com) - US, BR, CA, CN, DK, FI, FR, DE, JP, MX, NO, SE, GB, SG
@@ -119,6 +121,7 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta
119
121
  * [FirstData Global Gateway e4](http://www.firstdata.com) - CA, US
120
122
  * [FirstGiving](http://www.firstgiving.com/) - US
121
123
  * [Garanti Sanal POS](https://sanalposweb.garanti.com.tr) - US, TR
124
+ * [Global Transport](https://www.globalpaymentsinc.com) - CA, PR, US
122
125
  * [HDFC](http://www.hdfcbank.com/sme/sme-details/merchant-services/guzh6m0i) - IN
123
126
  * [Heartland Payment Systems](http://developer.heartlandpaymentsystems.com/SecureSubmit/) - US
124
127
  * [iATS Payments](http://home.iatspayments.com/) - AU, BR, CA, CH, DE, DK, ES, FI, FR, GR, HK, IE, IT, NL, NO, PT, SE, SG, TR, UK, US
@@ -169,7 +172,7 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta
169
172
  * [Payscout](http://www.payscout.com/) - US
170
173
  * [Paystation](http://paystation.co.nz) - NZ
171
174
  * [Pay Way](http://www.payway.com.au) - AU
172
- * [Pin](http://www.pin.net.au/) - AU
175
+ * [Pin Payments](http://www.pin.net.au/) - AU
173
176
  * [Plug'n Pay](http://www.plugnpay.com/) - US
174
177
  * [Psigate](http://www.psigate.com/) - CA
175
178
  * [PSL Payment Solutions](http://www.paymentsolutionsltd.com/) - GB
@@ -182,7 +185,6 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta
182
185
  * [SagePay](http://www.sagepay.com) - GB, IE
183
186
  * [Sage Payment Solutions](http://www.sagepayments.com) - US, CA
184
187
  * [Sallie Mae](http://www.salliemae.com/) - US
185
- * [Samurai](https://samurai.feefighters.com) - US
186
188
  * [SecureNet](http://www.securenet.com/) - US
187
189
  * [SecurePay](http://www.securepay.com/) - US, CA, GB, AU
188
190
  * [SecurePayTech](http://www.securepaytech.com/) - NZ
@@ -192,6 +194,7 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta
192
194
  * [Stripe](https://stripe.com/) - AU, BE, CA, CH, DE, ES, FI, FR, GB, IE, IT, LU, NL, US
193
195
  * [Swipe](https://www.swipehq.com/checkout) - CA, NZ
194
196
  * [TransFirst](http://www.transfirst.com/) - US
197
+ * [TNS](http://www.tnsi.com/) - AR, AU, BR, FR, DE, HK, MX, NZ, SG, GB, US
195
198
  * [NELiX TransaX](https://www.nelixtransax.com/) - US
196
199
  * [Transnational](http://www.tnbci.com/) - US
197
200
  * [TrustCommerce](http://www.trustcommerce.com/) - US
@@ -205,14 +208,21 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta
205
208
  * [Worldpay](http://www.worldpay.com/) - HK, US, GB, AU, AD, BE, CH, CY, CZ, DE, DK, ES, FI, FR, GI, GR, HU, IE, IL, IT, LI, LU, MC, MT, NL, NO, NZ, PL, PT, SE, SG, SI, SM, TR, UM, VA
206
209
  * [Worldpay US](http://www.worldpay.com/us) - US
207
210
 
208
- ## Contributing
211
+ ## Deprecation Policy
209
212
 
210
- The source code is hosted at [GitHub](http://github.com/Shopify/active_merchant), and can be fetched using:
213
+ Deprecated functionality is removed on major version changes - for example, deprecations from 2.x are removed in 3.x.
211
214
 
212
- git clone git://github.com/Shopify/active_merchant.git
215
+ ## Contributing
216
+
217
+ 1. [Fork it](http://github.com/Shopify/active_merchant/fork) and clone your new repo
218
+ 2. Create a branch (`git checkout -b my_awesome_feature`)
219
+ 3. Commit your changes (`git add my/awesome/file.rb; git commit -m "Added my awesome feature"`)
220
+ 4. Push your changes to your fork (`git push origin my_awesome_feature`)
221
+ 5. Open a [Pull Request](https://github.com/shopify/active_merchant/pulls)
213
222
 
214
223
  Please see the [ActiveMerchant Guide to Contributing](http://github.com/Shopify/active_merchant/wikis/contributing) for
215
224
  information on adding a new gateway to ActiveMerchant.
216
225
 
217
226
  Please don't touch the CHANGELOG in your pull requests, we'll add the appropriate CHANGELOG entries
218
227
  at release time.
228
+
@@ -7,5 +7,7 @@ require 'active_merchant/billing/credit_card_formatting'
7
7
  require 'active_merchant/billing/credit_card'
8
8
  require 'active_merchant/billing/base'
9
9
  require 'active_merchant/billing/check'
10
+ require 'active_merchant/billing/payment_token'
11
+ require 'active_merchant/billing/apple_pay_payment_token'
10
12
  require 'active_merchant/billing/response'
11
13
  require 'active_merchant/billing/gateways'
@@ -0,0 +1,22 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class ApplePayPaymentToken < PaymentToken
4
+ # This is a representation of the token object specified here:
5
+ # https://developer.apple.com/library/ios/documentation/PassKit/Reference/PKPaymentToken_Ref/
6
+ # https://developer.apple.com/library/IOs//documentation/PassKit/Reference/PaymentTokenJSON/PaymentTokenJSON.html
7
+
8
+ attr_reader :payment_instrument_name, :payment_network
9
+ attr_accessor :transaction_identifier
10
+
11
+ def initialize(payment_data, options = {})
12
+ super
13
+ @payment_instrument_name = @metadata[:payment_instrument_name]
14
+ @payment_network = @metadata[:payment_network]
15
+ end
16
+
17
+ def type
18
+ 'apple_pay'
19
+ end
20
+ end
21
+ end
22
+ end
@@ -58,9 +58,40 @@ module ActiveMerchant #:nodoc:
58
58
 
59
59
  DEBIT_CARDS = [ :switch, :solo ]
60
60
  CURRENCIES_WITHOUT_FRACTIONS = [ 'BIF', 'BYR', 'CLP', 'CVE', 'DJF', 'GNF', 'HUF', 'ISK', 'JPY', 'KMF', 'KRW', 'PYG', 'RWF', 'TWD', 'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF' ]
61
+
61
62
  CREDIT_DEPRECATION_MESSAGE = "Support for using credit to refund existing transactions is deprecated and will be removed from a future release of ActiveMerchant. Please use the refund method instead."
62
63
  RECURRING_DEPRECATION_MESSAGE = "Recurring functionality in ActiveMerchant is deprecated and will be removed in a future version. Please contact the ActiveMerchant maintainers if you have an interest in taking ownership of a separate gem that continues support for it."
63
64
 
65
+ # == Standardized Error Codes
66
+ #
67
+ # :incorrect_number - Card number does not comply with ISO/IEC 7812 numbering standard
68
+ # :invalid_number - Card number was not matched by processor
69
+ # :invalid_expiry_date - Expiry date deos not match correct formatting
70
+ # :invalid_cvc - Security codes does not match correct format (3-4 digits)
71
+ # :expired_card - Card number is expired
72
+ # :incorrect_cvc - Secerity code was not matched by the processor
73
+ # :incorrect_zip - Zip code is not in correct format
74
+ # :incorrect_address - Billing address info was not matched by the processor
75
+ # :card_declined - Card number declined by processor
76
+ # :processing_error - Processor error
77
+ # :call_issuer - Transaction requires voice authentication, call issuer
78
+ # :pickup_card - Issuer requests that you pickup the card from merchant
79
+
80
+ STANDARD_ERROR_CODE = {
81
+ :incorrect_number => 'incorrect_number',
82
+ :invalid_number => 'invalid_number',
83
+ :invalid_expiry_date => 'invalid_expiry_date',
84
+ :invalid_cvc => 'invalid_cvc',
85
+ :expired_card => 'expired_card',
86
+ :incorrect_cvc => 'incorrect_cvc',
87
+ :incorrect_zip => 'incorrect_zip',
88
+ :incorrect_address => 'incorrect_address',
89
+ :card_declined => 'card_declined',
90
+ :processing_error => 'processing_error',
91
+ :call_issuer => 'call_issuer',
92
+ :pickup_card => 'pick_up_card'
93
+ }
94
+
64
95
  cattr_reader :implementations
65
96
  @@implementations = []
66
97
 
@@ -112,6 +143,10 @@ module ActiveMerchant #:nodoc:
112
143
  result.to_s.downcase
113
144
  end
114
145
 
146
+ def self.non_fractional_currency?(currency)
147
+ CURRENCIES_WITHOUT_FRACTIONS.include?(currency.to_s)
148
+ end
149
+
115
150
  def self.supported_countries=(country_codes)
116
151
  country_codes.each do |country_code|
117
152
  unless ActiveMerchant::Country.find(country_code)
@@ -197,7 +232,7 @@ module ActiveMerchant #:nodoc:
197
232
  def localized_amount(money, currency)
198
233
  amount = amount(money)
199
234
 
200
- return amount unless non_fractional_currency?(currency)
235
+ return amount unless Gateway.non_fractional_currency?(currency)
201
236
 
202
237
  if self.money_format == :cents
203
238
  sprintf("%.0f", amount.to_f / 100)
@@ -206,9 +241,6 @@ module ActiveMerchant #:nodoc:
206
241
  end
207
242
  end
208
243
 
209
- def non_fractional_currency?(currency)
210
- CURRENCIES_WITHOUT_FRACTIONS.include?(currency.to_s)
211
- end
212
244
 
213
245
  def currency(money)
214
246
  money.respond_to?(:currency) ? money.currency : self.default_currency
@@ -51,7 +51,7 @@ module ActiveMerchant #:nodoc:
51
51
  post = {}
52
52
  post[:modificationRequest] = modification_request(authorization, options)
53
53
  post[:modificationRequest][:modificationAmount] = amount_hash(money, options[:currency])
54
-
54
+
55
55
  commit('Payment.capture', post)
56
56
  end
57
57
 
@@ -61,7 +61,7 @@ module ActiveMerchant #:nodoc:
61
61
  post = {}
62
62
  post[:modificationRequest] = modification_request(authorization, options)
63
63
  post[:modificationRequest][:modificationAmount] = amount_hash(money, options[:currency])
64
-
64
+
65
65
  commit('Payment.refund', post)
66
66
  end
67
67
 
@@ -74,6 +74,10 @@ module ActiveMerchant #:nodoc:
74
74
  commit('Payment.cancel', post)
75
75
  end
76
76
 
77
+ def verify(creditcard, options = {})
78
+ authorize(0, creditcard, options)
79
+ end
80
+
77
81
  private
78
82
 
79
83
  def commit(action, post)
@@ -1,162 +1,114 @@
1
+ require 'nokogiri'
2
+
1
3
  module ActiveMerchant #:nodoc:
2
4
  module Billing #:nodoc:
3
- # For more information on the Authorize.Net Gateway please visit their {Integration Center}[http://developer.authorize.net/]
4
- #
5
- # The login and password are not the username and password you use to
6
- # login to the Authorize.Net Merchant Interface. Instead, you will
7
- # use the API Login ID as the login and Transaction Key as the
8
- # password.
9
- #
10
- # ==== How to Get Your API Login ID and Transaction Key
11
- #
12
- # 1. Log into the Merchant Interface
13
- # 2. Select Settings from the Main Menu
14
- # 3. Click on API Login ID and Transaction Key in the Security section
15
- # 4. Type in the answer to the secret question configured on setup
16
- # 5. Click Submit
17
- #
18
5
  class AuthorizeNetGateway < Gateway
19
- API_VERSION = '3.1'
20
-
21
- self.test_url = "https://test.authorize.net/gateway/transact.dll"
22
- self.live_url = "https://secure.authorize.net/gateway/transact.dll"
23
-
24
- class_attribute :duplicate_window
25
-
26
- APPROVED, DECLINED, ERROR, FRAUD_REVIEW = 1, 2, 3, 4
6
+ include Empty
27
7
 
28
- RESPONSE_CODE, RESPONSE_REASON_CODE, RESPONSE_REASON_TEXT, AUTHORIZATION_CODE = 0, 2, 3, 4
29
- AVS_RESULT_CODE, TRANSACTION_ID, CARD_CODE_RESPONSE_CODE, CARDHOLDER_AUTH_CODE = 5, 6, 38, 39
30
-
31
- self.default_currency = 'USD'
8
+ self.test_url = 'https://apitest.authorize.net/xml/v1/request.api'
9
+ self.live_url = 'https://api.authorize.net/xml/v1/request.api'
32
10
 
33
11
  self.supported_countries = %w(AD AT AU BE BG CA CH CY CZ DE DK ES FI FR GB GB GI GR HU IE IT LI LU MC MT NL NO PL PT RO SE SI SK SM TR US VA)
12
+ self.default_currency = 'USD'
13
+ self.money_format = :dollars
34
14
  self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro]
15
+
35
16
  self.homepage_url = 'http://www.authorize.net/'
36
17
  self.display_name = 'Authorize.Net'
37
18
 
38
- CARD_CODE_ERRORS = %w( N S )
39
- AVS_ERRORS = %w( A E N R W Z )
40
- AVS_REASON_CODES = %w(27 45)
19
+ class_attribute :duplicate_window
20
+
21
+ APPROVED, DECLINED, ERROR, FRAUD_REVIEW = 1, 2, 3, 4
41
22
  TRANSACTION_ALREADY_ACTIONED = %w(310 311)
42
23
 
43
- # Creates a new AuthorizeNetGateway
44
- #
45
- # The gateway requires that a valid login and password be passed
46
- # in the +options+ hash.
47
- #
48
- # ==== Options
49
- #
50
- # * <tt>:login</tt> -- The Authorize.Net API Login ID (REQUIRED)
51
- # * <tt>:password</tt> -- The Authorize.Net Transaction Key. (REQUIRED)
52
- # * <tt>:test</tt> -- +true+ or +false+. If true, perform transactions against the test server.
53
- # Otherwise, perform transactions against the production server.
54
- def initialize(options = {})
24
+ CARD_CODE_ERRORS = %w(N S)
25
+ AVS_ERRORS = %w(A E N R W Z)
26
+ AVS_REASON_CODES = %w(27 45)
27
+
28
+ TRACKS = {
29
+ 1 => /^%(?<format_code>.)(?<pan>[\d]{1,19}+)\^(?<name>.{2,26})\^(?<expiration>[\d]{0,4}|\^)(?<service_code>[\d]{0,3}|\^)(?<discretionary_data>.*)\?\Z/,
30
+ 2 => /\A;(?<pan>[\d]{1,19}+)=(?<expiration>[\d]{0,4}|=)(?<service_code>[\d]{0,3}|=)(?<discretionary_data>.*)\?\Z/
31
+ }.freeze
32
+
33
+ APPLE_PAY_DATA_DESCRIPTOR = "COMMON.APPLE.INAPP.PAYMENT"
34
+
35
+ def initialize(options={})
55
36
  requires!(options, :login, :password)
56
37
  super
57
38
  end
58
39
 
59
- # Performs an authorization, which reserves the funds on the customer's credit card, but does not
60
- # charge the card.
61
- #
62
- # ==== Parameters
63
- #
64
- # * <tt>money</tt> -- The amount to be authorized as an Integer value in cents.
65
- # * <tt>paysource</tt> -- The CreditCard or Check details for the transaction.
66
- # * <tt>options</tt> -- A hash of optional parameters.
67
- def authorize(money, paysource, options = {})
68
- post = {}
69
- add_currency_code(post, money, options)
70
- add_invoice(post, options)
71
- add_payment_source(post, paysource, options)
72
- add_address(post, options)
73
- add_customer_data(post, options)
74
- add_duplicate_window(post)
75
-
76
- commit('AUTH_ONLY', money, post)
77
- end
78
-
79
- # Perform a purchase, which is essentially an authorization and capture in a single operation.
80
- #
81
- # ==== Parameters
82
- #
83
- # * <tt>money</tt> -- The amount to be purchased as an Integer value in cents.
84
- # * <tt>paysource</tt> -- The CreditCard or Check details for the transaction.
85
- # * <tt>options</tt> -- A hash of optional parameters.
86
- def purchase(money, paysource, options = {})
87
- post = {}
88
- add_currency_code(post, money, options)
89
- add_invoice(post, options)
90
- add_payment_source(post, paysource, options)
91
- add_address(post, options)
92
- add_customer_data(post, options)
93
- add_duplicate_window(post)
94
-
95
- commit('AUTH_CAPTURE', money, post)
40
+ def purchase(amount, payment, options = {})
41
+ commit("AUTH_CAPTURE") do |xml|
42
+ add_order_id(xml, options)
43
+ xml.transactionRequest do
44
+ xml.transactionType 'authCaptureTransaction'
45
+ xml.amount amount(amount)
46
+ add_payment_source(xml, payment)
47
+ add_invoice(xml, options)
48
+ add_customer_data(xml, payment, options)
49
+ add_retail_data(xml, payment)
50
+ add_settings(xml, payment, options)
51
+ add_user_fields(xml, amount, options)
52
+ end
53
+ end
96
54
  end
97
55
 
98
- # Captures the funds from an authorized transaction.
99
- #
100
- # ==== Parameters
101
- #
102
- # * <tt>money</tt> -- The amount to be captured as an Integer value in cents.
103
- # * <tt>authorization</tt> -- The authorization returned from the previous authorize request.
104
- def capture(money, authorization, options = {})
105
- post = {:trans_id => authorization}
106
- add_customer_data(post, options)
107
- add_invoice(post, options)
108
- commit('PRIOR_AUTH_CAPTURE', money, post)
56
+ def authorize(amount, payment, options={})
57
+ commit("AUTH_ONLY") do |xml|
58
+ add_order_id(xml, options)
59
+ xml.transactionRequest do
60
+ xml.transactionType 'authOnlyTransaction'
61
+ xml.amount amount(amount)
62
+ add_payment_source(xml, payment)
63
+ add_invoice(xml, options)
64
+ add_customer_data(xml, payment, options)
65
+ add_settings(xml, payment, options)
66
+ add_user_fields(xml, amount, options)
67
+ end
68
+ end
109
69
  end
110
70
 
111
- # Void a previous transaction
112
- #
113
- # ==== Parameters
114
- #
115
- # * <tt>authorization</tt> - The authorization returned from the previous authorize request.
116
- def void(authorization, options = {})
117
- post = {:trans_id => authorization}
118
- add_duplicate_window(post)
119
- commit('VOID', nil, post)
71
+ def capture(amount, authorization, options={})
72
+ commit("PRIOR_AUTH_CAPTURE") do |xml|
73
+ add_order_id(xml, options)
74
+ xml.transactionRequest do
75
+ xml.transactionType 'priorAuthCaptureTransaction'
76
+ xml.amount amount(amount)
77
+ xml.refTransId split_authorization(authorization)[0]
78
+ add_invoice(xml, options)
79
+ add_user_fields(xml, amount, options)
80
+ end
81
+ end
120
82
  end
121
83
 
122
- # Refund a transaction.
123
- #
124
- # This transaction indicates to the gateway that
125
- # money should flow from the merchant to the customer.
126
- #
127
- # ==== Parameters
128
- #
129
- # * <tt>money</tt> -- The amount to be credited to the customer as an Integer value in cents.
130
- # * <tt>identification</tt> -- The ID of the original transaction against which the refund is being issued.
131
- # * <tt>options</tt> -- A hash of parameters.
132
- #
133
- # ==== Options
134
- #
135
- # * <tt>:card_number</tt> -- The credit card number the refund is being issued to. (REQUIRED)
136
- # You can either pass the last four digits of the card number or the full card number.
137
- # * <tt>:first_name</tt> -- The first name of the account being refunded.
138
- # * <tt>:last_name</tt> -- The last name of the account being refunded.
139
- # * <tt>:zip</tt> -- The postal code of the account being refunded.
140
- def refund(money, identification, options = {})
141
- requires!(options, :card_number)
142
-
143
- post = { :trans_id => identification,
144
- :card_num => options[:card_number]
145
- }
146
-
147
- post[:first_name] = options[:first_name] if options[:first_name]
148
- post[:last_name] = options[:last_name] if options[:last_name]
149
- post[:zip] = options[:zip] if options[:zip]
150
-
151
- add_invoice(post, options)
152
- add_duplicate_window(post)
153
-
154
- commit('CREDIT', money, post)
84
+ def refund(amount, authorization, options={})
85
+ transaction_id, card_number = split_authorization(authorization)
86
+ commit("CREDIT") do |xml|
87
+ xml.transactionRequest do
88
+ xml.transactionType 'refundTransaction'
89
+ xml.amount (amount.nil? ? 0 : amount(amount))
90
+ xml.payment do
91
+ xml.creditCard do
92
+ xml.cardNumber(card_number || options[:card_number])
93
+ xml.expirationDate 'XXXX'
94
+ end
95
+ end
96
+ xml.refTransId transaction_id
97
+ add_customer_data(xml, nil, options)
98
+ add_user_fields(xml, amount, options)
99
+ end
100
+ end
155
101
  end
156
102
 
157
- def credit(money, identification, options = {})
158
- ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
159
- refund(money, identification, options)
103
+ def void(authorization, options={})
104
+ commit("VOID") do |xml|
105
+ add_order_id(xml, options)
106
+ xml.transactionRequest do
107
+ xml.transactionType 'voidTransaction'
108
+ xml.refTransId split_authorization(authorization)[0]
109
+ add_user_fields(xml, nil, options)
110
+ end
111
+ end
160
112
  end
161
113
 
162
114
  def verify(credit_card, options = {})
@@ -168,175 +120,300 @@ module ActiveMerchant #:nodoc:
168
120
 
169
121
  private
170
122
 
171
- def commit(action, money, parameters)
172
- parameters[:amount] = amount(money) unless action == 'VOID'
123
+ def add_payment_source(xml, source)
124
+ return unless source
125
+ if card_brand(source) == 'check'
126
+ add_check(xml, source)
127
+ elsif card_brand(source) == 'apple_pay'
128
+ add_apple_pay_payment_token(xml, source)
129
+ else
130
+ add_credit_card(xml, source)
131
+ end
132
+ end
173
133
 
174
- url = test? ? self.test_url : self.live_url
175
- data = ssl_post url, post_data(action, parameters)
134
+ def add_settings(xml, source, options)
135
+ xml.transactionSettings do
136
+ if(card_brand(source) == "check" && options[:recurring])
137
+ xml.setting do
138
+ xml.settingName "recurringBilling"
139
+ xml.settingValue "true"
140
+ end
141
+ end
142
+ if(self.class.duplicate_window)
143
+ xml.setting do
144
+ xml.settingName "duplicateWindow"
145
+ xml.settingValue self.class.duplicate_window
146
+ end
147
+ end
148
+ end
149
+ end
176
150
 
177
- response = parse(data)
178
- response[:action] = action
151
+ def add_user_fields(xml, amount, options)
152
+ xml.userFields do
153
+ if(currency = (options[:currency] || currency(amount)))
154
+ xml.userField do
155
+ xml.name "x_currency_code"
156
+ xml.value currency
157
+ end
158
+ end
159
+ if(application_id.present? && application_id != "ActiveMerchant")
160
+ xml.userField do
161
+ xml.name "x_solution_id"
162
+ xml.value application_id
163
+ end
164
+ end
165
+ end
166
+ end
179
167
 
180
- message = message_from(response)
168
+ def add_credit_card(xml, credit_card)
169
+ if credit_card.track_data
170
+ add_swipe_data(xml, credit_card)
171
+ else
172
+ xml.payment do
173
+ xml.creditCard do
174
+ xml.cardNumber credit_card.number
175
+ xml.expirationDate (format(credit_card.month, :two_digits) + '/' + format(credit_card.year, :four_digits))
176
+ unless empty?(credit_card.verification_value)
177
+ xml.cardCode credit_card.verification_value
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
181
183
 
182
- Response.new(success?(response), message, response,
183
- :test => test?,
184
- :authorization => response[:transaction_id],
185
- :fraud_review => fraud_review?(response),
186
- :avs_result => { :code => response[:avs_result_code] },
187
- :cvv_result => response[:card_code]
188
- )
184
+ def add_swipe_data(xml, credit_card)
185
+ TRACKS.each do |key, regex|
186
+ if regex.match(credit_card.track_data)
187
+ @valid_track_data = true
188
+ xml.payment do
189
+ xml.trackData do
190
+ xml.public_send(:"track#{key}", credit_card.track_data)
191
+ end
192
+ end
193
+ end
194
+ end
189
195
  end
190
196
 
191
- def success?(response)
192
- response[:response_code] == APPROVED && TRANSACTION_ALREADY_ACTIONED.exclude?(response[:response_reason_code])
197
+ # http://developer.authorize.net/api/reference/#apple-pay-transactions
198
+ def add_apple_pay_payment_token(xml, apple_pay_payment_token)
199
+ xml.payment do
200
+ xml.opaqueData do
201
+ xml.dataDescriptor APPLE_PAY_DATA_DESCRIPTOR
202
+ xml.dataValue Base64.strict_encode64(apple_pay_payment_token.payment_data.to_json)
203
+ end
204
+ end
193
205
  end
194
206
 
195
- def fraud_review?(response)
196
- response[:response_code] == FRAUD_REVIEW
207
+ def add_retail_data(xml, payment)
208
+ return unless valid_track_data
209
+ xml.retail do
210
+ # As per http://www.authorize.net/support/CP_guide.pdf, '2' is for Retail, the only current market_type
211
+ xml.marketType 2
212
+ end
197
213
  end
198
214
 
199
- def parse(body)
200
- fields = split(body)
201
-
202
- results = {
203
- :response_code => fields[RESPONSE_CODE].to_i,
204
- :response_reason_code => fields[RESPONSE_REASON_CODE],
205
- :response_reason_text => fields[RESPONSE_REASON_TEXT],
206
- :avs_result_code => fields[AVS_RESULT_CODE],
207
- :transaction_id => fields[TRANSACTION_ID],
208
- :card_code => fields[CARD_CODE_RESPONSE_CODE],
209
- :authorization_code => fields[AUTHORIZATION_CODE],
210
- :cardholder_authentication_code => fields[CARDHOLDER_AUTH_CODE]
211
- }
212
- results
215
+ def valid_track_data
216
+ @valid_track_data ||= false
213
217
  end
214
218
 
215
- def post_data(action, parameters = {})
216
- post = {}
217
-
218
- post[:version] = API_VERSION
219
- post[:login] = @options[:login]
220
- post[:tran_key] = @options[:password]
221
- post[:relay_response] = "FALSE"
222
- post[:type] = action
223
- post[:delim_data] = "TRUE"
224
- post[:delim_char] = ","
225
- post[:encap_char] = "$"
226
- post[:solution_ID] = application_id if application_id.present? && application_id != "ActiveMerchant"
227
-
228
- request = post.merge(parameters).collect { |key, value| "x_#{key}=#{CGI.escape(value.to_s)}" }.join("&")
229
- request
219
+ def add_check(xml, check)
220
+ xml.payment do
221
+ xml.bankAccount do
222
+ xml.routingNumber check.routing_number
223
+ xml.accountNumber check.account_number
224
+ xml.nameOnAccount check.name
225
+ xml.echeckType "WEB"
226
+ xml.bankName check.bank_name
227
+ xml.checkNumber check.number
228
+ end
229
+ end
230
230
  end
231
231
 
232
- def add_currency_code(post, money, options)
233
- post[:currency_code] = options[:currency] || currency(money)
232
+ def add_customer_data(xml, payment_source, options)
233
+ billing_address = options[:billing_address] || options[:address] || {}
234
+ shipping_address = options[:shipping_address] || options[:address] || {}
235
+
236
+ xml.customer do
237
+ xml.id(options[:customer]) unless empty?(options[:customer]) || options[:customer] !~ /^\d+$/
238
+ xml.email(options[:email]) unless empty?(options[:email])
239
+ end
240
+
241
+ xml.billTo do
242
+ first_name, last_name = names_from(payment_source, billing_address, options)
243
+ xml.firstName(truncate(first_name, 50)) unless empty?(first_name)
244
+ xml.lastName(truncate(last_name, 50)) unless empty?(last_name)
245
+
246
+ xml.company(truncate(billing_address[:company], 50)) unless empty?(billing_address[:company])
247
+ xml.address(truncate(billing_address[:address1], 60))
248
+ xml.city(truncate(billing_address[:city], 40))
249
+ xml.state(empty?(billing_address[:state]) ? 'n/a' : truncate(billing_address[:state], 40))
250
+ xml.zip(truncate((billing_address[:zip] || options[:zip]), 20))
251
+ xml.country(truncate(billing_address[:country], 60))
252
+ xml.phoneNumber(truncate(billing_address[:phone], 25)) unless empty?(billing_address[:phone])
253
+ xml.faxNumber(truncate(billing_address[:fax], 25)) unless empty?(billing_address[:fax])
254
+ end
255
+
256
+ unless shipping_address.blank?
257
+ xml.shipTo do
258
+ first_name, last_name = names_from(payment_source, shipping_address, options)
259
+ xml.firstName(truncate(first_name, 50)) unless empty?(first_name)
260
+ xml.lastName(truncate(last_name, 50)) unless empty?(last_name)
261
+
262
+ xml.company(truncate(shipping_address[:company], 50)) unless empty?(shipping_address[:company])
263
+ xml.address(truncate(shipping_address[:address1], 60))
264
+ xml.city(truncate(shipping_address[:city], 40))
265
+ xml.state(truncate(shipping_address[:state], 40))
266
+ xml.zip(truncate(shipping_address[:zip], 20))
267
+ xml.country(truncate(shipping_address[:country], 60))
268
+ end
269
+ end
270
+
271
+ xml.customerIP(options[:ip]) unless empty?(options[:ip])
272
+
273
+ xml.cardholderAuthentication do
274
+ xml.authenticationIndicator options[:authentication_indicator]
275
+ xml.cardholderAuthenticationValue options[:cardholder_authentication_value]
276
+ end
234
277
  end
235
278
 
236
- def add_invoice(post, options)
237
- post[:invoice_num] = options[:order_id]
238
- post[:description] = options[:description]
279
+ def add_order_id(xml, options)
280
+ xml.refId truncate(options[:order_id], 20)
239
281
  end
240
282
 
241
- def add_creditcard(post, creditcard, options={})
242
- post[:card_num] = creditcard.number
243
- post[:card_code] = creditcard.verification_value if creditcard.verification_value?
244
- post[:exp_date] = expdate(creditcard)
245
- post[:first_name] = creditcard.first_name
246
- post[:last_name] = creditcard.last_name
283
+ def add_invoice(xml, options)
284
+ xml.order do
285
+ xml.invoiceNumber truncate(options[:order_id], 20)
286
+ xml.description truncate(options[:description], 255)
287
+ end
247
288
  end
248
289
 
249
- def add_payment_source(params, source, options={})
250
- if card_brand(source) == "check"
251
- add_check(params, source, options)
290
+ def names_from(payment_source, address, options)
291
+ if payment_source && !payment_source.is_a?(PaymentToken)
292
+ first_name, last_name = (address[:name] || "").split
293
+ [(payment_source.first_name || first_name), (payment_source.last_name || last_name)]
252
294
  else
253
- add_creditcard(params, source, options)
295
+ [options[:first_name], options[:last_name]]
254
296
  end
255
297
  end
256
298
 
257
- def add_check(post, check, options)
258
- post[:method] = "ECHECK"
259
- post[:bank_name] = check.bank_name
260
- post[:bank_aba_code] = check.routing_number
261
- post[:bank_acct_num] = check.account_number
262
- post[:bank_acct_type] = check.account_type
263
- post[:echeck_type] = "WEB"
264
- post[:bank_acct_name] = check.name
265
- post[:bank_check_number] = check.number if check.number.present?
266
- post[:recurring_billing] = (options[:recurring] ? "TRUE" : "FALSE")
299
+ def commit(action, &payload)
300
+ url = (test? ? test_url : live_url)
301
+ response = parse(action, ssl_post(url, post_data(&payload), 'Content-Type' => 'text/xml'))
302
+
303
+ avs_result = AVSResult.new(code: response[:avs_result_code])
304
+ cvv_result = CVVResult.new(response[:card_code])
305
+ Response.new(
306
+ success_from(response),
307
+ message_from(response, avs_result, cvv_result),
308
+ response,
309
+ authorization: authorization_from(response),
310
+ test: test?,
311
+ avs_result: avs_result,
312
+ cvv_result: cvv_result,
313
+ fraud_review: fraud_review?(response)
314
+ )
267
315
  end
268
316
 
269
- def add_customer_data(post, options)
270
- if options.has_key? :email
271
- post[:email] = options[:email]
272
- post[:email_customer] = false
273
- end
317
+ def post_data
318
+ Nokogiri::XML::Builder.new do |xml|
319
+ xml.createTransactionRequest('xmlns' => 'AnetApi/xml/v1/schema/AnetApiSchema.xsd') do
320
+ xml.merchantAuthentication do
321
+ xml.name @options[:login]
322
+ xml.transactionKey @options[:password]
323
+ end
324
+ yield(xml)
325
+ end
326
+ end.to_xml(indent: 0)
327
+ end
274
328
 
275
- if options.has_key? :customer
276
- post[:cust_id] = options[:customer] if Float(options[:customer]) rescue nil
329
+ def parse(action, body)
330
+ doc = Nokogiri::XML(body)
331
+ doc.remove_namespaces!
332
+
333
+ response = {action: action}
334
+
335
+ response[:response_code] = if(element = doc.at_xpath("//transactionResponse/responseCode"))
336
+ (empty?(element.content) ? nil : element.content.to_i)
277
337
  end
278
338
 
279
- if options.has_key? :ip
280
- post[:customer_ip] = options[:ip]
339
+ if(element = doc.at_xpath("//errors/error"))
340
+ response[:response_reason_code] = element.at_xpath("errorCode").content[/0*(\d+)$/, 1]
341
+ response[:response_reason_text] = element.at_xpath("errorText").content.chomp('.')
342
+ elsif(element = doc.at_xpath("//transactionResponse/messages/message"))
343
+ response[:response_reason_code] = element.at_xpath("code").content[/0*(\d+)$/, 1]
344
+ response[:response_reason_text] = element.at_xpath("description").content.chomp('.')
345
+ elsif(element = doc.at_xpath("//messages/message"))
346
+ response[:response_reason_code] = element.at_xpath("code").content[/0*(\d+)$/, 1]
347
+ response[:response_reason_text] = element.at_xpath("text").content.chomp('.')
348
+ else
349
+ response[:response_reason_code] = nil
350
+ response[:response_reason_text] = ""
281
351
  end
282
352
 
283
- if options.has_key? :cardholder_authentication_value
284
- post[:cardholder_authentication_value] = options[:cardholder_authentication_value]
353
+ response[:avs_result_code] = if(element = doc.at_xpath("//avsResultCode"))
354
+ (empty?(element.content) ? nil : element.content)
285
355
  end
286
356
 
287
- if options.has_key? :authentication_indicator
288
- post[:authentication_indicator] = options[:authentication_indicator]
357
+ response[:transaction_id] = if(element = doc.at_xpath("//transId"))
358
+ (empty?(element.content) ? nil : element.content)
289
359
  end
290
360
 
291
- end
361
+ response[:card_code] = if(element = doc.at_xpath("//cvvResultCode"))
362
+ (empty?(element.content) ? nil : element.content)
363
+ end
292
364
 
293
- # x_duplicate_window won't be sent by default, because sending it changes the response.
294
- # "If this field is present in the request with or without a value, an enhanced duplicate transaction response will be sent."
295
- # (as of 2008-12-30) http://www.authorize.net/support/AIM_guide_SCC.pdf
296
- def add_duplicate_window(post)
297
- unless duplicate_window.nil?
298
- post[:duplicate_window] = duplicate_window
365
+ response[:authorization_code] = if(element = doc.at_xpath("//authCode"))
366
+ (empty?(element.content) ? nil : element.content)
299
367
  end
300
- end
301
368
 
302
- def add_address(post, options)
303
- if address = options[:billing_address] || options[:address]
304
- post[:address] = address[:address1].to_s
305
- post[:company] = address[:company].to_s
306
- post[:phone] = address[:phone].to_s
307
- post[:zip] = address[:zip].to_s
308
- post[:city] = address[:city].to_s
309
- post[:country] = address[:country].to_s
310
- post[:state] = address[:state].blank? ? 'n/a' : address[:state]
369
+ response[:cardholder_authentication_code] = if(element = doc.at_xpath("//cavvResultCode"))
370
+ (empty?(element.content) ? nil : element.content)
311
371
  end
312
372
 
313
- if address = options[:shipping_address]
314
- post[:ship_to_first_name] = address[:first_name].to_s
315
- post[:ship_to_last_name] = address[:last_name].to_s
316
- post[:ship_to_address] = address[:address1].to_s
317
- post[:ship_to_company] = address[:company].to_s
318
- post[:ship_to_phone] = address[:phone].to_s
319
- post[:ship_to_zip] = address[:zip].to_s
320
- post[:ship_to_city] = address[:city].to_s
321
- post[:ship_to_country] = address[:country].to_s
322
- post[:ship_to_state] = address[:state].blank? ? 'n/a' : address[:state]
373
+ response[:account_number] = if(element = doc.at_xpath("//accountNumber"))
374
+ (empty?(element.content) ? nil : element.content[-4..-1])
323
375
  end
376
+
377
+ response
324
378
  end
325
379
 
326
- def message_from(results)
327
- if results[:response_code] == DECLINED
328
- return CVVResult.messages[ results[:card_code] ] if CARD_CODE_ERRORS.include?(results[:card_code])
329
- if AVS_REASON_CODES.include?(results[:response_reason_code]) && AVS_ERRORS.include?(results[:avs_result_code])
330
- return AVSResult.messages[ results[:avs_result_code] ]
380
+ def success_from(response)
381
+ (
382
+ response[:response_code] == APPROVED &&
383
+ TRANSACTION_ALREADY_ACTIONED.exclude?(response[:response_reason_code])
384
+ )
385
+ end
386
+
387
+ def message_from(response, avs_result, cvv_result)
388
+ if response[:response_code] == DECLINED
389
+ if CARD_CODE_ERRORS.include?(cvv_result.code)
390
+ return cvv_result.message
391
+ elsif(AVS_REASON_CODES.include?(response[:response_reason_code]) && AVS_ERRORS.include?(avs_result.code))
392
+ return avs_result.message
331
393
  end
332
394
  end
333
395
 
334
- (results[:response_reason_text] ? results[:response_reason_text].chomp('.') : '')
396
+ response[:response_reason_text]
397
+ end
398
+
399
+ def authorization_from(response)
400
+ [response[:transaction_id], response[:account_number]].join("#")
401
+ end
402
+
403
+ def split_authorization(authorization)
404
+ transaction_id, card_number = authorization.split("#")
405
+ [transaction_id, card_number]
335
406
  end
336
407
 
337
- def split(response)
338
- response[1..-2].split(/\$,\$/)
408
+ def fraud_review?(response)
409
+ (response[:response_code] == FRAUD_REVIEW)
339
410
  end
411
+
412
+ def truncate(value, max_size)
413
+ return nil unless value
414
+ value.to_s[0, max_size]
415
+ end
416
+
340
417
  end
341
418
  end
342
419
  end