Skip to content

fix(billing): /sync Stripe expansion depth + observable error logging#3850

Merged
bokelley merged 2 commits into
mainfrom
bokelley/sync-stripe-expand-depth-fix
May 2, 2026
Merged

fix(billing): /sync Stripe expansion depth + observable error logging#3850
bokelley merged 2 commits into
mainfrom
bokelley/sync-stripe-expand-depth-fix

Conversation

@bokelley

@bokelley bokelley commented May 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Hotfix on top of #3829. I asked Stripe to expand subscriptions.data.items.data.price.product on customers.retrieve, which is 6 levels deep and exceeds Stripe's 4-level expansion limit. Every /sync call against an org with a Stripe customer threw and returned a generic "Failed to sync from Stripe" with no log line — the regression was invisible.

Caught after merging #3829 when re-running /sync against Adzymic returned success: false.

What changed

  • Drop the deep expand on customers.retrieve — that call now just confirms the customer isn't deleted.
  • Fetch subscriptions separately via stripe.subscriptions.list({ customer, expand: ['data.items.data.price.product'] }). That path is only 4 levels deep from the subscriptions resource, within Stripe's limit. The expansion is what makes isMembershipSub's metadata fallback work for founding-era prices.
  • Log the actual error on the /sync catch path so the next regression like this surfaces in admin logs.

Test plan

  • npm run typecheck clean
  • Precommit hook (full unit suite + typecheck + dynamic imports) passed
  • After merge: re-run /sync against Adzymic — row should now show populated stripe_subscription_id, subscription_price_lookup_key, membership_tier
  • After merge: hit GET /api/admin/integrity/check/every-entitled-org-has-resolvable-tier to confirm Adzymic clears

🤖 Generated with Claude Code

bokelley and others added 2 commits May 2, 2026 13:08
Hotfix on top of #3829. The customer.retrieve expand
'subscriptions.data.items.data.price.product' is 6 levels deep and exceeds
Stripe's 4-level expansion limit. /sync threw on every org with a Stripe
customer and returned a generic "Failed to sync from Stripe" with no log
line, so the regression was invisible.

- Drop the deep expand on customers.retrieve (just confirms not deleted)
- Fetch subscriptions separately via stripe.subscriptions.list with
  'data.items.data.price.product' expand — only 4 levels deep from the
  subscriptions resource, within Stripe's limit
- Log the actual error on the catch path

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The /sync endpoint now fetches subscriptions separately from customer.retrieve
(see prior commit) so it can expand price.product 4 levels deep without
exceeding Stripe's customer-retrieve depth limit. Update the test fixture
to mock subscriptions.list with the same fake sub data.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
});

const { FAKE_INVOICE, mockInvoicesList, mockCustomersRetrieve, mockProductsRetrieve } = mocks;
const { FAKE_INVOICE, mockInvoicesList, mockCustomersRetrieve, mockSubscriptionsList, mockProductsRetrieve } = mocks;
});

const { FAKE_INVOICE, mockInvoicesList, mockCustomersRetrieve, mockProductsRetrieve } = mocks;
const { FAKE_INVOICE, mockInvoicesList, mockCustomersRetrieve, mockSubscriptionsList, mockProductsRetrieve } = mocks;
});

const { FAKE_INVOICE, mockInvoicesList, mockCustomersRetrieve, mockProductsRetrieve } = mocks;
const { FAKE_INVOICE, mockInvoicesList, mockCustomersRetrieve, mockSubscriptionsList, mockProductsRetrieve } = mocks;
@bokelley bokelley merged commit 2c13563 into main May 2, 2026
13 checks passed
@bokelley bokelley deleted the bokelley/sync-stripe-expand-depth-fix branch May 2, 2026 17:18
bokelley added a commit that referenced this pull request May 2, 2026
…r-sub (#3853)

Hotfix on top of #3850. The previous attempt expanded
'data.items.data.price.product' on subscriptions.list, which is 5 levels
deep and still over Stripe's 4-level expansion limit. /sync kept failing.

New approach: list subs without product expansion. The lookup_key fast
path covers the modern cohort with zero extra round-trips. When no sub
has a recognized lookup_key, fall back to retrieving the product per
candidate via stripe.products.retrieve to check metadata.category=
membership — Adzymic/Advertible/Bidcliq/Equativ-shape recovers through
this path.

New pickMembershipSubWithProductFetch async helper. Sync pickMembershipSub
is unchanged for invariant callers that walk every sub on the account
and can't afford per-sub round-trips.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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