better_auth-stripe 0.2.1 → 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/lib/better_auth/plugins/stripe.rb +62 -874
- data/lib/better_auth/stripe/client_adapter.rb +71 -0
- data/lib/better_auth/stripe/error_codes.rb +32 -0
- data/lib/better_auth/stripe/hooks.rb +151 -0
- data/lib/better_auth/stripe/metadata.rb +85 -0
- data/lib/better_auth/stripe/middleware.rb +52 -0
- data/lib/better_auth/stripe/organization_hooks.rb +73 -0
- data/lib/better_auth/stripe/plugin_factory.rb +57 -0
- data/lib/better_auth/stripe/routes/cancel_subscription.rb +46 -0
- data/lib/better_auth/stripe/routes/cancel_subscription_callback.rb +33 -0
- data/lib/better_auth/stripe/routes/create_billing_portal.rb +35 -0
- data/lib/better_auth/stripe/routes/index.rb +24 -0
- data/lib/better_auth/stripe/routes/list_active_subscriptions.rb +32 -0
- data/lib/better_auth/stripe/routes/restore_subscription.rb +49 -0
- data/lib/better_auth/stripe/routes/stripe_webhook.rb +42 -0
- data/lib/better_auth/stripe/routes/subscription_success.rb +64 -0
- data/lib/better_auth/stripe/routes/upgrade_subscription.rb +149 -0
- data/lib/better_auth/stripe/schema.rb +93 -0
- data/lib/better_auth/stripe/types.rb +18 -0
- data/lib/better_auth/stripe/utils.rb +190 -0
- data/lib/better_auth/stripe/version.rb +1 -1
- data/lib/better_auth/stripe.rb +19 -5
- metadata +20 -1
|
@@ -1,76 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "securerandom"
|
|
4
|
-
require "stripe"
|
|
5
|
-
|
|
6
|
-
module BetterAuth
|
|
7
|
-
module Stripe
|
|
8
|
-
class ClientAdapter
|
|
9
|
-
attr_reader :customers, :checkout, :billing_portal, :subscriptions, :prices, :subscription_schedules, :webhooks
|
|
10
|
-
|
|
11
|
-
def initialize(api_key)
|
|
12
|
-
client = ::Stripe::StripeClient.new(api_key)
|
|
13
|
-
@customers = ResourceAdapter.new(client.v1.customers)
|
|
14
|
-
@checkout = NamespaceAdapter.new(sessions: ResourceAdapter.new(client.v1.checkout.sessions))
|
|
15
|
-
@billing_portal = NamespaceAdapter.new(sessions: ResourceAdapter.new(client.v1.billing_portal.sessions))
|
|
16
|
-
@subscriptions = ResourceAdapter.new(client.v1.subscriptions)
|
|
17
|
-
@prices = ResourceAdapter.new(client.v1.prices)
|
|
18
|
-
@subscription_schedules = ResourceAdapter.new(client.v1.subscription_schedules)
|
|
19
|
-
@webhooks = WebhooksAdapter.new
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
class NamespaceAdapter
|
|
24
|
-
def initialize(resources)
|
|
25
|
-
resources.each do |name, resource|
|
|
26
|
-
instance_variable_set(:"@#{name}", resource)
|
|
27
|
-
self.class.attr_reader(name) unless respond_to?(name)
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
class ResourceAdapter
|
|
33
|
-
def initialize(resource)
|
|
34
|
-
@resource = resource
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def create(params = {}, options = nil)
|
|
38
|
-
options ? @resource.create(params || {}, options) : @resource.create(params || {})
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def list(params = {})
|
|
42
|
-
@resource.list(params || {})
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def search(params = {})
|
|
46
|
-
@resource.search(params || {})
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def retrieve(id)
|
|
50
|
-
@resource.retrieve(id)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def update(id, params = {})
|
|
54
|
-
@resource.update(id, params || {})
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def release(id)
|
|
58
|
-
@resource.release(id)
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
class WebhooksAdapter
|
|
63
|
-
def construct_event(payload, signature, secret)
|
|
64
|
-
::Stripe::Webhook.construct_event(payload, signature, secret)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def construct_event_async(payload, signature, secret)
|
|
68
|
-
construct_event(payload, signature, secret)
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
3
|
module BetterAuth
|
|
75
4
|
module Plugins
|
|
76
5
|
singleton_class.remove_method(:stripe) if singleton_class.method_defined?(:stripe)
|
|
@@ -79,656 +8,79 @@ module BetterAuth
|
|
|
79
8
|
|
|
80
9
|
module_function
|
|
81
10
|
|
|
82
|
-
STRIPE_ERROR_CODES =
|
|
83
|
-
|
|
84
|
-
"EMAIL_VERIFICATION_REQUIRED" => "Email verification required",
|
|
85
|
-
"SUBSCRIPTION_NOT_FOUND" => "Subscription not found",
|
|
86
|
-
"SUBSCRIPTION_PLAN_NOT_FOUND" => "Subscription plan not found",
|
|
87
|
-
"ALREADY_SUBSCRIBED_PLAN" => "You're already subscribed to this plan",
|
|
88
|
-
"REFERENCE_ID_NOT_ALLOWED" => "Reference id is not allowed",
|
|
89
|
-
"CUSTOMER_NOT_FOUND" => "Stripe customer not found for this user",
|
|
90
|
-
"UNABLE_TO_CREATE_CUSTOMER" => "Unable to create customer",
|
|
91
|
-
"UNABLE_TO_CREATE_BILLING_PORTAL" => "Unable to create billing portal session",
|
|
92
|
-
"ORGANIZATION_NOT_FOUND" => "Organization not found",
|
|
93
|
-
"ORGANIZATION_SUBSCRIPTION_NOT_ENABLED" => "Organization subscription is not enabled",
|
|
94
|
-
"AUTHORIZE_REFERENCE_REQUIRED" => "Organization subscriptions require authorizeReference callback to be configured",
|
|
95
|
-
"ORGANIZATION_HAS_ACTIVE_SUBSCRIPTION" => "Cannot delete organization with active subscription",
|
|
96
|
-
"ORGANIZATION_REFERENCE_ID_REQUIRED" => "Reference ID is required. Provide referenceId or set activeOrganizationId in session",
|
|
97
|
-
"SUBSCRIPTION_NOT_ACTIVE" => "Subscription is not active",
|
|
98
|
-
"SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION" => "Subscription is not scheduled for cancellation",
|
|
99
|
-
"STRIPE_SIGNATURE_NOT_FOUND" => "Stripe signature not found",
|
|
100
|
-
"STRIPE_WEBHOOK_SECRET_NOT_FOUND" => "Stripe webhook secret not found",
|
|
101
|
-
"FAILED_TO_CONSTRUCT_STRIPE_EVENT" => "Failed to construct Stripe event",
|
|
102
|
-
"STRIPE_WEBHOOK_ERROR" => "Stripe webhook error",
|
|
103
|
-
"INVALID_CUSTOMER_TYPE" => "Customer type must be either user or organization",
|
|
104
|
-
"INVALID_REQUEST_BODY" => "Invalid request body"
|
|
105
|
-
}.freeze
|
|
106
|
-
STRIPE_UNSAFE_METADATA_KEYS = %w[__proto__ constructor prototype].freeze
|
|
11
|
+
STRIPE_ERROR_CODES = BetterAuth::Stripe::ERROR_CODES
|
|
12
|
+
STRIPE_UNSAFE_METADATA_KEYS = BetterAuth::Stripe::Metadata::UNSAFE_KEYS
|
|
107
13
|
|
|
108
14
|
def stripe(options = {})
|
|
109
|
-
|
|
110
|
-
Plugin.new(
|
|
111
|
-
id: "stripe",
|
|
112
|
-
init: ->(ctx) { {context: {schema: Schema.auth_tables(ctx.options)}} },
|
|
113
|
-
schema: stripe_schema(config),
|
|
114
|
-
endpoints: stripe_endpoints(config),
|
|
115
|
-
error_codes: STRIPE_ERROR_CODES,
|
|
116
|
-
options: config.merge(database_hooks: stripe_database_hooks(config), organization_hooks: stripe_organization_hooks(config))
|
|
117
|
-
)
|
|
15
|
+
BetterAuth::Stripe::PluginFactory.build(options)
|
|
118
16
|
end
|
|
119
17
|
|
|
120
18
|
def stripe_schema(config)
|
|
121
|
-
schema
|
|
122
|
-
user: {
|
|
123
|
-
fields: {
|
|
124
|
-
stripeCustomerId: {type: "string", required: false}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
if config.dig(:subscription, :enabled)
|
|
129
|
-
schema[:subscription] = {
|
|
130
|
-
fields: {
|
|
131
|
-
plan: {type: "string", required: true},
|
|
132
|
-
referenceId: {type: "string", required: true},
|
|
133
|
-
stripeCustomerId: {type: "string", required: false},
|
|
134
|
-
stripeSubscriptionId: {type: "string", required: false},
|
|
135
|
-
status: {type: "string", required: false, default_value: "incomplete"},
|
|
136
|
-
periodStart: {type: "date", required: false},
|
|
137
|
-
periodEnd: {type: "date", required: false},
|
|
138
|
-
trialStart: {type: "date", required: false},
|
|
139
|
-
trialEnd: {type: "date", required: false},
|
|
140
|
-
cancelAtPeriodEnd: {type: "boolean", required: false, default_value: false},
|
|
141
|
-
cancelAt: {type: "date", required: false},
|
|
142
|
-
canceledAt: {type: "date", required: false},
|
|
143
|
-
endedAt: {type: "date", required: false},
|
|
144
|
-
seats: {type: "number", required: false},
|
|
145
|
-
billingInterval: {type: "string", required: false},
|
|
146
|
-
stripeScheduleId: {type: "string", required: false},
|
|
147
|
-
limits: {type: "json", required: false}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
end
|
|
151
|
-
if config.dig(:organization, :enabled)
|
|
152
|
-
schema[:organization] = {fields: {stripeCustomerId: {type: "string", required: false}}}
|
|
153
|
-
end
|
|
154
|
-
schema
|
|
19
|
+
BetterAuth::Stripe::Schema.schema(config)
|
|
155
20
|
end
|
|
156
21
|
|
|
157
22
|
def stripe_endpoints(config)
|
|
158
|
-
endpoints
|
|
159
|
-
return endpoints unless config.dig(:subscription, :enabled)
|
|
160
|
-
|
|
161
|
-
endpoints.merge(
|
|
162
|
-
upgrade_subscription: stripe_upgrade_subscription_endpoint(config),
|
|
163
|
-
cancel_subscription_callback: stripe_cancel_callback_endpoint(config),
|
|
164
|
-
cancel_subscription: stripe_cancel_subscription_endpoint(config),
|
|
165
|
-
restore_subscription: stripe_restore_subscription_endpoint(config),
|
|
166
|
-
list_active_subscriptions: stripe_list_subscriptions_endpoint(config),
|
|
167
|
-
subscription_success: stripe_success_endpoint(config),
|
|
168
|
-
create_billing_portal: stripe_billing_portal_endpoint(config)
|
|
169
|
-
)
|
|
23
|
+
BetterAuth::Stripe::Routes.endpoints(config)
|
|
170
24
|
end
|
|
171
25
|
|
|
172
26
|
def stripe_database_hooks(config)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
{
|
|
176
|
-
user: {
|
|
177
|
-
create: {
|
|
178
|
-
before: lambda do |data, hook_ctx|
|
|
179
|
-
next unless data["email"] && !data["stripeCustomerId"]
|
|
180
|
-
|
|
181
|
-
data["id"] ||= SecureRandom.hex(16)
|
|
182
|
-
customer = stripe_find_or_create_user_customer(config, data, nil, hook_ctx)
|
|
183
|
-
{data: {id: data["id"], stripeCustomerId: stripe_id(customer)}}
|
|
184
|
-
rescue
|
|
185
|
-
nil
|
|
186
|
-
end
|
|
187
|
-
},
|
|
188
|
-
update: {
|
|
189
|
-
after: lambda do |user, _ctx|
|
|
190
|
-
next unless user && user["stripeCustomerId"]
|
|
191
|
-
|
|
192
|
-
customer = stripe_client(config).customers.retrieve(user["stripeCustomerId"])
|
|
193
|
-
next if stripe_fetch(customer, "deleted")
|
|
194
|
-
next if stripe_fetch(customer, "email") == user["email"]
|
|
195
|
-
|
|
196
|
-
stripe_client(config).customers.update(user["stripeCustomerId"], email: user["email"])
|
|
197
|
-
rescue
|
|
198
|
-
nil
|
|
199
|
-
end
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
27
|
+
BetterAuth::Stripe::PluginFactory.database_hooks(config)
|
|
203
28
|
end
|
|
204
29
|
|
|
205
30
|
def stripe_organization_hooks(config)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
{
|
|
209
|
-
after_update_organization: lambda do |data, _ctx|
|
|
210
|
-
organization = data[:organization] || data["organization"]
|
|
211
|
-
next unless organization && organization["stripeCustomerId"]
|
|
212
|
-
|
|
213
|
-
customer = stripe_client(config).customers.retrieve(organization["stripeCustomerId"])
|
|
214
|
-
next if stripe_fetch(customer, "deleted")
|
|
215
|
-
next if stripe_fetch(customer, "name") == organization["name"]
|
|
216
|
-
|
|
217
|
-
stripe_client(config).customers.update(organization["stripeCustomerId"], name: organization["name"])
|
|
218
|
-
rescue
|
|
219
|
-
nil
|
|
220
|
-
end,
|
|
221
|
-
before_delete_organization: lambda do |data, _ctx|
|
|
222
|
-
organization = data[:organization] || data["organization"]
|
|
223
|
-
next unless organization && organization["stripeCustomerId"]
|
|
224
|
-
|
|
225
|
-
subscriptions = stripe_client(config).subscriptions.list(customer: organization["stripeCustomerId"], status: "all", limit: 100)
|
|
226
|
-
active = Array(stripe_fetch(subscriptions, "data")).any? do |subscription|
|
|
227
|
-
!%w[canceled incomplete incomplete_expired].include?(stripe_fetch(subscription, "status").to_s)
|
|
228
|
-
end
|
|
229
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("ORGANIZATION_HAS_ACTIVE_SUBSCRIPTION")) if active
|
|
230
|
-
end,
|
|
231
|
-
after_add_member: ->(data, ctx) { stripe_sync_organization_seats(config, data, ctx) },
|
|
232
|
-
after_remove_member: ->(data, ctx) { stripe_sync_organization_seats(config, data, ctx) },
|
|
233
|
-
after_accept_invitation: ->(data, ctx) { stripe_sync_organization_seats(config, data, ctx) }
|
|
234
|
-
}
|
|
31
|
+
BetterAuth::Stripe::OrganizationHooks.hooks(config)
|
|
235
32
|
end
|
|
236
33
|
|
|
237
34
|
def stripe_upgrade_subscription_endpoint(config)
|
|
238
|
-
|
|
239
|
-
session = Routes.current_session(ctx)
|
|
240
|
-
body = normalize_hash(ctx.body)
|
|
241
|
-
subscription_options = stripe_subscription_options(config)
|
|
242
|
-
customer_type = stripe_customer_type!(body)
|
|
243
|
-
reference_id = stripe_reference_id!(ctx, session, customer_type, body[:reference_id], config)
|
|
244
|
-
stripe_authorize_reference!(ctx, session, reference_id, "upgrade-subscription", customer_type, subscription_options, explicit: body.key?(:reference_id))
|
|
245
|
-
|
|
246
|
-
user = session.fetch(:user)
|
|
247
|
-
if subscription_options[:require_email_verification] && !user["emailVerified"]
|
|
248
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("EMAIL_VERIFICATION_REQUIRED"))
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
plan = stripe_plan_by_name(config, body[:plan])
|
|
252
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("SUBSCRIPTION_PLAN_NOT_FOUND")) unless plan
|
|
253
|
-
|
|
254
|
-
subscription_to_update = nil
|
|
255
|
-
if body[:subscription_id]
|
|
256
|
-
subscription_to_update = ctx.context.adapter.find_one(model: "subscription", where: [{field: "stripeSubscriptionId", value: body[:subscription_id]}])
|
|
257
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("SUBSCRIPTION_NOT_FOUND")) unless subscription_to_update && subscription_to_update["referenceId"] == reference_id
|
|
258
|
-
end
|
|
259
|
-
|
|
260
|
-
subscriptions = subscription_to_update ? [subscription_to_update] : ctx.context.adapter.find_many(model: "subscription", where: [{field: "referenceId", value: reference_id}])
|
|
261
|
-
reference_customer_id = subscriptions.find { |entry| entry["stripeCustomerId"] }&.fetch("stripeCustomerId", nil)
|
|
262
|
-
customer_id = if customer_type == "organization"
|
|
263
|
-
subscription_to_update&.fetch("stripeCustomerId", nil) || reference_customer_id || stripe_organization_customer(config, ctx, reference_id, body[:metadata])
|
|
264
|
-
else
|
|
265
|
-
subscription_to_update&.fetch("stripeCustomerId", nil) || reference_customer_id || user["stripeCustomerId"] || stripe_create_customer(config, ctx, user, body[:metadata])
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
active_or_trialing = subscriptions.find { |entry| stripe_active_or_trialing?(entry) }
|
|
269
|
-
active_stripe_subscriptions = stripe_active_subscriptions(config, customer_id)
|
|
270
|
-
active_stripe = active_stripe_subscriptions.find do |entry|
|
|
271
|
-
if subscription_to_update&.fetch("stripeSubscriptionId", nil) || body[:subscription_id]
|
|
272
|
-
stripe_fetch(entry, "id") == subscription_to_update&.fetch("stripeSubscriptionId", nil) || stripe_fetch(entry, "id") == body[:subscription_id]
|
|
273
|
-
elsif active_or_trialing && active_or_trialing["stripeSubscriptionId"]
|
|
274
|
-
stripe_fetch(entry, "id") == active_or_trialing["stripeSubscriptionId"]
|
|
275
|
-
else
|
|
276
|
-
false
|
|
277
|
-
end
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
price_id = stripe_price_id(config, plan, body[:annual])
|
|
281
|
-
raise APIError.new("BAD_REQUEST", message: "Price ID not found for the selected plan") if price_id.to_s.empty?
|
|
282
|
-
auto_managed_seats = !!(plan[:seat_price_id] && customer_type == "organization")
|
|
283
|
-
member_count = auto_managed_seats ? ctx.context.adapter.count(model: "member", where: [{field: "organizationId", value: reference_id}]) : 0
|
|
284
|
-
requested_seats = auto_managed_seats ? member_count : (body[:seats] || 1)
|
|
285
|
-
seat_only_plan = auto_managed_seats && plan[:seat_price_id] == price_id
|
|
286
|
-
|
|
287
|
-
active_resolved = active_stripe && stripe_resolve_plan_item(config, active_stripe)
|
|
288
|
-
active_stripe_item = active_resolved&.fetch(:item, nil) || stripe_subscription_item(active_stripe || {})
|
|
289
|
-
stripe_price_id_value = stripe_fetch(stripe_fetch(active_stripe_item || {}, "price") || {}, "id")
|
|
290
|
-
same_plan = active_or_trialing && active_or_trialing["plan"].to_s.downcase == body[:plan].to_s.downcase
|
|
291
|
-
same_seats = auto_managed_seats || (active_or_trialing && active_or_trialing["seats"].to_i == requested_seats.to_i)
|
|
292
|
-
same_price = !active_stripe || stripe_price_id_value == price_id
|
|
293
|
-
valid_period = !active_or_trialing || !active_or_trialing["periodEnd"] || active_or_trialing["periodEnd"] > Time.now
|
|
294
|
-
if active_or_trialing&.fetch("status", nil) == "active" && same_plan && same_seats && same_price && valid_period
|
|
295
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("ALREADY_SUBSCRIBED_PLAN"))
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
if active_stripe
|
|
299
|
-
stripe_release_plugin_schedule(ctx, config, customer_id, active_stripe, active_or_trialing || subscription_to_update)
|
|
300
|
-
|
|
301
|
-
if body[:schedule_at_period_end]
|
|
302
|
-
url = stripe_schedule_plan_change(ctx, config, active_stripe, active_or_trialing, plan, price_id, requested_seats, seat_only_plan, body)
|
|
303
|
-
next ctx.json({url: url, redirect: stripe_redirect?(body)})
|
|
304
|
-
end
|
|
305
|
-
|
|
306
|
-
old_plan = active_or_trialing && stripe_plan_by_name(config, active_or_trialing["plan"])
|
|
307
|
-
if stripe_direct_subscription_update?(old_plan, plan, auto_managed_seats)
|
|
308
|
-
url = stripe_update_active_subscription_items(ctx, config, active_stripe, active_or_trialing, old_plan, plan, price_id, requested_seats, seat_only_plan, body)
|
|
309
|
-
next ctx.json({url: url, redirect: stripe_redirect?(body)})
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
portal = stripe_client(config).billing_portal.sessions.create(
|
|
313
|
-
customer: customer_id,
|
|
314
|
-
return_url: stripe_url(ctx, body[:return_url] || "/"),
|
|
315
|
-
flow_data: {
|
|
316
|
-
type: "subscription_update_confirm",
|
|
317
|
-
after_completion: {type: "redirect", redirect: {return_url: stripe_url(ctx, body[:return_url] || "/")}},
|
|
318
|
-
subscription_update_confirm: {
|
|
319
|
-
subscription: stripe_fetch(active_stripe, "id"),
|
|
320
|
-
items: [stripe_line_item(config, price_id, requested_seats).merge(id: stripe_fetch(active_stripe_item || {}, "id"))]
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
)
|
|
324
|
-
next ctx.json(stripe_stringify_keys(portal).merge(redirect: stripe_redirect?(body)))
|
|
325
|
-
end
|
|
326
|
-
|
|
327
|
-
incomplete = subscriptions.find { |entry| entry["status"] == "incomplete" }
|
|
328
|
-
subscription = active_or_trialing || incomplete
|
|
329
|
-
if subscription
|
|
330
|
-
update = {plan: plan[:name].to_s.downcase, seats: requested_seats}
|
|
331
|
-
subscription = ctx.context.adapter.update(model: "subscription", where: [{field: "id", value: subscription.fetch("id")}], update: update) || subscription.merge(update.transform_keys { |key| Schema.storage_key(key) })
|
|
332
|
-
else
|
|
333
|
-
subscription = ctx.context.adapter.create(
|
|
334
|
-
model: "subscription",
|
|
335
|
-
data: {plan: plan[:name].to_s.downcase, referenceId: reference_id, stripeCustomerId: customer_id, status: "incomplete", seats: requested_seats, limits: plan[:limits]}
|
|
336
|
-
)
|
|
337
|
-
end
|
|
338
|
-
|
|
339
|
-
has_ever_trialed = ctx.context.adapter.find_many(model: "subscription", where: [{field: "referenceId", value: reference_id}]).any? do |entry|
|
|
340
|
-
entry["trialStart"] || entry["trialEnd"] || entry["status"] == "trialing"
|
|
341
|
-
end
|
|
342
|
-
free_trial = (!has_ever_trialed && plan[:free_trial]) ? {trial_period_days: plan.dig(:free_trial, :days)} : {}
|
|
343
|
-
checkout_customization = subscription_options[:get_checkout_session_params]&.call(
|
|
344
|
-
{user: user, session: session.fetch(:session), plan: plan, subscription: subscription},
|
|
345
|
-
ctx.request,
|
|
346
|
-
ctx
|
|
347
|
-
) || {}
|
|
348
|
-
custom_params = stripe_fetch(checkout_customization, "params") || {}
|
|
349
|
-
custom_options = normalize_hash(stripe_fetch(checkout_customization, "options") || {})
|
|
350
|
-
custom_subscription_data = stripe_fetch(custom_params, "subscription_data") || stripe_fetch(custom_params, "subscriptionData") || {}
|
|
351
|
-
internal_metadata = {userId: user.fetch("id"), subscriptionId: subscription.fetch("id"), referenceId: reference_id}
|
|
352
|
-
metadata = stripe_subscription_metadata_set(internal_metadata, body[:metadata], stripe_fetch(custom_params, "metadata"))
|
|
353
|
-
subscription_metadata = stripe_subscription_metadata_set(internal_metadata, body[:metadata], stripe_fetch(custom_subscription_data, "metadata"))
|
|
354
|
-
checkout_params = stripe_deep_merge(
|
|
355
|
-
custom_params,
|
|
356
|
-
customer: customer_id,
|
|
357
|
-
customer_update: (customer_type == "user") ? {name: "auto", address: "auto"} : {address: "auto"},
|
|
358
|
-
locale: body[:locale],
|
|
359
|
-
success_url: stripe_url(ctx, "#{ctx.context.base_url}/subscription/success?callbackURL=#{Rack::Utils.escape(body[:success_url] || "/")}&checkoutSessionId={CHECKOUT_SESSION_ID}"),
|
|
360
|
-
cancel_url: stripe_url(ctx, body[:cancel_url] || "/"),
|
|
361
|
-
line_items: stripe_checkout_line_items(config, plan, price_id, requested_seats, auto_managed_seats, seat_only_plan),
|
|
362
|
-
subscription_data: free_trial.merge(metadata: subscription_metadata),
|
|
363
|
-
mode: "subscription",
|
|
364
|
-
client_reference_id: reference_id,
|
|
365
|
-
metadata: metadata
|
|
366
|
-
)
|
|
367
|
-
checkout_params[:metadata] = metadata
|
|
368
|
-
checkout_params[:subscription_data] ||= {}
|
|
369
|
-
checkout_params[:subscription_data][:metadata] = subscription_metadata
|
|
370
|
-
checkout = stripe_client(config).checkout.sessions.create(checkout_params, custom_options.empty? ? nil : custom_options)
|
|
371
|
-
ctx.json(stripe_stringify_keys(checkout).merge(redirect: stripe_redirect?(body)))
|
|
372
|
-
end
|
|
35
|
+
BetterAuth::Stripe::Routes::UpgradeSubscription.endpoint(config)
|
|
373
36
|
end
|
|
374
37
|
|
|
375
38
|
def stripe_cancel_subscription_endpoint(config)
|
|
376
|
-
|
|
377
|
-
session = Routes.current_session(ctx)
|
|
378
|
-
body = normalize_hash(ctx.body)
|
|
379
|
-
customer_type = stripe_customer_type!(body)
|
|
380
|
-
reference_id = stripe_reference_id!(ctx, session, customer_type, body[:reference_id], config)
|
|
381
|
-
stripe_authorize_reference!(ctx, session, reference_id, "cancel-subscription", customer_type, stripe_subscription_options(config), explicit: body.key?(:reference_id))
|
|
382
|
-
subscription = stripe_find_subscription_for_action(ctx, reference_id, body[:subscription_id], active_only: true)
|
|
383
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("SUBSCRIPTION_NOT_FOUND")) unless subscription && subscription["stripeCustomerId"]
|
|
384
|
-
|
|
385
|
-
active = stripe_active_subscriptions(config, subscription["stripeCustomerId"])
|
|
386
|
-
if active.empty?
|
|
387
|
-
ctx.context.adapter.delete_many(model: "subscription", where: [{field: "referenceId", value: reference_id}])
|
|
388
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("SUBSCRIPTION_NOT_FOUND"))
|
|
389
|
-
end
|
|
390
|
-
stripe_subscription = active.find { |entry| stripe_fetch(entry, "id") == subscription["stripeSubscriptionId"] }
|
|
391
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("SUBSCRIPTION_NOT_FOUND")) unless stripe_subscription
|
|
392
|
-
|
|
393
|
-
portal = stripe_client(config).billing_portal.sessions.create(
|
|
394
|
-
customer: subscription["stripeCustomerId"],
|
|
395
|
-
return_url: stripe_url(ctx, "#{ctx.context.base_url}/subscription/cancel/callback?callbackURL=#{Rack::Utils.escape(body[:return_url] || "/")}&subscriptionId=#{Rack::Utils.escape(subscription.fetch("id"))}"),
|
|
396
|
-
flow_data: {type: "subscription_cancel", subscription_cancel: {subscription: stripe_fetch(stripe_subscription, "id")}}
|
|
397
|
-
)
|
|
398
|
-
ctx.json(stripe_stringify_keys(portal).merge(redirect: stripe_redirect?(body)))
|
|
399
|
-
rescue APIError
|
|
400
|
-
raise
|
|
401
|
-
rescue => error
|
|
402
|
-
if error.message.include?("already set to be canceled") && subscription && !stripe_pending_cancel?(subscription)
|
|
403
|
-
stripe_sub = stripe_client(config).subscriptions.retrieve(subscription["stripeSubscriptionId"])
|
|
404
|
-
ctx.context.adapter.update(model: "subscription", where: [{field: "id", value: subscription.fetch("id")}], update: stripe_subscription_state(stripe_sub, include_status: false))
|
|
405
|
-
end
|
|
406
|
-
raise APIError.new("BAD_REQUEST", message: error.message)
|
|
407
|
-
end
|
|
39
|
+
BetterAuth::Stripe::Routes::CancelSubscription.endpoint(config)
|
|
408
40
|
end
|
|
409
41
|
|
|
410
42
|
def stripe_restore_subscription_endpoint(config)
|
|
411
|
-
|
|
412
|
-
session = Routes.current_session(ctx)
|
|
413
|
-
body = normalize_hash(ctx.body)
|
|
414
|
-
customer_type = stripe_customer_type!(body)
|
|
415
|
-
reference_id = stripe_reference_id!(ctx, session, customer_type, body[:reference_id], config)
|
|
416
|
-
stripe_authorize_reference!(ctx, session, reference_id, "restore-subscription", customer_type, stripe_subscription_options(config), explicit: body.key?(:reference_id))
|
|
417
|
-
subscription = stripe_find_subscription_for_action(ctx, reference_id, body[:subscription_id], active_only: false)
|
|
418
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("SUBSCRIPTION_NOT_FOUND")) unless subscription && subscription["stripeCustomerId"]
|
|
419
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("SUBSCRIPTION_NOT_ACTIVE")) unless stripe_active_or_trialing?(subscription)
|
|
420
|
-
|
|
421
|
-
if subscription["stripeScheduleId"]
|
|
422
|
-
schedule = stripe_client(config).subscription_schedules.retrieve(subscription["stripeScheduleId"])
|
|
423
|
-
if stripe_fetch(schedule, "status") == "active"
|
|
424
|
-
schedule = stripe_client(config).subscription_schedules.release(subscription["stripeScheduleId"])
|
|
425
|
-
end
|
|
426
|
-
ctx.context.adapter.update(model: "subscription", where: [{field: "id", value: subscription.fetch("id")}], update: {stripeScheduleId: nil})
|
|
427
|
-
next ctx.json(stripe_stringify_keys(schedule))
|
|
428
|
-
end
|
|
429
|
-
|
|
430
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION")) unless stripe_pending_cancel?(subscription)
|
|
431
|
-
|
|
432
|
-
active = stripe_active_subscriptions(config, subscription["stripeCustomerId"]).first
|
|
433
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("SUBSCRIPTION_NOT_FOUND")) unless active
|
|
434
|
-
|
|
435
|
-
update_params = if stripe_fetch(active, "cancel_at")
|
|
436
|
-
{cancel_at: ""}
|
|
437
|
-
elsif stripe_fetch(active, "cancel_at_period_end")
|
|
438
|
-
{cancel_at_period_end: false}
|
|
439
|
-
else
|
|
440
|
-
{}
|
|
441
|
-
end
|
|
442
|
-
restored = stripe_client(config).subscriptions.update(stripe_fetch(active, "id"), update_params)
|
|
443
|
-
ctx.context.adapter.update(model: "subscription", where: [{field: "id", value: subscription.fetch("id")}], update: {cancelAtPeriodEnd: false, cancelAt: nil, canceledAt: nil})
|
|
444
|
-
ctx.json(stripe_stringify_keys(restored))
|
|
445
|
-
end
|
|
43
|
+
BetterAuth::Stripe::Routes::RestoreSubscription.endpoint(config)
|
|
446
44
|
end
|
|
447
45
|
|
|
448
46
|
def stripe_list_subscriptions_endpoint(config)
|
|
449
|
-
|
|
450
|
-
session = Routes.current_session(ctx)
|
|
451
|
-
query = normalize_hash(ctx.query)
|
|
452
|
-
customer_type = stripe_customer_type!(query)
|
|
453
|
-
reference_id = stripe_reference_id!(ctx, session, customer_type, query[:reference_id], config)
|
|
454
|
-
stripe_authorize_reference!(ctx, session, reference_id, "list-subscription", customer_type, stripe_subscription_options(config), explicit: query.key?(:reference_id))
|
|
455
|
-
plans = stripe_plans(config)
|
|
456
|
-
subscriptions = ctx.context.adapter.find_many(model: "subscription", where: [{field: "referenceId", value: reference_id}]).select { |entry| stripe_active_or_trialing?(entry) }
|
|
457
|
-
ctx.json(subscriptions.map do |entry|
|
|
458
|
-
plan = plans.find { |item| item[:name].to_s.downcase == entry["plan"].to_s.downcase }
|
|
459
|
-
price_id = if entry["billingInterval"] == "year"
|
|
460
|
-
plan&.fetch(:annual_discount_price_id, nil) || plan&.fetch(:price_id, nil)
|
|
461
|
-
else
|
|
462
|
-
plan&.fetch(:price_id, nil)
|
|
463
|
-
end
|
|
464
|
-
entry.merge("limits" => plan&.fetch(:limits, nil), "priceId" => price_id)
|
|
465
|
-
end)
|
|
466
|
-
end
|
|
47
|
+
BetterAuth::Stripe::Routes::ListActiveSubscriptions.endpoint(config)
|
|
467
48
|
end
|
|
468
49
|
|
|
469
50
|
def stripe_billing_portal_endpoint(config)
|
|
470
|
-
|
|
471
|
-
session = Routes.current_session(ctx)
|
|
472
|
-
body = normalize_hash(ctx.body)
|
|
473
|
-
customer_type = stripe_customer_type!(body)
|
|
474
|
-
reference_id = stripe_reference_id!(ctx, session, customer_type, body[:reference_id], config)
|
|
475
|
-
stripe_authorize_reference!(ctx, session, reference_id, "billing-portal", customer_type, stripe_subscription_options(config), explicit: body.key?(:reference_id))
|
|
476
|
-
customer_id = if customer_type == "organization"
|
|
477
|
-
org = ctx.context.adapter.find_one(model: "organization", where: [{field: "id", value: reference_id}])
|
|
478
|
-
org&.fetch("stripeCustomerId", nil) || stripe_active_subscription(ctx, reference_id)&.fetch("stripeCustomerId", nil)
|
|
479
|
-
else
|
|
480
|
-
session.fetch(:user)["stripeCustomerId"] || stripe_active_subscription(ctx, reference_id)&.fetch("stripeCustomerId", nil)
|
|
481
|
-
end
|
|
482
|
-
raise APIError.new("NOT_FOUND", message: STRIPE_ERROR_CODES.fetch("CUSTOMER_NOT_FOUND")) unless customer_id
|
|
483
|
-
|
|
484
|
-
portal = stripe_client(config).billing_portal.sessions.create(customer: customer_id, return_url: stripe_url(ctx, body[:return_url] || "/"), locale: body[:locale])
|
|
485
|
-
ctx.json(stripe_stringify_keys(portal).merge(redirect: stripe_redirect?(body)))
|
|
486
|
-
rescue APIError
|
|
487
|
-
raise
|
|
488
|
-
rescue
|
|
489
|
-
raise APIError.new("INTERNAL_SERVER_ERROR", message: STRIPE_ERROR_CODES.fetch("UNABLE_TO_CREATE_BILLING_PORTAL"))
|
|
490
|
-
end
|
|
51
|
+
BetterAuth::Stripe::Routes::CreateBillingPortal.endpoint(config)
|
|
491
52
|
end
|
|
492
53
|
|
|
493
|
-
def stripe_cancel_callback_endpoint(config
|
|
494
|
-
|
|
495
|
-
query = normalize_hash(ctx.query)
|
|
496
|
-
callback = query[:callback_url] || "/"
|
|
497
|
-
unless query[:subscription_id]
|
|
498
|
-
raise ctx.redirect(stripe_url(ctx, callback))
|
|
499
|
-
end
|
|
500
|
-
session = Routes.current_session(ctx, allow_nil: true)
|
|
501
|
-
raise ctx.redirect(stripe_url(ctx, callback)) unless session
|
|
502
|
-
|
|
503
|
-
subscription = ctx.context.adapter.find_one(model: "subscription", where: [{field: "id", value: query[:subscription_id]}])
|
|
504
|
-
if subscription && !stripe_pending_cancel?(subscription) && subscription["stripeCustomerId"]
|
|
505
|
-
current = stripe_active_subscriptions(config || {}, subscription["stripeCustomerId"]).find { |entry| stripe_fetch(entry, "id") == subscription["stripeSubscriptionId"] }
|
|
506
|
-
if current && stripe_stripe_pending_cancel?(current)
|
|
507
|
-
ctx.context.adapter.update(model: "subscription", where: [{field: "id", value: subscription.fetch("id")}], update: stripe_subscription_state(current, include_status: true))
|
|
508
|
-
stripe_subscription_options(config || {})[:on_subscription_cancel]&.call({subscription: subscription, stripeSubscription: current, stripe_subscription: current, cancellationDetails: stripe_fetch(current, "cancellation_details"), cancellation_details: stripe_fetch(current, "cancellation_details"), event: nil})
|
|
509
|
-
end
|
|
510
|
-
end
|
|
511
|
-
raise ctx.redirect(stripe_url(ctx, callback))
|
|
512
|
-
end
|
|
54
|
+
def stripe_cancel_callback_endpoint(config)
|
|
55
|
+
BetterAuth::Stripe::Routes::CancelSubscriptionCallback.endpoint(config)
|
|
513
56
|
end
|
|
514
57
|
|
|
515
|
-
def stripe_success_endpoint(config
|
|
516
|
-
|
|
517
|
-
query = normalize_hash(ctx.query)
|
|
518
|
-
callback = query[:callback_url] || "/"
|
|
519
|
-
checkout_session_id = query[:checkout_session_id]
|
|
520
|
-
subscription_id = query[:subscription_id]
|
|
521
|
-
if checkout_session_id
|
|
522
|
-
callback = callback.to_s.gsub("{CHECKOUT_SESSION_ID}", checkout_session_id.to_s)
|
|
523
|
-
checkout_session = begin
|
|
524
|
-
stripe_client(config || {}).checkout.sessions.retrieve(checkout_session_id)
|
|
525
|
-
rescue
|
|
526
|
-
nil
|
|
527
|
-
end
|
|
528
|
-
raise ctx.redirect(stripe_url(ctx, callback)) unless checkout_session
|
|
529
|
-
|
|
530
|
-
metadata = normalize_hash(stripe_fetch(checkout_session || {}, "metadata") || {})
|
|
531
|
-
subscription_id = metadata[:subscription_id]
|
|
532
|
-
end
|
|
533
|
-
|
|
534
|
-
unless subscription_id
|
|
535
|
-
raise ctx.redirect(stripe_url(ctx, callback))
|
|
536
|
-
end
|
|
537
|
-
session = Routes.current_session(ctx, allow_nil: true)
|
|
538
|
-
raise ctx.redirect(stripe_url(ctx, callback)) unless session
|
|
539
|
-
|
|
540
|
-
subscription = ctx.context.adapter.find_one(model: "subscription", where: [{field: "id", value: subscription_id}])
|
|
541
|
-
raise ctx.redirect(stripe_url(ctx, callback)) unless subscription
|
|
542
|
-
raise ctx.redirect(stripe_url(ctx, callback)) if stripe_active_or_trialing?(subscription)
|
|
543
|
-
|
|
544
|
-
customer_id = subscription["stripeCustomerId"] || session.fetch(:user)["stripeCustomerId"]
|
|
545
|
-
raise ctx.redirect(stripe_url(ctx, callback)) unless customer_id
|
|
546
|
-
|
|
547
|
-
stripe_subscription = stripe_active_subscriptions(config || {}, customer_id).first
|
|
548
|
-
if stripe_subscription
|
|
549
|
-
resolved = stripe_resolve_plan_item(config || {}, stripe_subscription)
|
|
550
|
-
item = resolved&.fetch(:item, nil)
|
|
551
|
-
plan = resolved&.fetch(:plan, nil)
|
|
552
|
-
if item && plan
|
|
553
|
-
ctx.context.adapter.update(
|
|
554
|
-
model: "subscription",
|
|
555
|
-
where: [{field: "id", value: subscription.fetch("id")}],
|
|
556
|
-
update: stripe_subscription_state(stripe_subscription, include_status: true, compact: false).merge(
|
|
557
|
-
plan: plan[:name].to_s.downcase,
|
|
558
|
-
seats: stripe_resolve_quantity(stripe_subscription, item, plan),
|
|
559
|
-
stripeSubscriptionId: stripe_fetch(stripe_subscription, "id")
|
|
560
|
-
)
|
|
561
|
-
)
|
|
562
|
-
end
|
|
563
|
-
end
|
|
564
|
-
raise ctx.redirect(stripe_url(ctx, callback))
|
|
565
|
-
end
|
|
58
|
+
def stripe_success_endpoint(config)
|
|
59
|
+
BetterAuth::Stripe::Routes::SubscriptionSuccess.endpoint(config)
|
|
566
60
|
end
|
|
567
61
|
|
|
568
62
|
def stripe_webhook_endpoint(config)
|
|
569
|
-
|
|
570
|
-
signature = ctx.headers["stripe-signature"]
|
|
571
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("STRIPE_SIGNATURE_NOT_FOUND")) if signature.to_s.empty?
|
|
572
|
-
|
|
573
|
-
raise APIError.new("INTERNAL_SERVER_ERROR", message: STRIPE_ERROR_CODES.fetch("STRIPE_WEBHOOK_SECRET_NOT_FOUND")) if config[:stripe_webhook_secret].to_s.empty?
|
|
574
|
-
|
|
575
|
-
event = begin
|
|
576
|
-
if stripe_client(config).respond_to?(:webhooks)
|
|
577
|
-
webhooks = stripe_client(config).webhooks
|
|
578
|
-
if webhooks.respond_to?(:construct_event_async)
|
|
579
|
-
webhooks.construct_event_async(ctx.body, signature, config[:stripe_webhook_secret])
|
|
580
|
-
else
|
|
581
|
-
webhooks.construct_event(ctx.body, signature, config[:stripe_webhook_secret])
|
|
582
|
-
end
|
|
583
|
-
else
|
|
584
|
-
ctx.body
|
|
585
|
-
end
|
|
586
|
-
rescue
|
|
587
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("FAILED_TO_CONSTRUCT_STRIPE_EVENT"))
|
|
588
|
-
end
|
|
589
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("FAILED_TO_CONSTRUCT_STRIPE_EVENT")) unless event
|
|
590
|
-
begin
|
|
591
|
-
stripe_handle_event(ctx, event)
|
|
592
|
-
rescue
|
|
593
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("STRIPE_WEBHOOK_ERROR"))
|
|
594
|
-
end
|
|
595
|
-
ctx.json({success: true})
|
|
596
|
-
end
|
|
63
|
+
BetterAuth::Stripe::Routes::StripeWebhook.endpoint(config)
|
|
597
64
|
end
|
|
598
65
|
|
|
599
66
|
def stripe_handle_event(ctx, event)
|
|
600
|
-
|
|
601
|
-
type = event[:type].to_s
|
|
602
|
-
case type
|
|
603
|
-
when "checkout.session.completed"
|
|
604
|
-
stripe_on_checkout_completed(ctx, event)
|
|
605
|
-
when "customer.subscription.created"
|
|
606
|
-
stripe_on_subscription_created(ctx, event)
|
|
607
|
-
when "customer.subscription.updated"
|
|
608
|
-
stripe_on_subscription_updated(ctx, event)
|
|
609
|
-
when "customer.subscription.deleted"
|
|
610
|
-
stripe_on_subscription_deleted(ctx, event)
|
|
611
|
-
end
|
|
612
|
-
config = ctx.context.options.plugins.find { |plugin| plugin.id == "stripe" }&.options || {}
|
|
613
|
-
config[:on_event]&.call(event)
|
|
67
|
+
BetterAuth::Stripe::Hooks.handle_event(ctx, event)
|
|
614
68
|
end
|
|
615
69
|
|
|
616
70
|
def stripe_on_checkout_completed(ctx, event)
|
|
617
|
-
|
|
618
|
-
object = normalize_hash(event.dig(:data, :object) || {})
|
|
619
|
-
return if object[:mode] == "setup" || !config.dig(:subscription, :enabled)
|
|
620
|
-
|
|
621
|
-
stripe_subscription = stripe_client(config).subscriptions.retrieve(object[:subscription])
|
|
622
|
-
resolved = stripe_resolve_plan_item(config, stripe_subscription)
|
|
623
|
-
return unless resolved
|
|
624
|
-
|
|
625
|
-
item = resolved.fetch(:item)
|
|
626
|
-
plan = resolved.fetch(:plan)
|
|
627
|
-
metadata = normalize_hash(object[:metadata] || {})
|
|
628
|
-
reference_id = object[:client_reference_id] || metadata[:reference_id]
|
|
629
|
-
subscription_id = metadata[:subscription_id]
|
|
630
|
-
return unless plan && reference_id && subscription_id
|
|
631
|
-
|
|
632
|
-
update = stripe_subscription_state(stripe_subscription, include_status: true).merge(
|
|
633
|
-
plan: plan[:name].to_s.downcase,
|
|
634
|
-
stripeSubscriptionId: object[:subscription],
|
|
635
|
-
seats: stripe_resolve_quantity(stripe_subscription, item, plan),
|
|
636
|
-
trialStart: stripe_time(stripe_fetch(stripe_subscription, "trial_start")),
|
|
637
|
-
trialEnd: stripe_time(stripe_fetch(stripe_subscription, "trial_end"))
|
|
638
|
-
).compact
|
|
639
|
-
db_subscription = ctx.context.adapter.update(model: "subscription", where: [{field: "id", value: subscription_id}], update: update)
|
|
640
|
-
plan.dig(:free_trial, :on_trial_start)&.call(db_subscription) if db_subscription && update[:trialStart]
|
|
641
|
-
callback = config.dig(:subscription, :on_subscription_complete)
|
|
642
|
-
callback&.call({event: event, subscription: db_subscription, stripeSubscription: stripe_subscription, stripe_subscription: stripe_subscription, plan: plan}, ctx)
|
|
71
|
+
BetterAuth::Stripe::Hooks.on_checkout_completed(ctx, event)
|
|
643
72
|
end
|
|
644
73
|
|
|
645
74
|
def stripe_on_subscription_created(ctx, event)
|
|
646
|
-
|
|
647
|
-
object = normalize_hash(event.dig(:data, :object) || {})
|
|
648
|
-
customer_id = object[:customer].to_s
|
|
649
|
-
return if customer_id.empty?
|
|
650
|
-
metadata = normalize_hash(object[:metadata] || {})
|
|
651
|
-
existing = if metadata[:subscription_id]
|
|
652
|
-
ctx.context.adapter.find_one(model: "subscription", where: [{field: "id", value: metadata[:subscription_id]}])
|
|
653
|
-
else
|
|
654
|
-
ctx.context.adapter.find_one(model: "subscription", where: [{field: "stripeSubscriptionId", value: object[:id]}])
|
|
655
|
-
end
|
|
656
|
-
return if existing
|
|
657
|
-
|
|
658
|
-
reference = stripe_reference_by_customer(ctx, config, customer_id) || ((metadata[:reference_id] && metadata[:plan]) ? {reference_id: metadata[:reference_id], customer_type: metadata[:customer_type] || "user"} : nil)
|
|
659
|
-
return unless reference
|
|
660
|
-
resolved = stripe_resolve_plan_item(config, object)
|
|
661
|
-
return unless resolved
|
|
662
|
-
item = resolved.fetch(:item)
|
|
663
|
-
plan = resolved[:plan] || (metadata[:plan] && stripe_plan_by_name(config, metadata[:plan]))
|
|
664
|
-
return unless plan
|
|
665
|
-
|
|
666
|
-
created = ctx.context.adapter.create(
|
|
667
|
-
model: "subscription",
|
|
668
|
-
data: stripe_subscription_state(object, include_status: true).merge(
|
|
669
|
-
referenceId: reference.fetch(:reference_id),
|
|
670
|
-
stripeCustomerId: customer_id,
|
|
671
|
-
stripeSubscriptionId: object[:id],
|
|
672
|
-
plan: plan[:name].to_s.downcase,
|
|
673
|
-
seats: stripe_resolve_quantity(object, item, plan),
|
|
674
|
-
limits: plan[:limits]
|
|
675
|
-
).compact
|
|
676
|
-
)
|
|
677
|
-
config.dig(:subscription, :on_subscription_created)&.call({event: event, subscription: created, stripeSubscription: object, stripe_subscription: object, plan: plan})
|
|
75
|
+
BetterAuth::Stripe::Hooks.on_subscription_created(ctx, event)
|
|
678
76
|
end
|
|
679
77
|
|
|
680
78
|
def stripe_on_subscription_updated(ctx, event)
|
|
681
|
-
|
|
682
|
-
object = normalize_hash(event.dig(:data, :object) || {})
|
|
683
|
-
resolved = stripe_resolve_plan_item(config, object)
|
|
684
|
-
return unless resolved
|
|
685
|
-
item = resolved.fetch(:item)
|
|
686
|
-
|
|
687
|
-
metadata = normalize_hash(object[:metadata] || {})
|
|
688
|
-
subscription = if metadata[:subscription_id]
|
|
689
|
-
ctx.context.adapter.find_one(model: "subscription", where: [{field: "id", value: metadata[:subscription_id]}])
|
|
690
|
-
else
|
|
691
|
-
ctx.context.adapter.find_one(model: "subscription", where: [{field: "stripeSubscriptionId", value: object[:id]}])
|
|
692
|
-
end
|
|
693
|
-
unless subscription
|
|
694
|
-
candidates = ctx.context.adapter.find_many(model: "subscription", where: [{field: "stripeCustomerId", value: object[:customer]}])
|
|
695
|
-
subscription = if candidates.length > 1
|
|
696
|
-
candidates.find { |entry| stripe_active_or_trialing?(entry) }
|
|
697
|
-
else
|
|
698
|
-
candidates.first
|
|
699
|
-
end
|
|
700
|
-
end
|
|
701
|
-
return unless subscription
|
|
702
|
-
|
|
703
|
-
plan = resolved[:plan]
|
|
704
|
-
was_pending = stripe_pending_cancel?(subscription)
|
|
705
|
-
update = stripe_subscription_state(object, include_status: true, compact: false).merge(
|
|
706
|
-
stripeSubscriptionId: object[:id],
|
|
707
|
-
seats: stripe_resolve_quantity(object, item, plan)
|
|
708
|
-
)
|
|
709
|
-
update[:plan] = plan[:name].to_s.downcase if plan
|
|
710
|
-
update[:limits] = plan[:limits] if plan&.key?(:limits)
|
|
711
|
-
updated = ctx.context.adapter.update(model: "subscription", where: [{field: "id", value: subscription.fetch("id")}], update: update)
|
|
712
|
-
if object[:status] == "active" && stripe_stripe_pending_cancel?(object) && !was_pending
|
|
713
|
-
config.dig(:subscription, :on_subscription_cancel)&.call({event: event, subscription: subscription, stripeSubscription: object, stripe_subscription: object, cancellationDetails: object[:cancellation_details], cancellation_details: object[:cancellation_details]})
|
|
714
|
-
end
|
|
715
|
-
config.dig(:subscription, :on_subscription_update)&.call({event: event, subscription: updated || subscription})
|
|
716
|
-
if plan && subscription["status"] == "trialing" && object[:status] == "active"
|
|
717
|
-
plan.dig(:free_trial, :on_trial_end)&.call({subscription: subscription}, ctx)
|
|
718
|
-
end
|
|
719
|
-
if plan && subscription["status"] == "trialing" && object[:status] == "incomplete_expired"
|
|
720
|
-
plan.dig(:free_trial, :on_trial_expired)&.call(subscription, ctx)
|
|
721
|
-
end
|
|
79
|
+
BetterAuth::Stripe::Hooks.on_subscription_updated(ctx, event)
|
|
722
80
|
end
|
|
723
81
|
|
|
724
82
|
def stripe_on_subscription_deleted(ctx, event)
|
|
725
|
-
|
|
726
|
-
object = normalize_hash(event.dig(:data, :object) || {})
|
|
727
|
-
subscription = ctx.context.adapter.find_one(model: "subscription", where: [{field: "stripeSubscriptionId", value: object[:id]}])
|
|
728
|
-
return unless subscription
|
|
729
|
-
|
|
730
|
-
ctx.context.adapter.update(model: "subscription", where: [{field: "id", value: subscription.fetch("id")}], update: stripe_subscription_state(object, include_status: false, compact: false).merge(status: "canceled", stripeScheduleId: nil))
|
|
731
|
-
config.dig(:subscription, :on_subscription_deleted)&.call({event: event, subscription: subscription, stripeSubscription: object, stripe_subscription: object})
|
|
83
|
+
BetterAuth::Stripe::Hooks.on_subscription_deleted(ctx, event)
|
|
732
84
|
end
|
|
733
85
|
|
|
734
86
|
def stripe_create_customer(config, ctx, user, metadata = nil)
|
|
@@ -801,86 +153,51 @@ module BetterAuth
|
|
|
801
153
|
end
|
|
802
154
|
|
|
803
155
|
def stripe_id(object)
|
|
804
|
-
|
|
156
|
+
BetterAuth::Stripe::Utils.id(object)
|
|
805
157
|
end
|
|
806
158
|
|
|
807
159
|
def stripe_fetch(object, key)
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
object[key] || object[key.to_sym]
|
|
160
|
+
BetterAuth::Stripe::Utils.fetch(object, key)
|
|
811
161
|
end
|
|
812
162
|
|
|
813
163
|
def stripe_time(value)
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
Time.at(value.to_i)
|
|
164
|
+
BetterAuth::Stripe::Utils.time(value)
|
|
817
165
|
end
|
|
818
166
|
|
|
819
167
|
def stripe_subscription_options(config)
|
|
820
|
-
|
|
168
|
+
BetterAuth::Stripe::Utils.subscription_options(config)
|
|
821
169
|
end
|
|
822
170
|
|
|
823
171
|
def stripe_plans(config)
|
|
824
|
-
plans
|
|
825
|
-
plans = plans.call if plans.respond_to?(:call)
|
|
826
|
-
Array(plans).map do |plan|
|
|
827
|
-
normalized = normalize_hash(plan)
|
|
828
|
-
limits = stripe_fetch(plan, "limits")
|
|
829
|
-
normalized[:limits] = limits if limits
|
|
830
|
-
normalized
|
|
831
|
-
end
|
|
172
|
+
BetterAuth::Stripe::Utils.plans(config)
|
|
832
173
|
end
|
|
833
174
|
|
|
834
175
|
def stripe_plan_by_name(config, name)
|
|
835
|
-
|
|
176
|
+
BetterAuth::Stripe::Utils.plan_by_name(config, name)
|
|
836
177
|
end
|
|
837
178
|
|
|
838
179
|
def stripe_plan_by_price_info(config, price_id, lookup_key = nil)
|
|
839
|
-
|
|
840
|
-
plan[:price_id] == price_id || plan[:annual_discount_price_id] == price_id || (lookup_key && (plan[:lookup_key] == lookup_key || plan[:annual_discount_lookup_key] == lookup_key))
|
|
841
|
-
end
|
|
180
|
+
BetterAuth::Stripe::Utils.plan_by_price_info(config, price_id, lookup_key)
|
|
842
181
|
end
|
|
843
182
|
|
|
844
183
|
def stripe_price_id(config, plan, annual = false)
|
|
845
|
-
|
|
184
|
+
BetterAuth::Stripe::Utils.price_id(config, plan, annual)
|
|
846
185
|
end
|
|
847
186
|
|
|
848
187
|
def stripe_resolve_lookup(config, lookup_key)
|
|
849
|
-
|
|
850
|
-
return nil unless stripe_client(config).respond_to?(:prices)
|
|
851
|
-
|
|
852
|
-
prices = stripe_client(config).prices.list(lookup_keys: [lookup_key], active: true, limit: 1)
|
|
853
|
-
stripe_fetch(Array(stripe_fetch(prices, "data")).first || {}, "id")
|
|
188
|
+
BetterAuth::Stripe::Utils.resolve_lookup(config, lookup_key)
|
|
854
189
|
end
|
|
855
190
|
|
|
856
191
|
def stripe_reference_id!(ctx, session, customer_type, explicit_reference_id, config)
|
|
857
|
-
|
|
858
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("ORGANIZATION_SUBSCRIPTION_NOT_ENABLED")) unless config.dig(:organization, :enabled)
|
|
859
|
-
|
|
860
|
-
reference_id = explicit_reference_id || session.fetch(:session)["activeOrganizationId"]
|
|
861
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("ORGANIZATION_REFERENCE_ID_REQUIRED")) if reference_id.to_s.empty?
|
|
862
|
-
reference_id
|
|
192
|
+
BetterAuth::Stripe::Middleware.reference_id!(ctx, session, customer_type, explicit_reference_id, config)
|
|
863
193
|
end
|
|
864
194
|
|
|
865
195
|
def stripe_authorize_reference!(ctx, session, reference_id, action, customer_type, subscription_options, explicit: false)
|
|
866
|
-
|
|
867
|
-
if customer_type == "organization"
|
|
868
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("AUTHORIZE_REFERENCE_REQUIRED")) unless callback
|
|
869
|
-
elsif !explicit || reference_id == session.fetch(:user).fetch("id")
|
|
870
|
-
return
|
|
871
|
-
elsif !callback
|
|
872
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("REFERENCE_ID_NOT_ALLOWED"))
|
|
873
|
-
end
|
|
874
|
-
|
|
875
|
-
allowed = callback.call({user: session.fetch(:user), session: session.fetch(:session), referenceId: reference_id, reference_id: reference_id, action: action}, ctx)
|
|
876
|
-
raise APIError.new("UNAUTHORIZED", message: STRIPE_ERROR_CODES.fetch("UNAUTHORIZED")) unless allowed
|
|
196
|
+
BetterAuth::Stripe::Middleware.authorize_reference!(ctx, session, reference_id, action, customer_type, subscription_options, explicit: explicit)
|
|
877
197
|
end
|
|
878
198
|
|
|
879
199
|
def stripe_customer_type!(source)
|
|
880
|
-
customer_type
|
|
881
|
-
raise APIError.new("BAD_REQUEST", message: STRIPE_ERROR_CODES.fetch("INVALID_CUSTOMER_TYPE")) unless %w[user organization].include?(customer_type)
|
|
882
|
-
|
|
883
|
-
customer_type
|
|
200
|
+
BetterAuth::Stripe::Middleware.customer_type!(source)
|
|
884
201
|
end
|
|
885
202
|
|
|
886
203
|
def stripe_find_user_customer(config, email)
|
|
@@ -932,59 +249,39 @@ module BetterAuth
|
|
|
932
249
|
end
|
|
933
250
|
|
|
934
251
|
def stripe_active_or_trialing?(subscription)
|
|
935
|
-
|
|
252
|
+
BetterAuth::Stripe::Utils.active_or_trialing?(subscription)
|
|
936
253
|
end
|
|
937
254
|
|
|
938
255
|
def stripe_pending_cancel?(subscription)
|
|
939
|
-
|
|
256
|
+
BetterAuth::Stripe::Utils.pending_cancel?(subscription)
|
|
940
257
|
end
|
|
941
258
|
|
|
942
259
|
def stripe_stripe_pending_cancel?(subscription)
|
|
943
|
-
|
|
260
|
+
BetterAuth::Stripe::Utils.stripe_pending_cancel?(subscription)
|
|
944
261
|
end
|
|
945
262
|
|
|
946
263
|
def stripe_subscription_item(subscription)
|
|
947
|
-
|
|
264
|
+
BetterAuth::Stripe::Utils.subscription_item(subscription)
|
|
948
265
|
end
|
|
949
266
|
|
|
950
267
|
def stripe_resolve_plan_item(config, subscription)
|
|
951
|
-
|
|
952
|
-
first = items.first
|
|
953
|
-
return nil unless first
|
|
954
|
-
|
|
955
|
-
items.each do |item|
|
|
956
|
-
price = stripe_fetch(item, "price") || {}
|
|
957
|
-
plan = stripe_plan_by_price_info(config, stripe_fetch(price, "id"), stripe_fetch(price, "lookup_key"))
|
|
958
|
-
return {item: item, plan: plan} if plan
|
|
959
|
-
end
|
|
960
|
-
{item: first, plan: nil} if items.length == 1
|
|
268
|
+
BetterAuth::Stripe::Utils.resolve_plan_item(config, subscription)
|
|
961
269
|
end
|
|
962
270
|
|
|
963
271
|
def stripe_resolve_quantity(subscription, plan_item, plan = nil)
|
|
964
|
-
|
|
965
|
-
seat_price_id = plan && plan[:seat_price_id]
|
|
966
|
-
seat_item = seat_price_id && items.find { |item| stripe_fetch(stripe_fetch(item, "price") || {}, "id") == seat_price_id }
|
|
967
|
-
stripe_fetch(seat_item || plan_item, "quantity") || 1
|
|
272
|
+
BetterAuth::Stripe::Utils.resolve_quantity(subscription, plan_item, plan)
|
|
968
273
|
end
|
|
969
274
|
|
|
970
275
|
def stripe_line_item(config, price_id, quantity)
|
|
971
|
-
|
|
972
|
-
item[:quantity] = quantity unless stripe_metered_price?(config, price_id)
|
|
973
|
-
item
|
|
276
|
+
BetterAuth::Stripe::Utils.line_item(config, price_id, quantity)
|
|
974
277
|
end
|
|
975
278
|
|
|
976
279
|
def stripe_checkout_line_items(config, plan, price_id, quantity, auto_managed_seats, seat_only_plan)
|
|
977
|
-
|
|
978
|
-
items << stripe_line_item(config, price_id, auto_managed_seats ? 1 : quantity) unless seat_only_plan
|
|
979
|
-
items << {price: plan[:seat_price_id], quantity: quantity} if auto_managed_seats && plan[:seat_price_id]
|
|
980
|
-
items.concat(stripe_plan_line_items(plan))
|
|
981
|
-
items
|
|
280
|
+
BetterAuth::Stripe::Utils.checkout_line_items(config, plan, price_id, quantity, auto_managed_seats, seat_only_plan)
|
|
982
281
|
end
|
|
983
282
|
|
|
984
283
|
def stripe_plan_line_items(plan)
|
|
985
|
-
|
|
986
|
-
item.is_a?(Hash) ? normalize_hash(item) : item
|
|
987
|
-
end
|
|
284
|
+
BetterAuth::Stripe::Utils.plan_line_items(plan)
|
|
988
285
|
end
|
|
989
286
|
|
|
990
287
|
def stripe_schedule_plan_change(ctx, config, active_stripe, db_subscription, plan, price_id, quantity, seat_only_plan, body)
|
|
@@ -1060,9 +357,7 @@ module BetterAuth
|
|
|
1060
357
|
end
|
|
1061
358
|
|
|
1062
359
|
def stripe_direct_subscription_update?(old_plan, plan, auto_managed_seats)
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
stripe_plan_line_items(old_plan || {}).map { |item| item[:price] } != stripe_plan_line_items(plan).map { |item| item[:price] }
|
|
360
|
+
BetterAuth::Stripe::Utils.direct_subscription_update?(old_plan, plan, auto_managed_seats)
|
|
1066
361
|
end
|
|
1067
362
|
|
|
1068
363
|
def stripe_update_active_subscription_items(ctx, config, active_stripe, db_subscription, old_plan, plan, price_id, quantity, seat_only_plan, body)
|
|
@@ -1100,131 +395,47 @@ module BetterAuth
|
|
|
1100
395
|
end
|
|
1101
396
|
|
|
1102
397
|
def stripe_sync_organization_seats(config, data, ctx)
|
|
1103
|
-
|
|
1104
|
-
return unless config.dig(:subscription, :enabled) && organization && organization["stripeCustomerId"]
|
|
1105
|
-
|
|
1106
|
-
member_count = ctx.context.adapter.count(model: "member", where: [{field: "organizationId", value: organization.fetch("id")}])
|
|
1107
|
-
seat_plans = stripe_plans(config).select { |plan| plan[:seat_price_id] }
|
|
1108
|
-
return if seat_plans.empty?
|
|
1109
|
-
|
|
1110
|
-
subscription = ctx.context.adapter.find_many(model: "subscription", where: [{field: "referenceId", value: organization.fetch("id")}]).find { |entry| stripe_active_or_trialing?(entry) }
|
|
1111
|
-
return unless subscription && subscription["stripeSubscriptionId"]
|
|
1112
|
-
|
|
1113
|
-
plan = seat_plans.find { |entry| entry[:name].to_s.downcase == subscription["plan"].to_s.downcase }
|
|
1114
|
-
return unless plan
|
|
1115
|
-
|
|
1116
|
-
stripe_subscription = stripe_client(config).subscriptions.retrieve(subscription["stripeSubscriptionId"])
|
|
1117
|
-
return unless stripe_active_or_trialing?(stripe_subscription)
|
|
1118
|
-
|
|
1119
|
-
items = Array(stripe_fetch(stripe_fetch(stripe_subscription, "items") || {}, "data"))
|
|
1120
|
-
seat_item = items.find { |item| stripe_fetch(stripe_fetch(item, "price") || {}, "id") == plan[:seat_price_id] }
|
|
1121
|
-
return if seat_item && stripe_fetch(seat_item, "quantity").to_i == member_count.to_i
|
|
1122
|
-
|
|
1123
|
-
update_items = if seat_item
|
|
1124
|
-
[{id: stripe_fetch(seat_item, "id"), quantity: member_count}]
|
|
1125
|
-
else
|
|
1126
|
-
[{price: plan[:seat_price_id], quantity: member_count}]
|
|
1127
|
-
end
|
|
1128
|
-
stripe_client(config).subscriptions.update(subscription["stripeSubscriptionId"], items: update_items, proration_behavior: plan[:proration_behavior] || "create_prorations")
|
|
1129
|
-
ctx.context.adapter.update(model: "subscription", where: [{field: "id", value: subscription.fetch("id")}], update: {seats: member_count})
|
|
1130
|
-
rescue
|
|
1131
|
-
nil
|
|
398
|
+
BetterAuth::Stripe::OrganizationHooks.sync_seats(config, data, ctx)
|
|
1132
399
|
end
|
|
1133
400
|
|
|
1134
401
|
def stripe_metered_price?(config, price_id, lookup_key = nil)
|
|
1135
|
-
|
|
1136
|
-
recurring = stripe_fetch(price || {}, "recurring") || {}
|
|
1137
|
-
stripe_fetch(recurring, "usage_type") == "metered"
|
|
402
|
+
BetterAuth::Stripe::Utils.metered_price?(config, price_id, lookup_key)
|
|
1138
403
|
end
|
|
1139
404
|
|
|
1140
405
|
def stripe_resolve_stripe_price(config, price_id, lookup_key = nil)
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
prices = stripe_client(config).prices
|
|
1144
|
-
if lookup_key
|
|
1145
|
-
result = prices.list(lookup_keys: [lookup_key], active: true, limit: 1)
|
|
1146
|
-
Array(stripe_fetch(result, "data")).first
|
|
1147
|
-
elsif price_id && prices.respond_to?(:retrieve)
|
|
1148
|
-
prices.retrieve(price_id)
|
|
1149
|
-
end
|
|
1150
|
-
rescue
|
|
1151
|
-
nil
|
|
406
|
+
BetterAuth::Stripe::Utils.resolve_stripe_price(config, price_id, lookup_key)
|
|
1152
407
|
end
|
|
1153
408
|
|
|
1154
409
|
def stripe_subscription_state(subscription, include_status: true, compact: true)
|
|
1155
|
-
|
|
1156
|
-
price = stripe_fetch(item || {}, "price") || {}
|
|
1157
|
-
recurring = stripe_fetch(price, "recurring") || {}
|
|
1158
|
-
state = {
|
|
1159
|
-
periodStart: stripe_time(stripe_fetch(item || subscription, "current_period_start")),
|
|
1160
|
-
periodEnd: stripe_time(stripe_fetch(item || subscription, "current_period_end")),
|
|
1161
|
-
cancelAtPeriodEnd: stripe_fetch(subscription, "cancel_at_period_end"),
|
|
1162
|
-
cancelAt: stripe_time(stripe_fetch(subscription, "cancel_at")),
|
|
1163
|
-
canceledAt: stripe_time(stripe_fetch(subscription, "canceled_at")),
|
|
1164
|
-
endedAt: stripe_time(stripe_fetch(subscription, "ended_at")),
|
|
1165
|
-
trialStart: stripe_time(stripe_fetch(subscription, "trial_start")),
|
|
1166
|
-
trialEnd: stripe_time(stripe_fetch(subscription, "trial_end")),
|
|
1167
|
-
billingInterval: stripe_fetch(recurring, "interval"),
|
|
1168
|
-
stripeScheduleId: stripe_schedule_id(subscription)
|
|
1169
|
-
}
|
|
1170
|
-
state[:status] = stripe_fetch(subscription, "status") if include_status
|
|
1171
|
-
compact ? state.compact : state
|
|
410
|
+
BetterAuth::Stripe::Utils.subscription_state(subscription, include_status: include_status, compact: compact)
|
|
1172
411
|
end
|
|
1173
412
|
|
|
1174
413
|
def stripe_schedule_id(subscription)
|
|
1175
|
-
|
|
1176
|
-
return nil if schedule.nil?
|
|
1177
|
-
return schedule if schedule.is_a?(String)
|
|
1178
|
-
|
|
1179
|
-
stripe_id(schedule) || schedule.to_s
|
|
414
|
+
BetterAuth::Stripe::Utils.schedule_id(subscription)
|
|
1180
415
|
end
|
|
1181
416
|
|
|
1182
417
|
def stripe_reference_by_customer(ctx, config, customer_id)
|
|
1183
|
-
|
|
1184
|
-
org = ctx.context.adapter.find_one(model: "organization", where: [{field: "stripeCustomerId", value: customer_id}])
|
|
1185
|
-
return {customer_type: "organization", reference_id: org.fetch("id")} if org
|
|
1186
|
-
end
|
|
1187
|
-
user = ctx.context.adapter.find_one(model: "user", where: [{field: "stripeCustomerId", value: customer_id}])
|
|
1188
|
-
return {customer_type: "user", reference_id: user.fetch("id")} if user
|
|
1189
|
-
|
|
1190
|
-
nil
|
|
418
|
+
BetterAuth::Stripe::Middleware.reference_by_customer(ctx, config, customer_id)
|
|
1191
419
|
end
|
|
1192
420
|
|
|
1193
421
|
def stripe_metadata(internal, *user_metadata)
|
|
1194
|
-
user_metadata
|
|
1195
|
-
.reduce({}) do |acc, entry|
|
|
1196
|
-
next acc unless entry.respond_to?(:each)
|
|
1197
|
-
|
|
1198
|
-
acc.merge(entry.each_with_object({}) do |(key, value), result|
|
|
1199
|
-
metadata_key = stripe_metadata_key(key)
|
|
1200
|
-
result[metadata_key] = value unless STRIPE_UNSAFE_METADATA_KEYS.include?(metadata_key)
|
|
1201
|
-
end)
|
|
1202
|
-
end
|
|
1203
|
-
.merge(internal.transform_keys { |key| stripe_metadata_key(key) })
|
|
422
|
+
BetterAuth::Stripe::Metadata.merge(internal, *user_metadata)
|
|
1204
423
|
end
|
|
1205
424
|
|
|
1206
425
|
def stripe_customer_metadata_set(internal_fields, *user_metadata)
|
|
1207
|
-
|
|
426
|
+
BetterAuth::Stripe::Metadata.customer_set(internal_fields, *user_metadata)
|
|
1208
427
|
end
|
|
1209
428
|
|
|
1210
429
|
def stripe_customer_metadata_get(metadata)
|
|
1211
|
-
|
|
1212
|
-
userId: stripe_metadata_fetch(metadata, "userId"),
|
|
1213
|
-
organizationId: stripe_metadata_fetch(metadata, "organizationId"),
|
|
1214
|
-
customerType: stripe_metadata_fetch(metadata, "customerType")
|
|
1215
|
-
}
|
|
430
|
+
BetterAuth::Stripe::Metadata.customer_get(metadata)
|
|
1216
431
|
end
|
|
1217
432
|
|
|
1218
433
|
def stripe_subscription_metadata_set(internal_fields, *user_metadata)
|
|
1219
|
-
|
|
434
|
+
BetterAuth::Stripe::Metadata.subscription_set(internal_fields, *user_metadata)
|
|
1220
435
|
end
|
|
1221
436
|
|
|
1222
437
|
def stripe_subscription_metadata_get(metadata)
|
|
1223
|
-
|
|
1224
|
-
userId: stripe_metadata_fetch(metadata, "userId"),
|
|
1225
|
-
subscriptionId: stripe_metadata_fetch(metadata, "subscriptionId"),
|
|
1226
|
-
referenceId: stripe_metadata_fetch(metadata, "referenceId")
|
|
1227
|
-
}
|
|
438
|
+
BetterAuth::Stripe::Metadata.subscription_get(metadata)
|
|
1228
439
|
end
|
|
1229
440
|
|
|
1230
441
|
def stripe_notify_customer_created(config, customer, user, ctx)
|
|
@@ -1239,54 +450,31 @@ module BetterAuth
|
|
|
1239
450
|
end
|
|
1240
451
|
|
|
1241
452
|
def stripe_metadata_key(key)
|
|
1242
|
-
|
|
1243
|
-
when :user_id then "userId"
|
|
1244
|
-
when :organization_id then "organizationId"
|
|
1245
|
-
when :customer_type then "customerType"
|
|
1246
|
-
when :subscription_id then "subscriptionId"
|
|
1247
|
-
when :reference_id then "referenceId"
|
|
1248
|
-
else
|
|
1249
|
-
key.to_s
|
|
1250
|
-
end
|
|
453
|
+
BetterAuth::Stripe::Metadata.metadata_key(key)
|
|
1251
454
|
end
|
|
1252
455
|
|
|
1253
456
|
def stripe_metadata_fetch(metadata, key)
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
metadata[key] || metadata[key.to_sym] || metadata[normalize_key(key)] || metadata[normalize_key(key).to_s]
|
|
457
|
+
BetterAuth::Stripe::Metadata.metadata_fetch(metadata, key)
|
|
1257
458
|
end
|
|
1258
459
|
|
|
1259
460
|
def stripe_deep_merge(base, override)
|
|
1260
|
-
|
|
1261
|
-
if old.is_a?(Hash) && new.is_a?(Hash)
|
|
1262
|
-
stripe_deep_merge(old, new)
|
|
1263
|
-
else
|
|
1264
|
-
new
|
|
1265
|
-
end
|
|
1266
|
-
end
|
|
461
|
+
BetterAuth::Stripe::Metadata.deep_merge(base, override)
|
|
1267
462
|
end
|
|
1268
463
|
|
|
1269
464
|
def stripe_redirect?(body)
|
|
1270
|
-
body
|
|
465
|
+
BetterAuth::Stripe::Utils.redirect?(body)
|
|
1271
466
|
end
|
|
1272
467
|
|
|
1273
468
|
def stripe_stringify_keys(value)
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
value.each_with_object({}) do |(key, object), result|
|
|
1277
|
-
result[key.to_s] = object
|
|
1278
|
-
result[key.to_sym] = object
|
|
1279
|
-
end
|
|
469
|
+
BetterAuth::Stripe::Metadata.stringify_keys(value)
|
|
1280
470
|
end
|
|
1281
471
|
|
|
1282
472
|
def stripe_url(ctx, url)
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
"#{ctx.context.base_url}#{url.to_s.start_with?("/") ? url : "/#{url}"}"
|
|
473
|
+
BetterAuth::Stripe::Utils.url(ctx, url)
|
|
1286
474
|
end
|
|
1287
475
|
|
|
1288
476
|
def stripe_escape_search(value)
|
|
1289
|
-
|
|
477
|
+
BetterAuth::Stripe::Utils.escape_search(value)
|
|
1290
478
|
end
|
|
1291
479
|
end
|
|
1292
480
|
end
|