Improve membership restart flow to prevent accidental re-purchases
- [ ] Prevent customers from accidentally starting a new membership when they already have a canceled one that can be resumed
Why
- Save customers confusion and frustration
- Reduce support load
Currently, a previous purchase notice only shows when logged in, but it's not clear that restarting the membership is an option. Restarting is preferable especially when the content is delivered through workflows so customers can pick up where they left off without waiting to catch up.
@laugardie Hi Laura, Were you able to prepare the designs for this please?
@Hanaffi I'll take a look this week to see if anything is needed to be designed.
@vatsalkaushik Do you think showing a modal when someone with a canceled subscription clicks “Purchase again” on the product page would solve this issue?
Yes, this should help! Thanks
@laugardie @vatsalkaushik Can you please review my PR? https://github.com/antiwork/gumroad/pull/550
As part of this we may want to consider that a buyer should never have 2 of the same membership
Hi @vatsalkaushik,
After reviewing the codebase related to the membership repurchase issue, I conducted a preliminary assessment and would recommend the following solution approach prior to implementation:
The current prevention only blocks free trial purchases if you have a previous purchase There's no check at checkout to warn customers they have a canceled membership that can be resumed The "previous purchase" data passed to checkout doesn't include subscription status Customers can accidentally create a new membership instead of resuming their old one
Backend:
[app/models/subscription.rb](vscode-webview://0k3i3kcqavmknjlmbnl4qi072i8cu9jjvnrk87feqsdjrnbh6c1a/app/models/subscription.rb) - Core subscription logic, alive_or_restartable? method
[app/models/purchase.rb](vscode-webview://0k3i3kcqavmknjlmbnl4qi072i8cu9jjvnrk87feqsdjrnbh6c1a/app/models/purchase.rb#L3619-L3630) - Current free trial validation
[app/controllers/checkout_controller.rb](vscode-webview://0k3i3kcqavmknjlmbnl4qi072i8cu9jjvnrk87feqsdjrnbh6c1a/app/controllers/checkout_controller.rb) - Checkout flow
[app/presenters/checkout_presenter.rb](vscode-webview://0k3i3kcqavmknjlmbnl4qi072i8cu9jjvnrk87feqsdjrnbh6c1a/app/presenters/checkout_presenter.rb#L327-L328) - Builds checkout props with purchases
Frontend:
[app/javascript/components/server-components/CheckoutPage.tsx](vscode-webview://0k3i3kcqavmknjlmbnl4qi072i8cu9jjvnrk87feqsdjrnbh6c1a/app/javascript/components/server-components/CheckoutPage.tsx) - Checkout UI
[app/javascript/components/server-components/SubscriptionManager.tsx](vscode-webview://0k3i3kcqavmknjlmbnl4qi072i8cu9jjvnrk87feqsdjrnbh6c1a/app/javascript/components/server-components/SubscriptionManager.tsx) - Where resumption happens
Database:
[db/schema.rb:2151-2177](vscode-webview://0k3i3kcqavmknjlmbnl4qi072i8cu9jjvnrk87feqsdjrnbh6c1a/db/schema.rb#L2151-L2177) - Subscriptions table schema
Add Resumable Subscription Detection In app/models/subscription.rb, add a class method to find resumable subscriptions:
# Find resumable subscription for a user and product
def self.resumable_for_user_and_link(user_id, link_id)
where(user_id: user_id, link_id: link_id)
.where.not(cancelled_at: nil)
.where(deactivated_at: nil, ended_at: nil, failed_at: nil)
.first
end
This identifies subscriptions that are canceled but can be restarted (not ended by seller, not failed, not deactivated).
Backend - Enhance CheckoutPresenter In app/presenters/checkout_presenter.rb, add resumable subscription data to checkout props:
def resumable_subscription_for_link
return nil unless current_user && link.subscription?
subscription = Subscription.resumable_for_user_and_link(current_user.id, link.id)
return nil unless subscription
{
id: subscription.id,
cancelled_at: subscription.cancelled_at,
manage_url: "/subscriptions/#{subscription.id}/manage"
}
end
Include this in the props passed to the frontend checkout component.
Frontend - Display Warning in Checkout In app/javascript/components/server-components/CheckoutPage.tsx:
Check if resumableSubscription exists in props
Display a prominent notice above the checkout form:
"You already have a canceled membership for this product. You can restart your membership to pick up where you left off instead of purchasing again."
Link directly to the subscription management page (resumableSubscription.manage_url)
Optionally allow the user to proceed with purchase anyway (with confirmation)
Backend - Add Purchase Validation (Optional) In app/models/purchase.rb, add validation near the existing free trial check (around line 3630):
validate :prevent_duplicate_membership_purchase, if: :subscription_purchase?
def prevent_duplicate_membership_purchase
return unless user_id.present? && link.subscription?
resumable = Subscription.resumable_for_user_and_link(user_id, link_id)
if resumable.present?
errors.add(:base, "You already have a canceled membership. Please visit the Manage Membership page to restart your subscription instead of purchasing again.")
end
end
This provides a hard stop at the API level if the frontend check is bypassed. 5. Product Page Enhancement (Future Iteration) For logged-in users viewing a membership product page:
Check for resumable subscriptions using the same resumable_for_user_and_link method Replace "Subscribe" CTA with "Restart Your Membership" button that links to subscription manager Display workflow progress indicators if applicable For logged-out users: Keep the standard "Subscribe" CTA
The checkout flow will handle detection once they log in or enter their email.