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
@@ -0,0 +1,285 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class ProtxGateway < Gateway
4
+ cattr_accessor :simulate
5
+ self.simulate = false
6
+
7
+ TEST_URL = 'https://ukvpstest.protx.com/vspgateway/service'
8
+ LIVE_URL = 'https://ukvps.protx.com/vspgateway/service'
9
+ SIMULATOR_URL = 'https://ukvpstest.protx.com/VSPSimulator'
10
+
11
+ APPROVED = 'OK'
12
+
13
+ TRANSACTIONS = {
14
+ :purchase => 'PAYMENT',
15
+ :credit => 'REFUND',
16
+ :authorization => 'DEFERRED',
17
+ :capture => 'RELEASE',
18
+ :void => 'VOID'
19
+ }
20
+
21
+ CREDIT_CARDS = {
22
+ :visa => "VISA",
23
+ :master => "MC",
24
+ :delta => "DELTA",
25
+ :solo => "SOLO",
26
+ :maestro => "MAESTRO",
27
+ :american_express => "AMEX",
28
+ :electron => "UKE",
29
+ :diners_club => "DC",
30
+ :jcb => "JCB"
31
+ }
32
+
33
+ ELECTRON = /^(424519|42496[23]|450875|48440[6-8]|4844[1-5][1-5]|4917[3-5][0-9]|491880)\d{10}(\d{3})?$/
34
+
35
+ POST_HEADERS = { 'Content-Type' => 'application/x-www-form-urlencoded' }
36
+
37
+ attr_reader :url
38
+ attr_reader :response
39
+ attr_reader :options
40
+
41
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :solo, :maestro, :diners_club]
42
+ self.supported_countries = ['GB']
43
+ self.default_currency = 'GBP'
44
+
45
+ self.homepage_url = 'http://www.protx.com'
46
+ self.display_name = 'Protx'
47
+
48
+ def initialize(options = {})
49
+ requires!(options, :login)
50
+ @options = options
51
+ super
52
+ end
53
+
54
+ def test?
55
+ @options[:test] || Base.gateway_mode == :test
56
+ end
57
+
58
+ def purchase(money, credit_card, options = {})
59
+ requires!(options, :order_id)
60
+
61
+ post = {}
62
+
63
+ add_amount(post, money, options)
64
+ add_invoice(post, options)
65
+ add_credit_card(post, credit_card)
66
+ add_address(post, options)
67
+ add_customer_data(post, options)
68
+
69
+ commit(:purchase, post)
70
+ end
71
+
72
+ def authorize(money, credit_card, options = {})
73
+ requires!(options, :order_id)
74
+
75
+ post = {}
76
+
77
+ add_amount(post, money, options)
78
+ add_invoice(post, options)
79
+ add_credit_card(post, credit_card)
80
+ add_address(post, options)
81
+ add_customer_data(post, options)
82
+
83
+ commit(:authorization, post)
84
+ end
85
+
86
+ # Only supports capturing the original amount of the transaction
87
+ def capture(money, identification, options = {})
88
+ post = {}
89
+
90
+ add_reference(post, identification)
91
+ commit(:capture, post)
92
+ end
93
+
94
+ def void(identification, options = {})
95
+ post = {}
96
+
97
+ add_reference(post, identification)
98
+ commit(:void, post)
99
+ end
100
+
101
+ # Crediting requires a new order_id to passed in, as well as a description
102
+ def credit(money, identification, options = {})
103
+ requires!(options, :order_id, :description)
104
+
105
+ post = {}
106
+
107
+ add_credit_reference(post, identification)
108
+ add_amount(post, money, options)
109
+ add_invoice(post, options)
110
+
111
+ commit(:credit, post)
112
+ end
113
+
114
+ private
115
+ def add_reference(post, identification)
116
+ order_id, transaction_id, authorization, security_key = identification.split(';')
117
+
118
+ add_pair(post, :VendorTxCode, order_id)
119
+ add_pair(post, :VPSTxId, transaction_id)
120
+ add_pair(post, :TxAuthNo, authorization)
121
+ add_pair(post, :SecurityKey, security_key)
122
+ end
123
+
124
+ def add_credit_reference(post, identification)
125
+ order_id, transaction_id, authorization, security_key = identification.split(';')
126
+
127
+ add_pair(post, :RelatedVendorTxCode, order_id)
128
+ add_pair(post, :RelatedVPSTxId, transaction_id)
129
+ add_pair(post, :RelatedTxAuthNo, authorization)
130
+ add_pair(post, :RelatedSecurityKey, security_key)
131
+ end
132
+
133
+ def add_amount(post, money, options)
134
+ add_pair(post, :Amount, amount(money), :required => true)
135
+ add_pair(post, :Currency, options[:currency] || currency(money), :required => true)
136
+ end
137
+
138
+ def add_customer_data(post, options)
139
+ add_pair(post, :BillingEmail, options[:email])
140
+ add_pair(post, :ContactNumber, options[:phone])
141
+ add_pair(post, :ContactFax, options[:fax])
142
+ add_pair(post, :ClientIPAddress, options[:ip])
143
+ end
144
+
145
+ def add_address(post, options)
146
+ address = options[:billing_address] || options[:address]
147
+ shipping_address = options[:shipping_address] || ''
148
+
149
+ return if address.blank?
150
+
151
+ billing = "#{address[:address1]}\n#{address[:address2]}\n#{address[:city]}\n#{address[:state]}"
152
+
153
+ add_pair(post, :BillingAddress, billing)
154
+ add_pair(post, :BillingPostcode, address[:zip])
155
+
156
+ return if shipping_address.blank?
157
+
158
+ shipping = "#{shipping_address[:address1]}\n#{shipping_address[:address2]}\n#{shipping_address[:city]}\n#{shipping_address[:state]}"
159
+
160
+ add_pair(post, :DeliveryAddress, shipping)
161
+ add_pair(post, :DeliveryPostcode, shipping_address[:zip])
162
+ end
163
+
164
+ def add_invoice(post, options)
165
+ add_pair(post, :VendorTxCode, sanitize_order_id(options[:order_id]), :required => true)
166
+ add_pair(post, :Description, options[:description] || options[:order_id])
167
+ end
168
+
169
+ def add_credit_card(post, credit_card)
170
+ add_pair(post, :CardHolder, credit_card.name, :required => true)
171
+ add_pair(post, :CardNumber, credit_card.number, :required => true)
172
+
173
+ add_pair(post, :ExpiryDate, format_expiry_date(credit_card), :required => true)
174
+
175
+ if requires_start_date_or_issue_number?(credit_card)
176
+ add_pair(post, :StartDate, format(credit_card.start_year, :four_digits))
177
+
178
+ add_pair(post, :IssueNumber, format_issue_number(credit_card))
179
+ end
180
+ add_pair(post, :CardType, map_card_type(credit_card))
181
+
182
+ add_pair(post, :CV2, credit_card.verification_value)
183
+ end
184
+
185
+ def sanitize_order_id(order_id)
186
+ order_id.to_s.gsub(/[^-a-zA-Z0-9._]/, '')
187
+ end
188
+
189
+ def map_card_type(credit_card)
190
+ raise ArgumentError, "The credit card type must be provided" if credit_card.type.blank?
191
+
192
+ card_type = credit_card.type.to_sym
193
+
194
+ # Check if it is an electron card
195
+ if card_type == :visa && credit_card.number =~ ELECTRON
196
+ CREDIT_CARDS[:electron]
197
+ else
198
+ CREDIT_CARDS[card_type]
199
+ end
200
+ end
201
+
202
+ # MMYY format
203
+ def format_expiry_date(credit_card)
204
+ year = sprintf("%.4i", credit_card.year)
205
+ month = sprintf("%.2i", credit_card.month)
206
+
207
+ "#{month}#{year[-2..-1]}"
208
+ end
209
+
210
+ def format_issue_number(credit_card)
211
+ credit_card.type.to_s == 'solo' ? format(credit_card.issue_number, :two_digits) : credit_card.issue_number
212
+ end
213
+
214
+ def commit(action, parameters)
215
+ if result = test_result_from_cc_number(parameters[:CardNumber])
216
+ return result
217
+ end
218
+
219
+ data = ssl_post(build_endpoint_url(action), post_data(action, parameters), POST_HEADERS)
220
+
221
+ @response = parse(data)
222
+
223
+ success = @response["Status"] == APPROVED
224
+ message = message_from(@response)
225
+
226
+ authorization = [ parameters[:VendorTxCode],
227
+ @response["VPSTxId"],
228
+ @response["TxAuthNo"],
229
+ @response["SecurityKey"] ].compact.join(";")
230
+
231
+ Response.new(success, message, @response,
232
+ :test => test?,
233
+ :authorization => authorization
234
+ )
235
+ end
236
+
237
+ def build_endpoint_url(action)
238
+ simulate ? build_simulator_url(action) : build_url(action)
239
+ end
240
+
241
+ def build_url(action)
242
+ endpoint = [ :purchase, :authorization ].include?(action) ? "vspdirect-register" : TRANSACTIONS[action].downcase
243
+ "#{test? ? TEST_URL : LIVE_URL}/#{endpoint}.vsp"
244
+ end
245
+
246
+ def build_simulator_url(action)
247
+ endpoint = [ :purchase, :authorization ].include?(action) ? "VSPDirectGateway.asp" : "VSPServerGateway.asp?Service=Vendor#{TRANSACTIONS[action].capitalize}Tx"
248
+ "#{SIMULATOR_URL}/#{endpoint}"
249
+ end
250
+
251
+ def message_from(results)
252
+ if response["Status"] == APPROVED
253
+ return 'Success'
254
+ else
255
+ return 'Unspecified error' if response["StatusDetail"].blank?
256
+ return response["StatusDetail"]
257
+ end
258
+ end
259
+
260
+ def post_data(action, parameters = {})
261
+ parameters.update(
262
+ :Vendor => @options[:login],
263
+ :TxType => TRANSACTIONS[action],
264
+ :VPSProtocol => "2.22"
265
+ )
266
+
267
+ parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
268
+ end
269
+
270
+ # Protx returns data in the following format
271
+ # Key1=value1
272
+ # Key2=value2
273
+ def parse(body)
274
+ result = {}
275
+ body.to_a.collect {|v| c=v.split('='); result[c[0]] = c[1].chomp if !c[1].blank? }
276
+ result
277
+ end
278
+
279
+ def add_pair(post, key, value, options = {})
280
+ post[key] = value if !value.blank? || options[:required]
281
+ end
282
+ end
283
+ end
284
+ end
285
+
@@ -7,19 +7,19 @@
7
7
  #
8
8
  # Usage for a PreAuth (authorize) is as follows:
9
9
  #
10
- # twenty = Money.ca_dollar(2000)
11
- # gateway = PsigateGateway.new({
10
+ # twenty = 2000
11
+ # gateway = PsigateGateway.new(
12
12
  # :store_id => 'teststore',
13
- # :password => 'psigate1234',
14
- # })
13
+ # :password => 'psigate1234'
14
+ # )
15
15
  #
16
- # creditcard = CreditCard.new({
16
+ # creditcard = CreditCard.new(
17
17
  # :number => '4242424242424242',
18
18
  # :month => 8,
19
19
  # :year => 2006,
20
20
  # :first_name => 'Longbob',
21
21
  # :last_name => 'Longsen'
22
- # })
22
+ # )
23
23
  # response = @gateway.authorize(twenty, creditcard, {:order_id => 1234,
24
24
  # :billing_address => {
25
25
  # :address1 => '123 fairweather Lane',
@@ -46,6 +46,12 @@ module ActiveMerchant #:nodoc:
46
46
  TEST_URL = 'https://dev.psigate.com:7989/Messenger/XMLMessenger'
47
47
  LIVE_URL = 'https://secure.psigate.com:7934/Messenger/XMLMessenger'
48
48
 
49
+ self.supported_cardtypes = [:visa, :master, :american_express]
50
+ self.supported_countries = ['CA']
51
+ self.homepage_url = 'http://www.psigate.com/'
52
+ self.display_name = 'Psigate'
53
+
54
+
49
55
  def initialize(options = {})
50
56
  requires!(options, :login, :password)
51
57
 
@@ -85,12 +91,7 @@ module ActiveMerchant #:nodoc:
85
91
  options.update({ :CardAction => "3", :order_id => authorization })
86
92
  commit(money, nil, options)
87
93
  end
88
-
89
- # We support visa and master card
90
- def self.supported_cardtypes
91
- [:visa, :master]
92
- end
93
-
94
+
94
95
  private
95
96
 
96
97
  def commit(money, creditcard, options = {})
@@ -0,0 +1,297 @@
1
+ # Author:: MoneySpyder, http://moneyspyder.co.uk
2
+
3
+ module ActiveMerchant
4
+ module Billing
5
+ #
6
+ # ActiveMerchant PSL Card Gateway
7
+ #
8
+ # Notes:
9
+ # -To be able to use the capture function, the IP address of the machine must be
10
+ # registered with PSL
11
+ # -ESALE_KEYED should only be used in situations where the cardholder perceives the
12
+ # transaction to be Internet-based, such as purchasing from a web site/on-line store.
13
+ # If the Internet is used purely for the transport of information from the merchant
14
+ # directly to the gateway then the appropriate cardholder present or not present message
15
+ # type should be used rather than the ‘E’ equivalent.
16
+ # -The CV2 / AVS policies are set up with the account settings when signing up for an account
17
+ #
18
+ #
19
+ class PslCardGateway < Gateway
20
+ self.money_format = :cents
21
+ self.default_currency = 'GBP'
22
+
23
+ self.supported_countries = ['GB']
24
+ # Visa Credit, Visa Debit, Mastercard, Maestro, Solo, Electron,
25
+ # American Express, Diners Club, JCB, International Maestro,
26
+ # Style, Clydesdale Financial Services, Other
27
+
28
+ self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :jcb, :switch, :solo, :maestro ]
29
+ self.homepage_url = 'http://www.paymentsolutionsltd.com/'
30
+ self.display_name = 'PSL Payment Solutions'
31
+
32
+ # Default ISO 3166 country code (GB)
33
+ cattr_accessor :location
34
+ self.location = 826
35
+
36
+ # PslCard server URL - The url is the same whether testing or live - use
37
+ # the test account when testing...
38
+ URL = 'https://pslcard3.paymentsolutionsltd.com/secure/transact.asp?'
39
+
40
+ attr_reader :url
41
+ attr_reader :response
42
+ attr_reader :options
43
+
44
+ # eCommerce sale transaction, details keyed by merchant or cardholder
45
+ MESSAGE_TYPE = 'ESALE_KEYED'
46
+
47
+ # The type of response that we want to get from PSL, options are HTML, XML or REDIRECT
48
+ RESPONSE_ACTION = 'HTML'
49
+
50
+ # Currency Codes
51
+ CURRENCY_CODES = {
52
+ 'AUD' => 036,
53
+ 'GBP' => 826,
54
+ 'USD' => 840
55
+ }
56
+
57
+ #The terminal used - only for swipe transactions, so hard coded to 32 for online
58
+ EMV_TERMINAL_TYPE = 32
59
+
60
+ #Different Dispatch types
61
+ DISPATCH_LATER = 'LATER'
62
+ DISPATCH_NOW = 'NOW'
63
+
64
+ # Return codes
65
+ APPROVED = '00'
66
+
67
+ #Nominal amount to authorize for a 'dispatch later' type
68
+ #The nominal amount is held straight away, when the goods are ready
69
+ #to be dispatched, PSL is informed and the full amount is the
70
+ #taken.
71
+ NOMINAL_AMOUNT = 101
72
+
73
+ # Create a new PslCardGateway
74
+ #
75
+ # The gateway requires that a valid :login be passed in the options hash
76
+ #
77
+ # Paramaters:
78
+ # -options:
79
+ # :login - the PslCard account login (required)
80
+ def initialize(options = {})
81
+ requires!(options, :login)
82
+
83
+ @options = options
84
+ super
85
+ end
86
+
87
+ # Purchase the item straight away
88
+ #
89
+ # Parameters:
90
+ # -money: Money object for the total to be charged
91
+ # -authorization: the PSL cross reference from the previous authorization
92
+ # -options:
93
+ #
94
+ # Returns:
95
+ # -ActiveRecord::Billing::Response object
96
+ #
97
+ def purchase(money, credit_card, options = {})
98
+ post = {}
99
+
100
+ add_amount(post, money, DISPATCH_NOW, options)
101
+ add_credit_card(post, credit_card)
102
+ add_address(post, options)
103
+ add_invoice(post, options)
104
+ add_purchase_details(post)
105
+
106
+ commit(post)
107
+ end
108
+
109
+ # Authorize the transaction
110
+ #
111
+ # Reserves the funds on the customer's credit card, but does not
112
+ # charge the card.
113
+ #
114
+ # This implementation does not authorize the full amount, rather it checks that the full amount
115
+ # is available and only 'reserves' the nominal amount (currently a pound and a penny)
116
+ #
117
+ # Parameters:
118
+ # -money: Money object for the total to be charged
119
+ # -authorization: the PSL cross reference from the previous authorization
120
+ # -options:
121
+ #
122
+ # Returns:
123
+ # -ActiveRecord::Billing::Response object
124
+ #
125
+ def authorize(money, credit_card, options = {})
126
+ post = {}
127
+
128
+ add_amount(post, money, DISPATCH_LATER, options)
129
+ add_credit_card(post, credit_card)
130
+ add_address(post, options)
131
+ add_invoice(post, options)
132
+ add_purchase_details(post)
133
+
134
+ commit(post)
135
+ end
136
+
137
+ # Post an authorization.
138
+ #
139
+ # Captures the funds from an authorized transaction.
140
+ #
141
+ # Parameters:
142
+ # -money: Money object for the total to be charged
143
+ # -authorization: The PSL Cross Reference
144
+ # -options:
145
+ #
146
+ # Returns:
147
+ # -ActiveRecord::Billing::Response object
148
+ #
149
+ def capture(money, authorization, options = {})
150
+ post = {}
151
+
152
+ add_amount(post, money, DISPATCH_NOW, options)
153
+ add_reference(post, authorization)
154
+
155
+ commit(post)
156
+ end
157
+
158
+ private
159
+
160
+ def add_credit_card(post, credit_card)
161
+ post[:QAName] = credit_card.name
162
+ post[:CardNumber] = credit_card.number
163
+ post[:EMVTerminalType] = EMV_TERMINAL_TYPE
164
+ post[:ExpMonth] = credit_card.month
165
+ post[:ExpYear] = credit_card.year
166
+ post[:IssueNumber] = credit_card.issue_number unless credit_card.issue_number.blank?
167
+ post[:StartMonth] = credit_card.start_month unless credit_card.start_month.blank?
168
+ post[:StartYear] = credit_card.start_year unless credit_card.start_year.blank?
169
+
170
+ # CV2 check
171
+ post[:AVSCV2Check] = credit_card.verification_value? ? 'YES' : 'NO'
172
+ post[:CV2] = credit_card.verification_value if credit_card.verification_value?
173
+ end
174
+
175
+ def add_address(post, options)
176
+ address = options[:billing_address] || options[:address]
177
+ return if address.nil?
178
+
179
+ post[:QAAddress] = [:address1, :address2, :city, :state].collect{|a| address[a]}.reject{|a| a.blank?}.join(' ')
180
+ post[:QAPostcode] = address[:zip]
181
+ end
182
+
183
+ def add_invoice(post, options)
184
+ post[:MerchantName] = options[:merchant] || 'Merchant Name' # May use this as the order_id field
185
+ post[:OrderID] = options[:order_id] unless options[:order_id].blank?
186
+ end
187
+
188
+ def add_reference(post, authorization)
189
+ post[:CrossReference] = authorization
190
+ end
191
+
192
+ def add_amount(post, money, dispatch_type, options)
193
+ post[:CurrencyCode] = currency_code(options[:currency] || currency(money))
194
+
195
+ if dispatch_type == DISPATCH_LATER
196
+ post[:amount] = amount(NOMINAL_AMOUNT)
197
+ post[:DispatchLaterAmount] = amount(money)
198
+ else
199
+ post[:amount] = amount(money)
200
+ end
201
+
202
+ post[:Dispatch] = dispatch_type
203
+ end
204
+
205
+ def add_purchase_details(post)
206
+ post[:EchoAmount] = 'YES'
207
+ post[:SCBI] = 'YES' # Return information about the transaction
208
+ post[:MessageType] = MESSAGE_TYPE
209
+ end
210
+
211
+ # Get the currency code for the passed money object
212
+ #
213
+ # The money class stores the currency as an ISO 4217:2001 Alphanumeric,
214
+ # however PSL requires the ISO 4217:2001 Numeric code.
215
+ #
216
+ # Parameters:
217
+ # -money: Money object with the amount and currency
218
+ #
219
+ # Returns:
220
+ # -the ISO 4217:2001 Numberic currency code
221
+ #
222
+ def currency_code(currency)
223
+ CURRENCY_CODES[currency]
224
+ end
225
+
226
+ # Parse the PSL response and create a Response object
227
+ #
228
+ # Parameters:
229
+ # -body: The response string returned from PSL, Formatted:
230
+ # Key=value&key=value...
231
+ #
232
+ # Returns:
233
+ # -a hash with all of the values returned in the PSL response
234
+ #
235
+ def parse(body)
236
+
237
+ fields = {}
238
+ for line in body.split('&')
239
+ key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten
240
+ fields[key] = CGI.unescape(value)
241
+ end
242
+ fields.symbolize_keys
243
+ end
244
+
245
+ # Send the passed data to PSL for processing
246
+ #
247
+ # Parameters:
248
+ # -request: The data that is to be sent to PSL
249
+ #
250
+ # Returns:
251
+ # - ActiveMerchant::Billing::Response object
252
+ #
253
+ def commit(request)
254
+ if result = test_result_from_cc_number(request[:CardNumber])
255
+ return result
256
+ end
257
+
258
+ result = ssl_post(URL, post_data(request))
259
+
260
+ @response = parse(result)
261
+
262
+ success = @response[:ResponseCode] == APPROVED
263
+ message = @response[:Message]
264
+
265
+ Response.new(success, message, @response,
266
+ :test => test?,
267
+ :authorization => @response[:CrossReference]
268
+ )
269
+ end
270
+
271
+ # Put the passed data into a format that can be submitted to PSL
272
+ # Key=Value&Key=Value...
273
+ #
274
+ # Any ampersands and equal signs are removed from the data being posted
275
+ # as PSL puts them back into the response string which then cannot be parsed.
276
+ # This is after escaping before sending the request to PSL - this is a work
277
+ # around for the time being
278
+ #
279
+ # Parameters:
280
+ # -post: Hash of all the data to be sent
281
+ #
282
+ # Returns:
283
+ # -String: the data to be sent
284
+ #
285
+ def post_data(post)
286
+ post[:CountryCode] = self.location
287
+ post[:MerchantID] = @options[:login]
288
+ post[:ValidityID] = @options[:password]
289
+ post[:ResponseAction] = RESPONSE_ACTION
290
+
291
+ post.collect { |key, value|
292
+ "#{key}=#{CGI.escape(value.to_s.tr('&=', ' '))}"
293
+ }.join("&")
294
+ end
295
+ end
296
+ end
297
+ end