fix(stripe-checkout): guard null/WP_Error customer in run_preflight#1126
fix(stripe-checkout): guard null/WP_Error customer in run_preflight#1126superdav42 merged 1 commit intomainfrom
Conversation
Customer report: Stripe Checkout was fatalling with TypeError: Stripe_Checkout_Gateway::sync_billing_address_to_stripe(): Argument #1 ($stripe_customer_id) must be of type string, null given, called in .../class-stripe-checkout-gateway.php on line 230 Root cause walk: 1. run_preflight() calls get_or_create_customer(), which is documented to return \Stripe\Customer|\WP_Error. 2. The retrieve path silently swallows exceptions (e.g. stored cus_* belongs to a different Stripe account after a test/live switch or key rotation), then falls through to creating a new customer. 3. If the create call also throws, get_or_create_customer() returns a WP_Error. 4. run_preflight() never checked for WP_Error and read $s_customer->id — WP_Error's magic getter returns null for unknown properties — so PHP fatals on the typed string parameter of sync_billing_address_to_stripe(). Fix: - run_preflight() now returns the WP_Error from get_or_create_customer() unchanged, and returns a fresh WP_Error if the result is otherwise unusable (no id). The checkout flow already handles WP_Error returns. - get_or_create_customer() self-heals when the stored Stripe customer id cannot be retrieved (most common: test/live key mismatch or account swap) by logging the cause and creating a fresh customer instead of bubbling an opaque error. - Added a final defensive return guard so the caller is guaranteed to receive either a usable Stripe customer or a WP_Error — never an object whose id is null. - sync_billing_address_to_stripe() short-circuits on empty input as belt-and-braces for any future caller. Tests: - New: returns WP_Error when both retrieve and create fail. - New: self-heals (creates fresh customer) when stored cus_* cannot be retrieved. - New: sync_billing_address_to_stripe() never calls the API on empty id. - All 148 Stripe-related tests pass.
|
Warning Rate limit exceeded
To continue reviewing without waiting, purchase usage credits in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
🔨 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! Login credentials: |
|
Performance Test Results Performance test results for 6a22b1a 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:
|
SummaryFixes a fatal Root cause
FixThree layered changes so this failure mode can't recur:
TestsAdded three regression tests to
PHPCS is clean for the touched lines (the two pre-existing warnings on lines 2918/3295 of RiskLow. Behavioural changes are all in failure paths:
Files changed
aidevops.sh v3.14.75 plugin for OpenCode v1.14.33 with claude-sonnet-4-6 spent 1d 21h on this as a headless worker. Merged via PR #1126 to main. |
Summary
Fixes a fatal
TypeErrorreported via support that crashed Stripe Checkout for some merchants:Root cause
run_preflight()consumed$s_customer->idwithout checking the documented\Stripe\Customer|\WP_Errorreturn contract ofget_or_create_customer().When the stored
gateway_customer_idbelongs to a different Stripe account than the currently active API keys (most commonly after a test↔live mode switch, key rotation, or Stripe account swap), the retrieve call throws and is silently swallowed. Execution falls through tocustomers->create(), which can also fail (e.g. authentication error, network failure).get_or_create_customer()then returnsWP_Error, whose magic__getreturnsnullfor->id, and PHP fatals on the typedstringparameter ofsync_billing_address_to_stripe().Fix
Three layered changes so this failure mode can't recur:
Stripe_Checkout_Gateway::run_preflight()— handlesWP_Errorand empty-id results fromget_or_create_customer()and surfaces them as a clean checkout error rather than fatalling. The checkout flow already handlesWP_Errorreturns from preflight.Base_Stripe_Gateway::get_or_create_customer()— self-heals: when a storedcus_*cannot be retrieved (any reason), the swallowed exception is now logged viawu_log_add('stripe', …), and the function falls through to creating a fresh Stripe customer rather than bubbling an opaque error. A final defensive guard ensures the caller receives either a usable\Stripe\Customeror aWP_Error— never an object whoseidisnull.Stripe_Checkout_Gateway::sync_billing_address_to_stripe()— adds a defensiveempty()short-circuit for any future caller that might bypassrun_preflight.After this, account/key mismatches will quietly recover on the next checkout attempt, and any genuinely unrecoverable failure produces a
WP_Errorshown to the customer instead of a fatal stack trace.Tests
Added three regression tests to
tests/WP_Ultimo/Gateways/Stripe_Checkout_Gateway_Run_Preflight_Test.php:test_run_preflight_returns_wp_error_when_customer_creation_fails— both retrieve and create throw; preflight must returnWP_Error, never fatal.test_get_or_create_customer_self_heals_on_stale_stored_id— storedcus_*is unretrievable but create succeeds; a fresh customer id must be returned.test_sync_billing_address_to_stripe_short_circuits_on_empty_id— empty input must not produce any API call.PHPCS is clean for the touched lines (the two pre-existing warnings on lines 2918/3295 of
class-base-stripe-gateway.phpare unrelated to this change). PHPStan: no errors.Risk
Low. Behavioural changes are all in failure paths:
WP_Errorto the checkout flow.cus_*now silently self-heals.Files changed
inc/gateways/class-stripe-checkout-gateway.phpinc/gateways/class-base-stripe-gateway.phptests/WP_Ultimo/Gateways/Stripe_Checkout_Gateway_Run_Preflight_Test.phpaidevops.sh v3.14.75 plugin for OpenCode v1.14.33 with claude-sonnet-4-6 spent 1d 21h on this as a headless worker.