pay 7.3.0 → 11.2.2

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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -4
  3. data/app/controllers/pay/payments_controller.rb +2 -0
  4. data/app/controllers/pay/webhooks/lemon_squeezy_controller.rb +45 -0
  5. data/app/jobs/pay/customer_sync_job.rb +1 -1
  6. data/app/models/concerns/pay/routing.rb +13 -0
  7. data/{lib → app/models}/pay/braintree/charge.rb +7 -12
  8. data/{lib/pay/braintree/billable.rb → app/models/pay/braintree/customer.rb} +33 -71
  9. data/{lib → app/models}/pay/braintree/payment_method.rb +4 -10
  10. data/{lib → app/models}/pay/braintree/subscription.rb +23 -61
  11. data/app/models/pay/charge.rb +16 -45
  12. data/app/models/pay/customer.rb +5 -16
  13. data/app/models/pay/fake_processor/charge.rb +19 -0
  14. data/{lib/pay/fake_processor/billable.rb → app/models/pay/fake_processor/customer.rb} +28 -38
  15. data/{lib → app/models}/pay/fake_processor/merchant.rb +4 -9
  16. data/app/models/pay/fake_processor/payment_method.rb +13 -0
  17. data/app/models/pay/fake_processor/subscription.rb +70 -0
  18. data/app/models/pay/lemon_squeezy/charge.rb +96 -0
  19. data/app/models/pay/lemon_squeezy/customer.rb +80 -0
  20. data/app/models/pay/lemon_squeezy/payment_method.rb +29 -0
  21. data/app/models/pay/lemon_squeezy/subscription.rb +129 -0
  22. data/app/models/pay/merchant.rb +2 -11
  23. data/{lib → app/models}/pay/paddle_billing/charge.rb +15 -13
  24. data/{lib/pay/paddle_billing/billable.rb → app/models/pay/paddle_billing/customer.rb} +20 -35
  25. data/{lib → app/models}/pay/paddle_billing/payment_method.rb +13 -13
  26. data/{lib → app/models}/pay/paddle_billing/subscription.rb +40 -43
  27. data/{lib → app/models}/pay/paddle_classic/charge.rb +15 -18
  28. data/{lib/pay/paddle_classic/billable.rb → app/models/pay/paddle_classic/customer.rb} +11 -31
  29. data/{lib → app/models}/pay/paddle_classic/payment_method.rb +3 -11
  30. data/{lib → app/models}/pay/paddle_classic/subscription.rb +17 -37
  31. data/app/models/pay/payment_method.rb +4 -5
  32. data/app/models/pay/stripe/charge.rb +155 -0
  33. data/{lib/pay/stripe/billable.rb → app/models/pay/stripe/customer.rb} +78 -111
  34. data/{lib → app/models}/pay/stripe/merchant.rb +5 -20
  35. data/{lib → app/models}/pay/stripe/payment_method.rb +11 -17
  36. data/{lib → app/models}/pay/stripe/subscription.rb +83 -112
  37. data/app/models/pay/subscription.rb +13 -47
  38. data/app/models/pay/webhook.rb +5 -1
  39. data/app/views/pay/user_mailer/payment_action_required.text.erb +9 -0
  40. data/app/views/pay/user_mailer/payment_failed.text.erb +9 -0
  41. data/app/views/pay/user_mailer/receipt.text.erb +20 -0
  42. data/app/views/pay/user_mailer/refund.text.erb +21 -0
  43. data/app/views/pay/user_mailer/subscription_renewing.text.erb +8 -0
  44. data/app/views/pay/user_mailer/subscription_trial_ended.text.erb +8 -0
  45. data/app/views/pay/user_mailer/subscription_trial_will_end.text.erb +8 -0
  46. data/config/locales/en.yml +1 -0
  47. data/config/routes.rb +1 -0
  48. data/db/migrate/20250415151129_add_object_to_pay_models.rb +7 -0
  49. data/db/migrate/2_add_pay_sti_columns.rb +24 -0
  50. data/lib/pay/attributes.rb +16 -8
  51. data/lib/pay/braintree.rb +25 -6
  52. data/lib/pay/engine.rb +2 -0
  53. data/lib/pay/fake_processor.rb +2 -6
  54. data/lib/pay/lemon_squeezy/webhooks/order.rb +11 -0
  55. data/lib/pay/lemon_squeezy/webhooks/subscription.rb +3 -3
  56. data/lib/pay/lemon_squeezy/webhooks/subscription_payment.rb +11 -0
  57. data/lib/pay/lemon_squeezy.rb +58 -104
  58. data/lib/pay/nano_id.rb +1 -1
  59. data/lib/pay/paddle_billing.rb +15 -6
  60. data/lib/pay/paddle_classic/webhooks/signature_verifier.rb +1 -1
  61. data/lib/pay/paddle_classic.rb +11 -9
  62. data/lib/pay/receipts.rb +45 -44
  63. data/lib/pay/stripe/webhooks/charge_updated.rb +11 -0
  64. data/lib/pay/stripe/webhooks/customer_updated.rb +13 -9
  65. data/lib/pay/stripe/webhooks/payment_action_required.rb +10 -6
  66. data/lib/pay/stripe/webhooks/payment_failed.rb +6 -4
  67. data/lib/pay/stripe/webhooks/subscription_renewing.rb +9 -4
  68. data/lib/pay/stripe.rb +28 -9
  69. data/lib/pay/version.rb +1 -1
  70. data/lib/pay.rb +19 -1
  71. data/lib/tasks/pay.rake +2 -2
  72. metadata +45 -43
  73. data/app/views/pay/stripe/_checkout_button.html.erb +0 -21
  74. data/lib/pay/braintree/authorization_error.rb +0 -9
  75. data/lib/pay/braintree/error.rb +0 -23
  76. data/lib/pay/fake_processor/charge.rb +0 -21
  77. data/lib/pay/fake_processor/error.rb +0 -6
  78. data/lib/pay/fake_processor/payment_method.rb +0 -21
  79. data/lib/pay/fake_processor/subscription.rb +0 -90
  80. data/lib/pay/lemon_squeezy/billable.rb +0 -90
  81. data/lib/pay/lemon_squeezy/charge.rb +0 -68
  82. data/lib/pay/lemon_squeezy/error.rb +0 -7
  83. data/lib/pay/lemon_squeezy/payment_method.rb +0 -40
  84. data/lib/pay/lemon_squeezy/subscription.rb +0 -185
  85. data/lib/pay/lemon_squeezy/webhooks/transaction_completed.rb +0 -11
  86. data/lib/pay/paddle_billing/error.rb +0 -7
  87. data/lib/pay/paddle_classic/error.rb +0 -7
  88. data/lib/pay/stripe/charge.rb +0 -176
  89. data/lib/pay/stripe/error.rb +0 -7
@@ -1,21 +0,0 @@
1
- module Pay
2
- module FakeProcessor
3
- class Charge
4
- attr_reader :pay_charge
5
-
6
- delegate :processor_id, :owner, to: :pay_charge
7
-
8
- def initialize(pay_charge)
9
- @pay_charge = pay_charge
10
- end
11
-
12
- def charge
13
- pay_charge
14
- end
15
-
16
- def refund!(amount_to_refund)
17
- pay_charge.update(amount_refunded: amount_to_refund)
18
- end
19
- end
20
- end
21
- end
@@ -1,6 +0,0 @@
1
- module Pay
2
- module FakeProcessor
3
- class Error < Pay::Error
4
- end
5
- end
6
- end
@@ -1,21 +0,0 @@
1
- module Pay
2
- module FakeProcessor
3
- class PaymentMethod
4
- attr_reader :pay_payment_method
5
-
6
- delegate :customer, :processor_id, to: :pay_payment_method
7
-
8
- def initialize(pay_payment_method)
9
- @pay_payment_method = pay_payment_method
10
- end
11
-
12
- # Sets payment method as default on Stripe
13
- def make_default!
14
- end
15
-
16
- # Remove payment method
17
- def detach
18
- end
19
- end
20
- end
21
- end
@@ -1,90 +0,0 @@
1
- module Pay
2
- module FakeProcessor
3
- class Subscription
4
- attr_reader :pay_subscription
5
-
6
- delegate :active?,
7
- :canceled?,
8
- :on_grace_period?,
9
- :on_trial?,
10
- :ends_at,
11
- :ends_at?,
12
- :owner,
13
- :processor_subscription,
14
- :processor_id,
15
- :prorate,
16
- :processor_plan,
17
- :quantity?,
18
- :quantity,
19
- to: :pay_subscription
20
-
21
- def initialize(pay_subscription)
22
- @pay_subscription = pay_subscription
23
- end
24
-
25
- def subscription(**options)
26
- pay_subscription
27
- end
28
-
29
- # With trial, sets end to trial end (mimicing Stripe)
30
- # Without trial, sets can ends_at to end of month
31
- def cancel(**options)
32
- return if canceled?
33
-
34
- if pay_subscription.on_trial?
35
- pay_subscription.update(ends_at: pay_subscription.trial_ends_at)
36
- else
37
- pay_subscription.update(ends_at: Time.current.end_of_month)
38
- end
39
- end
40
-
41
- def cancel_now!(**options)
42
- return if canceled?
43
-
44
- ends_at = Time.current
45
- pay_subscription.update(
46
- status: :canceled,
47
- trial_ends_at: (ends_at if pay_subscription.trial_ends_at?),
48
- ends_at: ends_at
49
- )
50
- end
51
-
52
- def change_quantity(quantity, **options)
53
- pay_subscription.update(quantity: quantity)
54
- end
55
-
56
- def on_grace_period?
57
- ends_at? && ends_at > Time.current
58
- end
59
-
60
- def paused?
61
- pay_subscription.status == "paused"
62
- end
63
-
64
- def pause
65
- pay_subscription.update(status: :paused, trial_ends_at: Time.current)
66
- end
67
-
68
- def resumable?
69
- on_grace_period? || paused?
70
- end
71
-
72
- def resume
73
- unless resumable?
74
- raise StandardError, "You can only resume subscriptions within their grace period."
75
- end
76
-
77
- pay_subscription.update(status: :active, trial_ends_at: nil, ends_at: nil)
78
- end
79
-
80
- def swap(plan, **options)
81
- pay_subscription.update(processor_plan: plan, ends_at: nil, status: :active)
82
- end
83
-
84
- # Retries the latest invoice for a Past Due subscription
85
- def retry_failed_payment
86
- pay_subscription.update(status: :active)
87
- end
88
- end
89
- end
90
- end
@@ -1,90 +0,0 @@
1
- module Pay
2
- module PaddleBilling
3
- class Billable
4
- attr_reader :pay_customer
5
-
6
- delegate :processor_id,
7
- :processor_id?,
8
- :email,
9
- :customer_name,
10
- :card_token,
11
- to: :pay_customer
12
-
13
- def initialize(pay_customer)
14
- @pay_customer = pay_customer
15
- end
16
-
17
- def customer_attributes
18
- {email: email, name: customer_name}
19
- end
20
-
21
- # Retrieves a Paddle::Customer object
22
- #
23
- # Finds an existing Paddle::Customer if processor_id exists
24
- # Creates a new Paddle::Customer using `email` and `customer_name` if empty processor_id
25
- #
26
- # Returns a Paddle::Customer object
27
- def customer
28
- if processor_id?
29
- ::Paddle::Customer.retrieve(id: processor_id)
30
- else
31
- sc = ::Paddle::Customer.create(email: email, name: customer_name)
32
- pay_customer.update!(processor_id: sc.id)
33
- sc
34
- end
35
- rescue ::Paddle::Error => e
36
- raise Pay::PaddleBilling::Error, e
37
- end
38
-
39
- # Syncs name and email to Paddle::Customer
40
- # You can also pass in other attributes that will be merged into the default attributes
41
- def update_customer!(**attributes)
42
- customer unless processor_id?
43
- attrs = customer_attributes.merge(attributes)
44
- ::Paddle::Customer.update(id: processor_id, **attrs)
45
- end
46
-
47
- def charge(amount, options = {})
48
- return Pay::Error unless options
49
-
50
- items = options[:items]
51
- opts = options.except(:items).merge(customer_id: processor_id)
52
- transaction = ::Paddle::Transaction.create(items: items, **opts)
53
-
54
- attrs = {
55
- amount: transaction.details.totals.grand_total,
56
- created_at: transaction.created_at,
57
- currency: transaction.currency_code,
58
- metadata: transaction.details.line_items&.first&.id
59
- }
60
-
61
- charge = pay_customer.charges.find_or_initialize_by(processor_id: transaction.id)
62
- charge.update(attrs)
63
- charge
64
- rescue ::Paddle::Error => e
65
- raise Pay::PaddleBilling::Error, e
66
- end
67
-
68
- def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
69
- # pass
70
- end
71
-
72
- # Paddle does not use payment method tokens. The method signature has it here
73
- # to have a uniform API with the other payment processors.
74
- def add_payment_method(token = nil, default: true)
75
- Pay::PaddleBilling::PaymentMethod.sync(pay_customer: pay_customer)
76
- end
77
-
78
- def trial_end_date(subscription)
79
- return unless subscription.state == "trialing"
80
- Time.zone.parse(subscription.next_payment[:date]).end_of_day
81
- end
82
-
83
- def processor_subscription(subscription_id, options = {})
84
- ::Paddle::Subscription.retrieve(id: subscription_id, **options)
85
- rescue ::Paddle::Error => e
86
- raise Pay::PaddleBilling::Error, e
87
- end
88
- end
89
- end
90
- end
@@ -1,68 +0,0 @@
1
- module Pay
2
- module PaddleBilling
3
- class Charge
4
- attr_reader :pay_charge
5
-
6
- delegate :processor_id, :customer, to: :pay_charge
7
-
8
- def initialize(pay_charge)
9
- @pay_charge = pay_charge
10
- end
11
-
12
- def self.sync(charge_id, object: nil, try: 0, retries: 1)
13
- # Skip loading the latest charge details from the API if we already have it
14
- object ||= ::Paddle::Transaction.retrieve(id: charge_id)
15
-
16
- # Ignore transactions that aren't completed
17
- return unless object.status == "completed"
18
-
19
- # Ignore charges without a Customer
20
- return if object.customer_id.blank?
21
-
22
- pay_customer = Pay::Customer.find_by(processor: :paddle_billing, processor_id: object.customer_id)
23
- return unless pay_customer
24
-
25
- # Ignore transactions that are payment method changes
26
- # But update the customer's payment method
27
- if object.origin == "subscription_payment_method_change"
28
- Pay::PaddleBilling::PaymentMethod.sync(pay_customer: pay_customer, attributes: object.payments.first)
29
- return
30
- end
31
-
32
- attrs = {
33
- amount: object.details.totals.grand_total,
34
- created_at: object.created_at,
35
- currency: object.currency_code,
36
- metadata: object.details.line_items&.first&.id,
37
- subscription: pay_customer.subscriptions.find_by(processor_id: object.subscription_id)
38
- }
39
-
40
- if object.payment
41
- case object.payment.method_details.type.downcase
42
- when "card"
43
- attrs[:payment_method_type] = "card"
44
- attrs[:brand] = details.card.type
45
- attrs[:exp_month] = details.card.expiry_month
46
- attrs[:exp_year] = details.card.expiry_year
47
- attrs[:last4] = details.card.last4
48
- when "paypal"
49
- attrs[:payment_method_type] = "paypal"
50
- end
51
-
52
- # Update customer's payment method
53
- Pay::PaddleBilling::PaymentMethod.sync(pay_customer: pay_customer, attributes: object.payments.first)
54
- end
55
-
56
- # Update or create the charge
57
- if (pay_charge = pay_customer.charges.find_by(processor_id: object.id))
58
- pay_charge.with_lock do
59
- pay_charge.update!(attrs)
60
- end
61
- pay_charge
62
- else
63
- pay_customer.charges.create!(attrs.merge(processor_id: object.id))
64
- end
65
- end
66
- end
67
- end
68
- end
@@ -1,7 +0,0 @@
1
- module Pay
2
- module PaddleBilling
3
- class Error < Pay::Error
4
- delegate :message, to: :cause
5
- end
6
- end
7
- end
@@ -1,40 +0,0 @@
1
- module Pay
2
- module PaddleBilling
3
- class PaymentMethod
4
- attr_reader :pay_payment_method
5
-
6
- delegate :customer, :processor_id, to: :pay_payment_method
7
-
8
- def self.sync(pay_customer:, attributes:)
9
- details = attributes.method_details
10
- attrs = {
11
- type: details.type.downcase
12
- }
13
-
14
- case details.type.downcase
15
- when "card"
16
- attrs[:brand] = details.card.type
17
- attrs[:last4] = details.card.last4
18
- attrs[:exp_month] = details.card.expiry_month
19
- attrs[:exp_year] = details.card.expiry_year
20
- end
21
-
22
- payment_method = pay_customer.payment_methods.find_or_initialize_by(processor_id: attributes.stored_payment_method_id)
23
- payment_method.update!(attrs)
24
- payment_method
25
- end
26
-
27
- def initialize(pay_payment_method)
28
- @pay_payment_method = pay_payment_method
29
- end
30
-
31
- # Sets payment method as default
32
- def make_default!
33
- end
34
-
35
- # Remove payment method
36
- def detach
37
- end
38
- end
39
- end
40
- end
@@ -1,185 +0,0 @@
1
- module Pay
2
- module PaddleBilling
3
- class Subscription
4
- attr_reader :pay_subscription
5
-
6
- delegate :active?,
7
- :canceled?,
8
- :on_grace_period?,
9
- :on_trial?,
10
- :ends_at,
11
- :name,
12
- :owner,
13
- :pause_starts_at,
14
- :pause_starts_at?,
15
- :processor_id,
16
- :processor_plan,
17
- :processor_subscription,
18
- :prorate,
19
- :prorate?,
20
- :quantity,
21
- :quantity?,
22
- :trial_ends_at,
23
- to: :pay_subscription
24
-
25
- def self.sync_from_transaction(transaction_id)
26
- transaction = ::Paddle::Transaction.retrieve(id: transaction_id)
27
- sync(transaction.subscription_id) if transaction.subscription_id
28
- end
29
-
30
- def self.sync(subscription_id, object: nil, name: Pay.default_product_name)
31
- # Passthrough is not return from this API, so we can't use that
32
- object ||= ::Paddle::Subscription.retrieve(id: subscription_id)
33
-
34
- pay_customer = Pay::Customer.find_by(processor: :paddle_billing, processor_id: object.customer_id)
35
- return unless pay_customer
36
-
37
- attributes = {
38
- current_period_end: object.current_billing_period&.ends_at,
39
- current_period_start: object.current_billing_period&.starts_at,
40
- ends_at: (object.canceled_at ? Time.parse(object.canceled_at) : nil),
41
- metadata: object.custom_data,
42
- paddle_cancel_url: object.management_urls&.cancel,
43
- paddle_update_url: object.management_urls&.update_payment_method,
44
- pause_starts_at: (object.paused_at ? Time.parse(object.paused_at) : nil),
45
- status: object.status
46
- }
47
-
48
- if object.items&.first
49
- item = object.items.first
50
- attributes[:processor_plan] = item.price.id
51
- attributes[:quantity] = item.quantity
52
- end
53
-
54
- case attributes[:status]
55
- when "canceled"
56
- # Remove payment methods since customer cannot be reused after cancelling
57
- Pay::PaymentMethod.where(customer_id: object.customer_id).destroy_all
58
- when "trialing"
59
- attributes[:trial_ends_at] = Time.parse(object.next_billed_at)
60
- when "paused"
61
- attributes[:pause_starts_at] = Time.parse(object.paused_at)
62
- end
63
-
64
- case object.scheduled_change&.action
65
- when "cancel"
66
- attributes[:ends_at] = Time.parse(object.scheduled_change.effective_at)
67
- when "pause"
68
- attributes[:pause_starts_at] = Time.parse(object.scheduled_change.effective_at)
69
- when "resume"
70
- attributes[:pause_resumes_at] = Time.parse(object.scheduled_change.effective_at)
71
- end
72
-
73
- # Update or create the subscription
74
- if (pay_subscription = pay_customer.subscriptions.find_by(processor_id: subscription_id))
75
- pay_subscription.with_lock do
76
- pay_subscription.update!(attributes)
77
- end
78
- pay_subscription
79
- else
80
- pay_customer.subscriptions.create!(attributes.merge(name: name, processor_id: subscription_id))
81
- end
82
- end
83
-
84
- def initialize(pay_subscription)
85
- @pay_subscription = pay_subscription
86
- end
87
-
88
- def subscription(**options)
89
- @paddle_billing_subscription ||= ::Paddle::Subscription.retrieve(id: processor_id, **options)
90
- end
91
-
92
- # Get a transaction to update payment method
93
- def payment_method_transaction
94
- ::Paddle::Subscription.get_transaction(id: processor_id)
95
- end
96
-
97
- # If a subscription is paused, cancel immediately
98
- # Otherwise, cancel at period end
99
- def cancel(**options)
100
- return if canceled?
101
-
102
- response = ::Paddle::Subscription.cancel(
103
- id: processor_id,
104
- effective_from: options.fetch(:effective_from, (paused? ? "immediately" : "next_billing_period"))
105
- )
106
- pay_subscription.update(
107
- status: response.status,
108
- ends_at: response.scheduled_change.effective_at
109
- )
110
- rescue ::Paddle::Error => e
111
- raise Pay::PaddleBilling::Error, e
112
- end
113
-
114
- def cancel_now!(**options)
115
- cancel(options.merge(effective_from: "immediately"))
116
- rescue ::Paddle::Error => e
117
- raise Pay::PaddleBilling::Error, e
118
- end
119
-
120
- def change_quantity(quantity, **options)
121
- items = [{
122
- price_id: processor_plan,
123
- quantity: quantity
124
- }]
125
-
126
- ::Paddle::Subscription.update(id: processor_id, items: items, proration_billing_mode: "prorated_immediately")
127
- rescue ::Paddle::Error => e
128
- raise Pay::PaddleBilling::Error, e
129
- end
130
-
131
- # A subscription could be set to cancel or pause in the future
132
- # It is considered on grace period until the cancel or pause time begins
133
- def on_grace_period?
134
- (canceled? && Time.current < ends_at) || (paused? && pause_starts_at? && Time.current < pause_starts_at)
135
- end
136
-
137
- def paused?
138
- pay_subscription.status == "paused"
139
- end
140
-
141
- def pause
142
- response = ::Paddle::Subscription.pause(id: processor_id)
143
- pay_subscription.update!(status: :paused, pause_starts_at: response.scheduled_change.effective_at)
144
- rescue ::Paddle::Error => e
145
- raise Pay::PaddleBilling::Error, e
146
- end
147
-
148
- def resumable?
149
- paused?
150
- end
151
-
152
- def resume
153
- unless resumable?
154
- raise StandardError, "You can only resume paused subscriptions."
155
- end
156
-
157
- # Paddle Billing API only allows "resuming" subscriptions when they are paused
158
- # So cancel the scheduled change if it is in the future
159
- if paused? && pause_starts_at? && Time.current < pause_starts_at
160
- ::Paddle::Subscription.update(id: processor_id, scheduled_change: nil)
161
- else
162
- ::Paddle::Subscription.resume(id: processor_id, effective_from: "immediately")
163
- end
164
-
165
- pay_subscription.update(status: :active, pause_starts_at: nil)
166
- rescue ::Paddle::Error => e
167
- raise Pay::PaddleBilling::Error, e
168
- end
169
-
170
- def swap(plan, **options)
171
- items = [{
172
- price_id: plan,
173
- quantity: quantity || 1
174
- }]
175
-
176
- ::Paddle::Subscription.update(id: processor_id, items: items, proration_billing_mode: "prorated_immediately")
177
- pay_subscription.update(processor_plan: plan, ends_at: nil, status: :active)
178
- end
179
-
180
- # Retries the latest invoice for a Past Due subscription
181
- def retry_failed_payment
182
- end
183
- end
184
- end
185
- end
@@ -1,11 +0,0 @@
1
- module Pay
2
- module PaddleBilling
3
- module Webhooks
4
- class TransactionCompleted
5
- def call(event)
6
- Pay::PaddleBilling::Charge.sync(event.id)
7
- end
8
- end
9
- end
10
- end
11
- end
@@ -1,7 +0,0 @@
1
- module Pay
2
- module PaddleBilling
3
- class Error < Pay::Error
4
- delegate :message, to: :cause
5
- end
6
- end
7
- end
@@ -1,7 +0,0 @@
1
- module Pay
2
- module PaddleClassic
3
- class Error < Pay::Error
4
- delegate :message, to: :cause
5
- end
6
- end
7
- end