offsite_payments 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +70 -0
  4. data/lib/offsite_payments.rb +46 -0
  5. data/lib/offsite_payments/action_view_helper.rb +72 -0
  6. data/lib/offsite_payments/helper.rb +119 -0
  7. data/lib/offsite_payments/integrations.rb +14 -0
  8. data/lib/offsite_payments/integrations/a1agregator.rb +245 -0
  9. data/lib/offsite_payments/integrations/authorize_net_sim.rb +580 -0
  10. data/lib/offsite_payments/integrations/bit_pay.rb +150 -0
  11. data/lib/offsite_payments/integrations/bogus.rb +32 -0
  12. data/lib/offsite_payments/integrations/chronopay.rb +283 -0
  13. data/lib/offsite_payments/integrations/citrus.rb +227 -0
  14. data/lib/offsite_payments/integrations/direc_pay.rb +339 -0
  15. data/lib/offsite_payments/integrations/directebanking.rb +237 -0
  16. data/lib/offsite_payments/integrations/doku.rb +171 -0
  17. data/lib/offsite_payments/integrations/dotpay.rb +166 -0
  18. data/lib/offsite_payments/integrations/dwolla.rb +160 -0
  19. data/lib/offsite_payments/integrations/e_payment_plans.rb +146 -0
  20. data/lib/offsite_payments/integrations/easy_pay.rb +137 -0
  21. data/lib/offsite_payments/integrations/epay.rb +161 -0
  22. data/lib/offsite_payments/integrations/first_data.rb +133 -0
  23. data/lib/offsite_payments/integrations/gestpay.rb +201 -0
  24. data/lib/offsite_payments/integrations/hi_trust.rb +179 -0
  25. data/lib/offsite_payments/integrations/ipay88.rb +240 -0
  26. data/lib/offsite_payments/integrations/klarna.rb +291 -0
  27. data/lib/offsite_payments/integrations/liqpay.rb +216 -0
  28. data/lib/offsite_payments/integrations/maksuturva.rb +231 -0
  29. data/lib/offsite_payments/integrations/mollie_ideal.rb +213 -0
  30. data/lib/offsite_payments/integrations/moneybookers.rb +199 -0
  31. data/lib/offsite_payments/integrations/nochex.rb +228 -0
  32. data/lib/offsite_payments/integrations/pag_seguro.rb +255 -0
  33. data/lib/offsite_payments/integrations/paxum.rb +114 -0
  34. data/lib/offsite_payments/integrations/pay_fast.rb +269 -0
  35. data/lib/offsite_payments/integrations/paydollar.rb +142 -0
  36. data/lib/offsite_payments/integrations/payflow_link.rb +194 -0
  37. data/lib/offsite_payments/integrations/paypal.rb +362 -0
  38. data/lib/offsite_payments/integrations/paypal_payments_advanced.rb +23 -0
  39. data/lib/offsite_payments/integrations/paysbuy.rb +71 -0
  40. data/lib/offsite_payments/integrations/payu_in.rb +266 -0
  41. data/lib/offsite_payments/integrations/payu_in_paisa.rb +46 -0
  42. data/lib/offsite_payments/integrations/platron.rb +153 -0
  43. data/lib/offsite_payments/integrations/pxpay.rb +271 -0
  44. data/lib/offsite_payments/integrations/quickpay.rb +232 -0
  45. data/lib/offsite_payments/integrations/rbkmoney.rb +110 -0
  46. data/lib/offsite_payments/integrations/robokassa.rb +154 -0
  47. data/lib/offsite_payments/integrations/sage_pay_form.rb +425 -0
  48. data/lib/offsite_payments/integrations/two_checkout.rb +332 -0
  49. data/lib/offsite_payments/integrations/universal.rb +180 -0
  50. data/lib/offsite_payments/integrations/valitor.rb +200 -0
  51. data/lib/offsite_payments/integrations/verkkomaksut.rb +143 -0
  52. data/lib/offsite_payments/integrations/web_pay.rb +186 -0
  53. data/lib/offsite_payments/integrations/webmoney.rb +119 -0
  54. data/lib/offsite_payments/integrations/wirecard_checkout_page.rb +359 -0
  55. data/lib/offsite_payments/integrations/world_pay.rb +273 -0
  56. data/lib/offsite_payments/notification.rb +71 -0
  57. data/lib/offsite_payments/return.rb +37 -0
  58. data/lib/offsite_payments/version.rb +3 -0
  59. metadata +270 -0
@@ -0,0 +1,194 @@
1
+ module OffsitePayments #:nodoc:
2
+ module Integrations #:nodoc:
3
+ module PayflowLink
4
+ mattr_accessor :service_url
5
+ self.service_url = 'https://payflowlink.paypal.com'
6
+
7
+ def self.notification(post, options = {})
8
+ Notification.new(post)
9
+ end
10
+
11
+ def self.return(query_string, options = {})
12
+ OffsitePayments::Return.new(query_string)
13
+ end
14
+
15
+ class Helper < OffsitePayments::Helper
16
+ include ActiveMerchant::PostsData
17
+
18
+ def initialize(order, account, options = {})
19
+ super
20
+ add_field('login', account)
21
+ add_field('echodata', 'True')
22
+ add_field('user2', self.test?)
23
+ add_field('invoice', order)
24
+ add_field('vendor', account)
25
+ add_field('user', options[:credential4].presence || account)
26
+ add_field('trxtype', options[:transaction_type] || 'S')
27
+ end
28
+
29
+ mapping :account, 'login'
30
+ mapping :credential2, 'pwd'
31
+ mapping :credential3, 'partner'
32
+ mapping :order, 'user1'
33
+
34
+ mapping :amount, 'amt'
35
+
36
+
37
+ mapping :billing_address, :city => 'city',
38
+ :address => 'address',
39
+ :state => 'state',
40
+ :zip => 'zip',
41
+ :country => 'country',
42
+ :phone => 'phone',
43
+ :name => 'name'
44
+
45
+ mapping :customer, { :first_name => 'first_name', :last_name => 'last_name' }
46
+
47
+ def description(value)
48
+ add_field('description', normalize("#{value}").delete("#"))
49
+ end
50
+
51
+ def customer(params = {})
52
+ add_field(mappings[:customer][:first_name], params[:first_name])
53
+ add_field(mappings[:customer][:last_name], params[:last_name])
54
+ end
55
+
56
+ def billing_address(params = {})
57
+ # Get the country code in the correct format
58
+ # Use what we were given if we can't find anything
59
+ country_code = lookup_country_code(params.delete(:country))
60
+ add_field(mappings[:billing_address][:country], country_code)
61
+
62
+ add_field(mappings[:billing_address][:address], [params.delete(:address1), params.delete(:address2)].compact.join(' '))
63
+
64
+ province_code = params.delete(:state)
65
+ add_field(mappings[:billing_address][:state], province_code.blank? ? 'N/A' : province_code.upcase)
66
+
67
+ # Everything else
68
+ params.each do |k, v|
69
+ field = mappings[:billing_address][k]
70
+ add_field(field, v) unless field.nil?
71
+ end
72
+ end
73
+
74
+ def form_fields
75
+ token, token_id = request_secure_token
76
+
77
+ {"securetoken" => token, "securetokenid" => token_id, "mode" => test? ? "test" : "live"}
78
+ end
79
+
80
+ private
81
+
82
+ def secure_token_id
83
+ @secure_token_id ||= SecureRandom.hex(16)
84
+ end
85
+
86
+ def secure_token_url
87
+ test? ? "https://pilot-payflowpro.paypal.com" : "https://payflowpro.paypal.com"
88
+ end
89
+
90
+ def request_secure_token
91
+ @fields["securetokenid"] = secure_token_id
92
+ @fields["createsecuretoken"] = "Y"
93
+
94
+ fields = @fields.collect {|key, value| "#{key}[#{value.length}]=#{value}" }.join("&")
95
+
96
+ response = ssl_post(secure_token_url, fields)
97
+
98
+ parse_response(response)
99
+ end
100
+
101
+ def parse_response(response)
102
+ response = response.split("&").inject({}) do |hash, param|
103
+ key, value = param.split("=")
104
+ hash[key] = value
105
+ hash
106
+ end
107
+
108
+ [response['SECURETOKEN'], response['SECURETOKENID']] if response['RESPMSG'] && response['RESPMSG'].downcase == "approved"
109
+ end
110
+
111
+ def normalize(text)
112
+ return unless text
113
+
114
+ if ActiveSupport::Inflector.method(:transliterate).arity == -2
115
+ ActiveSupport::Inflector.transliterate(text,'')
116
+ elsif RUBY_VERSION >= '1.9'
117
+ text.gsub(/[^\x00-\x7F]+/, '')
118
+ else
119
+ ActiveSupport::Inflector.transliterate(text).to_s
120
+ end
121
+ end
122
+ end
123
+
124
+ class Notification < OffsitePayments::Notification
125
+
126
+ # Was the transaction complete?
127
+ def complete?
128
+ status == "Completed"
129
+ end
130
+
131
+ # When was this payment received by the client.
132
+ # sometimes it can happen that we get the notification much later.
133
+ # One possible scenario is that our web application was down. In this case paypal tries several
134
+ # times an hour to inform us about the notification
135
+ def received_at
136
+ DateTime.parse(params['TRANSTIME']) if params['TRANSTIME']
137
+ rescue ArgumentError
138
+ nil
139
+ end
140
+
141
+ def status
142
+ params['RESPMSG']
143
+ end
144
+
145
+ # Id of this transaction (paypal number)
146
+ def transaction_id
147
+ params['PNREF']
148
+ end
149
+
150
+ # What type of transaction are we dealing with?
151
+ def type
152
+ params['TYPE']
153
+ end
154
+
155
+ # the money amount we received in X.2 decimal.
156
+ def gross
157
+ params['AMT']
158
+ end
159
+
160
+ # What currency have we been dealing with
161
+ def currency
162
+ nil
163
+ end
164
+
165
+ def status
166
+ params['RESULT'] == '0' ? 'Completed' : 'Failed'
167
+ end
168
+
169
+ # This is the item number which we submitted to paypal
170
+ def item_id
171
+ params['USER1']
172
+ end
173
+
174
+ # This is the invoice which you passed to paypal
175
+ def invoice
176
+ params['INVNUM']
177
+ end
178
+
179
+ # Was this a test transaction?
180
+ def test?
181
+ params['USER2'] == 'true'
182
+ end
183
+
184
+ def account
185
+ params["ACCT"]
186
+ end
187
+
188
+ def acknowledge(authcode = nil)
189
+ true
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,362 @@
1
+ module OffsitePayments #:nodoc:
2
+ module Integrations #:nodoc:
3
+ module Paypal
4
+ # Overwrite this if you want to change the Paypal test url
5
+ mattr_accessor :test_url
6
+ self.test_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr'
7
+
8
+ # Overwrite this if you want to change the Paypal production url
9
+ mattr_accessor :production_url
10
+ self.production_url = 'https://www.paypal.com/cgi-bin/webscr'
11
+
12
+ def self.service_url
13
+ mode = OffsitePayments.mode
14
+ case mode
15
+ when :production
16
+ self.production_url
17
+ when :test
18
+ self.test_url
19
+ else
20
+ raise StandardError, "Integration mode set to an invalid value: #{mode}"
21
+ end
22
+ end
23
+
24
+ def self.notification(post, options = {})
25
+ Notification.new(post)
26
+ end
27
+
28
+ def self.return(query_string, options = {})
29
+ Return.new(query_string)
30
+ end
31
+
32
+ class Helper < OffsitePayments::Helper
33
+ CANADIAN_PROVINCES = { 'AB' => 'Alberta',
34
+ 'BC' => 'British Columbia',
35
+ 'MB' => 'Manitoba',
36
+ 'NB' => 'New Brunswick',
37
+ 'NL' => 'Newfoundland',
38
+ 'NS' => 'Nova Scotia',
39
+ 'NU' => 'Nunavut',
40
+ 'NT' => 'Northwest Territories',
41
+ 'ON' => 'Ontario',
42
+ 'PE' => 'Prince Edward Island',
43
+ 'QC' => 'Quebec',
44
+ 'SK' => 'Saskatchewan',
45
+ 'YT' => 'Yukon'
46
+ }
47
+ # See https://www.paypal.com/IntegrationCenter/ic_std-variable-reference.html for details on the following options.
48
+ mapping :order, [ 'item_number', 'custom' ]
49
+
50
+ def initialize(order, account, options = {})
51
+ super
52
+ add_field('cmd', '_ext-enter')
53
+ add_field('redirect_cmd', '_xclick')
54
+ add_field('quantity', 1)
55
+ add_field('item_name', 'Store purchase')
56
+ add_field('no_shipping', '1')
57
+ add_field('no_note', '1')
58
+ add_field('charset', 'utf-8')
59
+ add_field('address_override', '0')
60
+ add_field('bn', application_id.to_s.slice(0,32)) unless application_id.blank?
61
+ end
62
+
63
+ mapping :amount, 'amount'
64
+ mapping :account, 'business'
65
+ mapping :currency, 'currency_code'
66
+ mapping :notify_url, 'notify_url'
67
+ mapping :return_url, 'return'
68
+ mapping :cancel_return_url, 'cancel_return'
69
+ mapping :invoice, 'invoice'
70
+ mapping :item_name, 'item_name'
71
+ mapping :quantity, 'quantity'
72
+ mapping :no_shipping, 'no_shipping'
73
+ mapping :no_note, 'no_note'
74
+ mapping :address_override, 'address_override'
75
+
76
+ mapping :application_id, 'bn'
77
+
78
+ mapping :customer, :first_name => 'first_name',
79
+ :last_name => 'last_name',
80
+ :email => 'email'
81
+
82
+ mapping :shipping_address, :city => 'city',
83
+ :address1 => 'address1',
84
+ :address2 => 'address2',
85
+ :state => 'state',
86
+ :zip => 'zip',
87
+ :country => 'country'
88
+
89
+ def shipping_address(params = {})
90
+ # Get the country code in the correct format
91
+ # Use what we were given if we can't find anything
92
+ country_code = lookup_country_code(params.delete(:country))
93
+ add_field(mappings[:shipping_address][:country], country_code)
94
+
95
+ if params.has_key?(:phone)
96
+ phone = params.delete(:phone).to_s
97
+
98
+ # Wipe all non digits
99
+ phone.gsub!(/\D+/, '')
100
+
101
+ if ['US', 'CA'].include?(country_code) && phone =~ /(\d{3})(\d{3})(\d{4})$/
102
+ add_field('night_phone_a', $1)
103
+ add_field('night_phone_b', $2)
104
+ add_field('night_phone_c', $3)
105
+ else
106
+ add_field('night_phone_b', phone)
107
+ end
108
+ end
109
+
110
+ province_code = params.delete(:state)
111
+
112
+ case country_code
113
+ when 'CA'
114
+ add_field(mappings[:shipping_address][:state], CANADIAN_PROVINCES[province_code.upcase]) unless province_code.nil?
115
+ when 'US'
116
+ add_field(mappings[:shipping_address][:state], province_code)
117
+ else
118
+ add_field(mappings[:shipping_address][:state], province_code.blank? ? 'N/A' : province_code)
119
+ end
120
+
121
+ # Everything else
122
+ params.each do |k, v|
123
+ field = mappings[:shipping_address][k]
124
+ add_field(field, v) unless field.nil?
125
+ end
126
+ end
127
+
128
+ mapping :tax, 'tax'
129
+ mapping :shipping, 'shipping'
130
+ mapping :cmd, 'cmd'
131
+ mapping :custom, 'custom'
132
+ mapping :src, 'src'
133
+ mapping :sra, 'sra'
134
+ %w(a p t).each do |l|
135
+ (1..3).each do |i|
136
+ mapping "#{l}#{i}".to_sym, "#{l}#{i}"
137
+ end
138
+ end
139
+ end
140
+
141
+ # Parser and handler for incoming Instant payment notifications from paypal.
142
+ # The Example shows a typical handler in a rails application. Note that this
143
+ # is an example, please read the Paypal API documentation for all the details
144
+ # on creating a safe payment controller.
145
+ #
146
+ # Example
147
+ #
148
+ # class BackendController < ApplicationController
149
+ # include OffsitePayments::Integrations
150
+ #
151
+ # def paypal_ipn
152
+ # notify = Paypal::Notification.new(request.raw_post)
153
+ #
154
+ # if notify.masspay?
155
+ # masspay_items = notify.items
156
+ # end
157
+ #
158
+ # order = Order.find(notify.item_id)
159
+ #
160
+ # if notify.acknowledge
161
+ # begin
162
+ #
163
+ # if notify.complete? and order.total == notify.amount
164
+ # order.status = 'success'
165
+ #
166
+ # shop.ship(order)
167
+ # else
168
+ # logger.error("Failed to verify Paypal's notification, please investigate")
169
+ # end
170
+ #
171
+ # rescue => e
172
+ # order.status = 'failed'
173
+ # raise
174
+ # ensure
175
+ # order.save
176
+ # end
177
+ # end
178
+ #
179
+ # render :nothing
180
+ # end
181
+ # end
182
+ class Notification < OffsitePayments::Notification
183
+ include ActiveMerchant::PostsData
184
+
185
+ def initialize(post, options = {})
186
+ super
187
+ extend MassPayNotification if masspay?
188
+ end
189
+
190
+ # Was the transaction complete?
191
+ def complete?
192
+ status == "Completed"
193
+ end
194
+
195
+ # Is it a masspay notification?
196
+ def masspay?
197
+ type == "masspay"
198
+ end
199
+
200
+ # When was this payment received by the client.
201
+ # sometimes it can happen that we get the notification much later.
202
+ # One possible scenario is that our web application was down. In this case paypal tries several
203
+ # times an hour to inform us about the notification
204
+ def received_at
205
+ parsed_time_fields = DateTime._strptime(params['payment_date'], "%H:%M:%S %b %d, %Y %Z")
206
+ Time.gm(
207
+ parsed_time_fields[:year],
208
+ parsed_time_fields[:mon],
209
+ parsed_time_fields[:mday],
210
+ parsed_time_fields[:hour],
211
+ parsed_time_fields[:min],
212
+ parsed_time_fields[:sec]
213
+ ) - Time.zone_offset(parsed_time_fields[:zone])
214
+ end
215
+
216
+ # Status of transaction. List of possible values:
217
+ # <tt>Canceled-Reversal</tt>::
218
+ # <tt>Completed</tt>::
219
+ # <tt>Denied</tt>::
220
+ # <tt>Expired</tt>::
221
+ # <tt>Failed</tt>::
222
+ # <tt>In-Progress</tt>::
223
+ # <tt>Partially-Refunded</tt>::
224
+ # <tt>Pending</tt>::
225
+ # <tt>Processed</tt>::
226
+ # <tt>Refunded</tt>::
227
+ # <tt>Reversed</tt>::
228
+ # <tt>Voided</tt>::
229
+ def status
230
+ params['payment_status']
231
+ end
232
+
233
+ # Id of this transaction (paypal number)
234
+ def transaction_id
235
+ params['txn_id']
236
+ end
237
+
238
+ # What type of transaction are we dealing with?
239
+ # "cart" "send_money" "web_accept" are possible here.
240
+ def type
241
+ params['txn_type']
242
+ end
243
+
244
+ # the money amount we received in X.2 decimal.
245
+ def gross
246
+ params['mc_gross']
247
+ end
248
+
249
+ # the markup paypal charges for the transaction
250
+ def fee
251
+ params['mc_fee']
252
+ end
253
+
254
+ # What currency have we been dealing with
255
+ def currency
256
+ params['mc_currency']
257
+ end
258
+
259
+ # This is the item number which we submitted to paypal
260
+ # The custom field is also mapped to item_id because PayPal
261
+ # doesn't return item_number in dispute notifications
262
+ def item_id
263
+ params['item_number'] || params['custom']
264
+ end
265
+
266
+ # This is the invoice which you passed to paypal
267
+ def invoice
268
+ params['invoice']
269
+ end
270
+
271
+ # Was this a test transaction?
272
+ def test?
273
+ params['test_ipn'] == '1'
274
+ end
275
+
276
+ def account
277
+ params['business'] || params['receiver_email']
278
+ end
279
+
280
+ # Acknowledge the transaction to paypal. This method has to be called after a new
281
+ # ipn arrives. Paypal will verify that all the information we received are correct and will return a
282
+ # ok or a fail.
283
+ #
284
+ # Example:
285
+ #
286
+ # def paypal_ipn
287
+ # notify = PaypalNotification.new(request.raw_post)
288
+ #
289
+ # if notify.acknowledge
290
+ # ... process order ... if notify.complete?
291
+ # else
292
+ # ... log possible hacking attempt ...
293
+ # end
294
+ def acknowledge(authcode = nil)
295
+ payload = raw
296
+
297
+ response = ssl_post(Paypal.service_url + '?cmd=_notify-validate', payload,
298
+ 'Content-Length' => "#{payload.size}",
299
+ 'User-Agent' => "Active Merchant -- http://activemerchant.org"
300
+ )
301
+
302
+ raise StandardError.new("Faulty paypal result: #{response}") unless ["VERIFIED", "INVALID"].include?(response)
303
+
304
+ response == "VERIFIED"
305
+ end
306
+ end
307
+
308
+ module MassPayNotification
309
+ # Mass pay returns a collection of MassPay Items, so inspect items to get the values
310
+ def transaction_id
311
+ end
312
+
313
+ # Mass pay returns a collection of MassPay Items, so inspect items to get the values
314
+ def gross
315
+ end
316
+
317
+ # Mass pay returns a collection of MassPay Items, so inspect items to get the values
318
+ def fee
319
+ end
320
+
321
+ # Mass pay returns a collection of MassPay Items, so inspect items to get the values
322
+ def currency
323
+ end
324
+
325
+ # Mass pay returns a collection of MassPay Items, so inspect items to get the values
326
+ def item_id
327
+ end
328
+
329
+ # Mass pay returns a collection of MassPay Items, so inspect items to get the values
330
+ def account
331
+ end
332
+
333
+ # Collection of notification items returned for MassPay transactions
334
+ def items
335
+ @items ||= (1..number_of_mass_pay_items).map do |item_number|
336
+ MassPayItem.new(
337
+ params["masspay_txn_id_#{item_number}"],
338
+ params["mc_gross_#{item_number}"],
339
+ params["mc_fee_#{item_number}"],
340
+ params["mc_currency_#{item_number}"],
341
+ params["unique_id_#{item_number}"],
342
+ params["receiver_email_#{item_number}"],
343
+ params["status_#{item_number}"]
344
+ )
345
+ end
346
+ end
347
+
348
+ private
349
+
350
+ def number_of_mass_pay_items
351
+ @number_of_mass_pay_items ||= params.keys.select { |k| k.start_with? 'masspay_txn_id' }.size
352
+ end
353
+ end
354
+
355
+ class MassPayItem < Struct.new(:transaction_id, :gross, :fee, :currency, :item_id, :account, :status)
356
+ end
357
+
358
+ class Return < OffsitePayments::Return
359
+ end
360
+ end
361
+ end
362
+ end