activemerchant 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (178) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +226 -0
  3. data/CONTRIBUTERS +52 -0
  4. data/README +34 -24
  5. data/Rakefile +152 -0
  6. data/gem-public_cert.pem +20 -0
  7. data/init.rb +3 -0
  8. data/lib/active_merchant.rb +3 -1
  9. data/lib/active_merchant/billing/credit_card.rb +21 -17
  10. data/lib/active_merchant/billing/credit_card_methods.rb +17 -19
  11. data/lib/active_merchant/billing/gateway.rb +160 -44
  12. data/lib/active_merchant/billing/gateways.rb +2 -15
  13. data/lib/active_merchant/billing/gateways/authorize_net.rb +21 -21
  14. data/lib/active_merchant/billing/gateways/bogus.rb +6 -6
  15. data/lib/active_merchant/billing/gateways/brain_tree.rb +191 -0
  16. data/lib/active_merchant/billing/gateways/card_stream.rb +207 -0
  17. data/lib/active_merchant/billing/gateways/cyber_source.rb +402 -0
  18. data/lib/active_merchant/billing/gateways/data_cash.rb +41 -97
  19. data/lib/active_merchant/billing/gateways/efsnet.rb +256 -0
  20. data/lib/active_merchant/billing/gateways/eway.rb +77 -29
  21. data/lib/active_merchant/billing/gateways/exact.rb +230 -0
  22. data/lib/active_merchant/billing/gateways/linkpoint.rb +6 -33
  23. data/lib/active_merchant/billing/gateways/moneris.rb +155 -125
  24. data/lib/active_merchant/billing/gateways/net_registry.rb +257 -0
  25. data/lib/active_merchant/billing/gateways/pay_junction.rb +407 -0
  26. data/lib/active_merchant/billing/gateways/payflow.rb +163 -25
  27. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +56 -38
  28. data/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb +10 -1
  29. data/lib/active_merchant/billing/gateways/payflow/payflow_response.rb +9 -0
  30. data/lib/active_merchant/billing/gateways/payflow_express.rb +36 -11
  31. data/lib/active_merchant/billing/gateways/payflow_express_uk.rb +6 -0
  32. data/lib/active_merchant/billing/gateways/payflow_uk.rb +7 -3
  33. data/lib/active_merchant/billing/gateways/payment_express.rb +261 -0
  34. data/lib/active_merchant/billing/gateways/paypal.rb +18 -4
  35. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +31 -15
  36. data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +8 -0
  37. data/lib/active_merchant/billing/gateways/paypal_express.rb +33 -8
  38. data/lib/active_merchant/billing/gateways/plugnpay.rb +300 -0
  39. data/lib/active_merchant/billing/gateways/protx.rb +285 -0
  40. data/lib/active_merchant/billing/gateways/psigate.rb +13 -12
  41. data/lib/active_merchant/billing/gateways/psl_card.rb +297 -0
  42. data/lib/active_merchant/billing/gateways/quickpay.rb +197 -0
  43. data/lib/active_merchant/billing/gateways/realex.rb +212 -0
  44. data/lib/active_merchant/billing/gateways/secure_pay.rb +31 -0
  45. data/lib/active_merchant/billing/gateways/trans_first.rb +136 -0
  46. data/lib/active_merchant/billing/gateways/trust_commerce.rb +43 -20
  47. data/lib/active_merchant/billing/gateways/usa_epay.rb +6 -5
  48. data/lib/active_merchant/billing/gateways/verifi.rb +235 -0
  49. data/lib/active_merchant/billing/gateways/viaklix.rb +171 -0
  50. data/lib/active_merchant/billing/integrations/gestpay/helper.rb +0 -2
  51. data/lib/active_merchant/billing/integrations/helper.rb +8 -1
  52. data/lib/active_merchant/billing/integrations/nochex.rb +62 -1
  53. data/lib/active_merchant/billing/integrations/nochex/notification.rb +9 -16
  54. data/lib/active_merchant/billing/integrations/notification.rb +1 -1
  55. data/lib/active_merchant/billing/integrations/paypal/helper.rb +59 -46
  56. data/lib/active_merchant/billing/integrations/paypal/notification.rb +14 -47
  57. data/lib/active_merchant/lib/error.rb +4 -0
  58. data/lib/active_merchant/lib/post_data.rb +22 -0
  59. data/lib/active_merchant/lib/posts_data.rb +23 -5
  60. data/lib/active_merchant/lib/requires_parameters.rb +2 -2
  61. data/lib/active_merchant/lib/validateable.rb +1 -1
  62. data/lib/support/gateway_support.rb +45 -0
  63. data/lib/tasks/cia.rb +1 -1
  64. data/script/generate +14 -0
  65. data/script/generator/base.rb +45 -0
  66. data/script/generator/generator.rb +24 -0
  67. data/script/generator/generators/gateway/gateway_generator.rb +14 -0
  68. data/script/generator/generators/gateway/templates/gateway.rb +73 -0
  69. data/script/generator/generators/gateway/templates/gateway_test.rb +41 -0
  70. data/script/generator/generators/gateway/templates/remote_gateway_test.rb +56 -0
  71. data/script/generator/generators/integration/integration_generator.rb +25 -0
  72. data/script/generator/generators/integration/templates/helper.rb +34 -0
  73. data/script/generator/generators/integration/templates/helper_test.rb +54 -0
  74. data/script/generator/generators/integration/templates/integration.rb +18 -0
  75. data/script/generator/generators/integration/templates/module_test.rb +9 -0
  76. data/script/generator/generators/integration/templates/notification.rb +100 -0
  77. data/script/generator/generators/integration/templates/notification_test.rb +41 -0
  78. data/script/generator/manifest.rb +20 -0
  79. data/test/extra/binding_of_caller.rb +80 -0
  80. data/test/extra/breakpoint.rb +547 -0
  81. data/test/fixtures.yml +251 -0
  82. data/test/remote_tests/remote_authorize_net_test.rb +113 -0
  83. data/test/remote_tests/remote_brain_tree_test.rb +78 -0
  84. data/test/remote_tests/remote_card_stream_test.rb +160 -0
  85. data/test/remote_tests/remote_cyber_source_test.rb +130 -0
  86. data/test/remote_tests/remote_data_cash_test.rb +155 -0
  87. data/test/remote_tests/remote_efsnet_test.rb +93 -0
  88. data/test/remote_tests/remote_eway_test.rb +71 -0
  89. data/test/remote_tests/remote_exact_test.rb +59 -0
  90. data/test/remote_tests/remote_gestpay_integration_test.rb +37 -0
  91. data/test/remote_tests/remote_linkpoint_test.rb +144 -0
  92. data/test/remote_tests/remote_moneris_test.rb +110 -0
  93. data/test/remote_tests/remote_net_registry_test.rb +120 -0
  94. data/test/remote_tests/remote_pay_junction_test.rb +162 -0
  95. data/test/remote_tests/remote_payflow_express_test.rb +50 -0
  96. data/test/remote_tests/remote_payflow_test.rb +241 -0
  97. data/test/remote_tests/remote_payflow_uk_test.rb +172 -0
  98. data/test/remote_tests/remote_payment_express_test.rb +136 -0
  99. data/test/remote_tests/remote_paypal_express_test.rb +49 -0
  100. data/test/remote_tests/remote_paypal_integration_test.rb +14 -0
  101. data/test/remote_tests/remote_paypal_test.rb +163 -0
  102. data/test/remote_tests/remote_plugnpay_test.rb +70 -0
  103. data/test/remote_tests/remote_protx_test.rb +184 -0
  104. data/test/remote_tests/remote_psigate_test.rb +87 -0
  105. data/test/remote_tests/remote_psl_card_test.rb +105 -0
  106. data/test/remote_tests/remote_quickpay_test.rb +182 -0
  107. data/test/remote_tests/remote_realex_test.rb +227 -0
  108. data/test/remote_tests/remote_secure_pay_test.rb +36 -0
  109. data/test/remote_tests/remote_trans_first_test.rb +37 -0
  110. data/test/remote_tests/remote_trust_commerce_test.rb +136 -0
  111. data/test/remote_tests/remote_usa_epay_test.rb +57 -0
  112. data/test/remote_tests/remote_verifi_test.rb +107 -0
  113. data/test/remote_tests/remote_viaklix_test.rb +53 -0
  114. data/test/test_helper.rb +132 -0
  115. data/test/unit/base_test.rb +61 -0
  116. data/test/unit/country_code_test.rb +33 -0
  117. data/test/unit/country_test.rb +64 -0
  118. data/test/unit/credit_card_formatting_test.rb +24 -0
  119. data/test/unit/credit_card_methods_test.rb +37 -0
  120. data/test/unit/credit_card_test.rb +365 -0
  121. data/test/unit/gateways/authorize_net_test.rb +140 -0
  122. data/test/unit/gateways/bogus_test.rb +43 -0
  123. data/test/unit/gateways/brain_tree_test.rb +77 -0
  124. data/test/unit/gateways/card_stream_test.rb +37 -0
  125. data/test/unit/gateways/cyber_source_test.rb +151 -0
  126. data/test/unit/gateways/data_cash_test.rb +23 -0
  127. data/test/unit/gateways/efsnet_test.rb +70 -0
  128. data/test/unit/gateways/eway_test.rb +105 -0
  129. data/test/unit/gateways/exact_test.rb +118 -0
  130. data/test/unit/gateways/gateway_test.rb +24 -0
  131. data/test/unit/gateways/linkpoint_test.rb +165 -0
  132. data/test/unit/gateways/moneris_test.rb +167 -0
  133. data/test/unit/gateways/net_registry_test.rb +478 -0
  134. data/test/unit/gateways/pay_junction_test.rb +61 -0
  135. data/test/unit/gateways/payflow_express_test.rb +165 -0
  136. data/test/unit/gateways/payflow_express_uk_test.rb +14 -0
  137. data/test/unit/gateways/payflow_test.rb +230 -0
  138. data/test/unit/gateways/payflow_uk_test.rb +68 -0
  139. data/test/unit/gateways/payment_express_test.rb +215 -0
  140. data/test/unit/gateways/paypal_express_test.rb +222 -0
  141. data/test/unit/gateways/paypal_test.rb +241 -0
  142. data/test/unit/gateways/plugnpay_test.rb +79 -0
  143. data/test/unit/gateways/protx_test.rb +110 -0
  144. data/test/unit/gateways/psigate_test.rb +110 -0
  145. data/test/unit/gateways/psl_card_test.rb +51 -0
  146. data/test/unit/gateways/quickpay_test.rb +125 -0
  147. data/test/unit/gateways/realex_test.rb +150 -0
  148. data/test/unit/gateways/secure_pay_test.rb +78 -0
  149. data/test/unit/gateways/trans_first_test.rb +125 -0
  150. data/test/unit/gateways/trust_commerce_test.rb +57 -0
  151. data/test/unit/gateways/usa_epay_test.rb +117 -0
  152. data/test/unit/gateways/verifi_test.rb +91 -0
  153. data/test/unit/gateways/viaklix_test.rb +72 -0
  154. data/test/unit/integrations/action_view_helper_test.rb +54 -0
  155. data/test/unit/integrations/bogus_module_test.rb +16 -0
  156. data/test/unit/integrations/chronopay_module_test.rb +9 -0
  157. data/test/unit/integrations/gestpay_module_test.rb +10 -0
  158. data/test/unit/integrations/helpers/bogus_helper_test.rb +28 -0
  159. data/test/unit/integrations/helpers/chronopay_helper_test.rb +67 -0
  160. data/test/unit/integrations/helpers/gestpay_helper_test.rb +100 -0
  161. data/test/unit/integrations/helpers/nochex_helper_test.rb +53 -0
  162. data/test/unit/integrations/helpers/paypal_helper_test.rb +162 -0
  163. data/test/unit/integrations/helpers/two_checkout_helper_test.rb +92 -0
  164. data/test/unit/integrations/nochex_module_test.rb +9 -0
  165. data/test/unit/integrations/notifications/chronopay_notification_test.rb +66 -0
  166. data/test/unit/integrations/notifications/gestpay_notification_test.rb +60 -0
  167. data/test/unit/integrations/notifications/nochex_notification_test.rb +51 -0
  168. data/test/unit/integrations/notifications/notification_test.rb +41 -0
  169. data/test/unit/integrations/notifications/paypal_notification_test.rb +85 -0
  170. data/test/unit/integrations/notifications/two_checkout_notification_test.rb +55 -0
  171. data/test/unit/integrations/paypal_module_test.rb +24 -0
  172. data/test/unit/integrations/two_checkout_module_test.rb +9 -0
  173. data/test/unit/post_data_test.rb +55 -0
  174. data/test/unit/response_test.rb +14 -0
  175. data/test/unit/validateable_test.rb +56 -0
  176. metadata +160 -7
  177. metadata.gz.sig +0 -0
  178. data/lib/active_merchant/billing/gateways/payflow/f73e89fd.0 +0 -17
@@ -3,10 +3,21 @@ require 'rexml/document'
3
3
  module ActiveMerchant #:nodoc:
4
4
  module Billing #:nodoc:
5
5
 
6
+ # To learn more about the Moneris gateway, please contact
7
+ # eselectplus@moneris.com for a copy of their integration guide. For
8
+ # information on remote testing, please see "Test Environment Penny Value
9
+ # Response Table", and "Test Environment eFraud (AVS and CVD) Penny
10
+ # Response Values", available at Moneris' {eSelect Plus Documentation
11
+ # Centre}[https://www3.moneris.com/connect/en/documents/index.html].
6
12
  class MonerisGateway < Gateway
7
13
  attr_reader :url
8
14
  attr_reader :response
9
15
  attr_reader :options
16
+
17
+ self.supported_countries = ['CA']
18
+ self.supported_cardtypes = [:visa, :master]
19
+ self.homepage_url = 'http://www.moneris.com/'
20
+ self.display_name = 'Moneris'
10
21
 
11
22
  TEST_URL = 'https://esqa.moneris.com/gateway2/servlet/MpgRequest'
12
23
  LIVE_URL = 'https://www3.moneris.com/gateway2/servlet/MpgRequest'
@@ -15,145 +26,167 @@ module ActiveMerchant #:nodoc:
15
26
  # password is your API Token
16
27
  def initialize(options = {})
17
28
  requires!(options, :login, :password)
18
-
19
- @options = {
20
- :strict_ssl => true,
21
- :crypt_type => 7
22
- }.update(options)
23
-
29
+ @options = { :crypt_type => 7 }.update(options)
24
30
  @url = test? ? TEST_URL : LIVE_URL
25
-
26
31
  super
27
32
  end
28
33
 
34
+ # Referred to as "PreAuth" in the Moneris integration guide, this action
35
+ # verifies and locks funds on a customer's card, which then must be
36
+ # captured at a later date.
37
+ #
38
+ # Pass in +order_id+ and optionally a +customer+ parameter.
29
39
  def authorize(money, creditcard, options = {})
30
- requires!(options, :order_id)
31
-
32
- parameters = {
33
- :order_id => options[:order_id],
34
- :cust_id => options[:customer],
35
- :amount => amount(money),
36
- :pan => creditcard.number,
37
- :expdate => expdate(creditcard),
38
- :crypt_type => options[:crypt_type] || @options[:crypt_type]
39
- }
40
-
41
- commit('preauth', parameters)
40
+ debit_commit 'preauth', money, creditcard, options
42
41
  end
43
42
 
43
+ # This action verifies funding on a customer's card, and readies them for
44
+ # deposit in a merchant's account.
45
+ #
44
46
  # Pass in <tt>order_id</tt> and optionally a <tt>customer</tt> parameter
45
47
  def purchase(money, creditcard, options = {})
46
- requires!(options, :order_id)
47
-
48
- parameters = {
49
- :order_id => options[:order_id],
50
- :cust_id => options[:customer],
51
- :amount => amount(money),
52
- :pan => creditcard.number,
53
- :expdate => expdate(creditcard),
54
- :crypt_type => options[:crypt_type] || @options[:crypt_type]
55
- }
56
-
57
- commit('purchase', parameters)
48
+ debit_commit 'purchase', money, creditcard, options
58
49
  end
59
50
 
60
- # Moneris requires both the order_id and the transaction number of
51
+ # This method retrieves locked funds from a customer's account (from a
52
+ # PreAuth) and prepares them for deposit in a merchant's account.
53
+ #
54
+ # Note: Moneris requires both the order_id and the transaction number of
61
55
  # the original authorization. To maintain the same interface as the other
62
56
  # gateways the two numbers are concatenated together with a ; separator as
63
57
  # the authorization number returned by authorization
64
58
  def capture(money, authorization, options = {})
65
- txn_number, order_id = authorization.split(';')
66
-
67
- parameters = {
68
- :txn_number => txn_number,
69
- :order_id => order_id,
70
- :comp_amount => amount(money),
71
- :crypt_type => options[:crypt_type] || @options[:crypt_type]
72
- }
73
-
74
- commit('completion', parameters)
59
+ commit 'completion', crediting_params(authorization, :comp_amount => amount(money))
75
60
  end
76
61
 
62
+ # Voiding requires the original transaction ID and order ID of some open
63
+ # transaction. Closed transactions must be refunded. Note that the only
64
+ # methods which may be voided are +capture+ and +purchase+.
65
+ #
66
+ # Concatenate your transaction number and order_id by using a semicolon
67
+ # (';'). This is to keep the Moneris interface consistent with other
68
+ # gateways. (See +capture+ for details.)
77
69
  def void(authorization, options = {})
78
- txn_number, order_id = authorization.split(';')
79
-
80
- parameters = {
81
- :txn_number => txn_number,
82
- :order_id => order_id,
83
- :crypt_type => options[:crypt_type] || @options[:crypt_type]
84
- }
85
-
86
- commit('purchasecorrection', parameters)
70
+ commit 'purchasecorrection', crediting_params(authorization)
87
71
  end
88
-
89
- # We support visa and master card
90
- def self.supported_cardtypes
91
- [:visa, :master]
72
+
73
+ # Performs a refund. This method requires that the original transaction
74
+ # number and order number be included. Concatenate your transaction
75
+ # number and order_id by using a semicolon (';'). This is to keep the
76
+ # Moneris interface consistent with other gateways. (See +capture+ for
77
+ # details.)
78
+ def credit(money, authorization, options = {})
79
+ commit 'refund', crediting_params(authorization, :amount => amount(money))
92
80
  end
93
-
94
- private
81
+
82
+ private # :nodoc: all
95
83
 
96
84
  def expdate(creditcard)
97
- year = sprintf("%.4i", creditcard.year)
98
- month = sprintf("%.2i", creditcard.month)
99
-
100
- "#{year[-2..-1]}#{month}"
85
+ sprintf("%.4i", creditcard.year)[-2..-1] + sprintf("%.2i", creditcard.month)
86
+ end
87
+
88
+ def debit_commit(commit_type, money, creditcard, options)
89
+ requires!(options, :order_id)
90
+ commit(commit_type, debit_params(money, creditcard, options))
91
+ end
92
+
93
+ # Common params used amongst the +purchase+ and +authorization+ methods
94
+ def debit_params(money, creditcard, options = {})
95
+ {
96
+ :order_id => options[:order_id],
97
+ :cust_id => options[:customer],
98
+ :amount => amount(money),
99
+ :pan => creditcard.number,
100
+ :expdate => expdate(creditcard),
101
+ :crypt_type => options[:crypt_type] || @options[:crypt_type]
102
+ }
103
+ end
104
+
105
+ # Common params used amongst the +credit+, +void+ and +capture+ methods
106
+ def crediting_params(authorization, options = {})
107
+ {
108
+ :txn_number => split_authorization(authorization).first,
109
+ :order_id => split_authorization(authorization).last,
110
+ :crypt_type => options[:crypt_type] || @options[:crypt_type]
111
+ }.merge(options)
112
+ end
113
+
114
+ # Splits an +authorization+ param and retrives the order id and
115
+ # transaction number in that order.
116
+ def split_authorization(authorization)
117
+ if authorization.nil? || authorization.empty? || authorization !~ /;/
118
+ raise ArgumentError, 'You must include a valid authorization code (e.g. "1234;567")'
119
+ else
120
+ authorization.split(';')
121
+ end
101
122
  end
102
123
 
103
- def commit(action, parameters)
124
+ def commit(action, parameters = {})
125
+ # TODO This part still needs to be refactored
104
126
  if result = test_result_from_cc_number(parameters[:pan])
105
127
  return result
106
128
  end
107
129
 
108
- data = ssl_post @url, post_data(action, parameters)
109
- @response = parse(data)
130
+ @response = parse(ssl_post(@url, post_data(action, parameters)))
110
131
 
111
- success = (response[:response_code] and response[:complete] and (0..49).include?(response[:response_code].to_i) )
112
- message = message_form(response[:message])
113
- authorization = "#{response[:trans_id]};#{response[:receipt_id]}" if response[:trans_id] && response[:receipt_id]
114
-
115
- Response.new(success, message, @response,
116
- :test => test?,
117
- :authorization => authorization
132
+ Response.new(successful_response?(response), message_form(response[:message]), @response,
133
+ :test => test?,
134
+ :authorization => authorization_string(response)
118
135
  )
119
136
  end
137
+
138
+ # Generates a Moneris authorization string of the form 'trans_id;receipt_id'.
139
+ def authorization_string(response = {})
140
+ if response[:trans_id] && response[:receipt_id]
141
+ "#{response[:trans_id]};#{response[:receipt_id]}"
142
+ end
143
+ end
144
+
145
+ # Tests for a successful response from Moneris' servers
146
+ def successful_response?(response = {})
147
+ response[:response_code] &&
148
+ response[:complete] &&
149
+ (0..49).include?(response[:response_code].to_i)
150
+ end
120
151
 
121
- # Parse moneris response xml into a convinient hash
152
+ # Parse Moneris' response XML into a convinient Hash.
153
+ #
154
+ # Expected XML format:
155
+ #
156
+ # "<?xml version=\"1.0\"?><response><receipt>".
157
+ # "<ReceiptId>Global Error Receipt</ReceiptId>".
158
+ # "<ReferenceNum>null</ReferenceNum>
159
+ # <ResponseCode>null</ResponseCode>".
160
+ # "<ISO>null</ISO>
161
+ # <AuthCode>null</AuthCode>
162
+ # <TransTime>null</TransTime>".
163
+ # "<TransDate>null</TransDate>
164
+ # <TransType>null</TransType>
165
+ # <Complete>false</Complete>".
166
+ # "<Message>null</Message>
167
+ # <TransAmount>null</TransAmount>".
168
+ # "<CardType>null</CardType>".
169
+ # "<TransID>null</TransID>
170
+ # <TimedOut>null</TimedOut>".
171
+ # "</receipt></response>
122
172
  def parse(xml)
123
- # "<?xml version=\"1.0\"?><response><receipt>".
124
- # "<ReceiptId>Global Error Receipt</ReceiptId>".
125
- # "<ReferenceNum>null</ReferenceNum>
126
- # <ResponseCode>null</ResponseCode>".
127
- # "<ISO>null</ISO>
128
- # <AuthCode>null</AuthCode>
129
- # <TransTime>null</TransTime>".
130
- # "<TransDate>null</TransDate>
131
- # <TransType>null</TransType>
132
- # <Complete>false</Complete>".
133
- # "<Message>null</Message>
134
- # <TransAmount>null</TransAmount>".
135
- # "<CardType>null</CardType>".
136
- # "<TransID>null</TransID>
137
- # <TimedOut>null</TimedOut>".
138
- # "</receipt></response>
139
-
140
- response = {:message => "Global Error Receipt", :complete => false}
141
-
142
- xml = REXML::Document.new(xml)
143
-
173
+ response = { :message => "Global Error Receipt", :complete => false }
174
+ hashify_xml!(xml, response)
175
+ response
176
+ end
177
+
178
+ def hashify_xml!(xml, response)
179
+ xml = REXML::Document.new(xml)
180
+ return if xml.root.nil?
144
181
  xml.elements.each('//receipt/*') do |node|
145
-
146
182
  response[node.name.underscore.to_sym] = normalize(node.text)
147
-
148
- end unless xml.root.nil?
149
-
150
- response
151
- end
183
+ end
184
+ end
152
185
 
153
186
  def post_data(action, parameters = {})
154
187
  xml = REXML::Document.new
155
188
  root = xml.add_element("request")
156
- root.add_element("store_id").text = options[:login]
189
+ root.add_element("store_id").text = options[:login]
157
190
  root.add_element("api_token").text = options[:password]
158
191
  transaction = root.add_element(action)
159
192
 
@@ -170,36 +203,33 @@ module ActiveMerchant #:nodoc:
170
203
  message.gsub(/[^\w]/, ' ').split.join(" ").capitalize
171
204
  end
172
205
 
173
- # Make a ruby type out of the response string
206
+ # Make a Ruby type out of the response string
174
207
  def normalize(field)
175
208
  case field
176
- when "true" then true
177
- when "false" then false
178
- when "" then nil
179
- when "null" then nil
180
- else field
209
+ when "true" then true
210
+ when "false" then false
211
+ when '', "null" then nil
212
+ else field
181
213
  end
182
- end
183
-
214
+ end
215
+
184
216
  def actions
185
- ACTIONS
217
+ {
218
+ "purchase" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
219
+ "preauth" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
220
+ "command" => [:order_id],
221
+ "refund" => [:order_id, :amount, :txn_number, :crypt_type],
222
+ "indrefund" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
223
+ "completion" => [:order_id, :comp_amount, :txn_number, :crypt_type],
224
+ "purchasecorrection" => [:order_id, :txn_number, :crypt_type],
225
+ "cavvpurcha" => [:order_id, :cust_id, :amount, :pan, :expdate, :cav],
226
+ "cavvpreaut" => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv],
227
+ "transact" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
228
+ "Batchcloseall" => [],
229
+ "opentotals" => [:ecr_number],
230
+ "batchclose" => [:ecr_number]
231
+ }
186
232
  end
187
-
188
- ACTIONS = {
189
- "purchase" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
190
- "preauth" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
191
- "command" => [:order_id],
192
- "refund" => [:order_id, :amount, :txn_number, :crypt_type],
193
- "indrefund" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
194
- "completion" => [:order_id, :comp_amount, :txn_number, :crypt_type],
195
- "purchasecorrection" => [:order_id, :txn_number, :crypt_type],
196
- "cavvpurcha" => [:order_id, :cust_id, :amount, :pan, :expdate, :cav],
197
- "cavvpreaut" => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv],
198
- "transact" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
199
- "Batchcloseall" => [],
200
- "opentotals" => [:ecr_number],
201
- "batchclose" => [:ecr_number],
202
- }
203
233
  end
204
234
  end
205
235
  end
@@ -0,0 +1,257 @@
1
+ #
2
+ # Gateway for netregistry.com.au.
3
+ #
4
+ # Note that NetRegistry itself uses gateway service providers. At the
5
+ # time of this writing, there are at least two (Quest and Ingenico).
6
+ # This module has only been tested with Quest.
7
+ #
8
+ # Also note that NetRegistry does not offer a test mode, nor does it
9
+ # have support for the authorize/capture/void functionality by default
10
+ # (you may arrange for this as described in "Programming for
11
+ # NetRegistry's E-commerce Gateway." [http://rubyurl.com/hNG]), and no
12
+ # #void functionality is documented. As a result, the #authorize and
13
+ # #capture have not yet been tested through a live gateway, and #void
14
+ # will raise an error.
15
+ #
16
+ # If you have this functionality enabled, please consider contributing
17
+ # to ActiveMerchant by writing tests/code for these methods, and
18
+ # submitting a patch.
19
+ #
20
+ # In addition to the standard ActiveMerchant functionality, the
21
+ # response will contain a 'receipt' parameter
22
+ # (response.params['receipt']) if a receipt was issued by the gateway.
23
+ # Also, a logger may be provided when instantiating the gateway to log
24
+ # all data sent to/from the gateway (with sensitive information
25
+ # hidden).
26
+ #
27
+ module ActiveMerchant
28
+ module Billing
29
+ class NetRegistryGateway < Gateway
30
+ LIVE_URL = 'https://4tknox.au.com/cgi-bin/themerchant.au.com/ecom/external2.pl'
31
+
32
+ self.supported_countries = ['AU']
33
+
34
+ # Note that support for Diners, Amex, and JCB require extra
35
+ # steps in setting up your account, as detailed in
36
+ # "Programming for NetRegistry's E-commerce Gateway."
37
+ # [http://rubyurl.com/hNG]
38
+ self.supported_cardtypes = [:visa, :master, :diners_club, :american_express, :jcb]
39
+ self.display_name = 'NetRegistry'
40
+ self.homepage_url = 'http://www.netregistry.com.au'
41
+
42
+ # Create a new NetRegistry gateway.
43
+ #
44
+ # Options :login and :password must be given.
45
+ #
46
+ def initialize(options = {})
47
+ requires!(options, :login, :password)
48
+ @logger = options[:logger]
49
+ @options = options
50
+ super
51
+ end
52
+
53
+ #
54
+ # A Logger object used to write extra debugging output to. nil
55
+ # for none.
56
+ #
57
+ attr_accessor :logger
58
+
59
+ #
60
+ # Note that #authorize and #capture only work if your account
61
+ # vendor is St George, and if your account has been setup as
62
+ # described in "Programming for NetRegistry's E-commerce
63
+ # Gateway." [http://rubyurl.com/hNG]
64
+ #
65
+ def authorize(money, credit_card, options = {})
66
+ post(options[:description],
67
+ 'COMMAND' => 'preauth',
68
+ 'AMOUNT' => amount(money),
69
+ 'CCNUM' => credit_card.number,
70
+ 'CCEXP' => expiry(credit_card))
71
+ end
72
+
73
+ #
74
+ # Note that #authorize and #capture only work if your account
75
+ # vendor is St George, and if your account has been setup as
76
+ # described in "Programming for NetRegistry's E-commerce
77
+ # Gateway." [http://rubyurl.com/hNG]
78
+ #
79
+ def capture(money, authorization, options = {})
80
+ credit_card = options[:credit_card]
81
+ post(options[:description],
82
+ 'COMMAND' => 'completion',
83
+ 'PREAUTHNUM' => authorization,
84
+ 'AMOUNT' => amount(money),
85
+ 'CCNUM' => credit_card.number,
86
+ 'CCEXP' => expiry(credit_card))
87
+ end
88
+
89
+ def purchase(money, credit_card, options = {})
90
+ post(options[:description],
91
+ 'COMMAND' => 'purchase',
92
+ 'AMOUNT' => amount(money),
93
+ 'CCNUM' => credit_card.number,
94
+ 'CCEXP' => expiry(credit_card))
95
+ end
96
+
97
+ def credit(money, identification, options = {})
98
+ post(options[:description],
99
+ 'COMMAND' => 'refund',
100
+ 'AMOUNT' => amount(money),
101
+ 'TXNREF' => identification)
102
+ end
103
+
104
+ # Specific to NetRegistry.
105
+ #
106
+ # Run a 'status' command. This lets you view the status of a
107
+ # completed transaction.
108
+ #
109
+ def status(identification)
110
+ post(options[:description],
111
+ 'COMMAND' => 'status',
112
+ 'TXNREF' => identification)
113
+ end
114
+
115
+ private # -----------------------------------------------------
116
+
117
+ #
118
+ # Return the expiry for the given creditcard in the required
119
+ # format for a command.
120
+ #
121
+ def expiry(credit_card)
122
+ month = format(credit_card.month, :two_digits)
123
+ year = format(credit_card.year , :two_digits)
124
+ "#{month}/#{year}"
125
+ end
126
+
127
+ #
128
+ # Post the a request with the given parameters and return the
129
+ # response object.
130
+ #
131
+ # Login and password are added automatically, and the comment is
132
+ # omitted if nil.
133
+ #
134
+ def post(comment, keyvals)
135
+ if result = test_result_from_cc_number(keyvals['CCNUM'])
136
+ return result
137
+ end
138
+
139
+ log "Executing #{keyvals['COMMAND']}:"
140
+ login = @options[:login]
141
+ password = @options[:password]
142
+
143
+ # make query
144
+ keyvals['COMMENT'] = comment if comment
145
+ keyvals['LOGIN'] = "#{login}/#{password}"
146
+ str = URI.encode(keyvals.map{|k,v| "#{k}=#{v}"}.join('&'))
147
+ log " ActiveMerchant/NetRegistry: sending: #{obscure_send_string(str)}"
148
+
149
+ # get gateway response
150
+ text = ssl_post(LIVE_URL, str)
151
+ log " ActiveMerchant/NetRegistry: received:"
152
+ obscure_recv_string(text).each do |line|
153
+ log " #{line}"
154
+ end
155
+
156
+ # make response object
157
+ response = parse_response(text, keyvals['COMMAND'])
158
+
159
+ return response
160
+ end
161
+
162
+ #
163
+ # Parse the text returned from the gateway into a Response object.
164
+ #
165
+ def parse_response(text, command)
166
+ params = {'original_text' => text}
167
+ sio = StringIO.new(text)
168
+ params['status'] = sio.gets.chomp
169
+ params['rrn'] = sio.gets.chomp
170
+
171
+ if sio.eof?
172
+ # some short errors have nothing else, e.g., "Invalid expiry
173
+ # format"
174
+ message = params.delete('rrn')
175
+ return Response.new(false, message, params)
176
+ end
177
+
178
+ # parse receipt
179
+ receipt = ''
180
+ while (line = sio.gets)
181
+ break if line.strip == '.'
182
+ receipt << line
183
+ end
184
+
185
+ # parse params
186
+ while (line = sio.gets)
187
+ line.chomp!
188
+ key, val = line.split(/=/, 2)
189
+ params[key] = val
190
+ end
191
+
192
+ params['receipt'] = receipt
193
+ authorization =
194
+ case command
195
+ when 'purchase'
196
+ params['txn_ref']
197
+ when 'preauth'
198
+ params['transaction_no']
199
+ else
200
+ nil
201
+ end
202
+
203
+ Response.new(params['status'] == 'approved',
204
+ params['response_text'],
205
+ params,
206
+ :authorization => authorization)
207
+ end
208
+
209
+ #
210
+ # Log a message if logging is enabled.
211
+ #
212
+ def log(msg)
213
+ logger.info(msg.chomp) unless logger.nil?
214
+ end
215
+
216
+ #
217
+ # Return a copy of the given string (to be sent to the gateway),
218
+ # with sensitive information hidden.
219
+ #
220
+ def obscure_send_string(string)
221
+ string.gsub(/LOGIN=[^&]+/) do |keyval|
222
+ keyval.sub(/[^\/]+\z/){|pass| '*'*pass.size}
223
+ end.gsub(/CCNUM=[^&]+/) do |keyval|
224
+ keyval.sub(/[^=]+\z/){|num| obscure_card_number(num)}
225
+ end.gsub(/CCEXP=[^&]+/) do |keyval|
226
+ keyval.sub(/[^=]+\z/){|num| obscure_card_expiry(num)}
227
+ end
228
+ end
229
+
230
+ #
231
+ # Return a copy of the given string (received from the gateway),
232
+ # with sensitive information hidden.
233
+ #
234
+ def obscure_recv_string(string)
235
+ string.
236
+ gsub(/(card_(?:no|number)=)(.*)$/){$1 << obscure_card_number($2)}.
237
+ gsub(/(card_expiry=)(.*)$/){$1 << obscure_card_expiry($2)}
238
+ end
239
+
240
+ #
241
+ # Obscure a credit card number.
242
+ #
243
+ def obscure_card_number(number)
244
+ return number if number.size < 4
245
+ number[0...-4] = '*'*(number.size-4)
246
+ return number
247
+ end
248
+
249
+ #
250
+ # Obscure a credit card expiry.
251
+ #
252
+ def obscure_card_expiry(expiry)
253
+ expiry.gsub(/\d/, '*')
254
+ end
255
+ end
256
+ end
257
+ end