better_offsite_payments 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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