activemerchant 1.2.1 → 1.3.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 (206) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +310 -294
  3. data/CONTRIBUTERS +13 -0
  4. data/README +55 -31
  5. data/Rakefile +21 -13
  6. data/lib/active_merchant.rb +7 -0
  7. data/lib/active_merchant/billing/avs_result.rb +95 -0
  8. data/lib/active_merchant/billing/base.rb +8 -3
  9. data/lib/active_merchant/billing/check.rb +61 -0
  10. data/lib/active_merchant/billing/credit_card.rb +104 -80
  11. data/lib/active_merchant/billing/credit_card_formatting.rb +11 -8
  12. data/lib/active_merchant/billing/credit_card_methods.rb +76 -32
  13. data/lib/active_merchant/billing/cvv_result.rb +38 -0
  14. data/lib/active_merchant/billing/expiry_date.rb +28 -0
  15. data/lib/active_merchant/billing/gateway.rb +47 -111
  16. data/lib/active_merchant/billing/gateways/authorize_net.rb +508 -121
  17. data/lib/active_merchant/billing/gateways/bogus.rb +26 -32
  18. data/lib/active_merchant/billing/gateways/brain_tree.rb +82 -70
  19. data/lib/active_merchant/billing/gateways/card_stream.rb +43 -15
  20. data/lib/active_merchant/billing/gateways/cyber_source.rb +9 -29
  21. data/lib/active_merchant/billing/gateways/data_cash.rb +18 -38
  22. data/lib/active_merchant/billing/gateways/efsnet.rb +23 -50
  23. data/lib/active_merchant/billing/gateways/eway.rb +8 -19
  24. data/lib/active_merchant/billing/gateways/exact.rb +17 -25
  25. data/lib/active_merchant/billing/gateways/linkpoint.rb +18 -25
  26. data/lib/active_merchant/billing/gateways/moneris.rb +9 -39
  27. data/lib/active_merchant/billing/gateways/net_registry.rb +113 -182
  28. data/lib/active_merchant/billing/gateways/netbilling.rb +168 -0
  29. data/lib/active_merchant/billing/gateways/pay_junction.rb +52 -73
  30. data/lib/active_merchant/billing/gateways/pay_secure.rb +120 -0
  31. data/lib/active_merchant/billing/gateways/payflow.rb +13 -14
  32. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +55 -37
  33. data/lib/active_merchant/billing/gateways/payflow/payflow_response.rb +4 -0
  34. data/lib/active_merchant/billing/gateways/payflow_express.rb +2 -4
  35. data/lib/active_merchant/billing/gateways/payment_express.rb +11 -30
  36. data/lib/active_merchant/billing/gateways/paypal.rb +3 -14
  37. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +36 -16
  38. data/lib/active_merchant/billing/gateways/paypal_express.rb +1 -2
  39. data/lib/active_merchant/billing/gateways/plugnpay.rb +7 -15
  40. data/lib/active_merchant/billing/gateways/protx.rb +24 -25
  41. data/lib/active_merchant/billing/gateways/psigate.rb +34 -71
  42. data/lib/active_merchant/billing/gateways/psl_card.rb +24 -19
  43. data/lib/active_merchant/billing/gateways/quickpay.rb +10 -24
  44. data/lib/active_merchant/billing/gateways/realex.rb +7 -19
  45. data/lib/active_merchant/billing/gateways/secure_pay_tech.rb +113 -0
  46. data/lib/active_merchant/billing/gateways/skip_jack.rb +437 -0
  47. data/lib/active_merchant/billing/gateways/trans_first.rb +5 -14
  48. data/lib/active_merchant/billing/gateways/trust_commerce.rb +100 -39
  49. data/lib/active_merchant/billing/gateways/usa_epay.rb +60 -55
  50. data/lib/active_merchant/billing/gateways/verifi.rb +32 -39
  51. data/lib/active_merchant/billing/gateways/viaklix.rb +31 -37
  52. data/lib/active_merchant/billing/integrations.rb +2 -0
  53. data/lib/active_merchant/billing/integrations/bogus.rb +5 -0
  54. data/lib/active_merchant/billing/integrations/bogus/return.rb +10 -0
  55. data/lib/active_merchant/billing/integrations/chronopay.rb +5 -0
  56. data/lib/active_merchant/billing/integrations/chronopay/return.rb +10 -0
  57. data/lib/active_merchant/billing/integrations/gestpay.rb +5 -0
  58. data/lib/active_merchant/billing/integrations/gestpay/return.rb +10 -0
  59. data/lib/active_merchant/billing/integrations/hi_trust.rb +26 -0
  60. data/lib/active_merchant/billing/integrations/hi_trust/helper.rb +58 -0
  61. data/lib/active_merchant/billing/integrations/hi_trust/notification.rb +57 -0
  62. data/lib/active_merchant/billing/integrations/hi_trust/return.rb +67 -0
  63. data/lib/active_merchant/billing/integrations/nochex.rb +5 -0
  64. data/lib/active_merchant/billing/integrations/nochex/return.rb +10 -0
  65. data/lib/active_merchant/billing/integrations/paypal.rb +5 -0
  66. data/lib/active_merchant/billing/integrations/paypal/return.rb +10 -0
  67. data/lib/active_merchant/billing/integrations/return.rb +35 -0
  68. data/lib/active_merchant/billing/integrations/two_checkout.rb +5 -0
  69. data/lib/active_merchant/billing/integrations/two_checkout/return.rb +17 -0
  70. data/lib/active_merchant/billing/response.rb +12 -8
  71. data/lib/active_merchant/lib/posts_data.rb +39 -7
  72. data/lib/active_merchant/lib/requires_parameters.rb +2 -3
  73. data/lib/active_merchant/lib/utils.rb +18 -0
  74. data/lib/active_merchant/lib/validateable.rb +3 -3
  75. data/lib/support/gateway_support.rb +22 -9
  76. data/script/destroy +14 -0
  77. data/script/generate +7 -7
  78. data/test/fixtures.yml +25 -3
  79. data/test/remote/gateways/remote_authorize_net_test.rb +145 -0
  80. data/test/remote/gateways/remote_brain_tree_test.rb +118 -0
  81. data/test/{remote_tests → remote/gateways}/remote_card_stream_test.rb +56 -68
  82. data/test/{remote_tests → remote/gateways}/remote_cyber_source_test.rb +21 -32
  83. data/test/{remote_tests → remote/gateways}/remote_data_cash_test.rb +2 -2
  84. data/test/{remote_tests → remote/gateways}/remote_efsnet_test.rb +22 -34
  85. data/test/{remote_tests → remote/gateways}/remote_eway_test.rb +18 -15
  86. data/test/{remote_tests → remote/gateways}/remote_exact_test.rb +20 -19
  87. data/test/{remote_tests → remote/gateways}/remote_linkpoint_test.rb +31 -63
  88. data/test/remote/gateways/remote_moneris_test.rb +82 -0
  89. data/test/{remote_tests → remote/gateways}/remote_net_registry_test.rb +19 -54
  90. data/test/remote/gateways/remote_netbilling_test.rb +70 -0
  91. data/test/{remote_tests → remote/gateways}/remote_pay_junction_test.rb +41 -60
  92. data/test/remote/gateways/remote_pay_secure_test.rb +39 -0
  93. data/test/{remote_tests → remote/gateways}/remote_payflow_express_test.rb +2 -2
  94. data/test/{remote_tests → remote/gateways}/remote_payflow_test.rb +34 -38
  95. data/test/{remote_tests → remote/gateways}/remote_payflow_uk_test.rb +13 -12
  96. data/test/{remote_tests → remote/gateways}/remote_payment_express_test.rb +26 -36
  97. data/test/{remote_tests → remote/gateways}/remote_paypal_express_test.rb +3 -3
  98. data/test/{remote_tests → remote/gateways}/remote_paypal_test.rb +25 -21
  99. data/test/{remote_tests → remote/gateways}/remote_plugnpay_test.rb +18 -16
  100. data/test/{remote_tests → remote/gateways}/remote_protx_test.rb +33 -33
  101. data/test/remote/gateways/remote_psigate_test.rb +50 -0
  102. data/test/{remote_tests → remote/gateways}/remote_psl_card_test.rb +27 -26
  103. data/test/{remote_tests → remote/gateways}/remote_quickpay_test.rb +48 -48
  104. data/test/{remote_tests → remote/gateways}/remote_realex_test.rb +30 -33
  105. data/test/remote/gateways/remote_secure_pay_tech_test.rb +37 -0
  106. data/test/remote/gateways/remote_secure_pay_test.rb +28 -0
  107. data/test/remote/gateways/remote_skipjack_test.rb +105 -0
  108. data/test/{remote_tests → remote/gateways}/remote_trans_first_test.rb +7 -10
  109. data/test/remote/gateways/remote_trust_commerce_test.rb +152 -0
  110. data/test/{remote_tests → remote/gateways}/remote_usa_epay_test.rb +11 -22
  111. data/test/{remote_tests → remote/gateways}/remote_verifi_test.rb +27 -27
  112. data/test/{remote_tests → remote/gateways}/remote_viaklix_test.rb +8 -18
  113. data/test/{remote_tests → remote/integrations}/remote_gestpay_integration_test.rb +1 -1
  114. data/test/{remote_tests → remote/integrations}/remote_paypal_integration_test.rb +1 -1
  115. data/test/test_helper.rb +102 -61
  116. data/test/unit/avs_result_test.rb +59 -0
  117. data/test/unit/base_test.rb +33 -39
  118. data/test/unit/check_test.rb +76 -0
  119. data/test/unit/credit_card_formatting_test.rb +10 -15
  120. data/test/unit/credit_card_methods_test.rb +132 -17
  121. data/test/unit/credit_card_test.rb +157 -228
  122. data/test/unit/cvv_result_test.rb +33 -0
  123. data/test/unit/expiry_date_test.rb +21 -0
  124. data/test/unit/gateways/authorize_net_test.rb +180 -40
  125. data/test/unit/gateways/bogus_test.rb +2 -3
  126. data/test/unit/gateways/brain_tree_test.rb +63 -29
  127. data/test/unit/gateways/card_stream_test.rb +59 -6
  128. data/test/unit/gateways/cyber_source_test.rb +59 -40
  129. data/test/unit/gateways/data_cash_test.rb +82 -1
  130. data/test/unit/gateways/efsnet_test.rb +97 -44
  131. data/test/unit/gateways/eway_test.rb +55 -42
  132. data/test/unit/gateways/exact_test.rb +93 -55
  133. data/test/unit/gateways/gateway_test.rb +7 -0
  134. data/test/unit/gateways/linkpoint_test.rb +60 -58
  135. data/test/unit/gateways/moneris_test.rb +67 -76
  136. data/test/unit/gateways/net_registry_test.rb +351 -419
  137. data/test/unit/gateways/netbilling_test.rb +54 -0
  138. data/test/unit/gateways/pay_junction_test.rb +108 -46
  139. data/test/unit/gateways/pay_secure_test.rb +71 -0
  140. data/test/unit/gateways/payflow_express_test.rb +0 -8
  141. data/test/unit/gateways/payflow_test.rb +136 -65
  142. data/test/unit/gateways/payflow_uk_test.rb +0 -38
  143. data/test/unit/gateways/payment_express_test.rb +31 -51
  144. data/test/unit/gateways/paypal_express_test.rb +8 -2
  145. data/test/unit/gateways/paypal_test.rb +213 -54
  146. data/test/unit/gateways/plugnpay_test.rb +39 -32
  147. data/test/unit/gateways/protx_test.rb +45 -33
  148. data/test/unit/gateways/psigate_test.rb +146 -87
  149. data/test/unit/gateways/psl_card_test.rb +37 -24
  150. data/test/unit/gateways/quickpay_test.rb +33 -46
  151. data/test/unit/gateways/realex_test.rb +32 -31
  152. data/test/unit/gateways/secure_pay_tech_test.rb +44 -0
  153. data/test/unit/gateways/secure_pay_test.rb +35 -26
  154. data/test/unit/gateways/skip_jack_test.rb +125 -0
  155. data/test/unit/gateways/trans_first_test.rb +24 -37
  156. data/test/unit/gateways/trust_commerce_test.rb +47 -26
  157. data/test/unit/gateways/usa_epay_test.rb +52 -41
  158. data/test/unit/gateways/verifi_test.rb +41 -35
  159. data/test/unit/gateways/viaklix_test.rb +38 -32
  160. data/test/unit/generators/test_gateway_generator.rb +46 -0
  161. data/test/unit/generators/test_generator_helper.rb +20 -0
  162. data/test/unit/generators/test_integration_generator.rb +53 -0
  163. data/test/unit/integrations/action_view_helper_test.rb +7 -14
  164. data/test/unit/integrations/bogus_module_test.rb +4 -0
  165. data/test/unit/integrations/chronopay_module_test.rb +4 -0
  166. data/test/unit/integrations/gestpay_module_test.rb +4 -0
  167. data/test/unit/integrations/helpers/hi_trust_helper_test.rb +16 -0
  168. data/test/unit/integrations/hi_trust_module_test.rb +13 -0
  169. data/test/unit/integrations/nochex_module_test.rb +4 -0
  170. data/test/unit/integrations/notifications/hi_trust_notification_test.rb +38 -0
  171. data/test/unit/integrations/paypal_module_test.rb +4 -0
  172. data/test/unit/integrations/returns/chronopay_return_test.rb +11 -0
  173. data/test/unit/integrations/returns/gestpay_return_test.rb +10 -0
  174. data/test/unit/integrations/returns/hi_trust_return_test.rb +24 -0
  175. data/test/unit/integrations/returns/nochex_return_test.rb +10 -0
  176. data/test/unit/integrations/returns/paypal_return_test.rb +10 -0
  177. data/test/unit/integrations/returns/return_test.rb +11 -0
  178. data/test/unit/integrations/returns/two_checkout_return_test.rb +24 -0
  179. data/test/unit/integrations/two_checkout_module_test.rb +4 -0
  180. data/test/unit/posts_data_test.rb +86 -0
  181. data/test/unit/response_test.rb +15 -1
  182. data/test/unit/utils_test.rb +7 -0
  183. data/test/unit/validateable_test.rb +10 -6
  184. metadata +180 -142
  185. metadata.gz.sig +0 -0
  186. data/lib/active_merchant/billing/gateways/paypal/api_cert_chain.crt +0 -35
  187. data/script/generator/base.rb +0 -45
  188. data/script/generator/generator.rb +0 -24
  189. data/script/generator/generators/gateway/gateway_generator.rb +0 -14
  190. data/script/generator/generators/gateway/templates/gateway.rb +0 -73
  191. data/script/generator/generators/gateway/templates/gateway_test.rb +0 -41
  192. data/script/generator/generators/gateway/templates/remote_gateway_test.rb +0 -56
  193. data/script/generator/generators/integration/integration_generator.rb +0 -25
  194. data/script/generator/generators/integration/templates/helper.rb +0 -34
  195. data/script/generator/generators/integration/templates/helper_test.rb +0 -54
  196. data/script/generator/generators/integration/templates/integration.rb +0 -18
  197. data/script/generator/generators/integration/templates/module_test.rb +0 -9
  198. data/script/generator/generators/integration/templates/notification.rb +0 -100
  199. data/script/generator/generators/integration/templates/notification_test.rb +0 -41
  200. data/script/generator/manifest.rb +0 -20
  201. data/test/remote_tests/remote_authorize_net_test.rb +0 -113
  202. data/test/remote_tests/remote_brain_tree_test.rb +0 -78
  203. data/test/remote_tests/remote_moneris_test.rb +0 -110
  204. data/test/remote_tests/remote_psigate_test.rb +0 -87
  205. data/test/remote_tests/remote_secure_pay_test.rb +0 -36
  206. data/test/remote_tests/remote_trust_commerce_test.rb +0 -136
@@ -1,18 +1,21 @@
1
1
  module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
3
  module CreditCardFormatting
4
- def format(number, format)
4
+
5
+ # This method is used to format numerical information pertaining to credit cards.
6
+ #
7
+ # format(2005, :two_digits) # => "05"
8
+ # format(05, :four_digits) # => "0005"
9
+ def format(number, option)
5
10
  return '' if number.blank?
6
11
 
7
- case format
8
- when :two_digits
9
- sprintf("%.2i", number)[-2..-1]
10
- when :four_digits
11
- sprintf("%.4i", number)[-4..-1]
12
- else
13
- number
12
+ case option
13
+ when :two_digits ; sprintf("%.2i", number)[-2..-1]
14
+ when :four_digits ; sprintf("%.4i", number)[-4..-1]
15
+ else number
14
16
  end
15
17
  end
18
+
16
19
  end
17
20
  end
18
21
  end
@@ -2,6 +2,21 @@ module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
3
  # Convenience methods that can be included into a custom Credit Card object, such as an ActiveRecord based Credit Card object.
4
4
  module CreditCardMethods
5
+ CARD_COMPANIES = {
6
+ 'visa' => /^4\d{12}(\d{3})?$/,
7
+ 'master' => /^(5[1-5]\d{4}|677189)\d{10}$/,
8
+ 'discover' => /^(6011|65\d{2})\d{12}$/,
9
+ 'american_express' => /^3[47]\d{13}$/,
10
+ 'diners_club' => /^3(0[0-5]|[68]\d)\d{11}$/,
11
+ 'jcb' => /^3528\d{12}$/,
12
+ 'switch' => /^6759\d{12}(\d{2,3})?$/,
13
+ 'solo' => /^6767\d{12}(\d{2,3})?$/,
14
+ 'dankort' => /^5019\d{12}$/,
15
+ 'maestro' => /^(5[06-8]|6\d)\d{10,17}$/,
16
+ 'forbrugsforeningen' => /^600722\d{10}$/,
17
+ 'laser' => /^(6304[89]\d{11}(\d{2,3})?|670695\d{13})$/
18
+ }
19
+
5
20
  def self.included(base)
6
21
  base.extend(ClassMethods)
7
22
  end
@@ -23,51 +38,44 @@ module ActiveMerchant #:nodoc:
23
38
  end
24
39
 
25
40
  module ClassMethods
26
- # Returns true if it validates. Optionally, you can pass a card type as an argument and make sure it is of the correct type.
27
- # == References
41
+ # Returns true if it validates. Optionally, you can pass a card type as an argument and
42
+ # make sure it is of the correct type.
43
+ #
44
+ # References:
28
45
  # - http://perl.about.com/compute/perl/library/nosearch/P073000.htm
29
46
  # - http://www.beachnet.com/~hstiles/cardtype.html
30
47
  def valid_number?(number)
31
- return true if ActiveMerchant::Billing::Base.gateway_mode == :test and ['1','2','3','success','failure','error'].include?(number.to_s)
32
-
33
- return false unless number.to_s.length >= 13
34
-
35
- sum = 0
36
- for i in 0..number.length
37
- weight = number[-1 * (i + 2), 1].to_i * (2 - (i % 2))
38
- sum += (weight < 10) ? weight : weight - 9
39
- end
40
-
41
- (number[-1,1].to_i == (10 - sum % 10) % 10)
48
+ valid_test_mode_card_number?(number) ||
49
+ valid_card_number_length?(number) &&
50
+ valid_checksum?(number)
42
51
  end
43
52
 
44
- # Regular expressions for the known card companies
45
- # http://en.wikipedia.org/wiki/Credit_card_number
46
- # http://www.barclaycardbusiness.co.uk/information_zone/processing/bin_rules.html
53
+ # Regular expressions for the known card companies.
54
+ #
55
+ # References:
56
+ # - http://en.wikipedia.org/wiki/Credit_card_number
57
+ # - http://www.barclaycardbusiness.co.uk/information_zone/processing/bin_rules.html
47
58
  def card_companies
48
- {
49
- 'visa' => /^4\d{12}(\d{3})?$/,
50
- 'master' => /^(5[1-5]\d{4}|677189)\d{10}$/,
51
- 'discover' => /^6011\d{12}$/,
52
- 'american_express' => /^3[47]\d{13}$/,
53
- 'diners_club' => /^3(0[0-5]|[68]\d)\d{11}$/,
54
- 'jcb' => /^3528\d{12}$/,
55
- 'switch' => /^6759\d{12}(\d{2,3})?$/,
56
- 'solo' => /^6767\d{12}(\d{2,3})?$/,
57
- 'dankort' => /^5019\d{12}$/,
58
- 'maestro' => /^(5[06-8]|6\d)\d{14}$/,
59
- 'forbrugsforeningen' => /^600722\d{10}$/,
60
- 'laser' => /^(6304[89]\d{11}(\d{2,3})?|670695\d{12})$/
61
- }
59
+ CARD_COMPANIES
62
60
  end
63
61
 
64
62
  # Returns a string containing the type of card from the list of known information below.
65
63
  # Need to check the cards in a particular order, as there is some overlap of the allowable ranges
64
+ #--
65
+ # TODO Refactor this method. We basically need to tighten up the Maestro Regexp.
66
+ #
67
+ # Right now the Maestro regexp overlaps with the MasterCard regexp (IIRC). If we can tighten
68
+ # things up, we can boil this whole thing down to something like...
69
+ #
70
+ # def type?(number)
71
+ # return 'visa' if valid_test_mode_card_number?(number)
72
+ # card_companies.find([nil]) { |type, regexp| number =~ regexp }.first.dup
73
+ # end
66
74
  #
67
75
  def type?(number)
68
- return 'visa' if ActiveMerchant::Billing::Base.gateway_mode == :test and ['1','2','3','success','failure','error'].include?(number.to_s)
76
+ return 'bogus' if valid_test_mode_card_number?(number)
69
77
 
70
- card_companies.reject{ |c,p| c == 'maestro' }.each do |company, pattern|
78
+ card_companies.reject { |c,p| c == 'maestro' }.each do |company, pattern|
71
79
  return company.dup if number =~ pattern
72
80
  end
73
81
 
@@ -75,6 +83,42 @@ module ActiveMerchant #:nodoc:
75
83
 
76
84
  return nil
77
85
  end
86
+
87
+ def last_digits(number)
88
+ number.to_s.slice(-4..-1) if number.to_s.length >= 4
89
+ end
90
+
91
+ def mask(number)
92
+ "XXXX-XXXX-XXXX-#{last_digits(number)}" if number.to_s.length >= 4
93
+ end
94
+
95
+ # Checks to see if the calculated type matches the specified type
96
+ def matching_type?(number, type)
97
+ type?(number) == type
98
+ end
99
+
100
+ private
101
+
102
+ def valid_card_number_length?(number) #:nodoc:
103
+ number.to_s.length >= 12
104
+ end
105
+
106
+ def valid_test_mode_card_number?(number) #:nodoc:
107
+ ActiveMerchant::Billing::Base.test? &&
108
+ %w[1 2 3 success failure error].include?(number.to_s)
109
+ end
110
+
111
+ # Checks the validity of a card number by use of the the Luhn Algorithm.
112
+ # Please see http://en.wikipedia.org/wiki/Luhn_algorithm for details.
113
+ def valid_checksum?(number) #:nodoc:
114
+ sum = 0
115
+ for i in 0..number.length
116
+ weight = number[-1 * (i + 2), 1].to_i * (2 - (i % 2))
117
+ sum += (weight < 10) ? weight : weight - 9
118
+ end
119
+
120
+ (number[-1,1].to_i == (10 - sum % 10) % 10)
121
+ end
78
122
  end
79
123
  end
80
124
  end
@@ -0,0 +1,38 @@
1
+ module ActiveMerchant
2
+ module Billing
3
+ # Result of the Card Verification Value check
4
+ # http://www.bbbonline.org/eExport/doc/MerchantGuide_cvv2.pdf
5
+ # Check additional codes from cybersource website
6
+ class CVVResult
7
+
8
+ MESSAGES = {
9
+ 'D' => 'Suspicious transaction',
10
+ 'I' => 'Failed data validation check',
11
+ 'M' => 'Match',
12
+ 'N' => 'No Match',
13
+ 'P' => 'Not Processed',
14
+ 'S' => 'Should have been present',
15
+ 'U' => 'Issuer unable to process request',
16
+ 'X' => 'Card does not support verification'
17
+ }
18
+
19
+ def self.messages
20
+ MESSAGES
21
+ end
22
+
23
+ attr_reader :code, :message
24
+
25
+ def initialize(code)
26
+ @code = code.upcase unless code.blank?
27
+ @message = MESSAGES[@code]
28
+ end
29
+
30
+ def to_hash
31
+ {
32
+ 'code' => code,
33
+ 'message' => message
34
+ }
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,28 @@
1
+ module ActiveMerchant
2
+ module Billing
3
+ class CreditCard
4
+ class ExpiryDate #:nodoc:
5
+ attr_reader :month, :year
6
+ def initialize(month, year)
7
+ @month = month
8
+ @year = year
9
+ end
10
+
11
+ def expired? #:nodoc:
12
+ Time.now > expiration rescue true
13
+ end
14
+
15
+ def expiration #:nodoc:
16
+ Time.parse("#{month}/#{month_days}/#{year} 23:59:59") rescue Time.at(0)
17
+ end
18
+
19
+ private
20
+ def month_days
21
+ mdays = [nil,31,28,31,30,31,30,31,31,30,31,30,31]
22
+ mdays[2] = 29 if Date.leap?(year)
23
+ mdays[month]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,6 +1,5 @@
1
1
  require 'net/http'
2
2
  require 'net/https'
3
- require 'digest/md5'
4
3
  require 'active_merchant/billing/response'
5
4
 
6
5
  module ActiveMerchant #:nodoc:
@@ -9,93 +8,69 @@ module ActiveMerchant #:nodoc:
9
8
  # == Description
10
9
  # The Gateway class is the base class for all ActiveMerchant gateway implementations.
11
10
  #
12
- # The list of gateway functions that concrete gateway classes can and should implement include
13
- # the following:
11
+ # The standard list of gateway functions that most concrete gateway subclasses implement is:
14
12
  #
15
13
  # * <tt>purchase(money, creditcard, options = {})</tt>
16
14
  # * <tt>authorize(money, creditcard, options = {})</tt>
17
15
  # * <tt>capture(money, authorization, options = {})</tt>
18
16
  # * <tt>void(identification, options = {})</tt>
19
17
  # * <tt>credit(money, identification, options = {})</tt>
20
- #
21
- # == Setting Up Your Gateway
22
- # Aside from the obvious authorization parameters (login and password), you can set up your
23
- # gateway using numerous options. Be sure to reference your gateway of choice's documentation
24
- # before overriding it's default values that may be defined.
25
- #
26
- # * <tt>Gateway.default_currency</tt>: sets the default currency if none is provided. See
27
- # http://en.wikipedia.org/wiki/ISO_4217#Active_codes for active currency codes.
28
- #
29
- # * <tt>Gateway.supported_countries</tt>: sets the countries of _merchants_ the gateway supports.
30
- #
31
- # * <tt>Gateway.supported_cardtypes</tt>: sets the card types supported by the gateway.
32
- #
33
- # * <tt>Gateway.homepage_url</tt>: sets the URL at which the gateway may be found.
34
- #
35
- # * <tt>Gateway.display_name</tt>: sets the name of the gateway for display purposes, such as generating documentation.
36
- #
37
- # * <tt>Gateway.application_id</tt>: This is the application making calls to the gateway. This
38
- # is useful for things like the Paypal build notation (BN) id fields.
39
- #
40
- # * <tt>Gateway.money_format</tt>: this attribute may be set to <tt>:dollars</tt> or
41
- # <tt>:cents</tt>. Use this to set the expected money format you'll be inputting.
42
18
  #
19
+ # Some gateways include features for recurring billing
20
+ #
21
+ # * <tt>recurring(money, creditcard, options = {})</tt>
22
+ #
23
+ # Some gateways also support features for storing credit cards:
24
+ #
25
+ # * <tt>store(creditcard, options = {})</tt>
26
+ # * <tt>unstore(identification, options = {})</tt>
43
27
  #
44
- # Gateway.money_format = :dollars # => 12.50
45
- # Gateway.money_format = :cents # => 1250
46
- #
47
- #
48
- # == Testing Your Code
49
- # There are two kinds of tests performed with your code: local and remote.
50
- #
51
- # === Local Tests
52
- # Before running any remote tests, it's best to ensure that your code covers the basics
53
- # locally. Local tests run on your own machine and will run faster than remote tests.
54
- #
55
- # To run a local test, first ensure that your gateway is in test mode:
56
- #
57
- # ActiveMerchant::Base.mode = :test
58
- #
59
- # (See ActiveMerchant::Base for more details.) This is often best set in your test's +setup+
60
- # or +teardown+ methods, if you are using Test::Unit.
61
- #
62
- # The next step is to use one of three test credit card numbers:
63
- #
64
- # <tt>1</tt>:: Result will be successful
65
- # <tt>2</tt>:: Result will be a failure
66
- # <tt>3</tt>:: Result will raise a miscellaneous error
67
- #
68
- # For examples of test requests, please see your gateway of interest's unit test code.
69
- #
70
- # === Remote Tests
71
- # Remote tests aren't mandatory, but it's not a bad idea to write them to ensure everything
72
- # works as expected. You'll first need authorization parameters from the gateway you'll be
73
- # working with. Once you have these values you'll be able to use ActiveMerchant to run test
74
- # requests.
75
- #
76
- # As with local tests, first ensure that you are in test mode:
77
- #
78
- # ActiveMerchant::Base.mode = :test
79
- #
80
- # (See ActiveMerchant::Base for more details.)
81
- #
82
- # Test requests may then be made using appropriate parameters provided by your gateway of
83
- # choice. For instance, the Moneris gateway provides a test MasterCard and Visa number that
84
- # one may use to process test purchases and authorization requests.
85
- #
86
- # Given that these remote tests will take longer to run than local tests, it is recommended
87
- # that you comment them out, or disable them when not required.
28
+ # === Gateway Options
29
+ # The options hash consists of the following options:
30
+ #
31
+ # * <tt>:order_id</tt> - The order number
32
+ # * <tt>:ip</tt> - The IP address of the customer making the purchase
33
+ # * <tt>:customer</tt> - The name, customer number, or other information that identifies the customer
34
+ # * <tt>:invoice</tt> - The invoice number
35
+ # * <tt>:merchant</tt> - The name or description of the merchant offering the product
36
+ # * <tt>:description</tt> - A description of the transaction
37
+ # * <tt>:email</tt> - The email address of the customer
38
+ # * <tt>:currency</tt> - The currency of the transaction. Only important when you are using a currency that is not the default with a gateway that supports multiple currencies.
39
+ # * <tt>:billing_address</tt> - A hash containing the billing address of the customer.
40
+ # * <tt>:shipping_address</tt> - A hash containing the shipping address of the customer.
41
+ #
42
+ # The <tt>:billing_address</tt>, and <tt>:shipping_address</tt> hashes can have the following keys:
43
+ #
44
+ # * <tt>:name</tt> - The full name of the customer.
45
+ # * <tt>:company</tt> - The company name of the customer.
46
+ # * <tt>:address1</tt> - The primary street address of the customer.
47
+ # * <tt>:address2</tt> - Additional line of address information.
48
+ # * <tt>:city</tt> - The city of the customer.
49
+ # * <tt>:state</tt> - The state of the customer. The 2 digit code for US and Canadian addresses. The full name of the state or province for foreign addresses.
50
+ # * <tt>:country</tt> - The [ISO 3166-1-alpha-2 code](http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm) for the customer.
51
+ # * <tt>:zip</tt> - The zip or postal code of the customer.
52
+ # * <tt>:phone</tt> - The phone number of the customer.
53
+ #
54
+ # == Implmenting new gateways
55
+ #
56
+ # See the {ActiveMerchant Guide to Contributing}[http://code.google.com/p/activemerchant/wiki/Contributing]
57
+ #
88
58
  class Gateway
89
59
  include PostsData
90
60
  include RequiresParameters
91
61
  include CreditCardFormatting
92
-
93
- ## Constants
62
+ include Utils
94
63
 
95
64
  DEBIT_CARDS = [ :switch, :solo ]
96
65
 
97
- ## Attributes
66
+ cattr_reader :implementations
67
+ @@implementations = []
98
68
 
69
+ def self.inherited(subclass)
70
+ super
71
+ @@implementations << subclass
72
+ end
73
+
99
74
  # The format of the amounts used by the gateway
100
75
  # :dollars => '12.50'
101
76
  # :cents => '1250'
@@ -128,8 +103,6 @@ module ActiveMerchant #:nodoc:
128
103
  supported_cardtypes.include?(card_type.to_sym)
129
104
  end
130
105
 
131
- ## Instance Methods
132
-
133
106
  # Initialize a new gateway.
134
107
  #
135
108
  # See the documentation for the gateway you will be using to make sure there are no other
@@ -148,32 +121,6 @@ module ActiveMerchant #:nodoc:
148
121
  self.class.name.scan(/\:\:(\w+)Gateway/).flatten.first
149
122
  end
150
123
 
151
- # This is used to check if our credit card number implies that we are seeking a test
152
- # Response. Of course, this returns false if we are not in test mode.
153
- #
154
- # Recognized values:
155
- # <tt>1</tt>:: Result will be successful
156
- # <tt>2</tt>:: Result will be a failure
157
- # <tt>3</tt>:: Result will raise a miscellaneous error
158
- #
159
- # All other values will not be recognized.
160
- #--
161
- # TODO Refactor this method. It's kind of on the ugly side of things.
162
- def test_result_from_cc_number(card_number)
163
- return false unless test?
164
-
165
- case card_number.to_s
166
- when '1', 'success'
167
- Response.new(true, 'Successful test mode response', {:receiptid => '#0001'}, :test => true, :authorization => '5555')
168
- when '2', 'failure'
169
- Response.new(false, 'Failed test mode response', {:receiptid => '#0001'}, :test => true)
170
- when '3', 'error'
171
- raise Error, 'big bad exception'
172
- else
173
- false
174
- end
175
- end
176
-
177
124
  # Return a String with the amount in the appropriate format
178
125
  #--
179
126
  # TODO Refactor this method. It's a tad on the ugly side.
@@ -201,17 +148,6 @@ module ActiveMerchant #:nodoc:
201
148
  return false if credit_card.type.blank?
202
149
  DEBIT_CARDS.include?(credit_card.type.to_sym)
203
150
  end
204
-
205
- def generate_unique_id
206
- md5 = Digest::MD5.new
207
- now = Time.now
208
- md5 << now.to_s
209
- md5 << String(now.usec)
210
- md5 << String(rand(0))
211
- md5 << String($$)
212
- md5 << self.class.name
213
- md5.hexdigest
214
- end
215
151
  end
216
152
  end
217
153
  end
@@ -1,168 +1,292 @@
1
1
  module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
-
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
+ # ==== Automated Recurring Billing (ARB)
19
+ #
20
+ # Automated Recurring Billing (ARB) is an optional service for submitting and managing recurring, or subscription-based, transactions.
21
+ #
22
+ # To use recurring, update_recurring, and cancel_recurring ARB must be enabled for your account.
23
+ #
24
+ # Information about ARB is available on the {Authorize.Net website}[http://www.authorize.net/solutions/merchantsolutions/merchantservices/automatedrecurringbilling/].
25
+ # Information about the ARB API is available at the {Authorize.Net Integration Center}[http://developer.authorize.net/]
4
26
  class AuthorizeNetGateway < Gateway
5
27
  API_VERSION = '3.1'
6
-
7
- class_inheritable_accessor :test_url, :live_url
8
-
28
+
29
+ class_inheritable_accessor :test_url, :live_url, :arb_test_url, :arb_live_url
30
+
9
31
  self.test_url = "https://test.authorize.net/gateway/transact.dll"
10
32
  self.live_url = "https://secure.authorize.net/gateway/transact.dll"
11
-
12
- APPROVED, DECLINED, ERROR = 1, 2, 3
33
+
34
+ self.arb_test_url = 'https://apitest.authorize.net/xml/v1/request.api'
35
+ self.arb_live_url = 'https://api.authorize.net/xml/v1/request.api'
36
+
37
+ APPROVED, DECLINED, ERROR, FRAUD_REVIEW = 1, 2, 3, 4
13
38
 
14
39
  RESPONSE_CODE, RESPONSE_REASON_CODE, RESPONSE_REASON_TEXT = 0, 2, 3
15
40
  AVS_RESULT_CODE, TRANSACTION_ID, CARD_CODE_RESPONSE_CODE = 5, 6, 38
16
41
 
17
- CARD_CODE_ERRORS = %w( N S )
18
-
19
- CARD_CODE_MESSAGES = {
20
- "M" => "Card verification number matched",
21
- "N" => "Card verification number didn't match",
22
- "P" => "Card verification number was not processed",
23
- "S" => "Card verification number should be on card but was not indicated",
24
- "U" => "Issuer was not certified for card verification"
25
- }
42
+ self.supported_countries = ['US']
43
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
44
+ self.homepage_url = 'http://www.authorize.net/'
45
+ self.display_name = 'Authorize.Net'
26
46
 
47
+ CARD_CODE_ERRORS = %w( N S )
27
48
  AVS_ERRORS = %w( A E N R W Z )
28
49
 
29
- AVS_MESSAGES = {
30
- "A" => "Street address matches billing information, zip/postal code does not",
31
- "B" => "Address information not provided for address verification check",
32
- "E" => "Address verification service error",
33
- "G" => "Non-U.S. card-issuing bank",
34
- "N" => "Neither street address nor zip/postal match billing information",
35
- "P" => "Address verification not applicable for this transaction",
36
- "R" => "Payment gateway was unavailable or timed out",
37
- "S" => "Address verification service not supported by issuer",
38
- "U" => "Address information is unavailable",
39
- "W" => "9-digit zip/postal code matches billing information, street address does not",
40
- "X" => "Street address and 9-digit zip/postal code matches billing information",
41
- "Y" => "Street address and 5-digit zip/postal code matches billing information",
42
- "Z" => "5-digit zip/postal code matches billing information, street address does not",
50
+ AUTHORIZE_NET_ARB_NAMESPACE = 'AnetApi/xml/v1/schema/AnetApiSchema.xsd'
51
+
52
+ RECURRING_ACTIONS = {
53
+ :create => 'ARBCreateSubscription',
54
+ :update => 'ARBUpdateSubscription',
55
+ :cancel => 'ARBCancelSubscription'
43
56
  }
44
-
45
- # URL
46
- attr_reader :url
47
- attr_reader :response
48
- attr_reader :options
49
-
50
- self.supported_countries = ['US']
51
- self.supported_cardtypes = [:visa, :master, :american_express, :discover]
52
- self.homepage_url = 'http://www.authorize.net/'
53
- self.display_name = 'Authorize.net'
54
57
 
58
+ # Creates a new AuthorizeNetGateway
59
+ #
60
+ # The gateway requires that a valid login and password be passed
61
+ # in the +options+ hash.
62
+ #
63
+ # ==== Options
64
+ #
65
+ # * <tt>:login</tt> -- The Authorize.Net API Login ID (REQUIRED)
66
+ # * <tt>:password</tt> -- The Authorize.Net Transaction Key. (REQUIRED)
67
+ # * <tt>:test</tt> -- +true+ or +false+. If true, perform transactions against the test server.
68
+ # Otherwise, perform transactions against the production server.
55
69
  def initialize(options = {})
56
70
  requires!(options, :login, :password)
57
71
  @options = options
58
72
  super
59
- end
60
-
73
+ end
74
+
75
+ # Performs an authorization, which reserves the funds on the customer's credit card, but does not
76
+ # charge the card.
77
+ #
78
+ # ==== Parameters
79
+ #
80
+ # * <tt>money</tt> -- The amount to be authorized. Either an Integer value in cents or a Money object.
81
+ # * <tt>creditcard</tt> -- The CreditCard details for the transaction.
82
+ # * <tt>options</tt> -- A hash of optional parameters.
61
83
  def authorize(money, creditcard, options = {})
62
84
  post = {}
63
85
  add_invoice(post, options)
64
- add_creditcard(post, creditcard)
65
- add_address(post, options)
86
+ add_creditcard(post, creditcard)
87
+ add_address(post, options)
66
88
  add_customer_data(post, options)
67
-
89
+
68
90
  commit('AUTH_ONLY', money, post)
69
91
  end
70
-
92
+
93
+ # Perform a purchase, which is essentially an authorization and capture in a single operation.
94
+ #
95
+ # ==== Parameters
96
+ #
97
+ # * <tt>money</tt> -- The amount to be purchased. Either an Integer value in cents or a Money object.
98
+ # * <tt>creditcard</tt> -- The CreditCard details for the transaction.
99
+ # * <tt>options</tt> -- A hash of optional parameters.
71
100
  def purchase(money, creditcard, options = {})
72
101
  post = {}
73
102
  add_invoice(post, options)
74
- add_creditcard(post, creditcard)
75
- add_address(post, options)
103
+ add_creditcard(post, creditcard)
104
+ add_address(post, options)
76
105
  add_customer_data(post, options)
77
-
106
+
78
107
  commit('AUTH_CAPTURE', money, post)
79
- end
80
-
108
+ end
109
+
110
+ # Captures the funds from an authorized transaction.
111
+ #
112
+ # ==== Parameters
113
+ #
114
+ # * <tt>money</tt> -- The amount to be captured. Either an Integer value in cents or a Money object.
115
+ # * <tt>authorization</tt> -- The authorization returned from the previous authorize request.
81
116
  def capture(money, authorization, options = {})
82
117
  post = {:trans_id => authorization}
83
118
  add_customer_data(post, options)
84
119
  commit('PRIOR_AUTH_CAPTURE', money, post)
85
120
  end
86
121
 
122
+ # Void a previous transaction
123
+ #
124
+ # ==== Parameters
125
+ #
126
+ # * <tt>authorization</tt> - The authorization returned from the previous authorize request.
87
127
  def void(authorization, options = {})
88
128
  post = {:trans_id => authorization}
89
129
  commit('VOID', nil, post)
90
130
  end
91
-
131
+
132
+ # Credit an account.
133
+ #
134
+ # This transaction is also referred to as a Refund and indicates to the gateway that
135
+ # money should flow from the merchant to the customer.
136
+ #
137
+ # ==== Parameters
138
+ #
139
+ # * <tt>money</tt> -- The amount to be credited to the customer. Either an Integer value in cents or a Money object.
140
+ # * <tt>identification</tt> -- The ID of the original transaction against which the credit is being issued.
141
+ # * <tt>options</tt> -- A hash of parameters.
142
+ #
143
+ # ==== Options
144
+ #
145
+ # * <tt>:card_number</tt> -- The credit card number the credit is being issued to. (REQUIRED)
92
146
  def credit(money, identification, options = {})
93
147
  requires!(options, :card_number)
94
-
148
+
95
149
  post = { :trans_id => identification,
96
150
  :card_num => options[:card_number]
97
151
  }
152
+ add_invoice(post, options)
98
153
 
99
154
  commit('CREDIT', money, post)
100
155
  end
101
-
102
- private
156
+
157
+ # Create a recurring payment.
158
+ #
159
+ # This transaction creates a new Automated Recurring Billing (ARB) subscription. Your account must have ARB enabled.
160
+ #
161
+ # ==== Parameters
162
+ #
163
+ # * <tt>money</tt> -- The amount to be charged to the customer at each interval. Either an Integer value in cents or
164
+ # a Money object.
165
+ # * <tt>creditcard</tt> -- The CreditCard details for the transaction.
166
+ # * <tt>options</tt> -- A hash of parameters.
167
+ #
168
+ # ==== Options
169
+ #
170
+ # * <tt>:interval</tt> -- A hash containing information about the interval of time between payments. Must
171
+ # contain the keys <tt>:length</tt> and <tt>:unit</tt>. <tt>:unit</tt> can be either <tt>:months</tt> or <tt>:days</tt>.
172
+ # If <tt>:unit</tt> is <tt>:months</tt> then <tt>:interval</tt> must be an integer between 1 and 12 inclusive.
173
+ # If <tt>:unit</tt> is <tt>:days</tt> then <tt>:interval</tt> must be an integer between 7 and 365 inclusive.
174
+ # For example, to charge the customer once every three months the hash would be
175
+ # +{ :unit => :months, :interval => 3 }+ (REQUIRED)
176
+ # * <tt>:duration</tt> -- A hash containing keys for the <tt>:start_date</tt> the subscription begins (also the date the
177
+ # initial billing occurs) and the total number of billing <tt>:occurences</tt> or payments for the subscription. (REQUIRED)
178
+ def recurring(money, creditcard, options={})
179
+ requires!(options, :interval, :duration, :billing_address)
180
+ requires!(options[:interval], :length, [:unit, :days, :months])
181
+ requires!(options[:duration], :start_date, :occurrences)
182
+ requires!(options[:billing_address], :first_name, :last_name)
183
+
184
+ options[:credit_card] = creditcard
185
+ options[:amount] = money
186
+
187
+ request = build_recurring_request(:create, options)
188
+ recurring_commit(:create, request)
189
+ end
190
+
191
+ # Update a recurring payment's details.
192
+ #
193
+ # This transaction updates an existing Automated Recurring Billing (ARB) subscription. Your account must have ARB enabled
194
+ # and the subscription must have already been created previously by calling +recurring()+. The ability to change certain
195
+ # details about a recurring payment is dependent on transaction history and cannot be determined until after calling
196
+ # +update_recurring()+. See the ARB XML Guide for such conditions.
197
+ #
198
+ # ==== Parameters
199
+ #
200
+ # * <tt>options</tt> -- A hash of parameters.
201
+ #
202
+ # ==== Options
203
+ #
204
+ # * <tt>:subscription_id</tt> -- A string containing the <tt>:subscription_id</tt> of the recurring payment already in place
205
+ # for a given credit card. (REQUIRED)
206
+ def update_recurring(options={})
207
+ requires!(options, :subscription_id)
208
+ request = build_recurring_request(:update, options)
209
+ recurring_commit(:update, request)
210
+ end
211
+
212
+ # Cancel a recurring payment.
213
+ #
214
+ # This transaction cancels an existing Automated Recurring Billing (ARB) subscription. Your account must have ARB enabled
215
+ # and the subscription must have already been created previously by calling recurring()
216
+ #
217
+ # ==== Parameters
218
+ #
219
+ # * <tt>subscription_id</tt> -- A string containing the +subscription_id+ of the recurring payment already in place
220
+ # for a given credit card. (REQUIRED)
221
+ def cancel_recurring(subscription_id)
222
+ request = build_recurring_request(:cancel, :subscription_id => subscription_id)
223
+ recurring_commit(:cancel, request)
224
+ end
225
+
226
+ private
227
+
103
228
  def commit(action, money, parameters)
104
- parameters[:amount] = amount(money) unless action == 'VOID'
105
-
229
+ parameters[:amount] = amount(money) unless action == 'VOID'
230
+
106
231
  # Only activate the test_request when the :test option is passed in
107
- parameters[:test_request] = @options[:test] ? 'TRUE' : 'FALSE'
108
-
109
- if result = test_result_from_cc_number(parameters[:card_num])
110
- return result
111
- end
112
-
232
+ parameters[:test_request] = @options[:test] ? 'TRUE' : 'FALSE'
233
+
113
234
  url = test? ? self.test_url : self.live_url
114
235
  data = ssl_post url, post_data(action, parameters)
115
236
 
116
- @response = parse(data)
237
+ response = parse(data)
117
238
 
118
- success = @response[:response_code] == APPROVED
119
- message = message_from(@response)
239
+ message = message_from(response)
120
240
 
121
- # Return the response. The authorization can be taken out of the transaction_id
241
+ # Return the response. The authorization can be taken out of the transaction_id
122
242
  # Test Mode on/off is something we have to parse from the response text.
123
243
  # It usually looks something like this
124
244
  #
125
245
  # (TESTMODE) Successful Sale
126
- #
127
-
128
246
  test_mode = test? || message =~ /TESTMODE/
129
-
130
- Response.new(success, message, @response,
131
- :test => test_mode,
132
- :authorization => @response[:transaction_id]
133
- )
247
+
248
+ Response.new(success?(response), message, response,
249
+ :test => test_mode,
250
+ :authorization => response[:transaction_id],
251
+ :fraud_review => fraud_review?(response),
252
+ :avs_result => { :code => response[:avs_result_code] },
253
+ :cvv_result => response[:card_code]
254
+ )
255
+ end
256
+
257
+ def success?(response)
258
+ response[:response_code] == APPROVED
259
+ end
260
+
261
+ def fraud_review?(response)
262
+ response[:response_code] == FRAUD_REVIEW
134
263
  end
135
-
264
+
136
265
  def parse(body)
137
266
  fields = split(body)
138
-
139
- results = {
267
+
268
+ results = {
140
269
  :response_code => fields[RESPONSE_CODE].to_i,
141
270
  :response_reason_code => fields[RESPONSE_REASON_CODE],
142
271
  :response_reason_text => fields[RESPONSE_REASON_TEXT],
143
272
  :avs_result_code => fields[AVS_RESULT_CODE],
144
273
  :transaction_id => fields[TRANSACTION_ID],
145
- :card_code => fields[CARD_CODE_RESPONSE_CODE]
146
- }
147
-
148
-
149
- results[:card_code_message] = CARD_CODE_MESSAGES[results[:card_code]] if results[:card_code]
150
- results[:avs_message] = AVS_MESSAGES[results[:avs_result_code]] if results[:avs_result_code]
151
-
274
+ :card_code => fields[CARD_CODE_RESPONSE_CODE]
275
+ }
152
276
  results
153
- end
277
+ end
154
278
 
155
279
  def post_data(action, parameters = {})
156
280
  post = {}
157
281
 
158
- post[:version] = API_VERSION
159
- post[:login] = @options[:login]
160
- post[:tran_key] = @options[:password]
282
+ post[:version] = API_VERSION
283
+ post[:login] = @options[:login]
284
+ post[:tran_key] = @options[:password]
161
285
  post[:relay_response] = "FALSE"
162
- post[:type] = action
163
- post[:delim_data] = "TRUE"
164
- post[:delim_char] = ","
165
- post[:encap_char] = "$"
286
+ post[:type] = action
287
+ post[:delim_data] = "TRUE"
288
+ post[:delim_char] = ","
289
+ post[:encap_char] = "$"
166
290
 
167
291
  request = post.merge(parameters).collect { |key, value| "x_#{key}=#{CGI.escape(value.to_s)}" }.join("&")
168
292
  request
@@ -172,43 +296,43 @@ module ActiveMerchant #:nodoc:
172
296
  post[:invoice_num] = options[:order_id]
173
297
  post[:description] = options[:description]
174
298
  end
175
-
176
- def add_creditcard(post, creditcard)
177
- post[:card_num] = creditcard.number
178
- post[:card_code] = creditcard.verification_value if creditcard.verification_value?
179
- post[:exp_date] = expdate(creditcard)
299
+
300
+ def add_creditcard(post, creditcard)
301
+ post[:card_num] = creditcard.number
302
+ post[:card_code] = creditcard.verification_value if creditcard.verification_value?
303
+ post[:exp_date] = expdate(creditcard)
180
304
  post[:first_name] = creditcard.first_name
181
305
  post[:last_name] = creditcard.last_name
182
306
  end
183
-
307
+
184
308
  def add_customer_data(post, options)
185
309
  if options.has_key? :email
186
310
  post[:email] = options[:email]
187
311
  post[:email_customer] = false
188
312
  end
189
-
313
+
190
314
  if options.has_key? :customer
191
315
  post[:cust_id] = options[:customer]
192
316
  end
193
-
317
+
194
318
  if options.has_key? :ip
195
319
  post[:customer_ip] = options[:ip]
196
- end
320
+ end
197
321
  end
198
322
 
199
- def add_address(post, options)
323
+ def add_address(post, options)
200
324
 
201
325
  if address = options[:billing_address] || options[:address]
202
- post[:address] = address[:address1].to_s
203
- post[:company] = address[:company].to_s
204
- post[:phone] = address[:phone].to_s
205
- post[:zip] = address[:zip].to_s
206
- post[:city] = address[:city].to_s
207
- post[:country] = address[:country].to_s
208
- post[:state] = address[:state].blank? ? 'n/a' : address[:state]
209
- end
210
- end
211
-
326
+ post[:address] = address[:address1].to_s
327
+ post[:company] = address[:company].to_s
328
+ post[:phone] = address[:phone].to_s
329
+ post[:zip] = address[:zip].to_s
330
+ post[:city] = address[:city].to_s
331
+ post[:country] = address[:country].to_s
332
+ post[:state] = address[:state].blank? ? 'n/a' : address[:state]
333
+ end
334
+ end
335
+
212
336
  # Make a ruby type out of the response string
213
337
  def normalize(field)
214
338
  case field
@@ -217,30 +341,293 @@ module ActiveMerchant #:nodoc:
217
341
  when "" then nil
218
342
  when "null" then nil
219
343
  else field
220
- end
221
- end
222
-
223
- def message_from(results)
344
+ end
345
+ end
346
+
347
+ def message_from(results)
224
348
  if results[:response_code] == DECLINED
225
- return CARD_CODE_MESSAGES[results[:card_code]] if CARD_CODE_ERRORS.include?(results[:card_code])
226
- return AVS_MESSAGES[results[:avs_result_code]] if AVS_ERRORS.include?(results[:avs_result_code])
349
+ return CVVResult.messages[ results[:card_code] ] if CARD_CODE_ERRORS.include?(results[:card_code])
350
+ return AVSResult.messages[ results[:avs_result_code] ] if AVS_ERRORS.include?(results[:avs_result_code])
227
351
  end
228
-
352
+
229
353
  return results[:response_reason_text].nil? ? '' : results[:response_reason_text][0..-2]
230
354
  end
231
-
355
+
232
356
  def expdate(creditcard)
233
357
  year = sprintf("%.4i", creditcard.year)
234
358
  month = sprintf("%.2i", creditcard.month)
235
359
 
236
360
  "#{month}#{year[-2..-1]}"
237
361
  end
238
-
362
+
239
363
  def split(response)
240
364
  response[1..-2].split(/\$,\$/)
241
365
  end
366
+
367
+ # ARB
368
+
369
+ # Builds recurring billing request
370
+ def build_recurring_request(action, options = {})
371
+ unless RECURRING_ACTIONS.include?(action)
372
+ raise StandardError, "Invalid Automated Recurring Billing Action: #{action}"
373
+ end
374
+
375
+ xml = Builder::XmlMarkup.new(:indent => 2)
376
+ xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8')
377
+ xml.tag!("#{RECURRING_ACTIONS[action]}Request", :xmlns => AUTHORIZE_NET_ARB_NAMESPACE) do
378
+ add_arb_merchant_authentication(xml)
379
+ # Merchant-assigned reference ID for the request
380
+ xml.tag!('refId', options[:ref_id]) if options[:ref_id]
381
+ send("build_arb_#{action}_subscription_request", xml, options)
382
+ end
383
+ end
384
+
385
+ # Contains the merchant’s payment gateway account authentication information
386
+ def add_arb_merchant_authentication(xml)
387
+ xml.tag!('merchantAuthentication') do
388
+ xml.tag!('name', @options[:login])
389
+ xml.tag!('transactionKey', @options[:password])
390
+ end
391
+ end
392
+
393
+ # Builds body for ARBCreateSubscriptionRequest
394
+ def build_arb_create_subscription_request(xml, options)
395
+ # Subscription
396
+ add_arb_subscription(xml, options)
397
+
398
+ xml.target!
399
+ end
400
+
401
+ # Builds body for ARBUpdateSubscriptionRequest
402
+ def build_arb_update_subscription_request(xml, options)
403
+ xml.tag!('subscriptionId', options[:subscription_id])
404
+ # Adds Subscription
405
+ add_arb_subscription(xml, options)
406
+
407
+ xml.target!
408
+ end
409
+
410
+ # Builds body for ARBCancelSubscriptionRequest
411
+ def build_arb_cancel_subscription_request(xml, options)
412
+ xml.tag!('subscriptionId', options[:subscription_id])
413
+
414
+ xml.target!
415
+ end
416
+
417
+ # Adds subscription information
418
+ def add_arb_subscription(xml, options)
419
+ xml.tag!('subscription') do
420
+ # Merchant-assigned name for the subscription (optional)
421
+ xml.tag!('name', options[:subscription_name]) if options[:subscription_name]
422
+ # Contains information about the payment schedule
423
+ add_arb_payment_schedule(xml, options)
424
+ # The amount to be billed to the customer
425
+ # for each payment in the subscription
426
+ xml.tag!('amount', amount(options[:amount])) if options[:amount]
427
+ if trial = options[:trial]
428
+ # The amount to be charged for each payment during a trial period (conditional)
429
+ xml.tag!('trialAmount', amount(trial[:amount])) if trial[:amount]
430
+ end
431
+ # Contains either the customer’s credit card
432
+ # or bank account payment information
433
+ add_arb_payment(xml, options)
434
+ # Contains order information (optional)
435
+ add_arb_order(xml, options)
436
+ # Contains information about the customer
437
+ add_arb_customer(xml, options)
438
+ # Contains the customer's billing address information
439
+ add_arb_address(xml, 'billTo', options[:billing_address])
440
+ # Contains the customer's shipping address information (optional)
441
+ add_arb_address(xml, 'shipTo', options[:shipping_address])
442
+ end
443
+ end
444
+
445
+ # Adds information about the interval of time between payments
446
+ def add_arb_interval(xml, options)
447
+ interval = options[:interval]
448
+ return unless interval
449
+ xml.tag!('interval') do
450
+ # The measurement of time, in association with the Interval Unit,
451
+ # that is used to define the frequency of the billing occurrences
452
+ xml.tag!('length', interval[:length])
453
+ # The unit of time, in association with the Interval Length,
454
+ # between each billing occurrence
455
+ xml.tag!('unit', interval[:unit].to_s)
456
+ end
457
+ end
458
+
459
+ # Adds information about the subscription duration
460
+ def add_arb_duration(xml, options)
461
+ duration = options[:duration]
462
+ return unless duration
463
+ # The date the subscription begins
464
+ # (also the date the initial billing occurs)
465
+ xml.tag!('startDate', duration[:start_date]) if duration[:start_date]
466
+ # Number of billing occurrences or payments for the subscription
467
+ xml.tag!('totalOccurrences', duration[:occurrences]) if duration[:occurrences]
468
+ end
469
+
470
+ def add_arb_payment_schedule(xml, options)
471
+ return unless options[:interval] || options[:duration]
472
+ xml.tag!('paymentSchedule') do
473
+ # Contains information about the interval of time between payments
474
+ add_arb_interval(xml, options)
475
+ add_arb_duration(xml, options)
476
+ if trial = options[:trial]
477
+ # Number of billing occurrences or payments in the trial period (optional)
478
+ xml.tag!('trialOccurrences', trial[:occurrences]) if trial[:occurrences]
479
+ end
480
+ end
481
+ end
482
+
483
+ # Adds customer's credit card or bank account payment information
484
+ def add_arb_payment(xml, options)
485
+ return unless options[:credit_card] || options[:bank_account]
486
+ xml.tag!('payment') do
487
+ # Contains the customer’s credit card information
488
+ add_arb_credit_card(xml, options)
489
+ # Contains the customer’s bank account information
490
+ add_arb_bank_account(xml, options)
491
+ end
492
+ end
493
+
494
+ # Adds customer’s credit card information
495
+ # Note: This element should only be included
496
+ # when the payment method is credit card.
497
+ def add_arb_credit_card(xml, options)
498
+ credit_card = options[:credit_card]
499
+ return unless credit_card
500
+ xml.tag!('creditCard') do
501
+ # The credit card number used for payment of the subscription
502
+ xml.tag!('cardNumber', credit_card.number)
503
+ # The expiration date of the credit card used for the subscription
504
+ xml.tag!('expirationDate', arb_expdate(credit_card))
505
+ end
506
+ end
507
+
508
+ # Adds customer’s bank account information
509
+ # Note: This element should only be included
510
+ # when the payment method is bank account.
511
+ def add_arb_bank_account(xml, options)
512
+ bank_account = options[:bank_account]
513
+ return unless bank_account
514
+ xml.tag!('bankAccount') do
515
+ # The type of bank account used for payment of the subscription
516
+ xml.tag!('accountType', bank_account[:account_type])
517
+ # The routing number of the customer’s bank
518
+ xml.tag!('routingNumber', bank_account[:routing_number])
519
+ # The bank account number used for payment of the subscription
520
+ xml.tag!('accountNumber', bank_account[:account_number])
521
+ # The full name of the individual associated
522
+ # with the bank account number
523
+ xml.tag!('nameOfAccount', bank_account[:name_of_account])
524
+ # The full name of the individual associated
525
+ # with the bank account number (optional)
526
+ xml.tag!('bankName', bank_account[:bank_name]) if bank_account[:bank_name]
527
+ # The type of electronic check transaction used for the subscription
528
+ xml.tag!('echeckType', bank_account[:echeck_type])
529
+ end
530
+ end
531
+
532
+ # Adds order information (optional)
533
+ def add_arb_order(xml, options)
534
+ order = options[:order]
535
+ return unless order
536
+ xml.tag!('order') do
537
+ # Merchant-assigned invoice number for the subscription (optional)
538
+ xml.tag!('invoiceNumber', order[:invoice_number])
539
+ # Description of the subscription (optional)
540
+ xml.tag!('description', order[:description])
541
+ end
542
+ end
543
+
544
+ # Adds information about the customer
545
+ def add_arb_customer(xml, options)
546
+ customer = options[:customer]
547
+ return unless customer
548
+ xml.tag!('customer') do
549
+ xml.tag!('type', customer[:type]) if customer[:type]
550
+ xml.tag!('id', customer[:id]) if customer[:id]
551
+ xml.tag!('email', customer[:email]) if customer[:email]
552
+ xml.tag!('phoneNumber', customer[:phone_number]) if customer[:phone_number]
553
+ xml.tag!('faxNumber', customer[:fax_number]) if customer[:fax_number]
554
+ add_arb_drivers_license(xml, options)
555
+ xml.tag!('taxId', customer[:tax_id]) if customer[:tax_id]
556
+ end
557
+ end
558
+
559
+ # Adds the customer's driver's license information (conditional)
560
+ def add_arb_drivers_license(xml, options)
561
+ return unless customer = options[:customer]
562
+ return unless drivers_license = customer[:drivers_license]
563
+ xml.tag!('driversLicense') do
564
+ # The customer's driver's license number
565
+ xml.tag!('number', drivers_license[:number])
566
+ # The customer's driver's license state
567
+ xml.tag!('state', drivers_license[:state])
568
+ # The customer's driver's license date of birth
569
+ xml.tag!('dateOfBirth', drivers_license[:date_of_birth])
570
+ end
571
+ end
572
+
573
+ # Adds address information
574
+ def add_arb_address(xml, container_name, address)
575
+ return if address.blank?
576
+ xml.tag!(container_name) do
577
+ xml.tag!('firstName', address[:first_name])
578
+ xml.tag!('lastName', address[:last_name])
579
+ xml.tag!('company', address[:company])
580
+ xml.tag!('address', address[:address1])
581
+ xml.tag!('city', address[:city])
582
+ xml.tag!('state', address[:state])
583
+ xml.tag!('zip', address[:zip])
584
+ xml.tag!('country', address[:country])
585
+ end
586
+ end
587
+
588
+ def arb_expdate(credit_card)
589
+ sprintf('%04d-%02d', credit_card.year, credit_card.month)
590
+ end
591
+
592
+ def recurring_commit(action, request)
593
+ url = test? ? arb_test_url : arb_live_url
594
+ xml = ssl_post(url, request, "Content-Type" => "text/xml")
595
+
596
+ response = recurring_parse(action, xml)
597
+
598
+ message = response[:message] || response[:text]
599
+ test_mode = test? || message =~ /Test Mode/
600
+ success = response[:result_code] == 'Ok'
601
+
602
+ Response.new(success, message, response,
603
+ :test => test_mode,
604
+ :authorization => response[:subscription_id]
605
+ )
606
+ end
607
+
608
+ def recurring_parse(action, xml)
609
+ response = {}
610
+ xml = REXML::Document.new(xml)
611
+ root = REXML::XPath.first(xml, "//#{RECURRING_ACTIONS[action]}Response") ||
612
+ REXML::XPath.first(xml, "//ErrorResponse")
613
+ if root
614
+ root.elements.to_a.each do |node|
615
+ recurring_parse_element(response, node)
616
+ end
617
+ end
618
+
619
+ response
620
+ end
621
+
622
+ def recurring_parse_element(response, node)
623
+ if node.has_elements?
624
+ node.elements.each{|e| recurring_parse_element(response, e) }
625
+ else
626
+ response[node.name.underscore.to_sym] = node.text
627
+ end
628
+ end
242
629
  end
243
-
630
+
244
631
  AuthorizedNetGateway = AuthorizeNetGateway
245
632
  end
246
633
  end