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,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