gumroad icon indicating copy to clipboard operation
gumroad copied to clipboard

Improve membership restart flow to prevent accidental re-purchases

Open vatsalkaushik opened this issue 8 months ago • 6 comments

  • [ ] 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.

vatsalkaushik avatar Apr 27 '25 06:04 vatsalkaushik

@laugardie Hi Laura, Were you able to prepare the designs for this please?

Hanaffi avatar Jun 27 '25 19:06 Hanaffi

@Hanaffi I'll take a look this week to see if anything is needed to be designed.

laugardie avatar Jun 30 '25 12:06 laugardie

@vatsalkaushik Do you think showing a modal when someone with a canceled subscription clicks “Purchase again” on the product page would solve this issue? Image

laugardie avatar Jul 02 '25 12:07 laugardie

Yes, this should help! Thanks

vatsalkaushik avatar Jul 03 '25 03:07 vatsalkaushik

@laugardie @vatsalkaushik Can you please review my PR? https://github.com/antiwork/gumroad/pull/550

Hanaffi avatar Jul 03 '25 13:07 Hanaffi

As part of this we may want to consider that a buyer should never have 2 of the same membership

slavingia avatar Sep 27 '25 10:09 slavingia

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.

ayushshrivastv avatar Nov 16 '25 22:11 ayushshrivastv