better_auth-stripe 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +2 -0
- data/lib/better_auth/plugins/stripe.rb +75 -19
- data/lib/better_auth/stripe/error_codes.rb +2 -0
- data/lib/better_auth/stripe/hooks.rb +6 -0
- data/lib/better_auth/stripe/metadata.rb +12 -1
- data/lib/better_auth/stripe/middleware.rb +22 -0
- data/lib/better_auth/stripe/routes/cancel_subscription.rb +1 -1
- data/lib/better_auth/stripe/routes/cancel_subscription_callback.rb +2 -1
- data/lib/better_auth/stripe/routes/create_billing_portal.rb +1 -1
- data/lib/better_auth/stripe/routes/list_active_subscriptions.rb +1 -1
- data/lib/better_auth/stripe/routes/restore_subscription.rb +5 -3
- data/lib/better_auth/stripe/routes/stripe_webhook.rb +8 -3
- data/lib/better_auth/stripe/routes/subscription_success.rb +3 -2
- data/lib/better_auth/stripe/routes/upgrade_subscription.rb +2 -2
- data/lib/better_auth/stripe/utils.rb +6 -1
- data/lib/better_auth/stripe/version.rb +1 -1
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: db7335d48c0ece8f97dcb0d6ef1c29ceec461f643e5d11b3a8e3b902dfad185b
|
|
4
|
+
data.tar.gz: dee15dc386642b8cfeb5d447a1787a55b4c2333281e22a9dc0984020f8e510c1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1a9c1161420eabefd7062267e1051e932eb2a2c3a69a3dc4e51a199a778517bc08c6390a2fa6f1b09f9c3ff479ccded8570639f2a5959da737788912b7d50eee
|
|
7
|
+
data.tar.gz: 8d44b61fea832aac0e29eb73414fe612fb86ae4f57b322689d5847029fb957e176e588505b922bf28486c66c7b2dddb8b2410ceaeb763e8b1ff224b043bdef63
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.10.0] - 2026-05-21
|
|
6
|
+
|
|
7
|
+
- Improved Stripe metadata, utility behavior, adapter coverage, and rate-limit coverage.
|
|
8
|
+
|
|
5
9
|
## [0.7.0] - 2026-05-05
|
|
6
10
|
|
|
7
11
|
- Changed Stripe webhooks to reject requests when the configured Stripe client does not expose `webhooks.construct_event_async` or `webhooks.construct_event`, preventing unverified payload processing.
|
data/README.md
CHANGED
|
@@ -51,3 +51,5 @@ Configure plans under `subscription: { enabled: true, plans: [...] }`. Ruby acce
|
|
|
51
51
|
For organization subscriptions, `seat_price_id` enables upstream-style seat billing. Checkout sends the base plan item with quantity `1` and a separate seat item whose quantity is the current organization member count. Webhooks read the seat item quantity back into the local `subscription.seats` field.
|
|
52
52
|
|
|
53
53
|
`scheduleAtPeriodEnd` / `schedule_at_period_end` on `/subscription/upgrade` creates a Stripe subscription schedule for active subscriptions, stores `stripeScheduleId`, and returns the configured `returnUrl` instead of opening the billing portal immediately.
|
|
54
|
+
|
|
55
|
+
Ruby also routes subscription-cancel billing portal returns through `/subscription/cancel/callback` so a missed webhook can still sync the local cancellation fields before redirecting to the configured callback URL.
|
|
@@ -284,28 +284,78 @@ module BetterAuth
|
|
|
284
284
|
BetterAuth::Stripe::Utils.plan_line_items(plan)
|
|
285
285
|
end
|
|
286
286
|
|
|
287
|
+
def stripe_line_item_delta(old_plan, plan)
|
|
288
|
+
delta = Hash.new(0)
|
|
289
|
+
stripe_plan_line_items(old_plan || {}).each do |item|
|
|
290
|
+
price = stripe_fetch(item, "price")
|
|
291
|
+
delta[price] -= 1 if price
|
|
292
|
+
end
|
|
293
|
+
stripe_plan_line_items(plan || {}).each do |item|
|
|
294
|
+
price = stripe_fetch(item, "price")
|
|
295
|
+
delta[price] += 1 if price
|
|
296
|
+
end
|
|
297
|
+
delta.delete_if { |_price, count| count == 0 }
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def stripe_remove_quota(line_item_delta)
|
|
301
|
+
line_item_delta.each_with_object({}) do |(price, delta), result|
|
|
302
|
+
result[price] = -delta if delta.negative?
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def stripe_consume_positive_delta(line_item_delta, price)
|
|
307
|
+
delta = line_item_delta[price]
|
|
308
|
+
return unless delta&.positive?
|
|
309
|
+
|
|
310
|
+
(delta == 1) ? line_item_delta.delete(price) : line_item_delta[price] = delta - 1
|
|
311
|
+
end
|
|
312
|
+
|
|
287
313
|
def stripe_schedule_plan_change(ctx, config, active_stripe, db_subscription, plan, price_id, quantity, seat_only_plan, body)
|
|
288
314
|
schedule = stripe_client(config).subscription_schedules.create(from_subscription: stripe_fetch(active_stripe, "id"))
|
|
289
315
|
current_phase = Array(stripe_fetch(schedule, "phases")).first || {}
|
|
290
316
|
current_items = Array(stripe_fetch(current_phase, "items"))
|
|
291
317
|
active_item = stripe_resolve_plan_item(config, active_stripe)&.fetch(:item, nil) || stripe_subscription_item(active_stripe)
|
|
292
318
|
active_price_id = stripe_fetch(stripe_fetch(active_item || {}, "price") || {}, "id")
|
|
293
|
-
|
|
319
|
+
active_price_present = current_items.any? do |item|
|
|
320
|
+
item_price = stripe_fetch(item, "price")
|
|
321
|
+
item_price = stripe_fetch(item_price, "id") if item_price.is_a?(Hash)
|
|
322
|
+
item_price == active_price_id
|
|
323
|
+
end
|
|
324
|
+
old_plan = db_subscription && stripe_plan_by_name(config, db_subscription["plan"])
|
|
325
|
+
line_item_delta = stripe_line_item_delta(old_plan, plan)
|
|
326
|
+
remove_quota = stripe_remove_quota(line_item_delta)
|
|
327
|
+
price_map = {}
|
|
328
|
+
if plan[:seat_price_id] && old_plan && old_plan[:seat_price_id] && old_plan[:seat_price_id] != plan[:seat_price_id]
|
|
329
|
+
price_map[old_plan[:seat_price_id]] = {price: plan[:seat_price_id], quantity: quantity}
|
|
330
|
+
end
|
|
294
331
|
new_items = current_items.filter_map do |item|
|
|
295
332
|
item_price = stripe_fetch(item, "price")
|
|
296
333
|
item_price = stripe_fetch(item_price, "id") if item_price.is_a?(Hash)
|
|
334
|
+
quota = remove_quota[item_price].to_i
|
|
335
|
+
if quota.positive?
|
|
336
|
+
remove_quota[item_price] = quota - 1
|
|
337
|
+
next nil
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
replacement = price_map[item_price]
|
|
341
|
+
if replacement
|
|
342
|
+
next {price: replacement[:price], quantity: replacement[:quantity] || stripe_fetch(item, "quantity")}.compact
|
|
343
|
+
end
|
|
344
|
+
|
|
297
345
|
if item_price == active_price_id
|
|
298
|
-
replaced = true
|
|
299
346
|
next nil if seat_only_plan
|
|
300
347
|
|
|
301
|
-
stripe_line_item(config, price_id, quantity)
|
|
348
|
+
stripe_line_item(config, price_id, plan[:seat_price_id] ? 1 : quantity)
|
|
302
349
|
else
|
|
350
|
+
stripe_consume_positive_delta(line_item_delta, item_price)
|
|
303
351
|
{price: item_price, quantity: stripe_fetch(item, "quantity")}.compact
|
|
304
352
|
end
|
|
305
353
|
end
|
|
306
|
-
new_items << stripe_line_item(config, price_id, quantity) unless
|
|
307
|
-
new_items << {price: plan[:seat_price_id], quantity: quantity} if plan[:seat_price_id]
|
|
308
|
-
|
|
354
|
+
new_items << stripe_line_item(config, price_id, plan[:seat_price_id] ? 1 : quantity) unless active_price_present || seat_only_plan
|
|
355
|
+
new_items << {price: plan[:seat_price_id], quantity: quantity} if plan[:seat_price_id] && !new_items.any? { |item| item[:price] == plan[:seat_price_id] }
|
|
356
|
+
line_item_delta.each do |price, delta|
|
|
357
|
+
delta.times { new_items << {price: price} } if delta.positive?
|
|
358
|
+
end
|
|
309
359
|
|
|
310
360
|
stripe_client(config).subscription_schedules.update(
|
|
311
361
|
stripe_fetch(schedule, "id"),
|
|
@@ -363,26 +413,32 @@ module BetterAuth
|
|
|
363
413
|
def stripe_update_active_subscription_items(ctx, config, active_stripe, db_subscription, old_plan, plan, price_id, quantity, seat_only_plan, body)
|
|
364
414
|
active_item = stripe_resolve_plan_item(config, active_stripe)&.fetch(:item, nil) || stripe_subscription_item(active_stripe)
|
|
365
415
|
active_price_id = stripe_fetch(stripe_fetch(active_item || {}, "price") || {}, "id")
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
416
|
+
line_item_delta = stripe_line_item_delta(old_plan, plan)
|
|
417
|
+
remove_quota = stripe_remove_quota(line_item_delta)
|
|
418
|
+
price_map = {}
|
|
419
|
+
if plan[:seat_price_id] && old_plan && old_plan[:seat_price_id] && old_plan[:seat_price_id] != plan[:seat_price_id]
|
|
420
|
+
price_map[old_plan[:seat_price_id]] = {price: plan[:seat_price_id], quantity: quantity}
|
|
421
|
+
end
|
|
369
422
|
items = []
|
|
370
423
|
Array(stripe_fetch(stripe_fetch(active_stripe, "items") || {}, "data")).each do |item|
|
|
371
424
|
item_price = stripe_fetch(stripe_fetch(item, "price") || {}, "id")
|
|
372
|
-
|
|
425
|
+
quota = remove_quota[item_price].to_i
|
|
426
|
+
if quota.positive?
|
|
427
|
+
remove_quota[item_price] = quota - 1
|
|
428
|
+
items << {id: stripe_fetch(item, "id"), deleted: true}
|
|
429
|
+
elsif price_map[item_price]
|
|
430
|
+
replacement = price_map[item_price]
|
|
431
|
+
items << {id: stripe_fetch(item, "id"), price: replacement[:price], quantity: replacement[:quantity]}
|
|
432
|
+
elsif item_price == active_price_id
|
|
373
433
|
items << stripe_line_item(config, price_id, plan[:seat_price_id] ? 1 : quantity).merge(id: stripe_fetch(item, "id")) unless seat_only_plan
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
elsif old_line_prices.include?(item_price)
|
|
377
|
-
if new_line_prices.include?(item_price)
|
|
378
|
-
new_line_prices.delete_at(new_line_prices.index(item_price))
|
|
379
|
-
else
|
|
380
|
-
items << {id: stripe_fetch(item, "id"), deleted: true}
|
|
381
|
-
end
|
|
434
|
+
else
|
|
435
|
+
stripe_consume_positive_delta(line_item_delta, item_price)
|
|
382
436
|
end
|
|
383
437
|
end
|
|
384
438
|
items << {price: plan[:seat_price_id], quantity: quantity} if plan[:seat_price_id] && !items.any? { |item| item[:price] == plan[:seat_price_id] || item[:id] && item[:price] == plan[:seat_price_id] }
|
|
385
|
-
|
|
439
|
+
line_item_delta.each do |price, delta|
|
|
440
|
+
delta.times { items << {price: price} } if delta.positive?
|
|
441
|
+
end
|
|
386
442
|
stripe_client(config).subscriptions.update(stripe_fetch(active_stripe, "id"), items: items, proration_behavior: plan[:proration_behavior] || "create_prorations")
|
|
387
443
|
if db_subscription
|
|
388
444
|
ctx.context.adapter.update(
|
|
@@ -9,6 +9,7 @@ module BetterAuth
|
|
|
9
9
|
"EMAIL_VERIFICATION_REQUIRED" => "Email verification required",
|
|
10
10
|
"SUBSCRIPTION_NOT_FOUND" => "Subscription not found",
|
|
11
11
|
"SUBSCRIPTION_PLAN_NOT_FOUND" => "Subscription plan not found",
|
|
12
|
+
"FAILED_TO_FETCH_PLANS" => "Failed to fetch plans",
|
|
12
13
|
"ALREADY_SUBSCRIBED_PLAN" => "You're already subscribed to this plan",
|
|
13
14
|
"REFERENCE_ID_NOT_ALLOWED" => "Reference id is not allowed",
|
|
14
15
|
"CUSTOMER_NOT_FOUND" => "Stripe customer not found for this user",
|
|
@@ -21,6 +22,7 @@ module BetterAuth
|
|
|
21
22
|
"ORGANIZATION_REFERENCE_ID_REQUIRED" => "Reference ID is required. Provide referenceId or set activeOrganizationId in session",
|
|
22
23
|
"SUBSCRIPTION_NOT_ACTIVE" => "Subscription is not active",
|
|
23
24
|
"SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION" => "Subscription is not scheduled for cancellation",
|
|
25
|
+
"SUBSCRIPTION_NOT_PENDING_CHANGE" => "Subscription has no pending cancellation or scheduled plan change",
|
|
24
26
|
"STRIPE_SIGNATURE_NOT_FOUND" => "Stripe signature not found",
|
|
25
27
|
"STRIPE_WEBHOOK_SECRET_NOT_FOUND" => "Stripe webhook secret not found",
|
|
26
28
|
"FAILED_TO_CONSTRUCT_STRIPE_EVENT" => "Failed to construct Stripe event",
|
|
@@ -53,6 +53,8 @@ module BetterAuth
|
|
|
53
53
|
|
|
54
54
|
def on_subscription_created(ctx, event)
|
|
55
55
|
config = stripe_config(ctx)
|
|
56
|
+
return unless config.dig(:subscription, :enabled)
|
|
57
|
+
|
|
56
58
|
object = BetterAuth::Plugins.normalize_hash(event.dig(:data, :object) || {})
|
|
57
59
|
customer_id = object[:customer].to_s
|
|
58
60
|
return if customer_id.empty?
|
|
@@ -91,6 +93,8 @@ module BetterAuth
|
|
|
91
93
|
|
|
92
94
|
def on_subscription_updated(ctx, event)
|
|
93
95
|
config = stripe_config(ctx)
|
|
96
|
+
return unless config.dig(:subscription, :enabled)
|
|
97
|
+
|
|
94
98
|
object = BetterAuth::Plugins.normalize_hash(event.dig(:data, :object) || {})
|
|
95
99
|
resolved = BetterAuth::Stripe::Utils.resolve_plan_item(config, object)
|
|
96
100
|
return unless resolved
|
|
@@ -135,6 +139,8 @@ module BetterAuth
|
|
|
135
139
|
|
|
136
140
|
def on_subscription_deleted(ctx, event)
|
|
137
141
|
config = stripe_config(ctx)
|
|
142
|
+
return unless config.dig(:subscription, :enabled)
|
|
143
|
+
|
|
138
144
|
object = BetterAuth::Plugins.normalize_hash(event.dig(:data, :object) || {})
|
|
139
145
|
subscription = ctx.context.adapter.find_one(model: "subscription", where: [{field: "stripeSubscriptionId", value: object[:id]}])
|
|
140
146
|
return unless subscription
|
|
@@ -59,7 +59,18 @@ module BetterAuth
|
|
|
59
59
|
def metadata_fetch(metadata, key)
|
|
60
60
|
return nil unless metadata.respond_to?(:[])
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
candidates = [key, key.to_sym, BetterAuth::Plugins.normalize_key(key), BetterAuth::Plugins.normalize_key(key).to_s]
|
|
63
|
+
if metadata.respond_to?(:key?)
|
|
64
|
+
candidates.each do |candidate|
|
|
65
|
+
return metadata[candidate] if metadata.key?(candidate)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
candidates.each do |candidate|
|
|
70
|
+
value = metadata[candidate]
|
|
71
|
+
return value unless value.nil?
|
|
72
|
+
end
|
|
73
|
+
nil
|
|
63
74
|
end
|
|
64
75
|
|
|
65
76
|
def deep_merge(base, override)
|
|
@@ -48,6 +48,28 @@ module BetterAuth
|
|
|
48
48
|
nil
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
+
def authorized_subscription?(ctx, session, subscription, action, config)
|
|
52
|
+
reference_id = subscription && subscription["referenceId"]
|
|
53
|
+
return false if reference_id.to_s.empty?
|
|
54
|
+
return true if reference_id == session.fetch(:user).fetch("id")
|
|
55
|
+
|
|
56
|
+
subscription_options = BetterAuth::Stripe::Utils.subscription_options(config)
|
|
57
|
+
if config.dig(:organization, :enabled)
|
|
58
|
+
org = ctx.context.adapter.find_one(model: "organization", where: [{field: "id", value: reference_id}])
|
|
59
|
+
if org
|
|
60
|
+
return false unless subscription_options[:authorize_reference]
|
|
61
|
+
|
|
62
|
+
authorize_reference!(ctx, session, reference_id, action, "organization", subscription_options, explicit: true)
|
|
63
|
+
return true
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
authorize_reference!(ctx, session, reference_id, action, "user", subscription_options, explicit: true)
|
|
68
|
+
true
|
|
69
|
+
rescue BetterAuth::APIError
|
|
70
|
+
false
|
|
71
|
+
end
|
|
72
|
+
|
|
51
73
|
def validate_trusted_url!(ctx, value, label)
|
|
52
74
|
return if value.nil? || value.to_s.empty?
|
|
53
75
|
|
|
@@ -7,7 +7,7 @@ module BetterAuth
|
|
|
7
7
|
module_function
|
|
8
8
|
|
|
9
9
|
def endpoint(config)
|
|
10
|
-
BetterAuth::Endpoint.new(path: "/subscription/cancel", method: "POST") do |ctx|
|
|
10
|
+
BetterAuth::Endpoint.new(path: "/subscription/cancel", method: "POST", metadata: {openapi: {operationId: "cancelSubscription"}}) do |ctx|
|
|
11
11
|
session = BetterAuth::Routes.current_session(ctx)
|
|
12
12
|
body = BetterAuth::Plugins.normalize_hash(ctx.body)
|
|
13
13
|
BetterAuth::Stripe::Middleware.validate_trusted_urls!(ctx, body, return_url: "returnUrl")
|
|
@@ -7,7 +7,7 @@ module BetterAuth
|
|
|
7
7
|
module_function
|
|
8
8
|
|
|
9
9
|
def endpoint(config)
|
|
10
|
-
BetterAuth::Endpoint.new(path: "/subscription/cancel/callback", method: "GET") do |ctx|
|
|
10
|
+
BetterAuth::Endpoint.new(path: "/subscription/cancel/callback", method: "GET", metadata: {openapi: {operationId: "cancelSubscriptionCallback"}}) do |ctx|
|
|
11
11
|
query = BetterAuth::Plugins.normalize_hash(ctx.query)
|
|
12
12
|
callback = query[:callback_url] || "/"
|
|
13
13
|
BetterAuth::Stripe::Middleware.validate_trusted_url!(ctx, callback, "callbackURL")
|
|
@@ -18,6 +18,7 @@ module BetterAuth
|
|
|
18
18
|
raise ctx.redirect(BetterAuth::Plugins.stripe_url(ctx, callback)) unless session
|
|
19
19
|
|
|
20
20
|
subscription = ctx.context.adapter.find_one(model: "subscription", where: [{field: "id", value: query[:subscription_id]}])
|
|
21
|
+
raise ctx.redirect(BetterAuth::Plugins.stripe_url(ctx, callback)) if subscription && !BetterAuth::Stripe::Middleware.authorized_subscription?(ctx, session, subscription, "cancel-subscription", config || {})
|
|
21
22
|
if subscription && !BetterAuth::Plugins.stripe_pending_cancel?(subscription) && subscription["stripeCustomerId"]
|
|
22
23
|
current = BetterAuth::Plugins.stripe_active_subscriptions(config || {}, subscription["stripeCustomerId"]).find { |entry| BetterAuth::Plugins.stripe_fetch(entry, "id") == subscription["stripeSubscriptionId"] }
|
|
23
24
|
if current && BetterAuth::Plugins.stripe_stripe_pending_cancel?(current)
|
|
@@ -7,7 +7,7 @@ module BetterAuth
|
|
|
7
7
|
module_function
|
|
8
8
|
|
|
9
9
|
def endpoint(config)
|
|
10
|
-
BetterAuth::Endpoint.new(path: "/subscription/billing-portal", method: "POST") do |ctx|
|
|
10
|
+
BetterAuth::Endpoint.new(path: "/subscription/billing-portal", method: "POST", metadata: {openapi: {operationId: "createBillingPortal"}}) do |ctx|
|
|
11
11
|
session = BetterAuth::Routes.current_session(ctx)
|
|
12
12
|
body = BetterAuth::Plugins.normalize_hash(ctx.body)
|
|
13
13
|
BetterAuth::Stripe::Middleware.validate_trusted_urls!(ctx, body, return_url: "returnUrl")
|
|
@@ -7,7 +7,7 @@ module BetterAuth
|
|
|
7
7
|
module_function
|
|
8
8
|
|
|
9
9
|
def endpoint(config)
|
|
10
|
-
BetterAuth::Endpoint.new(path: "/subscription/list", method: "GET") do |ctx|
|
|
10
|
+
BetterAuth::Endpoint.new(path: "/subscription/list", method: "GET", metadata: {openapi: {operationId: "listActiveSubscriptions"}}) do |ctx|
|
|
11
11
|
session = BetterAuth::Routes.current_session(ctx)
|
|
12
12
|
query = BetterAuth::Plugins.normalize_hash(ctx.query)
|
|
13
13
|
customer_type = BetterAuth::Plugins.stripe_customer_type!(query)
|
|
@@ -7,7 +7,7 @@ module BetterAuth
|
|
|
7
7
|
module_function
|
|
8
8
|
|
|
9
9
|
def endpoint(config)
|
|
10
|
-
BetterAuth::Endpoint.new(path: "/subscription/restore", method: "POST") do |ctx|
|
|
10
|
+
BetterAuth::Endpoint.new(path: "/subscription/restore", method: "POST", metadata: {openapi: {operationId: "restoreSubscription"}}) do |ctx|
|
|
11
11
|
session = BetterAuth::Routes.current_session(ctx)
|
|
12
12
|
body = BetterAuth::Plugins.normalize_hash(ctx.body)
|
|
13
13
|
customer_type = BetterAuth::Plugins.stripe_customer_type!(body)
|
|
@@ -26,9 +26,11 @@ module BetterAuth
|
|
|
26
26
|
next ctx.json(BetterAuth::Plugins.stripe_stringify_keys(schedule))
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
raise BetterAuth::APIError.new("BAD_REQUEST", message: BetterAuth::Stripe::ERROR_CODES.fetch("
|
|
29
|
+
raise BetterAuth::APIError.new("BAD_REQUEST", message: BetterAuth::Stripe::ERROR_CODES.fetch("SUBSCRIPTION_NOT_PENDING_CHANGE")) unless BetterAuth::Plugins.stripe_pending_cancel?(subscription)
|
|
30
30
|
|
|
31
|
-
active = BetterAuth::Plugins.stripe_active_subscriptions(config, subscription["stripeCustomerId"]).
|
|
31
|
+
active = BetterAuth::Plugins.stripe_active_subscriptions(config, subscription["stripeCustomerId"]).find do |entry|
|
|
32
|
+
BetterAuth::Plugins.stripe_fetch(entry, "id") == subscription["stripeSubscriptionId"]
|
|
33
|
+
end
|
|
32
34
|
raise BetterAuth::APIError.new("BAD_REQUEST", message: BetterAuth::Stripe::ERROR_CODES.fetch("SUBSCRIPTION_NOT_FOUND")) unless active
|
|
33
35
|
|
|
34
36
|
update_params = if BetterAuth::Plugins.stripe_fetch(active, "cancel_at")
|
|
@@ -7,7 +7,7 @@ module BetterAuth
|
|
|
7
7
|
module_function
|
|
8
8
|
|
|
9
9
|
def endpoint(config)
|
|
10
|
-
BetterAuth::Endpoint.new(path: "/stripe/webhook", method: "POST", disable_body: true) do |ctx|
|
|
10
|
+
BetterAuth::Endpoint.new(path: "/stripe/webhook", method: "POST", metadata: {hide: true}, disable_body: true) do |ctx|
|
|
11
11
|
signature = ctx.headers["stripe-signature"]
|
|
12
12
|
raise BetterAuth::APIError.new("BAD_REQUEST", message: BetterAuth::Stripe::ERROR_CODES.fetch("STRIPE_SIGNATURE_NOT_FOUND")) if signature.to_s.empty?
|
|
13
13
|
|
|
@@ -36,8 +36,13 @@ module BetterAuth
|
|
|
36
36
|
raise BetterAuth::APIError.new("BAD_REQUEST", message: BetterAuth::Stripe::ERROR_CODES.fetch("FAILED_TO_CONSTRUCT_STRIPE_EVENT")) unless event
|
|
37
37
|
begin
|
|
38
38
|
BetterAuth::Plugins.stripe_handle_event(ctx, event)
|
|
39
|
-
rescue
|
|
40
|
-
|
|
39
|
+
rescue => error
|
|
40
|
+
logger = ctx.context.logger
|
|
41
|
+
if logger.respond_to?(:error)
|
|
42
|
+
logger.error("Stripe webhook failed. Error: #{error.message}")
|
|
43
|
+
elsif logger.respond_to?(:call)
|
|
44
|
+
logger.call(:error, "Stripe webhook failed. Error: #{error.message}")
|
|
45
|
+
end
|
|
41
46
|
end
|
|
42
47
|
ctx.json({success: true})
|
|
43
48
|
end
|
|
@@ -7,12 +7,12 @@ module BetterAuth
|
|
|
7
7
|
module_function
|
|
8
8
|
|
|
9
9
|
def endpoint(config)
|
|
10
|
-
BetterAuth::Endpoint.new(path: "/subscription/success", method: "GET") do |ctx|
|
|
10
|
+
BetterAuth::Endpoint.new(path: "/subscription/success", method: "GET", metadata: {openapi: {operationId: "subscriptionSuccess"}}) do |ctx|
|
|
11
11
|
query = BetterAuth::Plugins.normalize_hash(ctx.query)
|
|
12
12
|
callback = query[:callback_url] || "/"
|
|
13
13
|
BetterAuth::Stripe::Middleware.validate_trusted_url!(ctx, callback, "callbackURL")
|
|
14
14
|
checkout_session_id = query[:checkout_session_id]
|
|
15
|
-
subscription_id =
|
|
15
|
+
subscription_id = nil
|
|
16
16
|
if checkout_session_id
|
|
17
17
|
callback = callback.to_s.gsub("{CHECKOUT_SESSION_ID}", checkout_session_id.to_s)
|
|
18
18
|
checkout_session = begin
|
|
@@ -34,6 +34,7 @@ module BetterAuth
|
|
|
34
34
|
|
|
35
35
|
subscription = ctx.context.adapter.find_one(model: "subscription", where: [{field: "id", value: subscription_id}])
|
|
36
36
|
raise ctx.redirect(BetterAuth::Plugins.stripe_url(ctx, callback)) unless subscription
|
|
37
|
+
raise ctx.redirect(BetterAuth::Plugins.stripe_url(ctx, callback)) unless BetterAuth::Stripe::Middleware.authorized_subscription?(ctx, session, subscription, "subscription-success", config || {})
|
|
37
38
|
raise ctx.redirect(BetterAuth::Plugins.stripe_url(ctx, callback)) if BetterAuth::Plugins.stripe_active_or_trialing?(subscription)
|
|
38
39
|
|
|
39
40
|
customer_id = subscription["stripeCustomerId"] || session.fetch(:user)["stripeCustomerId"]
|
|
@@ -7,7 +7,7 @@ module BetterAuth
|
|
|
7
7
|
module_function
|
|
8
8
|
|
|
9
9
|
def endpoint(config)
|
|
10
|
-
BetterAuth::Endpoint.new(path: "/subscription/upgrade", method: "POST") do |ctx|
|
|
10
|
+
BetterAuth::Endpoint.new(path: "/subscription/upgrade", method: "POST", metadata: {openapi: {operationId: "upgradeSubscription"}}) do |ctx|
|
|
11
11
|
session = BetterAuth::Routes.current_session(ctx)
|
|
12
12
|
body = BetterAuth::Plugins.normalize_hash(ctx.body)
|
|
13
13
|
BetterAuth::Stripe::Middleware.validate_trusted_urls!(ctx, body, success_url: "successUrl", cancel_url: "cancelUrl", return_url: "returnUrl")
|
|
@@ -62,7 +62,7 @@ module BetterAuth
|
|
|
62
62
|
stripe_price_id_value = BetterAuth::Plugins.stripe_fetch(BetterAuth::Plugins.stripe_fetch(active_stripe_item || {}, "price") || {}, "id")
|
|
63
63
|
same_plan = active_or_trialing && active_or_trialing["plan"].to_s.downcase == body[:plan].to_s.downcase
|
|
64
64
|
same_seats = auto_managed_seats || (active_or_trialing && active_or_trialing["seats"].to_i == requested_seats.to_i)
|
|
65
|
-
same_price =
|
|
65
|
+
same_price = !!active_stripe && stripe_price_id_value == price_id
|
|
66
66
|
valid_period = !active_or_trialing || !active_or_trialing["periodEnd"] || active_or_trialing["periodEnd"] > Time.now
|
|
67
67
|
if active_or_trialing&.fetch("status", nil) == "active" && same_plan && same_seats && same_price && valid_period
|
|
68
68
|
raise BetterAuth::APIError.new("BAD_REQUEST", message: BetterAuth::Stripe::ERROR_CODES.fetch("ALREADY_SUBSCRIBED_PLAN"))
|
|
@@ -16,7 +16,12 @@ module BetterAuth
|
|
|
16
16
|
def fetch(object, key)
|
|
17
17
|
return nil unless object.respond_to?(:[])
|
|
18
18
|
|
|
19
|
-
object[key]
|
|
19
|
+
return object[key] if object.respond_to?(:key?) && object.key?(key)
|
|
20
|
+
|
|
21
|
+
symbol_key = key.to_sym
|
|
22
|
+
return object[symbol_key] if object.respond_to?(:key?) && object.key?(symbol_key)
|
|
23
|
+
|
|
24
|
+
object[key] || object[symbol_key]
|
|
20
25
|
end
|
|
21
26
|
|
|
22
27
|
def time(value)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: better_auth-stripe
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sebastian Sala
|
|
@@ -132,14 +132,14 @@ files:
|
|
|
132
132
|
- lib/better_auth/stripe/types.rb
|
|
133
133
|
- lib/better_auth/stripe/utils.rb
|
|
134
134
|
- lib/better_auth/stripe/version.rb
|
|
135
|
-
homepage: https://github.com/sebasxsala/better-auth
|
|
135
|
+
homepage: https://github.com/sebasxsala/better-auth-rb
|
|
136
136
|
licenses:
|
|
137
137
|
- MIT
|
|
138
138
|
metadata:
|
|
139
|
-
homepage_uri: https://github.com/sebasxsala/better-auth
|
|
140
|
-
source_code_uri: https://github.com/sebasxsala/better-auth
|
|
141
|
-
changelog_uri: https://github.com/sebasxsala/better-auth/blob/main/packages/better_auth-stripe/CHANGELOG.md
|
|
142
|
-
bug_tracker_uri: https://github.com/sebasxsala/better-auth/issues
|
|
139
|
+
homepage_uri: https://github.com/sebasxsala/better-auth-rb
|
|
140
|
+
source_code_uri: https://github.com/sebasxsala/better-auth-rb
|
|
141
|
+
changelog_uri: https://github.com/sebasxsala/better-auth-rb/blob/main/packages/better_auth-stripe/CHANGELOG.md
|
|
142
|
+
bug_tracker_uri: https://github.com/sebasxsala/better-auth-rb/issues
|
|
143
143
|
rdoc_options: []
|
|
144
144
|
require_paths:
|
|
145
145
|
- lib
|