1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-04 21:15:19 +02:00

Subscription tests and domain (#2209)

* Save work

* Subscriptions and trials domain

* Store family ID on customer

* Remove indirection of stripe calls

* Test simplifications

* Update brakeman

* Fix stripe tests in CI

* Update billing page to show subscription details

* Remove legacy columns

* Complete billing settings page

* Fix hardcoded plan name

* Handle subscriptions for self hosting mode

* Lint fixes
This commit is contained in:
Zach Gollwitzer 2025-05-06 14:05:21 -04:00 committed by GitHub
parent 8c10e87387
commit 5da4bb6dc3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 1041 additions and 233 deletions

View file

@ -7,7 +7,6 @@ class AccountableSparklinesController < ApplicationController
.where(accountable_type: @accountable.name)
.balance_series(
currency: family.currency,
timezone: family.timezone,
favorable_direction: @accountable.favorable_direction
)
end

View file

@ -3,24 +3,19 @@ module Onboardable
included do
before_action :require_onboarding_and_upgrade
helper_method :subscription_pending?
end
private
# A subscription goes into "pending" mode immediately after checkout, but before webhooks are processed.
def subscription_pending?
subscribed_at = Current.session.subscribed_at
subscribed_at.present? && subscribed_at <= Time.current && subscribed_at > 1.hour.ago
end
# First, we require onboarding, then once that's complete, we require an upgrade for non-subscribed users.
def require_onboarding_and_upgrade
return unless Current.user
return unless redirectable_path?(request.path)
if !Current.user.onboarded?
if Current.user.needs_onboarding?
redirect_to onboarding_path
elsif !Current.family.subscribed? && !Current.family.trialing? && !self_hosted?
elsif Current.family.needs_subscription?
redirect_to trial_onboarding_path
elsif Current.family.upgrade_required?
redirect_to upgrade_subscription_path
end
end

View file

@ -2,6 +2,6 @@ class Settings::BillingsController < ApplicationController
layout "settings"
def show
@user = Current.user
@family = Current.family
end
end

View file

@ -4,49 +4,40 @@ class SubscriptionsController < ApplicationController
# Upgrade page for unsubscribed users
def upgrade
render layout: "onboardings"
end
def start_trial
unless Current.family.trialing?
ActiveRecord::Base.transaction do
Current.user.update!(onboarded_at: Time.current)
Current.family.update!(trial_started_at: Time.current)
end
if Current.family.subscription&.active?
redirect_to root_path, notice: "You are already subscribed."
else
@plan = params[:plan] || "annual"
render layout: "onboardings"
end
redirect_to root_path, notice: "Welcome to Maybe!"
end
def new
price_map = {
monthly: ENV["STRIPE_MONTHLY_PRICE_ID"],
annual: ENV["STRIPE_ANNUAL_PRICE_ID"]
}
price_id = price_map[(params[:plan] || :monthly).to_sym]
unless Current.family.existing_customer?
customer = stripe.create_customer(
email: Current.family.primary_user.email,
metadata: { family_id: Current.family.id }
)
Current.family.update(stripe_customer_id: customer.id)
end
checkout_session_url = stripe.get_checkout_session_url(
price_id: price_id,
customer_id: Current.family.stripe_customer_id,
checkout_session = stripe.create_checkout_session(
plan: params[:plan],
family_id: Current.family.id,
family_email: Current.family.billing_email,
success_url: success_subscription_url + "?session_id={CHECKOUT_SESSION_ID}",
cancel_url: upgrade_subscription_url(plan: params[:plan])
cancel_url: upgrade_subscription_url
)
redirect_to checkout_session_url, allow_other_host: true, status: :see_other
Current.family.update!(stripe_customer_id: checkout_session.customer_id)
redirect_to checkout_session.url, allow_other_host: true, status: :see_other
end
# Only used for managing our "offline" trials. Paid subscriptions are handled in success callback of checkout session
def create
if Current.family.can_start_trial?
Current.family.start_trial_subscription!
redirect_to root_path, notice: "Welcome to Maybe!"
else
redirect_to root_path, alert: "You have already started or completed a trial. Please upgrade to continue."
end
end
def show
portal_session_url = stripe.get_billing_portal_session_url(
portal_session_url = stripe.create_billing_portal_session_url(
customer_id: Current.family.stripe_customer_id,
return_url: settings_billing_url
)
@ -54,12 +45,16 @@ class SubscriptionsController < ApplicationController
redirect_to portal_session_url, allow_other_host: true, status: :see_other
end
# Stripe redirects here after a successful checkout session and passes the session ID in the URL
def success
checkout_session = stripe.retrieve_checkout_session(params[:session_id])
Current.session.update(subscribed_at: Time.at(checkout_session.created))
redirect_to root_path, notice: "You have successfully subscribed to Maybe+."
rescue Stripe::InvalidRequestError
redirect_to settings_billing_path, alert: "Something went wrong processing your subscription. Please contact us to get this fixed."
checkout_result = stripe.get_checkout_result(params[:session_id])
if checkout_result.success?
Current.family.start_subscription!(checkout_result.subscription_id)
redirect_to root_path, notice: "Welcome to Maybe! Your subscription has been created."
else
redirect_to root_path, alert: "Something went wrong processing your subscription. Please contact us to get this fixed."
end
end
private

View file

@ -44,9 +44,11 @@ class WebhooksController < ApplicationController
head :ok
rescue JSON::ParserError => error
Sentry.capture_exception(error)
Rails.logger.error "JSON parser error: #{error.message}"
head :bad_request
rescue Stripe::SignatureVerificationError => error
Sentry.capture_exception(error)
Rails.logger.error "Stripe signature verification error: #{error.message}"
head :bad_request
end
end