better_offsite_payments 2.3.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 (66) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +76 -0
  4. data/lib/offsite_payments.rb +39 -0
  5. data/lib/offsite_payments/action_view_helper.rb +72 -0
  6. data/lib/offsite_payments/helper.rb +120 -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/coinbase.rb +172 -0
  15. data/lib/offsite_payments/integrations/direc_pay.rb +332 -0
  16. data/lib/offsite_payments/integrations/directebanking.rb +237 -0
  17. data/lib/offsite_payments/integrations/doku.rb +171 -0
  18. data/lib/offsite_payments/integrations/dotpay.rb +166 -0
  19. data/lib/offsite_payments/integrations/dwolla.rb +160 -0
  20. data/lib/offsite_payments/integrations/e_payment_plans.rb +146 -0
  21. data/lib/offsite_payments/integrations/easy_pay.rb +137 -0
  22. data/lib/offsite_payments/integrations/epay.rb +161 -0
  23. data/lib/offsite_payments/integrations/first_data.rb +133 -0
  24. data/lib/offsite_payments/integrations/gestpay.rb +205 -0
  25. data/lib/offsite_payments/integrations/hi_trust.rb +179 -0
  26. data/lib/offsite_payments/integrations/ipay88.rb +251 -0
  27. data/lib/offsite_payments/integrations/klarna.rb +275 -0
  28. data/lib/offsite_payments/integrations/liqpay.rb +216 -0
  29. data/lib/offsite_payments/integrations/maksuturva.rb +231 -0
  30. data/lib/offsite_payments/integrations/megakassa.rb +184 -0
  31. data/lib/offsite_payments/integrations/mollie.rb +32 -0
  32. data/lib/offsite_payments/integrations/mollie_ideal.rb +194 -0
  33. data/lib/offsite_payments/integrations/mollie_mistercash.rb +143 -0
  34. data/lib/offsite_payments/integrations/molpay.rb +193 -0
  35. data/lib/offsite_payments/integrations/moneybookers.rb +199 -0
  36. data/lib/offsite_payments/integrations/nochex.rb +228 -0
  37. data/lib/offsite_payments/integrations/pag_seguro.rb +268 -0
  38. data/lib/offsite_payments/integrations/paxum.rb +114 -0
  39. data/lib/offsite_payments/integrations/pay_fast.rb +269 -0
  40. data/lib/offsite_payments/integrations/paydollar.rb +142 -0
  41. data/lib/offsite_payments/integrations/payflow_link.rb +194 -0
  42. data/lib/offsite_payments/integrations/paypal.rb +362 -0
  43. data/lib/offsite_payments/integrations/paypal_payments_advanced.rb +23 -0
  44. data/lib/offsite_payments/integrations/paysbuy.rb +71 -0
  45. data/lib/offsite_payments/integrations/payu_in.rb +276 -0
  46. data/lib/offsite_payments/integrations/payu_in_paisa.rb +46 -0
  47. data/lib/offsite_payments/integrations/platron.rb +153 -0
  48. data/lib/offsite_payments/integrations/pxpay.rb +273 -0
  49. data/lib/offsite_payments/integrations/quickpay.rb +232 -0
  50. data/lib/offsite_payments/integrations/rbkmoney.rb +110 -0
  51. data/lib/offsite_payments/integrations/realex_offsite.rb +317 -0
  52. data/lib/offsite_payments/integrations/robokassa.rb +154 -0
  53. data/lib/offsite_payments/integrations/sage_pay_form.rb +431 -0
  54. data/lib/offsite_payments/integrations/two_checkout.rb +329 -0
  55. data/lib/offsite_payments/integrations/universal.rb +190 -0
  56. data/lib/offsite_payments/integrations/valitor.rb +200 -0
  57. data/lib/offsite_payments/integrations/verkkomaksut.rb +143 -0
  58. data/lib/offsite_payments/integrations/web_pay.rb +186 -0
  59. data/lib/offsite_payments/integrations/webmoney.rb +119 -0
  60. data/lib/offsite_payments/integrations/wirecard_checkout_page.rb +359 -0
  61. data/lib/offsite_payments/integrations/world_pay.rb +280 -0
  62. data/lib/offsite_payments/integrations/yandex_money.rb +175 -0
  63. data/lib/offsite_payments/notification.rb +71 -0
  64. data/lib/offsite_payments/return.rb +37 -0
  65. data/lib/offsite_payments/version.rb +3 -0
  66. metadata +297 -0
@@ -0,0 +1,431 @@
1
+ module OffsitePayments #:nodoc:
2
+ module Integrations #:nodoc:
3
+ module SagePayForm
4
+ mattr_accessor :production_url
5
+ mattr_accessor :test_url
6
+ mattr_accessor :simulate_url
7
+ self.production_url = 'https://live.sagepay.com/gateway/service/vspform-register.vsp'
8
+ self.test_url = 'https://test.sagepay.com/gateway/service/vspform-register.vsp'
9
+ self.simulate_url = 'https://test.sagepay.com/Simulator/VSPFormGateway.asp'
10
+
11
+ def self.return(query_string, options = {})
12
+ Return.new(query_string, options)
13
+ end
14
+
15
+ def self.service_url
16
+ mode = OffsitePayments.mode
17
+ case mode
18
+ when :production
19
+ self.production_url
20
+ when :test
21
+ self.test_url
22
+ when :simulate
23
+ self.simulate_url
24
+ else
25
+ raise StandardError, "Integration mode set to an invalid value: #{mode}"
26
+ end
27
+ end
28
+
29
+ module Encryption
30
+ def sage_encrypt(plaintext, key)
31
+ encrypted = cipher(:encrypt, key, plaintext)
32
+ "@#{encrypted.upcase}"
33
+ end
34
+
35
+ def sage_decrypt(ciphertext, key)
36
+ ciphertext = ciphertext[1..-1] # remove @ symbol at the beginning of a string
37
+ cipher(:decrypt, key, ciphertext)
38
+ rescue OpenSSL::Cipher::CipherError => e
39
+ return '' if e.message == 'wrong final block length'
40
+ raise
41
+ end
42
+
43
+ def sage_encrypt_salt(min, max)
44
+ length = rand(max - min + 1) + min
45
+ SecureRandom.base64(length + 4)[0, length]
46
+ end
47
+
48
+ private
49
+
50
+ def cipher(action, key, payload)
51
+ if action == :decrypt
52
+ payload = [payload].pack('H*')
53
+ end
54
+
55
+ cipher = OpenSSL::Cipher::AES128.new(:CBC)
56
+ cipher.public_send(action)
57
+ cipher.key = key
58
+ cipher.iv = key
59
+ result = cipher.update(payload) + cipher.final
60
+
61
+ if action == :encrypt
62
+ result = result.unpack('H*')[0]
63
+ end
64
+
65
+ result
66
+ end
67
+ end
68
+
69
+ class Helper < OffsitePayments::Helper
70
+ include Encryption
71
+
72
+ attr_reader :identifier
73
+
74
+ def initialize(order, account, options={})
75
+ super
76
+ @identifier = rand(0..99999).to_s.rjust(5, '0')
77
+ add_field 'VendorTxCode', "#{order}-#{@identifier}"
78
+ end
79
+
80
+ mapping :credential2, 'EncryptKey'
81
+
82
+ mapping :account, 'Vendor'
83
+ mapping :amount, 'Amount'
84
+ mapping :currency, 'Currency'
85
+
86
+ mapping :customer,
87
+ :first_name => 'BillingFirstnames',
88
+ :last_name => 'BillingSurname',
89
+ :email => 'CustomerEMail',
90
+ :phone => 'BillingPhone',
91
+ :send_email_confirmation => 'SendEmail'
92
+
93
+ mapping :billing_address,
94
+ :city => 'BillingCity',
95
+ :address1 => 'BillingAddress1',
96
+ :address2 => 'BillingAddress2',
97
+ :state => 'BillingState',
98
+ :zip => 'BillingPostCode',
99
+ :country => 'BillingCountry'
100
+
101
+ mapping :shipping_address,
102
+ :city => 'DeliveryCity',
103
+ :address1 => 'DeliveryAddress1',
104
+ :address2 => 'DeliveryAddress2',
105
+ :state => 'DeliveryState',
106
+ :zip => 'DeliveryPostCode',
107
+ :country => 'DeliveryCountry'
108
+
109
+ mapping :return_url, 'SuccessURL'
110
+ mapping :description, 'Description'
111
+
112
+ class_attribute :referrer_id
113
+
114
+ def shipping_address(params = {})
115
+ @shipping_address_set = true unless params.empty?
116
+
117
+ params.each do |k, v|
118
+ field = mappings[:shipping_address][k]
119
+ add_field(field, v) unless field.nil?
120
+ end
121
+ end
122
+
123
+ def map_billing_address_to_shipping_address
124
+ %w(City Address1 Address2 State PostCode Country).each do |field|
125
+ fields["Delivery#{field}"] = fields["Billing#{field}"]
126
+ end
127
+ end
128
+
129
+ def form_fields
130
+ map_billing_address_to_shipping_address unless @shipping_address_set
131
+
132
+ fields['DeliveryFirstnames'] ||= fields['BillingFirstnames']
133
+ fields['DeliverySurname'] ||= fields['BillingSurname']
134
+
135
+ fields['FailureURL'] ||= fields['SuccessURL']
136
+
137
+ fields['BillingPostCode'] ||= "0000"
138
+ fields['DeliveryPostCode'] ||= "0000"
139
+
140
+ fields['ReferrerID'] = referrer_id if referrer_id
141
+
142
+ crypt_skip = ['Vendor', 'EncryptKey', 'SendEmail']
143
+ crypt_skip << 'BillingState' unless fields['BillingCountry'] == 'US'
144
+ crypt_skip << 'DeliveryState' unless fields['DeliveryCountry'] == 'US'
145
+ crypt_skip << 'CustomerEMail' unless fields['SendEmail']
146
+ key = fields['EncryptKey']
147
+ @crypt ||= create_crypt_field(fields.except(*crypt_skip), key)
148
+
149
+ {
150
+ 'VPSProtocol' => '3.00',
151
+ 'TxType' => 'PAYMENT',
152
+ 'Vendor' => @fields['Vendor'],
153
+ 'Crypt' => @crypt
154
+ }
155
+ end
156
+
157
+ private
158
+
159
+ def create_crypt_field(fields, key)
160
+ parts = fields.map { |k, v| "#{k}=#{sanitize(k, v)}" unless v.nil? }.compact.shuffle
161
+ parts.unshift(sage_encrypt_salt(key.length, key.length * 2))
162
+ sage_encrypt(parts.join('&'), key)
163
+ rescue OpenSSL::Cipher::CipherError => e
164
+ if e.message == 'key length too short'
165
+ raise ActionViewHelperError, 'Invalid encryption key.'
166
+ else
167
+ raise
168
+ end
169
+ end
170
+
171
+ def sanitize(key, value)
172
+ reject = exact = nil
173
+
174
+ case key
175
+ when /URL$/
176
+ # allow all
177
+ when 'VendorTxCode'
178
+ reject = /[^A-Za-z0-9{}._-]+/
179
+ when /[Nn]ames?$/
180
+ reject = %r{[^[:alpha:] /\\.'-]+}
181
+ when /(?:Address[12]|City)$/
182
+ reject = %r{[^[:alnum:] +'/\\:,.\n()-]+}
183
+ when /PostCode$/
184
+ reject = /[^A-Za-z0-9 -]+/
185
+ when /Phone$/
186
+ reject = /[^0-9A-Za-z+ ()-]+/
187
+ when 'Currency'
188
+ exact = /^[A-Z]{3}$/
189
+ when /State$/
190
+ exact = /^[A-Z]{2}$/
191
+ when 'Description'
192
+ value = value[0...100]
193
+ else
194
+ reject = /&+/
195
+ end
196
+
197
+ if exact
198
+ raise ArgumentError, "Invalid value for #{key}: #{value.inspect}" unless value =~ exact
199
+ value
200
+ elsif reject
201
+ value.gsub(reject, ' ')
202
+ else
203
+ value
204
+ end
205
+ end
206
+ end
207
+
208
+ class Notification < OffsitePayments::Notification
209
+ class CryptError < StandardError; end
210
+
211
+ include Encryption
212
+
213
+ def initialize(post_data, options)
214
+ super
215
+ load_crypt_params(params['crypt'], options[:credential2])
216
+ end
217
+
218
+ # Was the transaction complete?
219
+ def complete?
220
+ status_code == 'OK'
221
+ end
222
+
223
+ # Was the transaction cancelled?
224
+ # Unfortunately, we can't distinguish "user abort" from "idle too long".
225
+ def cancelled?
226
+ status_code == 'ABORT'
227
+ end
228
+
229
+ # Text version of #complete?, since we don't support Pending.
230
+ def status
231
+ complete? ? 'Completed' : 'Failed'
232
+ end
233
+
234
+ # Status of transaction. List of possible values:
235
+ # <tt>OK</tt>:: Transaction completed successfully.
236
+ # <tt>NOTAUTHED</tt>:: Incorrect card details / insufficient funds.
237
+ # <tt>MALFORMED</tt>:: Invalid input data.
238
+ # <tt>INVALID</tt>:: Valid input data, but some fields are incorrect.
239
+ # <tt>ABORT</tt>:: User hit cancel button or went idle for 15+ minutes.
240
+ # <tt>REJECTED</tt>:: Rejected by account fraud screening rules.
241
+ # <tt>AUTHENTICATED</tt>:: Authenticated card details secured at SagePay.
242
+ # <tt>REGISTERED</tt>:: Non-authenticated card details secured at SagePay.
243
+ # <tt>ERROR</tt>:: Problem internal to SagePay.
244
+ def status_code
245
+ params['Status']
246
+ end
247
+
248
+ # Check this if #completed? is false.
249
+ def message
250
+ params['StatusDetail']
251
+ end
252
+
253
+ # Vendor-supplied code (:order mapping).
254
+ def item_id
255
+ params['VendorTxCode'].rpartition('-').first
256
+ end
257
+
258
+ # Internal SagePay code, typically "{LONG-UUID}".
259
+ def transaction_id
260
+ params['VPSTxId']
261
+ end
262
+
263
+ # Authorization number (only if #completed?).
264
+ def auth_id
265
+ params['TxAuthNo']
266
+ end
267
+
268
+ # Total amount (no fees).
269
+ def gross
270
+ params['Amount'].gsub(/,(?=\d{3}\b)/, '')
271
+ end
272
+
273
+ # AVS and CV2 check results. Possible values:
274
+ # <tt>ALL MATCH</tt>::
275
+ # <tt>SECURITY CODE MATCH ONLY</tt>::
276
+ # <tt>ADDRESS MATCH ONLY</tt>::
277
+ # <tt>NO DATA MATCHES</tt>::
278
+ # <tt>DATA NOT CHECKED</tt>::
279
+ def avs_cv2_result
280
+ params['AVSCV2']
281
+ end
282
+
283
+ # Numeric address check. Possible values:
284
+ # <tt>NOTPROVIDED</tt>::
285
+ # <tt>NOTCHECKED</tt>::
286
+ # <tt>MATCHED</tt>::
287
+ # <tt>NOTMATCHED</tt>::
288
+ def address_result
289
+ params['AddressResult']
290
+ end
291
+
292
+ # Post code check. Possible values:
293
+ # <tt>NOTPROVIDED</tt>::
294
+ # <tt>NOTCHECKED</tt>::
295
+ # <tt>MATCHED</tt>::
296
+ # <tt>NOTMATCHED</tt>::
297
+ def post_code_result
298
+ params['PostCodeResult']
299
+ end
300
+
301
+ # CV2 code check. Possible values:
302
+ # <tt>NOTPROVIDED</tt>::
303
+ # <tt>NOTCHECKED</tt>::
304
+ # <tt>MATCHED</tt>::
305
+ # <tt>NOTMATCHED</tt>::
306
+ def cv2_result
307
+ params['CV2Result']
308
+ end
309
+
310
+ # Was the Gift Aid box checked?
311
+ def gift_aid?
312
+ params['GiftAid'] == '1'
313
+ end
314
+
315
+ # Result of 3D Secure checks. Possible values:
316
+ # <tt>OK</tt>:: Authenticated correctly.
317
+ # <tt>NOTCHECKED</tt>:: Authentication not performed.
318
+ # <tt>NOTAVAILABLE</tt>:: Card not auth-capable, or auth is otherwise impossible.
319
+ # <tt>NOTAUTHED</tt>:: User failed authentication.
320
+ # <tt>INCOMPLETE</tt>:: Authentication unable to complete.
321
+ # <tt>ERROR</tt>:: Unable to attempt authentication due to data / service errors.
322
+ def buyer_auth_result
323
+ params['3DSecureStatus']
324
+ end
325
+
326
+ # Encoded 3D Secure result code.
327
+ def buyer_auth_result_code
328
+ params['CAVV']
329
+ end
330
+
331
+ # Address confirmation status. PayPal only. Possible values:
332
+ # <tt>NONE</tt>::
333
+ # <tt>CONFIRMED</tt>::
334
+ # <tt>UNCONFIRMED</tt>::
335
+ def address_status
336
+ params['AddressStatus']
337
+ end
338
+
339
+ # Payer verification. Undocumented.
340
+ def payer_verified?
341
+ params['PayerStatus'] == 'VERIFIED'
342
+ end
343
+
344
+ # Credit card type. Possible values:
345
+ # <tt>VISA</tt>:: Visa
346
+ # <tt>MC</tt>:: MasterCard
347
+ # <tt>DELTA</tt>:: Delta
348
+ # <tt>SOLO</tt>:: Solo
349
+ # <tt>MAESTRO</tt>:: Maestro (UK and International)
350
+ # <tt>UKE</tt>:: Visa Electron
351
+ # <tt>AMEX</tt>:: American Express
352
+ # <tt>DC</tt>:: Diners Club
353
+ # <tt>JCB</tt>:: JCB
354
+ # <tt>LASER</tt>:: Laser
355
+ # <tt>PAYPAL</tt>:: PayPal
356
+ def credit_card_type
357
+ params['CardType']
358
+ end
359
+
360
+ # Last four digits of credit card.
361
+ def credit_card_last_4_digits
362
+ params['Last4Digits']
363
+ end
364
+
365
+ # Used by composition methods, but not supplied by SagePay.
366
+ def currency
367
+ nil
368
+ end
369
+
370
+ def test?
371
+ false
372
+ end
373
+
374
+ def acknowledge
375
+ true
376
+ end
377
+
378
+ private
379
+
380
+ def load_crypt_params(crypt, key)
381
+ raise MissingCryptData if crypt.blank?
382
+ raise MissingCryptKey if key.blank?
383
+
384
+ crypt_data = sage_decrypt(crypt.gsub(' ', '+'), key)
385
+ raise InvalidCryptData unless crypt_data =~ /(^|&)Status=/
386
+
387
+ params.clear
388
+ parse(crypt_data)
389
+ end
390
+
391
+ class MissingCryptKey < CryptError
392
+ def message
393
+ 'No merchant decryption key supplied'
394
+ end
395
+ end
396
+ class MissingCryptData < CryptError
397
+ def message
398
+ 'No data received from SagePay'
399
+ end
400
+ end
401
+ class InvalidCryptData < CryptError
402
+ def message
403
+ 'Invalid data received from SagePay'
404
+ end
405
+ end
406
+ end
407
+
408
+ class Return < OffsitePayments::Return
409
+ def initialize(query_string, options)
410
+ begin
411
+ @notification = Notification.new(query_string, options)
412
+ rescue Notification::CryptError => e
413
+ @message = e.message
414
+ end
415
+ end
416
+
417
+ def success?
418
+ @notification && @notification.complete?
419
+ end
420
+
421
+ def cancelled?
422
+ @notification && @notification.cancelled?
423
+ end
424
+
425
+ def message
426
+ @message || @notification.message
427
+ end
428
+ end
429
+ end
430
+ end
431
+ end
@@ -0,0 +1,329 @@
1
+ module OffsitePayments #:nodoc:
2
+ module Integrations #:nodoc:
3
+ module TwoCheckout
4
+ mattr_accessor :payment_routine
5
+ self.payment_routine = :single_page
6
+
7
+ def self.service_url
8
+ case self.payment_routine
9
+ when :multi_page
10
+ 'https://www.2checkout.com/checkout/purchase'
11
+ when :single_page
12
+ 'https://www.2checkout.com/checkout/spurchase'
13
+ else
14
+ raise StandardError, "Integration payment routine set to an invalid value: #{self.payment_routine}"
15
+ end
16
+ end
17
+
18
+ def self.service_url=(service_url)
19
+ # Note: do not use this method, it is here for backward compatibility
20
+ # Use the payment_routine method to change service_url
21
+ if service_url =~ /spurchase/
22
+ self.payment_routine = :single_page
23
+ else
24
+ self.payment_routine = :multi_page
25
+ end
26
+ end
27
+
28
+ def self.notification(post, options = {})
29
+ Notification.new(post, options)
30
+ end
31
+
32
+ def self.return(query_string, options = {})
33
+ Return.new(query_string, options)
34
+ end
35
+
36
+ class Helper < OffsitePayments::Helper
37
+ def initialize(order, account, options = {})
38
+ super
39
+ if OffsitePayments.mode == :test || options[:test]
40
+ add_field('demo', 'Y')
41
+ end
42
+ end
43
+
44
+ # The 2checkout vendor account number
45
+ mapping :account, 'sid'
46
+
47
+ # The total amount to be billed, in decimal form, without a currency symbol. (8 characters, decimal, 2 characters: Example: 99999999.99)
48
+ # This field is only used with the Third Party Cart parameter set.
49
+ mapping :amount, 'total'
50
+
51
+ # Pass the sale's currency code.
52
+ mapping :currency, 'currency_code'
53
+
54
+ # Pass your order id. (50 characters max)
55
+ mapping :order, 'merchant_order_id'
56
+
57
+ # Pass your cart identifier if you are using Third Part Cart Parameters. (128 characters max)
58
+ # This value is visible to the buyer and will be listed as the sale's lineitem.
59
+ mapping :invoice, 'cart_order_id'
60
+
61
+ mapping :customer,
62
+ :email => 'email',
63
+ :phone => 'phone'
64
+
65
+ mapping :billing_address,
66
+ :city => 'city',
67
+ :address1 => 'street_address',
68
+ :address2 => 'street_address2',
69
+ :state => 'state',
70
+ :zip => 'zip',
71
+ :country => 'country'
72
+
73
+ mapping :shipping_address,
74
+ :name => 'ship_name',
75
+ :city => 'ship_city',
76
+ :address1 => 'ship_street_address',
77
+ :address2 => 'ship_street_address2',
78
+ :state => 'ship_state',
79
+ :zip => 'ship_zip',
80
+ :country => 'ship_country'
81
+
82
+ # Overrides Approved URL for return process redirects
83
+ mapping :return_url, 'x_receipt_link_url'
84
+
85
+ # Allow seller to indicate the step of the checkout page
86
+ # Possible values: ‘review-cart’, ‘shipping-information’, ‘shipping-method’, ‘billing-information’ and ‘payment-method’
87
+ mapping :purchase_step, 'purchase_step'
88
+
89
+ # Allow referral partners to indicate their shopping cart
90
+ mapping :cart_type, '2co_cart_type'
91
+
92
+ def customer(params = {})
93
+ add_field(mappings[:customer][:email], params[:email])
94
+ add_field(mappings[:customer][:phone], params[:phone])
95
+ add_field('card_holder_name', "#{params[:first_name]} #{params[:last_name]}")
96
+ end
97
+
98
+ def shipping_address(params = {})
99
+ super
100
+ add_field(mappings[:shipping_address][:name], "#{params[:first_name]} #{params[:last_name]}")
101
+ end
102
+
103
+ # Uses Third Party Cart parameter set to pass in lineitem details.
104
+ # You must also specify `service.invoice` when using this method.
105
+ def third_party_cart(params = {})
106
+ add_field('id_type', '1')
107
+ (max_existing_line_item_id = form_fields.keys.map do |key|
108
+ i = key.to_s[/^c_prod_(\d+)/, 1]
109
+ (i && i.to_i)
110
+ end.compact.max || 0)
111
+
112
+ line_item_id = max_existing_line_item_id + 1
113
+ params.each do |key, value|
114
+ add_field("c_#{key}_#{line_item_id}", value)
115
+ end
116
+ end
117
+ end
118
+
119
+ class Notification < OffsitePayments::Notification
120
+ # message_type - Indicates type of message
121
+ # message_description - Human readable description of message_type
122
+ # timestamp - Timestamp of event; format YYYY-MM-DD HH:MM:SS ZZZ
123
+ # md5_hash - UPPERCASE(MD5_ENCRYPTED(sale_id + vendor_id + invoice_id + Secret Word))
124
+ # message_id - This number is incremented for each message sent to a given seller.
125
+ # key_count - Indicates the number of parameters sent in message
126
+ # vendor_id - Seller account number
127
+ # sale_id - 2Checkout sale number
128
+ # sale_date_placed - Date of sale; format YYYY-MM-DD
129
+ # vendor_order_id - Custom order id provided by seller, if available.
130
+ # invoice_id - 2Checkout invoice number; Each recurring sale can have several invoices
131
+ # recurring - recurring=1 if any item on the invoice is a recurring item, 0 otherwise
132
+ # payment_type - Buyer’s payment method (credit card, online check, paypal ec, OR paypal pay later)
133
+ # list_currency - 3-Letter ISO code for seller currency
134
+ # cust_currency - 3-Letter ISO code for buyer currency
135
+ # auth_exp - The date credit authorization will expire; format YYYY-MM-DD
136
+ # invoice_status - Status of a transaction (approved, pending, deposited, or declined)
137
+ # fraud_status - Status of 2Checkout fraud review (pass, fail, or wait); This parameter could be empty.
138
+ # invoice_list_amount - Total in seller pricing currency; format as appropriate to currency=
139
+ # invoice_usd_amount - Total in US Dollars; format with 2 decimal places
140
+ # invoice_cust_amount - Total in buyer currency; format as appropriate to currency=
141
+ # customer_first_name - Buyer’s first name (may not be available on older sales)
142
+ # customer_last_name - Buyer’s last name (may not be available on older sales)
143
+ # customer_name - Buyer's full name (name as it appears on credit card)
144
+ # customer_email - Buyer's email address
145
+ # customer_phone - Buyer's phone number; all but digits stripped out
146
+ # customer_ip - Buyer's IP address at time of sale
147
+ # customer_ip_country - Country of record for buyer's IP address at time of sale
148
+ # bill_street_address - Billing street address
149
+ # bill_street_address2 - Billing street address line 2
150
+ # bill_city - Billing address city
151
+ # bill_state - Billing address state or province
152
+ # bill_postal_code - Billing address postal code
153
+ # bill_country - 3-Letter ISO country code of billing address
154
+ # ship_status - not_shipped, shipped, or empty (if intangible / does not need shipped)
155
+ # ship_tracking_number - Tracking Number as entered in Seller Admin
156
+ # ship_name - Shipping Recipient’s name (as it should appears on shipping label)
157
+ # ship_street_address - Shipping street address
158
+ # ship_street_address2 - Shipping street address line 2
159
+ # ship_city - Shipping address city
160
+ # ship_state - Shipping address state or province
161
+ # ship_postal_code - Shipping address postal code
162
+ # ship_country - 3-Letter ISO country code of shipping address
163
+ # item_count - Indicates how many numbered sets of item parameters to expect
164
+ # item_name_# - Product name
165
+ # item_id_# - Seller product id
166
+ # item_list_amount_# - Total in seller pricing currency; format as appropriate to currency
167
+ # item_usd_amount_# - Total in US Dollars; format with 2 decimal places
168
+ # item_cust_amount_# - Total in buyer currency; format as appropriate to currency
169
+ # item_type_# - Indicates if item is a bill or refund; Value will be bill or refund
170
+ # item_duration_# - Product duration, how long it re-bills for Ex. 1 Year
171
+ # item_recurrence_# - Product recurrence, how often it re-bills Ex. 1 Month
172
+ # item_rec_list_amount_# - Product price; format as appropriate to currency
173
+ # item_rec_status_# - Indicates status of recurring subscription: live, canceled, or completed
174
+ # item_rec_date_next_# - Date of next recurring installment; format YYYY-MM-DD
175
+ # item_rec_install_billed_# - The number of successful recurring installments successfully billed
176
+
177
+ # INS message type
178
+ def type
179
+ params['message_type']
180
+ end
181
+
182
+ # Seller currency sale was placed in
183
+ def currency
184
+ params['list_currency']
185
+ end
186
+
187
+ def complete?
188
+ status == 'Completed'
189
+ end
190
+
191
+ # The value passed with 'merchant_order_id' is passed back as 'vendor_order_id'
192
+ def item_id
193
+ params['vendor_order_id'] || params['merchant_order_id']
194
+ end
195
+
196
+ # 2Checkout Sale ID
197
+ def transaction_id
198
+ params['sale_id'] || params['order_number']
199
+ end
200
+
201
+ # 2Checkout Invoice ID
202
+ def invoice_id
203
+ params['invoice_id']
204
+ end
205
+
206
+ def received_at
207
+ params['timestamp']
208
+ end
209
+
210
+ #Customer Email
211
+ def payer_email
212
+ params['customer_email']
213
+ end
214
+
215
+ # The MD5 Hash
216
+ def security_key
217
+ params['md5_hash'] || params['key']
218
+ end
219
+
220
+ # The money amount we received in X.2 decimal.
221
+ # passback || INS gross amount for new orders || default INS gross
222
+ def gross
223
+ params['invoice_list_amount'] || params['total'] || params['item_list_amount_1']
224
+ end
225
+
226
+ # Determine status based on parameter set, if the params include a fraud status we know we're being
227
+ # notified of the finalization of an order (an INS message)
228
+ # If the params include 'credit_card_processed' we know we're being notified of a new order being inbound,
229
+ # which we handle in the deferred demo sale scenario.
230
+ def status
231
+ if params['fraud_status'] == 'pass' || params['credit_card_processed'] == 'Y'
232
+ 'Completed'
233
+ elsif params['fraud_status'] == 'wait'
234
+ 'Pending'
235
+ else
236
+ 'Failed'
237
+ end
238
+ end
239
+
240
+ # Secret Word defined in 2Checkout account
241
+ def secret
242
+ @options[:credential2]
243
+ end
244
+
245
+ # Checks against MD5 Hash
246
+ def acknowledge(authcode = nil)
247
+ return false if security_key.blank?
248
+ if ins_message?
249
+ Digest::MD5.hexdigest("#{ transaction_id }#{ params['vendor_id'] }#{ invoice_id }#{ secret }").upcase == security_key.upcase
250
+ elsif passback?
251
+ order_number = params['demo'] == 'Y' ? 1 : params['order_number']
252
+ Digest::MD5.hexdigest("#{ secret }#{ params['sid'] }#{ order_number }#{ gross }").upcase == params['key'].upcase
253
+ else
254
+ false
255
+ end
256
+ end
257
+
258
+ private
259
+
260
+ # Parses Header Redirect Query String
261
+ def parse(post)
262
+ @raw = post.to_s
263
+ for line in @raw.split('&')
264
+ key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten
265
+ params[key] = CGI.unescape(value || '')
266
+ end
267
+ end
268
+
269
+ def ins_message?
270
+ params.include? 'message_type'
271
+ end
272
+
273
+ def passback?
274
+ params.include? 'credit_card_processed'
275
+ end
276
+ end
277
+
278
+ class Return < OffsitePayments::Return
279
+ # card_holder_name - Provides the customer’s name.
280
+ # city - Provides the customer’s city.
281
+ # country - Provides the customer’s country.
282
+ # credit_card_processed - This parameter will always be passed back as Y.
283
+ # demo - Defines if an order was live, or if the order was a demo order. If the order was a demo, the MD5 hash will fail.
284
+ # email - Provides the email address the customer provided when placing the order.
285
+ # fixed - This parameter will only be passed back if it was passed into the purchase routine.
286
+ # ip_country - Provides the customer’s IP location.
287
+ # key - An MD5 hash used to confirm the validity of a sale.
288
+ # lang - Customer language
289
+ # merchant_order_id - The order ID you had assigned to the order.
290
+ # order_number - The 2Checkout order number associated with the order.
291
+ # invoice_id - The 2Checkout invoice number.
292
+ # pay_method - Provides seller with the customer’s payment method. CC for Credit Card, PPI for PayPal.
293
+ # phone - Provides the phone number the customer provided when placing the order.
294
+ # ship_name - Provides the ship to name for the order.
295
+ # ship_street_address - Provides ship to address.
296
+ # ship_street_address2 - Provides more detailed shipping address if this information was provided by the customer.
297
+ # ship_city - Provides ship to city.
298
+ # ship_state - Provides ship to state.
299
+ # ship_zip - Ship Zip
300
+
301
+ # Pass Through Products Only
302
+ # li_#_name - Name of the corresponding lineitem.
303
+ # li_#_quantity - Quantity of the corresponding lineitem.
304
+ # li_#_price - Price of the corresponding lineitem.
305
+ # li_#_tangible - Specifies if the corresponding li_#_type is a tangible or intangible. ‘Y’ OR ‘N’
306
+ # li_#_product_id - ID of the corresponding lineitem.
307
+ # li_#_product_description - Description of the corresponding lineitem.
308
+ # li_#_recurrence - # WEEK | MONTH | YEAR – always singular.
309
+ # li_#_duration - Forever or # WEEK | MONTH | YEAR – always singular, defaults to Forever.
310
+ # li_#_startup_fee - Amount in account pricing currency.
311
+ # li_#_option_#_name - Name of option. 64 characters max – cannot include '<' or '>'.
312
+ # li_#_option_#_value - Name of option. 64 characters max – cannot include '<' or '>'.
313
+ # li_#_option_#_surcharge - Amount in account pricing currency.
314
+
315
+ #Third Party Cart Only
316
+ # cart_order_id - The order ID you had assigned to the order
317
+
318
+ def initialize(query_string, options = {})
319
+ super
320
+ @notification = Notification.new(query_string, options)
321
+ end
322
+
323
+ def success?
324
+ @notification.status != 'Failed'
325
+ end
326
+ end
327
+ end
328
+ end
329
+ end