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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +76 -0
- data/lib/offsite_payments.rb +39 -0
- data/lib/offsite_payments/action_view_helper.rb +72 -0
- data/lib/offsite_payments/helper.rb +120 -0
- data/lib/offsite_payments/integrations.rb +14 -0
- data/lib/offsite_payments/integrations/a1agregator.rb +245 -0
- data/lib/offsite_payments/integrations/authorize_net_sim.rb +580 -0
- data/lib/offsite_payments/integrations/bit_pay.rb +150 -0
- data/lib/offsite_payments/integrations/bogus.rb +32 -0
- data/lib/offsite_payments/integrations/chronopay.rb +283 -0
- data/lib/offsite_payments/integrations/citrus.rb +227 -0
- data/lib/offsite_payments/integrations/coinbase.rb +172 -0
- data/lib/offsite_payments/integrations/direc_pay.rb +332 -0
- data/lib/offsite_payments/integrations/directebanking.rb +237 -0
- data/lib/offsite_payments/integrations/doku.rb +171 -0
- data/lib/offsite_payments/integrations/dotpay.rb +166 -0
- data/lib/offsite_payments/integrations/dwolla.rb +160 -0
- data/lib/offsite_payments/integrations/e_payment_plans.rb +146 -0
- data/lib/offsite_payments/integrations/easy_pay.rb +137 -0
- data/lib/offsite_payments/integrations/epay.rb +161 -0
- data/lib/offsite_payments/integrations/first_data.rb +133 -0
- data/lib/offsite_payments/integrations/gestpay.rb +205 -0
- data/lib/offsite_payments/integrations/hi_trust.rb +179 -0
- data/lib/offsite_payments/integrations/ipay88.rb +251 -0
- data/lib/offsite_payments/integrations/klarna.rb +275 -0
- data/lib/offsite_payments/integrations/liqpay.rb +216 -0
- data/lib/offsite_payments/integrations/maksuturva.rb +231 -0
- data/lib/offsite_payments/integrations/megakassa.rb +184 -0
- data/lib/offsite_payments/integrations/mollie.rb +32 -0
- data/lib/offsite_payments/integrations/mollie_ideal.rb +194 -0
- data/lib/offsite_payments/integrations/mollie_mistercash.rb +143 -0
- data/lib/offsite_payments/integrations/molpay.rb +193 -0
- data/lib/offsite_payments/integrations/moneybookers.rb +199 -0
- data/lib/offsite_payments/integrations/nochex.rb +228 -0
- data/lib/offsite_payments/integrations/pag_seguro.rb +268 -0
- data/lib/offsite_payments/integrations/paxum.rb +114 -0
- data/lib/offsite_payments/integrations/pay_fast.rb +269 -0
- data/lib/offsite_payments/integrations/paydollar.rb +142 -0
- data/lib/offsite_payments/integrations/payflow_link.rb +194 -0
- data/lib/offsite_payments/integrations/paypal.rb +362 -0
- data/lib/offsite_payments/integrations/paypal_payments_advanced.rb +23 -0
- data/lib/offsite_payments/integrations/paysbuy.rb +71 -0
- data/lib/offsite_payments/integrations/payu_in.rb +276 -0
- data/lib/offsite_payments/integrations/payu_in_paisa.rb +46 -0
- data/lib/offsite_payments/integrations/platron.rb +153 -0
- data/lib/offsite_payments/integrations/pxpay.rb +273 -0
- data/lib/offsite_payments/integrations/quickpay.rb +232 -0
- data/lib/offsite_payments/integrations/rbkmoney.rb +110 -0
- data/lib/offsite_payments/integrations/realex_offsite.rb +317 -0
- data/lib/offsite_payments/integrations/robokassa.rb +154 -0
- data/lib/offsite_payments/integrations/sage_pay_form.rb +431 -0
- data/lib/offsite_payments/integrations/two_checkout.rb +329 -0
- data/lib/offsite_payments/integrations/universal.rb +190 -0
- data/lib/offsite_payments/integrations/valitor.rb +200 -0
- data/lib/offsite_payments/integrations/verkkomaksut.rb +143 -0
- data/lib/offsite_payments/integrations/web_pay.rb +186 -0
- data/lib/offsite_payments/integrations/webmoney.rb +119 -0
- data/lib/offsite_payments/integrations/wirecard_checkout_page.rb +359 -0
- data/lib/offsite_payments/integrations/world_pay.rb +280 -0
- data/lib/offsite_payments/integrations/yandex_money.rb +175 -0
- data/lib/offsite_payments/notification.rb +71 -0
- data/lib/offsite_payments/return.rb +37 -0
- data/lib/offsite_payments/version.rb +3 -0
- 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
|