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,227 @@
1
+ module OffsitePayments
2
+ module Integrations
3
+ module Citrus
4
+ def self.helper(order, account, options = {})
5
+ Helper.new(order, account, options)
6
+ end
7
+
8
+ def self.notification(post, options = {})
9
+ Notification.new(post, options)
10
+ end
11
+
12
+ def self.return(query_string, options = {})
13
+ Return.new(query_string, options)
14
+ end
15
+
16
+ def self.checksum(secret_key, payload_items )
17
+ digest = OpenSSL::Digest.new('sha1')
18
+ OpenSSL::HMAC.hexdigest(digest, secret_key, payload_items)
19
+ end
20
+
21
+ class Helper < OffsitePayments::Helper
22
+ mapping :order, 'merchantTxnId'
23
+ mapping :amount, 'orderAmount'
24
+ mapping :account, 'merchantAccessKey'
25
+ mapping :credential2, 'secret_key'
26
+ mapping :credential3, 'pmt_url'
27
+ mapping :currency, 'currency'
28
+
29
+ mapping :customer, :first_name => 'firstName',:last_name => 'lastName', :email => 'email', :phone => 'mobileNo'
30
+
31
+ mapping :billing_address, :city => 'addressCity', :address1 => 'addressStreet1', :address2 => 'addressStreet2',:state => 'addressState',:zip => 'addressZip', :country => 'addressCountry'
32
+
33
+ mapping :checksum, 'secSignature'
34
+ mapping :return_url, 'returnUrl'
35
+
36
+ SANDBOX_URL = 'https://sandbox.citruspay.com/'.freeze
37
+ STAGING_URL = 'https://stg.citruspay.com/'.freeze
38
+ PRODUCTION_URL = 'https://www.citruspay.com/'.freeze
39
+
40
+ def credential_based_url
41
+ pmt_url = @fields['pmt_url']
42
+ case OffsitePayments.mode
43
+ when :production
44
+ PRODUCTION_URL + pmt_url
45
+ when :test
46
+ SANDBOX_URL + pmt_url
47
+ when :staging
48
+ STAGING_URL + pmt_url
49
+ else
50
+ raise StandardError, "Integration mode set to an invalid value: #{mode}"
51
+ end
52
+ end
53
+
54
+ def initialize(order, account, options = {})
55
+ super
56
+ add_field 'paymentMode', 'NET_BANKING'
57
+ add_field 'reqtime', (Time.now.to_i * 1000).to_s
58
+ end
59
+
60
+ def form_fields
61
+ @fields.merge(mappings[:checksum] => generate_checksum)
62
+ end
63
+
64
+ def generate_checksum
65
+ checksum_fields = @fields["pmt_url"] + @fields["orderAmount"].to_s + @fields["merchantTxnId"] + @fields["currency"]
66
+ Citrus.checksum(@fields["secret_key"], checksum_fields )
67
+ end
68
+ end
69
+
70
+ class Notification < OffsitePayments::Notification
71
+ def initialize(post, options = {})
72
+ super(post, options)
73
+ @secret_key = options[:credential2]
74
+ end
75
+
76
+ def complete?
77
+ status == "Completed" || status == 'Canceled'
78
+ end
79
+
80
+ def status
81
+ @status ||= if checksum_ok?
82
+ if transaction_id.blank?
83
+ 'Invalid'
84
+ else
85
+ case transaction_status.downcase
86
+ when 'success' then 'Completed'
87
+ when 'canceled' then 'Cancelled'
88
+ end
89
+ end
90
+ else
91
+ 'Tampered'
92
+ end
93
+ end
94
+
95
+ def invoice_ok?( order_id )
96
+ order_id.to_s == invoice.to_s
97
+ end
98
+
99
+ def amount_ok?( order_amount )
100
+ BigDecimal.new( amount ) == order_amount
101
+ end
102
+
103
+ def item_id
104
+ params['TxId']
105
+ end
106
+
107
+ def invoice
108
+ item_id
109
+ end
110
+
111
+ # Status of transaction return from the Citrus. List of possible values:
112
+ # <tt>SUCCESS</tt>::
113
+ # <tt>CANCELED</tt>::
114
+ def transaction_status
115
+ params['TxStatus']
116
+ end
117
+
118
+ def gross
119
+ params['amount']
120
+ end
121
+
122
+ def amount
123
+ gross
124
+ end
125
+
126
+ def transaction_id
127
+ params['pgTxnNo']
128
+ end
129
+
130
+ def issuerrefno
131
+ params['issuerRefNo']
132
+ end
133
+
134
+ def authidcode
135
+ params['authIdCode']
136
+ end
137
+
138
+ def pgrespcode
139
+ params['pgRespCode']
140
+ end
141
+
142
+ def checksum
143
+ params['signature']
144
+ end
145
+
146
+ def paymentmode
147
+ params['paymentMode']
148
+ end
149
+
150
+ def currency
151
+ params['currency']
152
+ end
153
+
154
+ def customer_email
155
+ params['email']
156
+ end
157
+
158
+ def customer_phone
159
+ params['mobileNo']
160
+ end
161
+
162
+ def customer_first_name
163
+ params['firstName']
164
+ end
165
+
166
+ def customer_last_name
167
+ params['lastName']
168
+ end
169
+
170
+ def customer_address
171
+ { :address1 => params['addressStreet1'], :address2 => params['addressStreet2'],
172
+ :city => params['addressCity'], :state => params['addressState'],
173
+ :country => params['addressCountry'], :zip => params['addressZip'] }
174
+ end
175
+
176
+ def message
177
+ @message || params['TxMsg']
178
+ end
179
+
180
+ def acknowledge(authcode = nil)
181
+ checksum_ok?
182
+ end
183
+
184
+ def checksum_ok?
185
+ fields = [invoice, transaction_status, amount.to_s, transaction_id, issuerrefno, authidcode, customer_first_name, customer_last_name, pgrespcode, customer_address[:zip]].join
186
+
187
+ unless Citrus.checksum(@secret_key, fields ) == checksum
188
+ @message = 'checksum mismatch...'
189
+ return false
190
+ end
191
+ true
192
+ end
193
+ end
194
+
195
+ class Return < OffsitePayments::Return
196
+ def initialize(query_string, options = {})
197
+ super
198
+ @notification = Notification.new(query_string, options)
199
+ end
200
+
201
+ def transaction_id
202
+ @notification.transaction_id
203
+ end
204
+
205
+ def status( order_id, order_amount )
206
+ if @notification.invoice_ok?( order_id ) && @notification.amount_ok?( BigDecimal.new(order_amount) )
207
+ @notification.status
208
+ else
209
+ 'Mismatch'
210
+ end
211
+ end
212
+
213
+ def success?
214
+ status( @params['TxId'], @params['amount'] ) == 'Completed'
215
+ end
216
+
217
+ def message
218
+ @notification.message
219
+ end
220
+
221
+ def cancelled?
222
+ @notification.status == 'Cancelled'
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,339 @@
1
+ module OffsitePayments #:nodoc:
2
+ module Integrations #:nodoc:
3
+ module DirecPay
4
+ mattr_accessor :production_url, :test_url
5
+
6
+ self.production_url = "https://www.timesofmoney.com/direcpay/secure/dpMerchantTransaction.jsp"
7
+ self.test_url = "https://test.direcpay.com/direcpay/secure/dpMerchantTransaction.jsp"
8
+
9
+ def self.service_url
10
+ mode = OffsitePayments.mode
11
+ case mode
12
+ when :production
13
+ self.production_url
14
+ when :test
15
+ self.test_url
16
+ else
17
+ raise StandardError, "Integration mode set to an invalid value: #{mode}"
18
+ end
19
+ end
20
+
21
+ def self.notification(post, options = {})
22
+ Notification.new(post)
23
+ end
24
+
25
+ def self.return(query_string, options = {})
26
+ Return.new(query_string, options)
27
+ end
28
+
29
+ def self.request_status_update(mid, transaction_id, notification_url)
30
+ Status.new(mid).update(transaction_id, notification_url)
31
+ end
32
+
33
+ class Helper < OffsitePayments::Helper
34
+ mapping :account, 'MID'
35
+ mapping :order, 'Merchant Order No'
36
+ mapping :amount, 'Amount'
37
+ mapping :currency, 'Currency'
38
+ mapping :country, 'Country'
39
+
40
+ mapping :billing_address, :city => 'custCity',
41
+ :address1 => 'custAddress',
42
+ :state => 'custState',
43
+ :zip => 'custPinCode',
44
+ :country => 'custCountry',
45
+ :phone => 'custMobileNo'
46
+
47
+ mapping :shipping_address, :name => 'deliveryName',
48
+ :city => 'deliveryCity',
49
+ :address1 => 'deliveryAddress',
50
+ :state => 'deliveryState',
51
+ :zip => 'deliveryPinCode',
52
+ :country => 'deliveryCountry',
53
+ :phone => 'deliveryMobileNo'
54
+
55
+ mapping :customer, :name => 'custName',
56
+ :email => 'custEmailId'
57
+
58
+ mapping :description, 'otherNotes'
59
+ mapping :edit_allowed, 'editAllowed'
60
+
61
+ mapping :return_url, 'Success URL'
62
+ mapping :failure_url, 'Failure URL'
63
+
64
+ mapping :operating_mode, 'Operating Mode'
65
+ mapping :other_details, 'Other Details'
66
+ mapping :collaborator, 'Collaborator'
67
+
68
+ OPERATING_MODE = 'DOM'
69
+ COUNTRY = 'IND'
70
+ CURRENCY = 'INR'
71
+ OTHER_DETAILS = 'NULL'
72
+ EDIT_ALLOWED = 'Y'
73
+
74
+ PHONE_CODES = {
75
+ 'IN' => '91',
76
+ 'US' => '01',
77
+ 'CA' => '01'
78
+ }
79
+
80
+ ENCODED_PARAMS = [ :account, :operating_mode, :country, :currency, :amount, :order, :other_details, :return_url, :failure_url, :collaborator ]
81
+
82
+ def initialize(order, account, options = {})
83
+ super
84
+ collaborator = OffsitePayments.mode == :test || options[:test] ? 'TOML' : 'DirecPay'
85
+ add_field(mappings[:collaborator], collaborator)
86
+ add_field(mappings[:country], 'IND')
87
+ add_field(mappings[:operating_mode], OPERATING_MODE)
88
+ add_field(mappings[:other_details], OTHER_DETAILS)
89
+ add_field(mappings[:edit_allowed], EDIT_ALLOWED)
90
+ end
91
+
92
+
93
+ def customer(params = {})
94
+ add_field(mappings[:customer][:name], full_name(params))
95
+ add_field(mappings[:customer][:email], params[:email])
96
+ end
97
+
98
+ # Need to format the amount to have 2 decimal places
99
+ def amount=(money)
100
+ cents = money.respond_to?(:cents) ? money.cents : money
101
+ raise ArgumentError, "amount must be a Money object or an integer" if money.is_a?(String)
102
+ raise ActionViewHelperError, "amount must be greater than $0.00" if cents.to_i <= 0
103
+
104
+ add_field(mappings[:amount], sprintf("%.2f", cents.to_f/100))
105
+ end
106
+
107
+ def shipping_address(params = {})
108
+ super(update_address(:shipping_address, params))
109
+ end
110
+
111
+ def billing_address(params = {})
112
+ super(update_address(:billing_address, params))
113
+ end
114
+
115
+ def form_fields
116
+ add_failure_url
117
+ add_request_parameters
118
+
119
+ unencoded_parameters
120
+ end
121
+
122
+ private
123
+
124
+ def add_request_parameters
125
+ params = ENCODED_PARAMS.map{ |param| fields[mappings[param]] }
126
+ encoded = encode_value(params.join('|'))
127
+
128
+ add_field('requestparameter', encoded)
129
+ end
130
+
131
+ def unencoded_parameters
132
+ params = fields.dup
133
+ # remove all encoded params from exported fields
134
+ ENCODED_PARAMS.each{ |param| params.delete(mappings[param]) }
135
+ # remove all special characters from each field value
136
+ params = params.collect{|name, value| [name, remove_special_characters(value)] }
137
+ Hash[params]
138
+ end
139
+
140
+ def add_failure_url
141
+ if fields[mappings[:failure_url]].nil?
142
+ add_field(mappings[:failure_url], fields[mappings[:return_url]])
143
+ end
144
+ end
145
+
146
+ def update_address(address_type, params)
147
+ params = params.dup
148
+ address = params[:address1]
149
+ address = "#{address} #{params[:address2]}" if params[:address2].present?
150
+ address = "#{params[:company]} #{address}" if params[:company].present?
151
+ params[:address1] = address
152
+
153
+ params[:phone] = normalize_phone_number(params[:phone])
154
+ add_land_line_phone_for(address_type, params)
155
+
156
+ if address_type == :shipping_address
157
+ shipping_name = full_name(params) || fields[mappings[:customer][:name]]
158
+ add_field(mappings[:shipping_address][:name], shipping_name)
159
+ end
160
+ params
161
+ end
162
+
163
+ # Split a single phone number into the country code, area code and local number as best as possible
164
+ def add_land_line_phone_for(address_type, params)
165
+ address_field = address_type == :billing_address ? 'custPhoneNo' : 'deliveryPhNo'
166
+
167
+ if params.has_key?(:phone2)
168
+ phone = normalize_phone_number(params[:phone2])
169
+ phone_country_code, phone_area_code, phone_number = nil
170
+
171
+ if params[:country] == 'IN' && phone =~ /(91)? *(\d{3}) *(\d{4,})$/
172
+ phone_country_code, phone_area_code, phone_number = $1, $2, $3
173
+ else
174
+ numbers = phone.split(' ')
175
+ case numbers.size
176
+ when 3
177
+ phone_country_code, phone_area_code, phone_number = numbers
178
+ when 2
179
+ phone_area_code, phone_number = numbers
180
+ else
181
+ phone =~ /(\d{3})(\d+)$/
182
+ phone_area_code, phone_number = $1, $2
183
+ end
184
+ end
185
+
186
+ add_field("#{address_field}1", phone_country_code || phone_code_for_country(params[:country]) || '91')
187
+ add_field("#{address_field}2", phone_area_code)
188
+ add_field("#{address_field}3", phone_number)
189
+ end
190
+ end
191
+
192
+ def normalize_phone_number(phone)
193
+ phone.gsub(/[^\d ]+/, '') if phone
194
+ end
195
+
196
+ # Special characters are NOT allowed while posting transaction parameters on DirecPay system
197
+ def remove_special_characters(string)
198
+ string.gsub(/[~"'&#%]/, '-')
199
+ end
200
+
201
+ def encode_value(value)
202
+ encoded = Base64.strict_encode64(value)
203
+ string_to_encode = encoded[0, 1] + "T" + encoded[1, encoded.length]
204
+ Base64.strict_encode64(string_to_encode)
205
+ end
206
+
207
+ def decode_value(value)
208
+ decoded = Base64.decode64(value)
209
+ string_to_decode = decoded[0, 1] + decoded[2, decoded.length]
210
+ Base64.decode64(string_to_decode)
211
+ end
212
+
213
+ def phone_code_for_country(country)
214
+ PHONE_CODES[country]
215
+ end
216
+
217
+ def full_name(params)
218
+ return if params[:name].blank? && params[:first_name].blank? && params[:last_name].blank?
219
+
220
+ params[:name] || "#{params[:first_name]} #{params[:last_name]}"
221
+ end
222
+ end
223
+
224
+ class Notification < OffsitePayments::Notification
225
+ RESPONSE_PARAMS = ['DirecPay Reference ID', 'Flag', 'Country', 'Currency', 'Other Details', 'Merchant Order No', 'Amount']
226
+
227
+ def acknowledge(authcode = nil)
228
+ true
229
+ end
230
+
231
+ def complete?
232
+ status == 'Completed' || status == 'Pending'
233
+ end
234
+
235
+ def status
236
+ case params['Flag']
237
+ when 'SUCCESS'
238
+ 'Completed'
239
+ when 'PENDING'
240
+ 'Pending'
241
+ when 'FAIL'
242
+ 'Failed'
243
+ else
244
+ 'Error'
245
+ end
246
+ end
247
+
248
+ def item_id
249
+ params['Merchant Order No']
250
+ end
251
+
252
+ def transaction_id
253
+ params['DirecPay Reference ID']
254
+ end
255
+
256
+ # the money amount we received in X.2 decimal
257
+ def gross
258
+ params['Amount']
259
+ end
260
+
261
+ def currency
262
+ params['Currency']
263
+ end
264
+
265
+ def country
266
+ params['Country']
267
+ end
268
+
269
+ def other_details
270
+ params['Other Details']
271
+ end
272
+
273
+ def test?
274
+ false
275
+ end
276
+
277
+ # Take the posted data and move the relevant data into a hash
278
+ def parse(post)
279
+ super
280
+
281
+ values = params['responseparams'].to_s.split('|')
282
+ response_params = values.size == 3 ? ['DirecPay Reference ID', 'Flag', 'Error message'] : RESPONSE_PARAMS
283
+ response_params.each_with_index do |name, index|
284
+ params[name] = values[index]
285
+ end
286
+ params
287
+ end
288
+ end
289
+
290
+ class Return < OffsitePayments::Return
291
+ def initialize(post_data, options = {})
292
+ @notification = Notification.new(treat_failure_as_pending(post_data), options)
293
+ end
294
+
295
+ def success?
296
+ notification.complete?
297
+ end
298
+
299
+ def message
300
+ notification.status
301
+ end
302
+
303
+ private
304
+
305
+ # Work around the issue that the initial return from DirecPay is always either SUCCESS or FAIL, there is no PENDING
306
+ def treat_failure_as_pending(post_data)
307
+ post_data.sub(/FAIL/, 'PENDING')
308
+ end
309
+ end
310
+
311
+ class Status
312
+ include ActiveMerchant::PostsData
313
+
314
+ STATUS_TEST_URL = 'https://test.direcpay.com/direcpay/secure/dpMerchantTransaction.jsp'
315
+ STATUS_LIVE_URL = 'https://www.timesofmoney.com/direcpay/secure/dpPullMerchAtrnDtls.jsp'
316
+
317
+ attr_reader :account, :options
318
+
319
+ def initialize(account, options = {})
320
+ @account, @options = account, options
321
+ end
322
+
323
+ # Use this method to manually request a status update to the provided notification_url
324
+ def update(authorization, notification_url)
325
+ url = test? ? STATUS_TEST_URL : STATUS_LIVE_URL
326
+ parameters = [ authorization, account, notification_url ]
327
+ data = ActiveMerchant::PostData.new
328
+ data[:requestparams] = parameters.join('|')
329
+
330
+ response = ssl_get("#{url}?#{data.to_post_data}")
331
+ end
332
+
333
+ def test?
334
+ OffsitePayments.mode == :test || options[:test]
335
+ end
336
+ end
337
+ end
338
+ end
339
+ end