offsite_payments 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,332 @@
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
+ # notifications are sent via static URLs in the Instant Notification Settings of 2Checkout admin
86
+ mapping :notify_url, 'notify_url'
87
+
88
+ # Allow seller to indicate the step of the checkout page
89
+ # Possible values: ‘review-cart’, ‘shipping-information’, ‘shipping-method’, ‘billing-information’ and ‘payment-method’
90
+ mapping :purchase_step, 'purchase_step'
91
+
92
+ # Allow referral partners to indicate their shopping cart
93
+ mapping :cart_type, '2co_cart_type'
94
+
95
+ def customer(params = {})
96
+ add_field(mappings[:customer][:email], params[:email])
97
+ add_field(mappings[:customer][:phone], params[:phone])
98
+ add_field('card_holder_name', "#{params[:first_name]} #{params[:last_name]}")
99
+ end
100
+
101
+ def shipping_address(params = {})
102
+ super
103
+ add_field(mappings[:shipping_address][:name], "#{params[:first_name]} #{params[:last_name]}")
104
+ end
105
+
106
+ # Uses Third Party Cart parameter set to pass in lineitem details.
107
+ # You must also specify `service.invoice` when using this method.
108
+ def third_party_cart(params = {})
109
+ add_field('id_type', '1')
110
+ (max_existing_line_item_id = form_fields.keys.map do |key|
111
+ i = key.to_s[/^c_prod_(\d+)/, 1]
112
+ (i && i.to_i)
113
+ end.compact.max || 0)
114
+
115
+ line_item_id = max_existing_line_item_id + 1
116
+ params.each do |key, value|
117
+ add_field("c_#{key}_#{line_item_id}", value)
118
+ end
119
+ end
120
+ end
121
+
122
+ class Notification < OffsitePayments::Notification
123
+ # message_type - Indicates type of message
124
+ # message_description - Human readable description of message_type
125
+ # timestamp - Timestamp of event; format YYYY-MM-DD HH:MM:SS ZZZ
126
+ # md5_hash - UPPERCASE(MD5_ENCRYPTED(sale_id + vendor_id + invoice_id + Secret Word))
127
+ # message_id - This number is incremented for each message sent to a given seller.
128
+ # key_count - Indicates the number of parameters sent in message
129
+ # vendor_id - Seller account number
130
+ # sale_id - 2Checkout sale number
131
+ # sale_date_placed - Date of sale; format YYYY-MM-DD
132
+ # vendor_order_id - Custom order id provided by seller, if available.
133
+ # invoice_id - 2Checkout invoice number; Each recurring sale can have several invoices
134
+ # recurring - recurring=1 if any item on the invoice is a recurring item, 0 otherwise
135
+ # payment_type - Buyer’s payment method (credit card, online check, paypal ec, OR paypal pay later)
136
+ # list_currency - 3-Letter ISO code for seller currency
137
+ # cust_currency - 3-Letter ISO code for buyer currency
138
+ # auth_exp - The date credit authorization will expire; format YYYY-MM-DD
139
+ # invoice_status - Status of a transaction (approved, pending, deposited, or declined)
140
+ # fraud_status - Status of 2Checkout fraud review (pass, fail, or wait); This parameter could be empty.
141
+ # invoice_list_amount - Total in seller pricing currency; format as appropriate to currency=
142
+ # invoice_usd_amount - Total in US Dollars; format with 2 decimal places
143
+ # invoice_cust_amount - Total in buyer currency; format as appropriate to currency=
144
+ # customer_first_name - Buyer’s first name (may not be available on older sales)
145
+ # customer_last_name - Buyer’s last name (may not be available on older sales)
146
+ # customer_name - Buyer's full name (name as it appears on credit card)
147
+ # customer_email - Buyer's email address
148
+ # customer_phone - Buyer's phone number; all but digits stripped out
149
+ # customer_ip - Buyer's IP address at time of sale
150
+ # customer_ip_country - Country of record for buyer's IP address at time of sale
151
+ # bill_street_address - Billing street address
152
+ # bill_street_address2 - Billing street address line 2
153
+ # bill_city - Billing address city
154
+ # bill_state - Billing address state or province
155
+ # bill_postal_code - Billing address postal code
156
+ # bill_country - 3-Letter ISO country code of billing address
157
+ # ship_status - not_shipped, shipped, or empty (if intangible / does not need shipped)
158
+ # ship_tracking_number - Tracking Number as entered in Seller Admin
159
+ # ship_name - Shipping Recipient’s name (as it should appears on shipping label)
160
+ # ship_street_address - Shipping street address
161
+ # ship_street_address2 - Shipping street address line 2
162
+ # ship_city - Shipping address city
163
+ # ship_state - Shipping address state or province
164
+ # ship_postal_code - Shipping address postal code
165
+ # ship_country - 3-Letter ISO country code of shipping address
166
+ # item_count - Indicates how many numbered sets of item parameters to expect
167
+ # item_name_# - Product name
168
+ # item_id_# - Seller product id
169
+ # item_list_amount_# - Total in seller pricing currency; format as appropriate to currency
170
+ # item_usd_amount_# - Total in US Dollars; format with 2 decimal places
171
+ # item_cust_amount_# - Total in buyer currency; format as appropriate to currency
172
+ # item_type_# - Indicates if item is a bill or refund; Value will be bill or refund
173
+ # item_duration_# - Product duration, how long it re-bills for Ex. 1 Year
174
+ # item_recurrence_# - Product recurrence, how often it re-bills Ex. 1 Month
175
+ # item_rec_list_amount_# - Product price; format as appropriate to currency
176
+ # item_rec_status_# - Indicates status of recurring subscription: live, canceled, or completed
177
+ # item_rec_date_next_# - Date of next recurring installment; format YYYY-MM-DD
178
+ # item_rec_install_billed_# - The number of successful recurring installments successfully billed
179
+
180
+ # INS message type
181
+ def type
182
+ params['message_type']
183
+ end
184
+
185
+ # Seller currency sale was placed in
186
+ def currency
187
+ params['list_currency']
188
+ end
189
+
190
+ def complete?
191
+ status == 'Completed'
192
+ end
193
+
194
+ # The value passed with 'merchant_order_id' is passed back as 'vendor_order_id'
195
+ def item_id
196
+ params['vendor_order_id'] || params['merchant_order_id']
197
+ end
198
+
199
+ # 2Checkout Sale ID
200
+ def transaction_id
201
+ params['sale_id'] || params['order_number']
202
+ end
203
+
204
+ # 2Checkout Invoice ID
205
+ def invoice_id
206
+ params['invoice_id']
207
+ end
208
+
209
+ def received_at
210
+ params['timestamp']
211
+ end
212
+
213
+ #Customer Email
214
+ def payer_email
215
+ params['customer_email']
216
+ end
217
+
218
+ # The MD5 Hash
219
+ def security_key
220
+ params['md5_hash'] || params['key']
221
+ end
222
+
223
+ # The money amount we received in X.2 decimal.
224
+ # passback || INS gross amount for new orders || default INS gross
225
+ def gross
226
+ params['invoice_list_amount'] || params['total'] || params['item_list_amount_1']
227
+ end
228
+
229
+ # Determine status based on parameter set, if the params include a fraud status we know we're being
230
+ # notified of the finalization of an order (an INS message)
231
+ # If the params include 'credit_card_processed' we know we're being notified of a new order being inbound,
232
+ # which we handle in the deferred demo sale scenario.
233
+ def status
234
+ if params['fraud_status'] == 'pass' || params['credit_card_processed'] == 'Y'
235
+ 'Completed'
236
+ elsif params['fraud_status'] == 'wait'
237
+ 'Pending'
238
+ else
239
+ 'Failed'
240
+ end
241
+ end
242
+
243
+ # Secret Word defined in 2Checkout account
244
+ def secret
245
+ @options[:credential2]
246
+ end
247
+
248
+ # Checks against MD5 Hash
249
+ def acknowledge(authcode = nil)
250
+ return false if security_key.blank?
251
+ if ins_message?
252
+ Digest::MD5.hexdigest("#{ transaction_id }#{ params['vendor_id'] }#{ invoice_id }#{ secret }").upcase == security_key.upcase
253
+ elsif passback?
254
+ order_number = params['demo'] == 'Y' ? 1 : params['order_number']
255
+ Digest::MD5.hexdigest("#{ secret }#{ params['sid'] }#{ order_number }#{ gross }").upcase == params['key'].upcase
256
+ else
257
+ false
258
+ end
259
+ end
260
+
261
+ private
262
+
263
+ # Parses Header Redirect Query String
264
+ def parse(post)
265
+ @raw = post.to_s
266
+ for line in @raw.split('&')
267
+ key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten
268
+ params[key] = CGI.unescape(value || '')
269
+ end
270
+ end
271
+
272
+ def ins_message?
273
+ params.include? 'message_type'
274
+ end
275
+
276
+ def passback?
277
+ params.include? 'credit_card_processed'
278
+ end
279
+ end
280
+
281
+ class Return < OffsitePayments::Return
282
+ # card_holder_name - Provides the customer’s name.
283
+ # city - Provides the customer’s city.
284
+ # country - Provides the customer’s country.
285
+ # credit_card_processed - This parameter will always be passed back as Y.
286
+ # 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.
287
+ # email - Provides the email address the customer provided when placing the order.
288
+ # fixed - This parameter will only be passed back if it was passed into the purchase routine.
289
+ # ip_country - Provides the customer’s IP location.
290
+ # key - An MD5 hash used to confirm the validity of a sale.
291
+ # lang - Customer language
292
+ # merchant_order_id - The order ID you had assigned to the order.
293
+ # order_number - The 2Checkout order number associated with the order.
294
+ # invoice_id - The 2Checkout invoice number.
295
+ # pay_method - Provides seller with the customer’s payment method. CC for Credit Card, PPI for PayPal.
296
+ # phone - Provides the phone number the customer provided when placing the order.
297
+ # ship_name - Provides the ship to name for the order.
298
+ # ship_street_address - Provides ship to address.
299
+ # ship_street_address2 - Provides more detailed shipping address if this information was provided by the customer.
300
+ # ship_city - Provides ship to city.
301
+ # ship_state - Provides ship to state.
302
+ # ship_zip - Ship Zip
303
+
304
+ # Pass Through Products Only
305
+ # li_#_name - Name of the corresponding lineitem.
306
+ # li_#_quantity - Quantity of the corresponding lineitem.
307
+ # li_#_price - Price of the corresponding lineitem.
308
+ # li_#_tangible - Specifies if the corresponding li_#_type is a tangible or intangible. ‘Y’ OR ‘N’
309
+ # li_#_product_id - ID of the corresponding lineitem.
310
+ # li_#_product_description - Description of the corresponding lineitem.
311
+ # li_#_recurrence - # WEEK | MONTH | YEAR – always singular.
312
+ # li_#_duration - Forever or # WEEK | MONTH | YEAR – always singular, defaults to Forever.
313
+ # li_#_startup_fee - Amount in account pricing currency.
314
+ # li_#_option_#_name - Name of option. 64 characters max – cannot include '<' or '>'.
315
+ # li_#_option_#_value - Name of option. 64 characters max – cannot include '<' or '>'.
316
+ # li_#_option_#_surcharge - Amount in account pricing currency.
317
+
318
+ #Third Party Cart Only
319
+ # cart_order_id - The order ID you had assigned to the order
320
+
321
+ def initialize(query_string, options = {})
322
+ super
323
+ @notification = Notification.new(query_string, options)
324
+ end
325
+
326
+ def success?
327
+ @notification.status != 'Failed'
328
+ end
329
+ end
330
+ end
331
+ end
332
+ end
@@ -0,0 +1,180 @@
1
+ module OffsitePayments #:nodoc:
2
+ module Integrations #:nodoc:
3
+ module Universal
4
+ def self.notification(post, options = {})
5
+ Notification.new(post, options)
6
+ end
7
+
8
+ def self.return(query_string, options = {})
9
+ Return.new(query_string, options)
10
+ end
11
+
12
+ def self.sign(fields, key)
13
+ Digest::HMAC.hexdigest(fields.sort.join, key, Digest::SHA256)
14
+ end
15
+
16
+ class Helper < OffsitePayments::Helper
17
+ CURRENCY_SPECIAL_MINOR_UNITS = {
18
+ 'BIF' => 0,
19
+ 'BYR' => 0,
20
+ 'CLF' => 0,
21
+ 'CLP' => 0,
22
+ 'CVE' => 0,
23
+ 'DJF' => 0,
24
+ 'GNF' => 0,
25
+ 'HUF' => 0,
26
+ 'ISK' => 0,
27
+ 'JPY' => 0,
28
+ 'KMF' => 0,
29
+ 'KRW' => 0,
30
+ 'PYG' => 0,
31
+ 'RWF' => 0,
32
+ 'UGX' => 0,
33
+ 'UYI' => 0,
34
+ 'VND' => 0,
35
+ 'VUV' => 0,
36
+ 'XAF' => 0,
37
+ 'XOF' => 0,
38
+ 'XPF' => 0,
39
+ 'BHD' => 3,
40
+ 'IQD' => 3,
41
+ 'JOD' => 3,
42
+ 'KWD' => 3,
43
+ 'LYD' => 3,
44
+ 'OMR' => 3,
45
+ 'TND' => 3,
46
+ 'COU' => 4
47
+ }
48
+
49
+ def initialize(order, account, options = {})
50
+ @forward_url = options[:forward_url]
51
+ @key = options[:credential2]
52
+ @currency = options[:currency]
53
+ super
54
+ self.country = options[:country]
55
+ self.account_name = options[:account_name]
56
+ self.transaction_type = options[:transaction_type]
57
+ add_field 'x_test', @test.to_s
58
+ end
59
+
60
+ def credential_based_url
61
+ @forward_url
62
+ end
63
+
64
+ def form_fields
65
+ sign_fields
66
+ end
67
+
68
+ def amount=(amount)
69
+ add_field 'x_amount', format_amount(amount, @currency)
70
+ end
71
+
72
+ def shipping(amount)
73
+ add_field 'x_amount_shipping', format_amount(amount, @currency)
74
+ end
75
+
76
+ def tax(amount)
77
+ add_field 'x_amount_tax', format_amount(amount, @currency)
78
+ end
79
+
80
+ def sign_fields
81
+ @fields.merge!('x_signature' => generate_signature)
82
+ end
83
+
84
+ def generate_signature
85
+ Universal.sign(@fields, @key)
86
+ end
87
+
88
+ mapping :account, 'x_account_id'
89
+ mapping :currency, 'x_currency'
90
+ mapping :order, 'x_reference'
91
+ mapping :country, 'x_shop_country'
92
+ mapping :account_name, 'x_shop_name'
93
+ mapping :transaction_type, 'x_transaction_type'
94
+ mapping :description, 'x_description'
95
+ mapping :invoice, 'x_invoice'
96
+
97
+ mapping :customer, :first_name => 'x_customer_first_name',
98
+ :last_name => 'x_customer_last_name',
99
+ :email => 'x_customer_email',
100
+ :phone => 'x_customer_phone'
101
+
102
+ mapping :shipping_address, :first_name => 'x_customer_shipping_first_name',
103
+ :last_name => 'x_customer_shipping_last_name',
104
+ :city => 'x_customer_shipping_city',
105
+ :company => 'x_customer_shipping_company',
106
+ :address1 => 'x_customer_shipping_address1',
107
+ :address2 => 'x_customer_shipping_address2',
108
+ :state => 'x_customer_shipping_state',
109
+ :zip => 'x_customer_shipping_zip',
110
+ :country => 'x_customer_shipping_country',
111
+ :phone => 'x_customer_shipping_phone'
112
+
113
+ mapping :notify_url, 'x_url_callback'
114
+ mapping :return_url, 'x_url_complete'
115
+ mapping :cancel_return_url, 'x_url_cancel'
116
+
117
+ private
118
+
119
+ def format_amount(amount, currency)
120
+ units = CURRENCY_SPECIAL_MINOR_UNITS[currency] || 2
121
+ sprintf("%.#{units}f", amount)
122
+ end
123
+ end
124
+
125
+ class Notification < OffsitePayments::Notification
126
+ def initialize(post, options = {})
127
+ super
128
+ @key = options[:credential2]
129
+ end
130
+
131
+ def acknowledge(authcode = nil)
132
+ signature = @params.delete('x_signature')
133
+ signature && signature.casecmp(generate_signature) == 0
134
+ end
135
+
136
+ def item_id
137
+ @params['x_reference']
138
+ end
139
+
140
+ def currency
141
+ @params['x_currency']
142
+ end
143
+
144
+ def gross
145
+ @params['x_amount']
146
+ end
147
+
148
+ def transaction_id
149
+ @params['x_gateway_reference']
150
+ end
151
+
152
+ def status
153
+ result = @params['x_result']
154
+ result && result.capitalize
155
+ end
156
+
157
+ def test?
158
+ @params['x_test'] == 'true'
159
+ end
160
+
161
+ private
162
+
163
+ def generate_signature
164
+ Universal.sign(@params, @key)
165
+ end
166
+ end
167
+
168
+ class Return < OffsitePayments::Return
169
+ def initialize(query_string, options = {})
170
+ super
171
+ @notification = Notification.new(query_string, options)
172
+ end
173
+
174
+ def success?
175
+ @notification.acknowledge
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end