Skip to content

fix: block duplicate signup when customer has an active membership#963

Open
superdav42 wants to merge 1 commit intomainfrom
bugfix/block-duplicate-signup-active-membership
Open

fix: block duplicate signup when customer has an active membership#963
superdav42 wants to merge 1 commit intomainfrom
bugfix/block-duplicate-signup-active-membership

Conversation

@superdav42
Copy link
Copy Markdown
Collaborator

@superdav42 superdav42 commented Apr 28, 2026

Summary

  • Prevents logged-in customers with active/trialing/on-hold memberships from going through the registration form as a "new" checkout, which would replace their existing subscription and lose any applied coupons or custom pricing.
  • The guard fires in process_order() before any customer, membership, or payment records are created — no orphaned records when the checkout is blocked.
  • Adds a wu_allow_duplicate_signup filter for sites that intentionally allow re-registration (e.g. multi-membership setups).

Problem

When a customer with an active subscription (e.g. $64/mo with coupon "web35") visits the registration page again and completes a "new" checkout:

  1. UMS creates a new cart without the original coupon (the customer didn't re-enter it)
  2. The WooCommerce gateway creates a new WC subscription at full price ($99/mo)
  3. process_subscription_created() cancels the old subscription ("Auto-cancelled: Replaced by subscription #XXXXX")
  4. The customer's coupon pricing is permanently lost

The root cause is that logged-in users bypass the "email already in use" check at maybe_create_customer() (line 1081), and there was no guard against creating a "new" checkout when an active membership already exists.

Fix

Added a check in class-checkout.php::process_order() (after cart type is determined, before any data creation) that returns a WP_Error('duplicate_signup', ...) when all three conditions are met:

  1. cart_type is 'new'
  2. User is logged in
  3. User has at least one membership with status active, trialing, or on-hold

The error message directs the customer to their account page and suggests contacting support.

How to test

  1. Create a customer with an active subscription (with or without a coupon)
  2. Log in as that customer
  3. Navigate to the registration/checkout page and attempt a new signup
  4. Expected: checkout is blocked with "You already have an active subscription" error
  5. Expected: upgrade/downgrade/addon flows continue to work normally (they use cart_type='upgrade' etc., not 'new')
  6. Expected: a site can override via add_filter('wu_allow_duplicate_signup', '__return_true')

Context

A customer (KursoPro) built a custom workaround ("kp-prevent-duplicate-signup") that hooks into wu_checkout_after_validate and woocommerce_checkout_process to block this scenario. However, their UMS-level hook (wu_checkout_after_validate) doesn't actually exist in UMS core — only their WC-level hook was active, and it fires late (after UMS has already created pending records). This fix addresses the issue at the correct level in the checkout pipeline.


aidevops.sh v3.13.5 plugin for OpenCode v1.3.17 with gemma4:e4b spent 7d 8h and 43,427 tokens on this as a headless worker.

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced checkout to prevent duplicate membership signups for logged-in users with existing active memberships. Users attempting duplicate signups receive a message directing them to their account management page to review their existing memberships.

When a logged-in customer with an active subscription goes through the
registration form as a new checkout, the gateway replaces the old
subscription. Any coupons or custom pricing on the original subscription
are lost because the new cart has no discount applied.

Add a guard in process_order() that blocks 'new' checkouts when the
logged-in user already has an active, trialing, or on-hold membership.
The check fires before any customer, membership, or payment records are
created so there are no orphaned records when the checkout is blocked.

Filterable via wu_allow_duplicate_signup for sites that intentionally
permit re-registration (e.g. multi-membership setups).
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

📝 Walkthrough

Walkthrough

An early control-flow check is added to process_order() method that prevents logged-in users with existing active, trialing, or on-hold memberships from completing "new" cart checkouts. The rejection returns a WP_Error('duplicate_signup') and is overrideable via the wu_allow_duplicate_signup filter.

Changes

Cohort / File(s) Summary
Duplicate Signup Prevention
inc/checkout/class-checkout.php
Added early-return logic in process_order() that blocks checkout for logged-in users with active/trialing/on-hold memberships when attempting a "new" cart checkout. Includes membership lookup, conditional validation, and overrideable filter hook.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

origin:worker

Poem

🐰 A guard at the checkout gate,
Checks if members are already great,
No double signups allowed today,
But filters can have their say! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately describes the main change: blocking duplicate signups for customers with active memberships, which is the primary purpose of the new checkout guard logic.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bugfix/block-duplicate-signup-active-membership

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@inc/checkout/class-checkout.php`:
- Around line 788-796: The code returns an admin URL explicitly built with
wu_get_main_site_id(), which forces the main-site domain and can break
mapped-domain auth; change the URL construction to use the current checkout blog
instead (e.g., build the account link with get_admin_url(get_current_blog_id(),
'admin.php?page=account') or call the project’s existing blog-aware account
helper if one exists) so the "manage your existing subscription" link keeps the
user on the same domain when returning the WP_Error in this block (the code
around $allow / get_admin_url(... 'admin.php?page=account') in
class-checkout.php).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3cef2b63-039b-4646-8d0a-07a79f5d7b6e

📥 Commits

Reviewing files that changed from the base of the PR and between 869b7ac and b35253a.

📒 Files selected for processing (1)
  • inc/checkout/class-checkout.php

Comment on lines +788 to +796
if ( ! $allow) {
$account_url = get_admin_url(wu_get_main_site_id(), 'admin.php?page=account');

return new \WP_Error(
'duplicate_signup',
sprintf(
/* translators: %s is a link to the account page. */
__('You already have an active subscription. To manage your existing subscription, <a href="%s">visit your account page</a>. If you need help, please contact support.', 'ultimate-multisite'),
esc_url($account_url)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use a blog-aware account URL here.

Line 789 always sends the customer to the main site's admin.php?page=account. This file already has to preserve the checkout blog for auth-sensitive links because main-site URLs can miss the user's valid cookie on mapped domains. If that happens here, the "manage your existing subscription" path is broken for the exact users this guard blocks. Build the URL from the current checkout blog, or reuse the existing helper that keeps the customer on the same domain.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@inc/checkout/class-checkout.php` around lines 788 - 796, The code returns an
admin URL explicitly built with wu_get_main_site_id(), which forces the
main-site domain and can break mapped-domain auth; change the URL construction
to use the current checkout blog instead (e.g., build the account link with
get_admin_url(get_current_blog_id(), 'admin.php?page=account') or call the
project’s existing blog-aware account helper if one exists) so the "manage your
existing subscription" link keeps the user on the same domain when returning the
WP_Error in this block (the code around $allow / get_admin_url(...
'admin.php?page=account') in class-checkout.php).

@github-actions
Copy link
Copy Markdown

Performance Test Results

Performance test results for 6ca86b1 are in 🛎️!

Note: the numbers in parentheses show the difference to the previous (baseline) test run. Differences below 2% or 0.5 in absolute values are not shown.

URL: /

Run DB Queries Memory Before Template Template WP Total LCP TTFB LCP - TTFB
0 41 (+1 / +2% ) 37.78 MB 849.00 ms 160.00 ms (-18.00 ms / -11% ) 1042.00 ms 1994.00 ms (-44.00 ms / -2% ) 1902.95 ms (-44.85 ms / -2% ) 93.50 ms
1 56 49.03 MB 925.00 ms 148.00 ms 1073.00 ms 2044.00 ms 1958.15 ms 81.30 ms

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant