Skip to content

fix(checkout): prevent epoch membership expiration - #1692

Open
superdav42 wants to merge 3 commits into
mainfrom
feature/auto-20260803-114025
Open

fix(checkout): prevent epoch membership expiration#1692
superdav42 wants to merge 3 commits into
mainfrom
feature/auto-20260803-114025

Conversation

@superdav42

@superdav42 superdav42 commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • use the calculated next charge date when recurring billing starts immediately
  • preserve null expiration dates for lifetime memberships
  • cover persisted, email-placeholder, and customer-facing expiration output

Root cause

Recurring carts without trials report a billing start timestamp of 0. Formatting that value directly persisted a January 1970 membership expiration, which then propagated to email event payloads and customer views.

Verification

  • vendor/bin/phpunit --no-coverage --filter 'WP_Ultimo\\Checkout\\Checkout_Test' — 204 tests, 349 assertions, 1 skip
  • vendor/bin/phpcs inc/checkout/class-checkout.php tests/WP_Ultimo/Checkout/Checkout_Test.php
  • vendor/bin/phpstan analyse inc/checkout/class-checkout.php tests/WP_Ultimo/Checkout/Checkout_Test.php --no-progress
  • git diff --check

aidevops.sh v3.32.219 plugin for OpenCode v1.18.9 with gpt-5.6-sol spent 2h 16m and 440,196 tokens on this with the user in an interactive session.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed membership expiration dates for recurring plans without trials.
    • Prevented memberships from incorrectly expiring at the Unix epoch.
    • Improved cleanup of expired pending payments and memberships.
    • Recent pending payments and memberships are no longer cancelled prematurely.
    • Cancelled expired pending memberships now also remove associated payment metadata.
  • Tests

    • Added regression coverage for recurring memberships, recent pending payments, and expired payment cleanup.

@superdav42 superdav42 added the origin:interactive Created by interactive user session label Aug 3, 2026
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Checkout now assigns correct expiration dates to recurring memberships and removes only sufficiently old draft or pending payments. Tests cover recent-payment preservation, recurring expiration dates, and cancellation of expired pending payments and memberships.

Changes

Checkout lifecycle handling

Layer / File(s) Summary
Membership expiration dates
inc/checkout/class-checkout.php, tests/WP_Ultimo/Checkout/Checkout_Test.php
Lifetime memberships retain a null expiration. Immediate recurring memberships use the next charge date. Tests verify the date and its event and formatted-output propagation.
Stale payment cleanup
inc/checkout/class-checkout.php, tests/WP_Ultimo/Checkout/Checkout_Test.php
Cleanup uses a shared date_created query for records older than 30 days. Tests verify recent pending records remain unchanged and expired payments cancel their pending memberships and remove pending_site metadata.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested labels: review-feedback-scanned

🚥 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 clearly and concisely describes the main checkout fix that prevents epoch membership expiration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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 feature/auto-20260803-114025

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.

@github-actions

github-actions Bot commented Aug 3, 2026

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

@github-actions

github-actions Bot commented Aug 4, 2026

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

@github-actions

github-actions Bot commented Aug 4, 2026

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

@superdav42
superdav42 marked this pull request as ready for review August 4, 2026 18:46

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
tests/WP_Ultimo/Checkout/Checkout_Test.php (1)

4130-4150: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Assert against the persisted membership.

wu_create_membership() saves the membership but returns the same in-memory object. These assertions do not verify the value after the persistence read path. Retrieve the membership with wu_get_membership() before checking its expiration, event payload, and formatted output.

Proposed test update
 		$this->assertInstanceOf(\WP_Ultimo\Models\Membership::class, $result);
-		$this->assertSame($expected_expiration, $result->get_date_expiration());
-		$this->assertStringStartsNotWith('1970-', $result->get_date_expiration());
+		$persisted_membership = wu_get_membership($result->get_id());
+		$this->assertInstanceOf(\WP_Ultimo\Models\Membership::class, $persisted_membership);
+		$this->assertSame($expected_expiration, $persisted_membership->get_date_expiration());
+		$this->assertStringStartsNotWith('1970-', $persisted_membership->get_date_expiration());
 
-		$event_payload = wu_generate_event_payload('membership', $result);
+		$event_payload = wu_generate_event_payload('membership', $persisted_membership);
 		$this->assertSame($expected_expiration, $event_payload['membership_date_expiration']);
 		$this->assertSame(
 			date_i18n(get_option('date_format'), wu_date($expected_expiration)->format('U')),
-			$result->get_formatted_date('date_expiration')
+			$persisted_membership->get_formatted_date('date_expiration')
 		);
 
-		$result->delete();
+		$persisted_membership->delete();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/WP_Ultimo/Checkout/Checkout_Test.php` around lines 4130 - 4150, After
the successful wu_create_membership() invocation, retrieve the persisted
membership through wu_get_membership() using the created membership’s
identifier, then run the expiration, event payload, and formatted-date
assertions against that reloaded object instead of the original $result. Keep
the existing WP_Error skip and cleanup behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/WP_Ultimo/Checkout/Checkout_Test.php`:
- Around line 4130-4150: After the successful wu_create_membership() invocation,
retrieve the persisted membership through wu_get_membership() using the created
membership’s identifier, then run the expiration, event payload, and
formatted-date assertions against that reloaded object instead of the original
$result. Keep the existing WP_Error skip and cleanup behavior unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0ab4cede-993d-4b69-9b11-fa914b2d7e02

📥 Commits

Reviewing files that changed from the base of the PR and between f5ae9b2 and 7e50d35.

📒 Files selected for processing (2)
  • inc/checkout/class-checkout.php
  • tests/WP_Ultimo/Checkout/Checkout_Test.php

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

Labels

origin:interactive Created by interactive user session

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant