offsite_payments 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +70 -0
  4. data/lib/offsite_payments.rb +46 -0
  5. data/lib/offsite_payments/action_view_helper.rb +72 -0
  6. data/lib/offsite_payments/helper.rb +119 -0
  7. data/lib/offsite_payments/integrations.rb +14 -0
  8. data/lib/offsite_payments/integrations/a1agregator.rb +245 -0
  9. data/lib/offsite_payments/integrations/authorize_net_sim.rb +580 -0
  10. data/lib/offsite_payments/integrations/bit_pay.rb +150 -0
  11. data/lib/offsite_payments/integrations/bogus.rb +32 -0
  12. data/lib/offsite_payments/integrations/chronopay.rb +283 -0
  13. data/lib/offsite_payments/integrations/citrus.rb +227 -0
  14. data/lib/offsite_payments/integrations/direc_pay.rb +339 -0
  15. data/lib/offsite_payments/integrations/directebanking.rb +237 -0
  16. data/lib/offsite_payments/integrations/doku.rb +171 -0
  17. data/lib/offsite_payments/integrations/dotpay.rb +166 -0
  18. data/lib/offsite_payments/integrations/dwolla.rb +160 -0
  19. data/lib/offsite_payments/integrations/e_payment_plans.rb +146 -0
  20. data/lib/offsite_payments/integrations/easy_pay.rb +137 -0
  21. data/lib/offsite_payments/integrations/epay.rb +161 -0
  22. data/lib/offsite_payments/integrations/first_data.rb +133 -0
  23. data/lib/offsite_payments/integrations/gestpay.rb +201 -0
  24. data/lib/offsite_payments/integrations/hi_trust.rb +179 -0
  25. data/lib/offsite_payments/integrations/ipay88.rb +240 -0
  26. data/lib/offsite_payments/integrations/klarna.rb +291 -0
  27. data/lib/offsite_payments/integrations/liqpay.rb +216 -0
  28. data/lib/offsite_payments/integrations/maksuturva.rb +231 -0
  29. data/lib/offsite_payments/integrations/mollie_ideal.rb +213 -0
  30. data/lib/offsite_payments/integrations/moneybookers.rb +199 -0
  31. data/lib/offsite_payments/integrations/nochex.rb +228 -0
  32. data/lib/offsite_payments/integrations/pag_seguro.rb +255 -0
  33. data/lib/offsite_payments/integrations/paxum.rb +114 -0
  34. data/lib/offsite_payments/integrations/pay_fast.rb +269 -0
  35. data/lib/offsite_payments/integrations/paydollar.rb +142 -0
  36. data/lib/offsite_payments/integrations/payflow_link.rb +194 -0
  37. data/lib/offsite_payments/integrations/paypal.rb +362 -0
  38. data/lib/offsite_payments/integrations/paypal_payments_advanced.rb +23 -0
  39. data/lib/offsite_payments/integrations/paysbuy.rb +71 -0
  40. data/lib/offsite_payments/integrations/payu_in.rb +266 -0
  41. data/lib/offsite_payments/integrations/payu_in_paisa.rb +46 -0
  42. data/lib/offsite_payments/integrations/platron.rb +153 -0
  43. data/lib/offsite_payments/integrations/pxpay.rb +271 -0
  44. data/lib/offsite_payments/integrations/quickpay.rb +232 -0
  45. data/lib/offsite_payments/integrations/rbkmoney.rb +110 -0
  46. data/lib/offsite_payments/integrations/robokassa.rb +154 -0
  47. data/lib/offsite_payments/integrations/sage_pay_form.rb +425 -0
  48. data/lib/offsite_payments/integrations/two_checkout.rb +332 -0
  49. data/lib/offsite_payments/integrations/universal.rb +180 -0
  50. data/lib/offsite_payments/integrations/valitor.rb +200 -0
  51. data/lib/offsite_payments/integrations/verkkomaksut.rb +143 -0
  52. data/lib/offsite_payments/integrations/web_pay.rb +186 -0
  53. data/lib/offsite_payments/integrations/webmoney.rb +119 -0
  54. data/lib/offsite_payments/integrations/wirecard_checkout_page.rb +359 -0
  55. data/lib/offsite_payments/integrations/world_pay.rb +273 -0
  56. data/lib/offsite_payments/notification.rb +71 -0
  57. data/lib/offsite_payments/return.rb +37 -0
  58. data/lib/offsite_payments/version.rb +3 -0
  59. metadata +270 -0
@@ -0,0 +1,110 @@
1
+ module OffsitePayments #:nodoc:
2
+ module Integrations #:nodoc:
3
+ module Rbkmoney
4
+ mattr_accessor :service_url
5
+ self.service_url = 'https://rbkmoney.ru/acceptpurchase.aspx'
6
+
7
+ def self.notification(*args)
8
+ Notification.new(*args)
9
+ end
10
+
11
+ class Helper < OffsitePayments::Helper
12
+ mapping :account, 'eshopId'
13
+ mapping :amount, 'recipientAmount'
14
+
15
+ # NOTE: rbkmoney uses outdated currency code 'RUR'
16
+ mapping :currency, 'recipientCurrency'
17
+
18
+ mapping :order, 'orderId'
19
+
20
+ mapping :customer, :email => 'user_email'
21
+
22
+ mapping :credential2, 'serviceName'
23
+ mapping :credential3, 'successUrl'
24
+ mapping :credential4, 'failUrl'
25
+ end
26
+
27
+ class Notification < OffsitePayments::Notification
28
+ %w(
29
+ eshopId
30
+ paymentId
31
+ orderId
32
+ eshopAccount
33
+ serviceName
34
+ recipientAmount
35
+ recipientCurrency
36
+ paymentStatus
37
+ userName
38
+ userEmail
39
+ paymentData
40
+ secretKey
41
+ hash
42
+ ).each do |param_name|
43
+ define_method(param_name.underscore){ params[param_name] }
44
+ end
45
+
46
+ def complete?
47
+ (payment_status == '5')
48
+ end
49
+
50
+ def test?
51
+ false
52
+ end
53
+
54
+ def status
55
+ case payment_status
56
+ when '3'
57
+ 'pending'
58
+ when '5'
59
+ 'completed'
60
+ else 'unknown'
61
+ end
62
+ end
63
+
64
+ def user_fields
65
+ params.inject({}) do |fields, (k,v)|
66
+ if /\AuserField_[\d+]\z/.match(k)
67
+ fields[k] = v
68
+ end
69
+ fields
70
+ end
71
+ end
72
+
73
+ alias_method :client_id, :eshop_id
74
+ alias_method :item_id, :order_id
75
+ alias_method :transaction_id, :payment_id
76
+ alias_method :received_at, :payment_data
77
+ alias_method :payer_email, :user_email
78
+ alias_method :gross, :recipient_amount
79
+ alias_method :currency, :recipient_currency
80
+
81
+ def acknowledge(authcode = nil)
82
+ string = [
83
+ eshop_id,
84
+ order_id,
85
+ service_name,
86
+ eshop_account,
87
+ recipient_amount,
88
+ recipient_currency,
89
+ payment_status,
90
+ user_name,
91
+ user_email,
92
+ payment_data,
93
+ @options[:secret]
94
+ ].join '::'
95
+
96
+ signature = case hash.to_s.length
97
+ when 32
98
+ Digest::MD5.hexdigest(string)
99
+ when 128
100
+ Digest::SHA512.hexdigest(string)
101
+ else
102
+ return false
103
+ end
104
+
105
+ signature == hash
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,154 @@
1
+ module OffsitePayments #:nodoc:
2
+ module Integrations #:nodoc:
3
+ # Documentation: http://robokassa.ru/Doc/En/Interface.aspx
4
+ module Robokassa
5
+ # Overwrite this if you want to change the Robokassa test url
6
+ mattr_accessor :test_url
7
+ self.test_url = 'http://test.robokassa.ru/Index.aspx'
8
+
9
+ # Overwrite this if you want to change the Robokassa production url
10
+ mattr_accessor :production_url
11
+ self.production_url = 'https://merchant.roboxchange.com/Index.aspx'
12
+
13
+ mattr_accessor :signature_parameter_name
14
+ self.signature_parameter_name = 'SignatureValue'
15
+
16
+ def self.service_url
17
+ mode = OffsitePayments.mode
18
+ case mode
19
+ when :production
20
+ self.production_url
21
+ when :test
22
+ self.test_url
23
+ else
24
+ raise StandardError, "Integration mode set to an invalid value: #{mode}"
25
+ end
26
+ end
27
+
28
+ def self.helper(order, account, options = {})
29
+ Helper.new(order, account, options)
30
+ end
31
+
32
+ def self.notification(query_string, options = {})
33
+ Notification.new(query_string, options)
34
+ end
35
+
36
+ def self.return(query_string)
37
+ Return.new(query_string)
38
+ end
39
+
40
+ module Common
41
+ def generate_signature_string
42
+ custom_param_keys = params.keys.select {|key| key =~ /^shp/}.sort
43
+ custom_params = custom_param_keys.map {|key| "#{key}=#{params[key]}"}
44
+ [main_params, secret, custom_params.compact].flatten.join(':')
45
+ end
46
+
47
+ def generate_signature
48
+ Digest::MD5.hexdigest(generate_signature_string)
49
+ end
50
+ end
51
+
52
+ class Helper < OffsitePayments::Helper
53
+ include Common
54
+
55
+ def initialize(order, account, options = {})
56
+ @md5secret = options.delete(:secret)
57
+ super
58
+ end
59
+
60
+ def form_fields
61
+ @fields.merge(OffsitePayments::Integrations::Robokassa.signature_parameter_name => generate_signature)
62
+ end
63
+
64
+ def main_params
65
+ [:account, :amount, :order].map {|key| @fields[mappings[key]]}
66
+ end
67
+
68
+ def params
69
+ @fields
70
+ end
71
+
72
+ def secret
73
+ @md5secret
74
+ end
75
+
76
+ def method_missing(method_id, *args)
77
+ method_id = method_id.to_s.gsub(/=$/, '')
78
+
79
+ # support for robokassa custom parameters
80
+ if method_id =~ /^shp/
81
+ add_field method_id, args.last
82
+ end
83
+
84
+ super
85
+ end
86
+
87
+ mapping :account, 'MrchLogin'
88
+ mapping :amount, 'OutSum'
89
+ mapping :currency, 'IncCurrLabel'
90
+ mapping :order, 'InvId'
91
+ mapping :description, 'Desc'
92
+ mapping :email, 'Email'
93
+ end
94
+
95
+ class Notification < OffsitePayments::Notification
96
+ include Common
97
+
98
+ def self.recognizes?(params)
99
+ params.has_key?('InvId') && params.has_key?('OutSum')
100
+ end
101
+
102
+ def complete?
103
+ true
104
+ end
105
+
106
+ def amount
107
+ BigDecimal.new(gross)
108
+ end
109
+
110
+ def item_id
111
+ params['InvId']
112
+ end
113
+
114
+ def security_key
115
+ params[OffsitePayments::Integrations::Robokassa.signature_parameter_name].to_s.downcase
116
+ end
117
+
118
+ def gross
119
+ params['OutSum']
120
+ end
121
+
122
+ def status
123
+ 'success'
124
+ end
125
+
126
+ def secret
127
+ @options[:secret]
128
+ end
129
+
130
+ def main_params
131
+ [gross, item_id]
132
+ end
133
+
134
+ def acknowledge(authcode = nil)
135
+ security_key == generate_signature
136
+ end
137
+
138
+ def success_response(*args)
139
+ "OK#{item_id}"
140
+ end
141
+ end
142
+
143
+ class Return < OffsitePayments::Return
144
+ def item_id
145
+ @params['InvId']
146
+ end
147
+
148
+ def amount
149
+ @params['OutSum']
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,425 @@
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
+ mapping :credential2, 'EncryptKey'
73
+
74
+ mapping :account, 'Vendor'
75
+ mapping :amount, 'Amount'
76
+ mapping :currency, 'Currency'
77
+
78
+ mapping :order, 'VendorTxCode'
79
+
80
+ mapping :customer,
81
+ :first_name => 'BillingFirstnames',
82
+ :last_name => 'BillingSurname',
83
+ :email => 'CustomerEMail',
84
+ :phone => 'BillingPhone',
85
+ :send_email_confirmation => 'SendEmail'
86
+
87
+ mapping :billing_address,
88
+ :city => 'BillingCity',
89
+ :address1 => 'BillingAddress1',
90
+ :address2 => 'BillingAddress2',
91
+ :state => 'BillingState',
92
+ :zip => 'BillingPostCode',
93
+ :country => 'BillingCountry'
94
+
95
+ mapping :shipping_address,
96
+ :city => 'DeliveryCity',
97
+ :address1 => 'DeliveryAddress1',
98
+ :address2 => 'DeliveryAddress2',
99
+ :state => 'DeliveryState',
100
+ :zip => 'DeliveryPostCode',
101
+ :country => 'DeliveryCountry'
102
+
103
+ mapping :return_url, 'SuccessURL'
104
+ mapping :description, 'Description'
105
+
106
+ class_attribute :referrer_id
107
+
108
+ def shipping_address(params = {})
109
+ @shipping_address_set = true unless params.empty?
110
+
111
+ params.each do |k, v|
112
+ field = mappings[:shipping_address][k]
113
+ add_field(field, v) unless field.nil?
114
+ end
115
+ end
116
+
117
+ def map_billing_address_to_shipping_address
118
+ %w(City Address1 Address2 State PostCode Country).each do |field|
119
+ fields["Delivery#{field}"] = fields["Billing#{field}"]
120
+ end
121
+ end
122
+
123
+ def form_fields
124
+ map_billing_address_to_shipping_address unless @shipping_address_set
125
+
126
+ fields['DeliveryFirstnames'] ||= fields['BillingFirstnames']
127
+ fields['DeliverySurname'] ||= fields['BillingSurname']
128
+
129
+ fields['FailureURL'] ||= fields['SuccessURL']
130
+
131
+ fields['BillingPostCode'] ||= "0000"
132
+ fields['DeliveryPostCode'] ||= "0000"
133
+
134
+ crypt_skip = ['Vendor', 'EncryptKey', 'SendEmail']
135
+ crypt_skip << 'BillingState' unless fields['BillingCountry'] == 'US'
136
+ crypt_skip << 'DeliveryState' unless fields['DeliveryCountry'] == 'US'
137
+ crypt_skip << 'CustomerEMail' unless fields['SendEmail']
138
+ key = fields['EncryptKey']
139
+ @crypt ||= create_crypt_field(fields.except(*crypt_skip), key)
140
+
141
+ result = {
142
+ 'VPSProtocol' => '3.00',
143
+ 'TxType' => 'PAYMENT',
144
+ 'Vendor' => @fields['Vendor'],
145
+ 'Crypt' => @crypt
146
+ }
147
+ result['ReferrerID'] = referrer_id if referrer_id
148
+ result
149
+ end
150
+
151
+ private
152
+
153
+ def create_crypt_field(fields, key)
154
+ parts = fields.map { |k, v| "#{k}=#{sanitize(k, v)}" unless v.nil? }.compact.shuffle
155
+ parts.unshift(sage_encrypt_salt(key.length, key.length * 2))
156
+ sage_encrypt(parts.join('&'), key)
157
+ rescue OpenSSL::Cipher::CipherError => e
158
+ if e.message == 'key length too short'
159
+ raise ActionViewHelperError, 'Invalid encryption key.'
160
+ else
161
+ raise
162
+ end
163
+ end
164
+
165
+ def sanitize(key, value)
166
+ reject = exact = nil
167
+
168
+ case key
169
+ when /URL$/
170
+ # allow all
171
+ when 'VendorTxCode'
172
+ reject = /[^A-Za-z0-9{}._-]+/
173
+ when /[Nn]ames?$/
174
+ reject = %r{[^[:alpha:] /\\.'-]+}
175
+ when /(?:Address[12]|City)$/
176
+ reject = %r{[^[:alnum:] +'/\\:,.\n()-]+}
177
+ when /PostCode$/
178
+ reject = /[^A-Za-z0-9 -]+/
179
+ when /Phone$/
180
+ reject = /[^0-9A-Za-z+ ()-]+/
181
+ when 'Currency'
182
+ exact = /^[A-Z]{3}$/
183
+ when /State$/
184
+ exact = /^[A-Z]{2}$/
185
+ when 'Description'
186
+ value = value[0...100]
187
+ else
188
+ reject = /&+/
189
+ end
190
+
191
+ if exact
192
+ raise ArgumentError, "Invalid value for #{key}: #{value.inspect}" unless value =~ exact
193
+ value
194
+ elsif reject
195
+ value.gsub(reject, ' ')
196
+ else
197
+ value
198
+ end
199
+ end
200
+ end
201
+
202
+ class Notification < OffsitePayments::Notification
203
+ class CryptError < StandardError; end
204
+
205
+ include Encryption
206
+
207
+ def initialize(post_data, options)
208
+ super
209
+ load_crypt_params(params['crypt'], options[:credential2])
210
+ end
211
+
212
+ # Was the transaction complete?
213
+ def complete?
214
+ status_code == 'OK'
215
+ end
216
+
217
+ # Was the transaction cancelled?
218
+ # Unfortunately, we can't distinguish "user abort" from "idle too long".
219
+ def cancelled?
220
+ status_code == 'ABORT'
221
+ end
222
+
223
+ # Text version of #complete?, since we don't support Pending.
224
+ def status
225
+ complete? ? 'Completed' : 'Failed'
226
+ end
227
+
228
+ # Status of transaction. List of possible values:
229
+ # <tt>OK</tt>:: Transaction completed successfully.
230
+ # <tt>NOTAUTHED</tt>:: Incorrect card details / insufficient funds.
231
+ # <tt>MALFORMED</tt>:: Invalid input data.
232
+ # <tt>INVALID</tt>:: Valid input data, but some fields are incorrect.
233
+ # <tt>ABORT</tt>:: User hit cancel button or went idle for 15+ minutes.
234
+ # <tt>REJECTED</tt>:: Rejected by account fraud screening rules.
235
+ # <tt>AUTHENTICATED</tt>:: Authenticated card details secured at SagePay.
236
+ # <tt>REGISTERED</tt>:: Non-authenticated card details secured at SagePay.
237
+ # <tt>ERROR</tt>:: Problem internal to SagePay.
238
+ def status_code
239
+ params['Status']
240
+ end
241
+
242
+ # Check this if #completed? is false.
243
+ def message
244
+ params['StatusDetail']
245
+ end
246
+
247
+ # Vendor-supplied code (:order mapping).
248
+ def item_id
249
+ params['VendorTxCode']
250
+ end
251
+
252
+ # Internal SagePay code, typically "{LONG-UUID}".
253
+ def transaction_id
254
+ params['VPSTxId']
255
+ end
256
+
257
+ # Authorization number (only if #completed?).
258
+ def auth_id
259
+ params['TxAuthNo']
260
+ end
261
+
262
+ # Total amount (no fees).
263
+ def gross
264
+ params['Amount'].gsub(/,(?=\d{3}\b)/, '')
265
+ end
266
+
267
+ # AVS and CV2 check results. Possible values:
268
+ # <tt>ALL MATCH</tt>::
269
+ # <tt>SECURITY CODE MATCH ONLY</tt>::
270
+ # <tt>ADDRESS MATCH ONLY</tt>::
271
+ # <tt>NO DATA MATCHES</tt>::
272
+ # <tt>DATA NOT CHECKED</tt>::
273
+ def avs_cv2_result
274
+ params['AVSCV2']
275
+ end
276
+
277
+ # Numeric address check. Possible values:
278
+ # <tt>NOTPROVIDED</tt>::
279
+ # <tt>NOTCHECKED</tt>::
280
+ # <tt>MATCHED</tt>::
281
+ # <tt>NOTMATCHED</tt>::
282
+ def address_result
283
+ params['AddressResult']
284
+ end
285
+
286
+ # Post code check. Possible values:
287
+ # <tt>NOTPROVIDED</tt>::
288
+ # <tt>NOTCHECKED</tt>::
289
+ # <tt>MATCHED</tt>::
290
+ # <tt>NOTMATCHED</tt>::
291
+ def post_code_result
292
+ params['PostCodeResult']
293
+ end
294
+
295
+ # CV2 code check. Possible values:
296
+ # <tt>NOTPROVIDED</tt>::
297
+ # <tt>NOTCHECKED</tt>::
298
+ # <tt>MATCHED</tt>::
299
+ # <tt>NOTMATCHED</tt>::
300
+ def cv2_result
301
+ params['CV2Result']
302
+ end
303
+
304
+ # Was the Gift Aid box checked?
305
+ def gift_aid?
306
+ params['GiftAid'] == '1'
307
+ end
308
+
309
+ # Result of 3D Secure checks. Possible values:
310
+ # <tt>OK</tt>:: Authenticated correctly.
311
+ # <tt>NOTCHECKED</tt>:: Authentication not performed.
312
+ # <tt>NOTAVAILABLE</tt>:: Card not auth-capable, or auth is otherwise impossible.
313
+ # <tt>NOTAUTHED</tt>:: User failed authentication.
314
+ # <tt>INCOMPLETE</tt>:: Authentication unable to complete.
315
+ # <tt>ERROR</tt>:: Unable to attempt authentication due to data / service errors.
316
+ def buyer_auth_result
317
+ params['3DSecureStatus']
318
+ end
319
+
320
+ # Encoded 3D Secure result code.
321
+ def buyer_auth_result_code
322
+ params['CAVV']
323
+ end
324
+
325
+ # Address confirmation status. PayPal only. Possible values:
326
+ # <tt>NONE</tt>::
327
+ # <tt>CONFIRMED</tt>::
328
+ # <tt>UNCONFIRMED</tt>::
329
+ def address_status
330
+ params['AddressStatus']
331
+ end
332
+
333
+ # Payer verification. Undocumented.
334
+ def payer_verified?
335
+ params['PayerStatus'] == 'VERIFIED'
336
+ end
337
+
338
+ # Credit card type. Possible values:
339
+ # <tt>VISA</tt>:: Visa
340
+ # <tt>MC</tt>:: MasterCard
341
+ # <tt>DELTA</tt>:: Delta
342
+ # <tt>SOLO</tt>:: Solo
343
+ # <tt>MAESTRO</tt>:: Maestro (UK and International)
344
+ # <tt>UKE</tt>:: Visa Electron
345
+ # <tt>AMEX</tt>:: American Express
346
+ # <tt>DC</tt>:: Diners Club
347
+ # <tt>JCB</tt>:: JCB
348
+ # <tt>LASER</tt>:: Laser
349
+ # <tt>PAYPAL</tt>:: PayPal
350
+ def credit_card_type
351
+ params['CardType']
352
+ end
353
+
354
+ # Last four digits of credit card.
355
+ def credit_card_last_4_digits
356
+ params['Last4Digits']
357
+ end
358
+
359
+ # Used by composition methods, but not supplied by SagePay.
360
+ def currency
361
+ nil
362
+ end
363
+
364
+ def test?
365
+ false
366
+ end
367
+
368
+ def acknowledge
369
+ true
370
+ end
371
+
372
+ private
373
+
374
+ def load_crypt_params(crypt, key)
375
+ raise MissingCryptData if crypt.blank?
376
+ raise MissingCryptKey if key.blank?
377
+
378
+ crypt_data = sage_decrypt(crypt.gsub(' ', '+'), key)
379
+ raise InvalidCryptData unless crypt_data =~ /(^|&)Status=/
380
+
381
+ params.clear
382
+ parse(crypt_data)
383
+ end
384
+
385
+ class MissingCryptKey < CryptError
386
+ def message
387
+ 'No merchant decryption key supplied'
388
+ end
389
+ end
390
+ class MissingCryptData < CryptError
391
+ def message
392
+ 'No data received from SagePay'
393
+ end
394
+ end
395
+ class InvalidCryptData < CryptError
396
+ def message
397
+ 'Invalid data received from SagePay'
398
+ end
399
+ end
400
+ end
401
+
402
+ class Return < OffsitePayments::Return
403
+ def initialize(query_string, options)
404
+ begin
405
+ @notification = Notification.new(query_string, options)
406
+ rescue Notification::CryptError => e
407
+ @message = e.message
408
+ end
409
+ end
410
+
411
+ def success?
412
+ @notification && @notification.complete?
413
+ end
414
+
415
+ def cancelled?
416
+ @notification && @notification.cancelled?
417
+ end
418
+
419
+ def message
420
+ @message || @notification.message
421
+ end
422
+ end
423
+ end
424
+ end
425
+ end