activemerchant 1.1.0 → 1.2.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 (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