Skip to content

fix: recover outstanding invoices on active memberships - #1976

Merged
superdav42 merged 5 commits into
mainfrom
fix/active-membership-payment-recovery
Oct 17, 2026
Merged

superdav42 merged 5 commits into
mainfrom
fix/active-membership-payment-recovery

Conversation

@superdav42

@superdav42 superdav42 commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Fix native invoice recovery for active memberships: an outstanding payment must remain a retry, not fall through into a new signup or conflicting-payment cancellation.
  • Accept failed invoices alongside pending invoices, matching Payment::is_payable() and the native payment URL API. Completed/cancelled payment handling and ownership checks remain unchanged.
  • Preserve the existing payment, membership, invoice currency, line items and recurring amount; do not change customer subscriptions while displaying checkout.

Files and reference pattern

  • inc/checkout/class-cart.php: retain the existing ownership-checked build_from_payment() recovery path regardless of membership activation.
  • tests/WP_Ultimo/Checkout/Cart_Payment_Recovery_Test.php: cover active/pending/trialing recovery, failed invoices, unchanged identities/status, EUR totals and recurring-payment eligibility. Explicitly clean custom-table fixtures.
  • tests/WP_Ultimo/Gateways/Stripe_Gateway_Process_Checkout_Test.php: exercise the real recovered cart with mocked Stripe services; assert settlement links a subscription, enables auto-renewal and sets a future expiration on the original membership.

Verification

  • Focused PHPCS: cart and both changed test files pass.
  • PHPStan: vendor/bin/phpstan analyse inc/checkout/class-cart.php --memory-limit=1G --no-progress passes.
  • vendor/bin/phpunit --no-coverage --filter 'Stripe_Gateway_Process_Checkout_Test|Cart_Payment_Recovery_Test|Cart_Coverage_Test::test_build_from_payment|Cart_Test::.*trial': 20 tests, 115 assertions passed. Stripe services are mocked; the recovery test asserts subscription linkage, auto-renewal and a future expiration on the same membership.
  • No production writes, live gateway requests or customer charges. Live settlement remains a customer action after a separately authorized rollout.

Deployment coordination

Pair with the Multinetwork hosting-account change exposing owner-scoped native invoice checkout links:

https://github.com/Ultimate-Multisite/ultimate-multisite-multinetwork/pull/56

Deploy the core recovery fix before exposing those links; otherwise an active membership's invoice can still take the wrong cart path. No release or production deployment is authorized by this PR.


aidevops.sh v3.39.27 plugin for OpenCode

@superdav42 superdav42 added the origin:interactive Created by interactive user session label Oct 16, 2026
@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 78d74fb1-3516-4203-9392-f9fd069f51f4
📥 Commits

Reviewing files that changed from the base of the PR and between eaa7701 and 0a8fd85.

📒 Files selected for processing: 4
  • inc/checkout/class-cart.php
  • tests/WP_Ultimo/Checkout/Cart_Payment_Recovery_Test.php
  • tests/WP_Ultimo/Gateways/Stripe_Gateway_Process_Checkout_Test.php
  • views/checkout/templates/order-summary/simple.php
+------------------------------------------------------------------------------------------------------------------------+
| Don't worry if it doesn't work right. If everything did, you'd be out of a job. - Mosher's Law of Software Engineering |
+------------------------------------------------------------------------------------------------------------------------+
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

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

Copy link
Copy Markdown

Performance Test Results

Performance test results for 1a9362f are in 🛎️!

Recovered invoice line items were re-run through add_line_item(), which
re-applied the coupon at its current base-currency value: a EUR 19.64
invoice (EUR 31.24 less a EUR 11.60 fixed coupon) became USD 18.24.
Payable invoices now keep their stored line items, discounts and taxes.

The order summary no longer offers Remove links for products or coupons
on a retry (invoice) order, since an issued invoice cannot be edited.
@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

@superdav42

Copy link
Copy Markdown
Collaborator Author

Tested end to end against a local copy of production data with the real case behind this PR: membership #26, invoice #354, EUR 19.64, which is EUR 31.24 less a fixed coupon converted to EUR 11.60. Two follow-up commits were added:

  • aebd17fb fix: pay outstanding invoices exactly as issued. build_from_payment() still re-ran each invoice line through add_line_item(), so apply_discounts_to_item() replaced the stored EUR 11.60 discount with the coupon's current base value (13.00). The EUR 19.64 invoice was offered as USD 18.24; the currency part is fixed in Ultimate-Multisite/ultimate-multisite-multi-currency#30. Payable invoices now keep their stored line items, discounts and taxes. The order summary also hides the product and coupon Remove links when order.type === 'retry'.
  • 0a8fd855 test: Cart_Payment_Recovery_Test::test_discounted_invoice_keeps_issued_amount.

Verification:

  • vendor/bin/phpunit --no-coverage --filter 'Cart_Payment_Recovery_Test|Stripe_Gateway_Process_Checkout_Test|Cart_Coverage_Test::test_build_from_payment|Cart_Test::.*(trial|payment|retry|discount)': 42 tests, 178 assertions OK.
  • tests/WP_Ultimo/Checkout: 1093 tests OK (8 skipped). tests/WP_Ultimo/Gateways: 565 tests OK (14 skipped).
  • PHPStan on inc/checkout/class-cart.php: no errors. PHPCS on both changed files: clean.
  • Browser, as the invoice owner with Stripe sandbox and test card 4242, together with multinetwork Fix plugin checks for wp.org #56 and multi-currency Circle CI not running for all PRs #30: invoice fix(checkout): correct addon pricing to only charge for new products #354 completed at EUR 19.64. The same membership got auto-renew, a Stripe subscription and a next-month expiration, and its currency stayed EUR.

aidevops.sh v3.39.36 plugin for OpenCode

@superdav42
superdav42 marked this pull request as ready for review October 17, 2026 00:58
@superdav42
superdav42 merged commit 540a87e into main Oct 17, 2026
14 of 15 checks passed
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