offsite_payments 2.0.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 (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