How to handle failed payments and canceled subscriptions?
What sort of lifecycle does pay gem follow for failed payments?
I see that in Jumpstart template there is a flash message for failed payments and when payment fails current_team.subscribed? returns false even the the subscription is not canceled in Stripe. I guess it is not the best method for status check.
I also could not see a flash message for the case of canceled subscriptions. What do users see when they login but subscription is canceled in the default template? It seems like Teams::SubscriptionStatus only checks the status but does not do anything else.
What would be the best way to handle a failed payment and canceled subscription?
I see that in Jumpstart template there is a flash message for failed payments and when payment fails current_team.subscribed? returns false even the the subscription is not canceled in Stripe. I guess it is not the best method for status check.
I also could not see a flash message for the case of canceled subscriptions. What do users see when they login but subscription is canceled in the default template? It seems like Teams::SubscriptionStatus only checks the status but does not do anything else.
What would be the best way to handle a failed payment and canceled subscription?
I use ProfitWell's Retain for emailing customers about failed payments. Baremetrics has a recover feature, I've used stunning.co in the past and there's a handful other options too. My friend Steve built JustDunning.com as well.
I like not having to deal with those things myself, but you can listen to the invoice.payment_failed event from Stripe and do things yourself if you would like.
I like not having to deal with those things myself, but you can listen to the invoice.payment_failed event from Stripe and do things yourself if you would like.
We can build something simple that will redirect the user to the payment screen and block access if an invoice failed and subscription canceled. It will just require a couple of additional helpers.
I dunno, I mean I've never built it into my apps. Stripe also has dunning email functionality built-in that you can enable. I don't think that Braintree has quite the same level of detail.
Most services are lenient if a payment failed, since your card might be stolen and they don't actually cancel the subscription until several attempts to recover fail.
It might be something we should add. I'd need to figure out exactly what should be added before I can tackle it.
Most services are lenient if a payment failed, since your card might be stolen and they don't actually cancel the subscription until several attempts to recover fail.
It might be something we should add. I'd need to figure out exactly what should be added before I can tackle it.
Stripe also has the "incomplete" status on a subscription that we're already handling in Jumpstart Pro.
https://gitlab.com/gorails/jumpstart-pro/-/blob/master/app/views/shared/_flash.html.erb#L15
https://gitlab.com/gorails/jumpstart-pro/-/blob/master/app/views/shared/_flash.html.erb#L15
There is a status value in the subscription object: https://stripe.com/docs/api/subscriptions/object
We need to have a status field in the jumpstart database which needs to sycn to the Stripe value and it will get updated by webhooks.
After that, the only thing we need to do is to expand the current_team.subscribed? method. It currently returns false after the first failed payment. It should check for the actual subscription status instead of charges.
Not sure how Braintree works but perhaps Pay gem might set the status if they don't provide a status value.
Then we just need to add another helper method to the application_controller that will handle the redirect and triggers flash messages.
what I am trying to confirm is whether that causes any conflicts anywhere.
If this helper did not exist, current_account.subscribed? would return the method on Account that comes from Pay.
But now current_account.subscribed? also checks for
If this helper did not exist, current_account.subscribed? would return the method on Account that comes from Pay.
But now current_account.subscribed? also checks for
user_signed_in? && current_account && current_account.subscribed?
doesnt it? how and where does rails make the distinction between the two?
I'm not sure where the confusion is. Let me try explaining a different way.
SubscriptionStatus lets you write
So the subscribed? helper just makes sure you're logged in before asking Pay if the account is subscribed.
No conflicts, just wrapping the method so it doesn't fail.
SubscriptionStatus lets you write
if subscribed?
in the view. That's the equivalent of if user_signed_in && current_account && current_account.subscribed?
.current_account.subscribed?
is the same as calling Account.last.subscribed?
which asks Pay if the user is subscribed.So the subscribed? helper just makes sure you're logged in before asking Pay if the account is subscribed.
No conflicts, just wrapping the method so it doesn't fail.
Stripe has built-in email settings to comply with this. Does Braintree have anything similar?
Also can jumpstart handle 3D secure? Is the recent upgrade you released was for that? I could not be sure if I should turn on 3D secure in Stripe.
ActiveRecord::RecordInvalid
Validation failed: Owner must exist
/GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/validations.rb:81→ raise_validation_error /GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/validations.rb:53→ save! /GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/transactions.rb:319→ block in save! /GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/transactions.rb:375→ block in with_transaction_returning_status /GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/abstract/database_statements.rb:279→ transaction /GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/transactions.rb:212→ transaction /GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/transactions.rb:366→ with_transaction_returning_status /GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/transactions.rb:319→ save! /GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/suppressor.rb:48→ save! /GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/persistence.rb:635→ block in update! /GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/transactions.rb:375→ block in with_transaction_returning_status /GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/abstract/database_statements.rb:281→ block in transaction /GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/abstract/transaction.rb:280→ block in within_new_transaction /GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/abstract/transaction.rb:278→ synchronize /GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/abstract/transaction.rb:278→ within_new_transaction /GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/abstract/database_statements.rb:281→ transaction /GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/transactions.rb:212→ transaction /GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/transactions.rb:366→ with_transaction_returning_status /GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/persistence.rb:633→ update! /GEM_ROOT/gems/pay-2.1.1/lib/pay/stripe/webhooks/subscription_deleted.rb:14→ call /GEM_ROOT/gems/stripe_event-2.3.0/lib/stripe_event.rb:61→ call /GEM_ROOT/gems/activesupport-6.0.2.2/lib/active_support/notifications/fanout.rb:189→ finish /GEM_ROOT/gems/activesupport-6.0.2.2/lib/active_support/notifications/fanout.rb:62→ block in finish /GEM_ROOT/gems/activesupport-6.0.2.2/lib/active_support/notifications/fanout.rb:62→ each /GEM_ROOT/gems/activesupport-6.0.2.2/lib/active_support/notifications/fanout.rb:62→ finish /GEM_ROOT/gems/activesupport-6.0.2.2/lib/active_support/notifications/instrumenter.rb:45→ finish_with_state /GEM_ROOT/gems/activesupport-6.0.2.2/lib/active_support/notifications/instrumenter.rb:30→ instrument /GEM_ROOT/gems/activesupport-6.0.2.2/lib/active_support/notifications.rb:180→ instrument /GEM_ROOT/gems/stripe_event-2.3.0/lib/stripe_event.rb:18→ instrument /GEM_ROOT/gems/stripe_event-2.3.0/app/controllers/stripe_event/webhook_controller.rb:8→ event /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_controller/metal/basic_implicit_render.rb:6→ send_action /GEM_ROOT/gems/actionpack-6.0.2.2/lib/abstract_controller/base.rb:196→ process_action /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_controller/metal/rendering.rb:30→ process_action /GEM_ROOT/gems/actionpack-6.0.2.2/lib/abstract_controller/callbacks.rb:42→ block in process_action /GEM_ROOT/gems/activesupport-6.0.2.2/lib/active_support/callbacks.rb:135→ run_callbacks /GEM_ROOT/gems/actionpack-6.0.2.2/lib/abstract_controller/callbacks.rb:41→ process_action /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_controller/metal/rescue.rb:22→ process_action /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_controller/metal/instrumentation.rb:33→ block in process_action /GEM_ROOT/gems/activesupport-6.0.2.2/lib/active_support/notifications.rb:180→ block in instrument /GEM_ROOT/gems/activesupport-6.0.2.2/lib/active_support/notifications/instrumenter.rb:24→ instrument /GEM_ROOT/gems/activesupport-6.0.2.2/lib/active_support/notifications.rb:180→ instrument /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_controller/metal/instrumentation.rb:32→ process_action /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_controller/metal/params_wrapper.rb:245→ process_action /GEM_ROOT/gems/activerecord-6.0.2.2/lib/active_record/railties/controller_runtime.rb:27→ process_action /GEM_ROOT/gems/actionpack-6.0.2.2/lib/abstract_controller/base.rb:136→ process /GEM_ROOT/gems/actionview-6.0.2.2/lib/action_view/rendering.rb:39→ process /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_controller/metal.rb:191→ dispatch /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_controller/metal.rb:252→ dispatch /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/routing/route_set.rb:51→ dispatch /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/routing/route_set.rb:33→ serve /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/journey/router.rb:49→ block in serve /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/journey/router.rb:32→ each /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/journey/router.rb:32→ serve /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/routing/route_set.rb:837→ call /GEM_ROOT/gems/railties-6.0.2.2/lib/rails/engine.rb:526→ call /GEM_ROOT/gems/railties-6.0.2.2/lib/rails/railtie.rb:190→ public_send /GEM_ROOT/gems/railties-6.0.2.2/lib/rails/railtie.rb:190→ method_missing /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/routing/mapper.rb:19→ block in <class:Constraints> /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/routing/mapper.rb:48→ serve /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/journey/router.rb:49→ block in serve /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/journey/router.rb:32→ each /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/journey/router.rb:32→ serve /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/routing/route_set.rb:837→ call /GEM_ROOT/gems/omniauth-1.9.1/lib/omniauth/strategy.rb:192→ call! /GEM_ROOT/gems/omniauth-1.9.1/lib/omniauth/strategy.rb:169→ call /GEM_ROOT/gems/omniauth-1.9.1/lib/omniauth/strategy.rb:192→ call! /GEM_ROOT/gems/omniauth-1.9.1/lib/omniauth/strategy.rb:169→ call /GEM_ROOT/gems/omniauth-1.9.1/lib/omniauth/builder.rb:45→ call /GEM_ROOT/gems/warden-1.2.8/lib/warden/manager.rb:36→ block in call /GEM_ROOT/gems/warden-1.2.8/lib/warden/manager.rb:34→ catch /GEM_ROOT/gems/warden-1.2.8/lib/warden/manager.rb:34→ call /GEM_ROOT/gems/rack-2.2.2/lib/rack/tempfile_reaper.rb:15→ call /GEM_ROOT/gems/rack-2.2.2/lib/rack/etag.rb:27→ call /GEM_ROOT/gems/rack-2.2.2/lib/rack/conditional_get.rb:40→ call /GEM_ROOT/gems/rack-2.2.2/lib/rack/head.rb:12→ call /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/http/content_security_policy.rb:18→ call /GEM_ROOT/gems/rack-2.2.2/lib/rack/session/abstract/id.rb:266→ context /GEM_ROOT/gems/rack-2.2.2/lib/rack/session/abstract/id.rb:260→ call /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/cookies.rb:648→ call /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/callbacks.rb:27→ block in call /GEM_ROOT/gems/activesupport-6.0.2.2/lib/active_support/callbacks.rb:101→ run_callbacks /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/callbacks.rb:26→ call /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/actionable_exceptions.rb:17→ call /GEM_ROOT/gems/airbrake-10.0.1/lib/airbrake/rack/middleware.rb:34→ call! /GEM_ROOT/gems/airbrake-10.0.1/lib/airbrake/rack/middleware.rb:23→ call /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/debug_exceptions.rb:32→ call /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/show_exceptions.rb:33→ call /GEM_ROOT/gems/turbolinks_render-0.9.17/lib/turbolinks_render/middleware.rb:77→ call /GEM_ROOT/gems/railties-6.0.2.2/lib/rails/rack/logger.rb:38→ call_app /GEM_ROOT/gems/railties-6.0.2.2/lib/rails/rack/logger.rb:26→ block in call /GEM_ROOT/gems/activesupport-6.0.2.2/lib/active_support/tagged_logging.rb:80→ block in tagged /GEM_ROOT/gems/activesupport-6.0.2.2/lib/active_support/tagged_logging.rb:28→ tagged /GEM_ROOT/gems/activesupport-6.0.2.2/lib/active_support/tagged_logging.rb:80→ tagged /GEM_ROOT/gems/railties-6.0.2.2/lib/rails/rack/logger.rb:26→ call /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/remote_ip.rb:81→ call /GEM_ROOT/gems/request_store-1.5.0/lib/request_store/middleware.rb:19→ call /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/request_id.rb:27→ call /GEM_ROOT/gems/rack-2.2.2/lib/rack/method_override.rb:24→ call /GEM_ROOT/gems/shopify_app-13.1.0/lib/shopify_app/middleware/same_site_cookie_middleware.rb:11→ call /GEM_ROOT/gems/rack-2.2.2/lib/rack/runtime.rb:22→ call /GEM_ROOT/gems/activesupport-6.0.2.2/lib/active_support/cache/strategy/local_cache_middleware.rb:29→ call /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/executor.rb:14→ call /GEM_ROOT/gems/rack-2.2.2/lib/rack/sendfile.rb:110→ call /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/ssl.rb:74→ call /GEM_ROOT/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/host_authorization.rb:77→ call /GEM_ROOT/gems/rack-cors-1.1.1/lib/rack/cors.rb:100→ call /GEM_ROOT/gems/railties-6.0.2.2/lib/rails/engine.rb:526→ call /GEM_ROOT/gems/puma-4.3.3/lib/puma/configuration.rb:228→ call /GEM_ROOT/gems/puma-4.3.3/lib/puma/server.rb:682→ handle_request /GEM_ROOT/gems/puma-4.3.3/lib/puma/server.rb:472→ process_client /GEM_ROOT/gems/puma-4.3.3/lib/puma/server.rb:328→ block in run /GEM_ROOT/gems/puma-4.3.3/lib/puma/thread_pool.rb:134→ block in spawn_thread
Since this is the line of code that's failing to update the Subscription record, it sounds like the record in your database doesn't have an Owner associated with it currently.
https://github.com/pay-rails/pay/blob/master/lib/pay/stripe/webhooks/subscription_deleted.rb#L14
Did you backfill the existing subscriptions for the Pay 2.1 update? It should be setting all the owner_type columns to "Account".
https://github.com/pay-rails/pay/blob/master/lib/pay/stripe/webhooks/subscription_deleted.rb#L14
Did you backfill the existing subscriptions for the Pay 2.1 update? It should be setting all the owner_type columns to "Account".
Notifications
You’re not receiving notifications from this thread.