Skip to content

Replace Stripe SDK with a smaller edge client - #1864

Merged
stefan-burke merged 18 commits into
mainfrom
replace-stripe-sdk
Jul 21, 2026
Merged

stefan-burke merged 18 commits into
mainfrom
replace-stripe-sdk

Conversation

@stefan-burke

@stefan-burke stefan-burke commented Jul 19, 2026

Copy link
Copy Markdown
Member

Summary

  • replace the production Stripe SDK with a small fetch-based client for the eight Stripe operations the app uses
  • validate Stripe responses at the network boundary with Valibot
  • keep Stripe-compatible form encoding, API versioning, timeouts, retries, idempotency keys, errors, and webhook verification
  • split checkout, endpoint management, runtime setup, form encoding, schemas, and webhook verification into focused modules
  • keep stripe-node only in tests as the request-parity oracle

Why

Stripe was the largest dependency in the edge bundle even though the app uses only a small part of it. This removes 200,991 bytes from each bundle variant and about 46,000 parsed JavaScript nodes, which reduces edge cold-start work.

Review decisions

  • removed the stale asynchronous client creation path
  • split the former 703-line Stripe module into files no longer than 216 lines
  • retained unescaped brackets in form values because this matches stripe-node; direct and request-parity tests cover bracket values
  • reject malformed webhook timestamp and signature fields, including suffixes and extra delimiters
  • define privacy-safe Stripe error fields once and process them through one shared path
  • pass the recorded webhook endpoint id through to `setupWebhookEndpoint` even on a key rotation, so the endpoint-limit retry path cannot delete the live webhook before its replacement has been created and saved (regression: `keeps the recorded endpoint during a key-rotation limit retry` in the rotationCases test)
  • isolate every teardown in the payments e2e runner (`reportFailure`, `stopRun`) with `.catch(() => {})` so a failed `session.stop`/`tunnel.stop` cannot hang the CI job instead of failing cleanly
  • annotate `createWithClient` with an exported `ClientRunner` type and rename the nullable Stripe runtime resolver to `runtimeConfigOrNull` to match the `*OrNull` convention for intentional nullable resolvers
  • retry Stripe 429 `lock_timeout` responses (object contention) per Stripe's rate-limits docs; vanilla 429 rate-limits still only retry when Stripe asks via its `stripe-should-retry: true` header — three regression tests cover the retryable, non-retryable, and unparseable-body cases
  • stop gating the e2e-payments Stripe webhook wait on `pending_webhooks === 0`; that counter is account-wide, so a stale enabled webhook endpoint left by an earlier run could stall the nightly Stripe leg even after the app's own webhook had been delivered. The wait now gates on the `checkout.session.completed` event existing for the session id, which is the actual signal we need (the booking was already confirmed via `assertPaidBookingConfirmed`)

Verification

Summary by CodeRabbit

  • New Features
    • Improved Stripe checkout, payment-intent, refund, balance, and webhook operations.
    • Added Stripe connection testing with webhook status visibility and endpoint rotation.
    • Added stronger validation for Stripe responses and safer error reporting.
    • Added end-to-end coverage for signed webhooks, payment confirmation, and refunds.
  • Bug Fixes
    • Improved handling of missing payment references, invalid webhook signatures, retries, and malformed responses.
    • Quantity fields now use consistent styling without inline styles.
  • Tests
    • Expanded coverage for Stripe requests, schemas, webhooks, refunds, and payment flows.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR replaces embedded Stripe SDK usage with a typed REST client, validated schemas, centralized runtime caching, dedicated webhook modules, expanded Stripe tests, and enhanced Stripe payment sandbox journeys. It also centralizes non-empty text validation and moves attendee quantity styling into CSS.

Changes

Stripe REST integration

Layer / File(s) Summary
Typed transport and schemas
src/shared/stripe/*
Adds form encoding, Valibot response schemas, typed REST operations, retries, timeouts, idempotency keys, mock configuration, and structured Stripe errors.
Runtime and API wiring
src/shared/stripe.ts, src/shared/stripe-provider.ts, src/features/admin/settings-stripe.ts
Routes checkout, retrieval, refunds, webhook setup, cleanup, and connection testing through stripeApi and the shared runtime.
Webhook handling
src/shared/stripe/webhook.ts, src/shared/stripe/endpoints.ts
Adds strict signature parsing and verification plus webhook endpoint lifecycle and connection-status operations.
Validation updates
src/shared/validation/string.ts, src/features/api/request-schemas.ts, src/shared/bulk-email-targets.ts
Introduces NonEmptyTextSchema and reuses it across request and bulk-email target schemas.

Payment E2E flow

Layer / File(s) Summary
Journey orchestration
e2e-payments/src/main.ts, e2e-payments/src/flow.ts, e2e-payments/src/browser.ts
Centralizes setup, payment journeys, failure reporting, cleanup, and reusable page-text assertions.
Stripe post-payment validation
e2e-payments/src/providers/stripe.ts, e2e-payments/src/providers/types.ts
Rotates webhook endpoints, tests connectivity, waits for signed webhook delivery, validates PaymentIntent state, and performs a refund through the admin flow.

Validation and test migration

Layer / File(s) Summary
Stripe client and provider tests
test/shared/stripe/*, test/shared/stripe-provider*, test/integration/stripe/*
Adds request parity, transport, runtime, schema, webhook, endpoint, checkout, connection, and provider-operation coverage.
Fixtures and mocks
test/lib/stripe/*
Updates Stripe fixtures, response mappings, webhook mocks, signing helpers, and harnesses for the new API surface.
Existing integration wiring
test/integration/server/**, test/integration/routes/*, test/e2e/accounting/drivers.ts
Removes deprecated cached-client resets, updates helper imports, and routes Stripe stubs through stripeApi.

UI and supporting changes

Layer / File(s) Summary
CSP-safe quantity styling
src/ui/static/style.scss, src/ui/templates/admin/attendee-form.tsx, test/ui/templates/admin/attendee-form/quantity.test.ts
Moves quantity input width from inline markup to .line-qty CSS and adds a rendered-markup assertion.
Documentation and workflow references
.github/workflows/payment-sandbox-e2e.yml, e2e-payments/README.md
Documents webhook rotation, signed delivery, PaymentIntent validation, and refund coverage.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: replacing the Stripe SDK with a smaller edge client.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch replace-stripe-sdk

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

@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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/shared/stripe.ts (1)

1-51: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

File exceeds the ~400-line guideline.

This file spans well past 400 lines (checkout/refund/webhook/connection-test logic is all shown up to line 528+). As per coding guidelines, **/*.{ts,tsx} files should be kept under roughly 400 lines with overloaded concepts split into focused modules (e.g., checkout, webhook management, and connection-testing could become separate modules importing shared client/schema types).

🤖 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 `@src/shared/stripe.ts` around lines 1 - 51, Split the oversized Stripe
integration module into focused modules for checkout/refund operations, webhook
management, and connection testing, while keeping shared client, schema, and
helper types reusable. Update the existing exported entry points and imports so
behavior remains unchanged and each TypeScript file stays near the 400-line
guideline.

Source: Coding guidelines

🤖 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.

Inline comments:
In `@src/shared/stripe.ts`:
- Around line 153-180: Remove the unnecessary await from the createStripeClient
call in setupWebhookEndpointImpl, keeping the client assignment synchronous now
that createStripeClient returns a StripeClient directly.

In `@src/shared/stripe/form.ts`:
- Around line 10-18: Split encodePart into separate key and value encoding
paths: keep bracket un-escaping only for structural keys, while values must
retain encodeURIComponent’s %5B/%5D escaping. Update the form construction logic
using encodePart around the key/value handling, and add the specified regression
test in the Stripe form tests for a value containing brackets.

---

Outside diff comments:
In `@src/shared/stripe.ts`:
- Around line 1-51: Split the oversized Stripe integration module into focused
modules for checkout/refund operations, webhook management, and connection
testing, while keeping shared client, schema, and helper types reusable. Update
the existing exported entry points and imports so behavior remains unchanged and
each TypeScript file stays near the 400-line guideline.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 271dfb59-c04b-4ea6-b99b-a7b524465225

📥 Commits

Reviewing files that changed from the base of the PR and between 054111a and a8131a7.

📒 Files selected for processing (12)
  • src/shared/stripe.ts
  • src/shared/stripe/client.ts
  • src/shared/stripe/form.ts
  • src/shared/stripe/schemas.ts
  • test/integration/stripe-client-parity.test.ts
  • test/lib/stripe/config.test.ts
  • test/lib/stripe/webhook-mocks.ts
  • test/lib/stripe/webhook-setup.test.ts
  • test/shared/stripe/client.test.ts
  • test/shared/stripe/form.test.ts
  • test/shared/stripe/schemas.test.ts
  • test/shared/stripe/webhook-cleanup.test.ts

Comment thread src/shared/stripe.ts Outdated
Comment thread src/shared/stripe/form.ts
@stefan-burke

Copy link
Copy Markdown
Member Author

Addressed the review’s oversized-module finding in 42c727c. The former 703-line Stripe module is now split by responsibility: stripe.ts is 216 lines, stripe/endpoints.ts is 158, stripe/runtime.ts is 74, and stripe/webhook.ts is 90. Focused direct tests map to each module, and the final branch mutation gate detected all 373/373 mutants.

@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.

Actionable comments posted: 2

🤖 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.

Inline comments:
In `@src/shared/stripe/runtime.ts`:
- Around line 17-33: Refactor sanitizeStripeError to replace the four repeated
statusCode, code, type, and requestId checks with a shared field definition and
per-entry extractor/handler, while preserving the existing type validation and
output formats. Keep the unknown-error fallback and error.name fallback
unchanged.

In `@src/shared/stripe/webhook.ts`:
- Around line 22-37: Reject malformed timestamp values in the webhook header
parser by retaining the final t value, validating that it is a complete safe
decimal integer, and only then converting it; preserve the existing
missing-timestamp and signature outcomes. Add a regression test using a valid
signedHeader with a suffix appended to its timestamp and assert the structured
invalid-header result.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d23150d1-4fb7-4589-84ce-2c21dab70efc

📥 Commits

Reviewing files that changed from the base of the PR and between a8131a7 and c30bb5d.

📒 Files selected for processing (21)
  • src/shared/stripe-provider.ts
  • src/shared/stripe.ts
  • src/shared/stripe/endpoints.ts
  • src/shared/stripe/runtime.ts
  • src/shared/stripe/webhook.ts
  • test/integration/code-quality.test.ts
  • test/integration/server-settings/stripe.test.ts
  • test/integration/stripe-client-parity.test.ts
  • test/integration/stripe/connection.test.ts
  • test/integration/stripe/core.test.ts
  • test/integration/stripe/webhook-setup.test.ts
  • test/lib/code-quality/detectors.ts
  • test/lib/stripe/config.test.ts
  • test/lib/stripe/fixtures.ts
  • test/lib/stripe/webhook-mocks.ts
  • test/shared/stripe-provider.test.ts
  • test/shared/stripe.test.ts
  • test/shared/stripe/endpoints.test.ts
  • test/shared/stripe/form.test.ts
  • test/shared/stripe/runtime.test.ts
  • test/shared/stripe/webhook.test.ts
💤 Files with no reviewable changes (1)
  • test/lib/stripe/config.test.ts

Comment thread src/shared/stripe/runtime.ts
Comment thread src/shared/stripe/webhook.ts Outdated

@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.

Actionable comments posted: 2

🤖 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.

Inline comments:
In `@src/shared/stripe/webhook.ts`:
- Around line 22-27: Update the header parsing loop in the webhook verification
function to locate the separator explicitly and reject any part containing
additional "=" delimiters before assigning timestampText or collecting
signatures. Preserve existing timestamp and signature validation, and add a
regression test covering a valid timestamp followed by "=junk" that must be
rejected.

In `@test/shared/stripe/webhook.test.ts`:
- Around line 36-46: Update the test around verifyWebhookSignature to assert
that errorSpy recorded exactly one call before inspecting its arguments. After
validating the call count, access the first call directly rather than using
optional chaining, while preserving the existing detail assertion.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bdcc5fae-553b-44aa-a181-254f3055005a

📥 Commits

Reviewing files that changed from the base of the PR and between c30bb5d and 3565902.

📒 Files selected for processing (3)
  • src/shared/stripe/runtime.ts
  • src/shared/stripe/webhook.ts
  • test/shared/stripe/webhook.test.ts

Comment thread src/shared/stripe/webhook.ts Outdated
Comment thread test/shared/stripe/webhook.test.ts

@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.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
test/integration/server/payments/purchase.test.ts (1)

27-30: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove stale comments describing the deleted Stripe reset teardown.

  • test/integration/server/payments/purchase.test.ts#L27-L30: delete or update the comment claiming the client is reset after each test.
  • test/integration/server/payments/success.test.ts#L27-L30: delete or update the comment claiming the client is reset after each test.

As per coding guidelines, comments must explain the current code and must not describe replaced implementations.

🤖 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 `@test/integration/server/payments/purchase.test.ts` around lines 27 - 30,
Remove or update the stale Stripe client reset teardown comments in the test
setup for test/integration/server/payments/purchase.test.ts lines 27-30 and
test/integration/server/payments/success.test.ts lines 27-30; ensure any
remaining comments accurately describe the current code and do not reference the
deleted reset behavior.

Source: Coding guidelines

src/shared/stripe-provider.ts (1)

114-115: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Don't fall back to "" for a missing payment_intent — reuse asString like the sibling path does.

This falls back to the empty-string sentinel "" when payment_intent isn't a string, which the coding guidelines explicitly flag: "must throw when an expected result is missing instead of returning a sentinel such as null, '', 0, -1, or []." It's also inconsistent with resolveWebhookSession a few lines above in this same file, which handles the identical field via asString(obj.payment_intent) (yielding undefined, not "").

🐛 Proposed fix
     return validatedPaymentSession({
       amountTotal: amount_total,
       createdAt: isoFromUnixSeconds(session.created),
       id,
       metadata,
-      paymentReference:
-        typeof payment_intent === "string" ? payment_intent : "",
+      paymentReference: asString(payment_intent),
       paymentStatus: toPaymentStatus(payment_status),
     });

As per coding guidelines, "A lookup, resolver, computation, or finder must throw when an expected result is missing instead of returning a sentinel such as null, '', 0, -1, or []."

🤖 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 `@src/shared/stripe-provider.ts` around lines 114 - 115, Update the
paymentReference assignment in the current resolver to reuse
asString(payment_intent), matching resolveWebhookSession, instead of returning
an empty-string fallback for non-string or missing values.

Source: Coding guidelines

🤖 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.

Inline comments:
In `@e2e-payments/src/main.ts`:
- Around line 148-176: Isolate every teardown and failure-report action so one
failure cannot prevent later steps or replace the original journey error. Update
reportFailure to independently guard screenshot, dumpServerLog, and
notifyFailure while preserving and rethrowing its original error; update stopRun
to independently attempt session.stop, tunnel.stop, provider cleanup, and
server.stop, ensuring all cleanup steps run even when an earlier step fails.

In `@src/shared/payment-helpers.ts`:
- Around line 173-176: Update the exported createWithClient function to declare
an explicit return type, using the existing named or inferred helper type
associated with guardedWithValue rather than leaving the signature inferred.
Preserve its current generic Client parameter, getClient input, and
errorHandling behavior.

In `@src/shared/stripe/mock.ts`:
- Around line 6-13: Rename the inner variable in the port function to avoid
shadowing the enclosing function name, and update the validation and return
references to use the new variable consistently.

In `@src/shared/stripe/request.ts`:
- Around line 208-218: Update the exported createStripeRequest function to
declare an explicit return type, preferably by introducing or reusing a named
type for the returned generic request function rather than using an inline
anonymous function type.

In `@src/shared/stripe/runtime.ts`:
- Around line 70-77: Rename the nullable resolver runtimeConfig to
runtimeConfigOrNull to reflect its intentional null result, and update the
getConfig reference in cachedClientFactory to use the renamed function. Keep the
existing secret-key check and returned configuration behavior unchanged.

In `@test/integration/server/webhooks/modifier-refunds.test.ts`:
- Line 1: Update the imports of createServiceChargeScenario in
modifier-refunds.test.ts and modifiers.test.ts to use the established
`#test/lib/server-webhooks/service-charge-scenario.ts` alias instead of the
../../../lib/server-webhooks relative path.

In `@test/shared/stripe-provider/operations.test.ts`:
- Around line 68-91: Replace the manual setupWebhookEndpoint reassignment and
try/finally restoration in the setupWebhookEndpoint test with the file’s
existing stub() helper, and apply the same refactor to createCheckoutSession.
Configure each stub with the current mocked result and let the standard stub
lifecycle handle restoration, removing the origSetup variables and manual
cleanup.

In `@test/shared/stripe/request.test.ts`:
- Around line 243-270: Strengthen the test “returns structured error fields
without exposing the response body” by asserting that the caught error’s message
and relevant observable string representation do not contain the mocked private
value “Private value.” Keep the existing structured-field and error-name
assertions unchanged, using mutation-resistant negative assertions that would
fail if the response body were leaked.

In `@test/ui/templates/admin/attendee-form/quantity.test.ts`:
- Around line 123-137: Update the test “the quantity input uses CSP-safe
styling” to target the quantity input element specifically, then assert that
this element has the line-qty class and does not contain the inline width style.
Replace the broad HTML string checks while preserving the existing page fetch
and mutation-resistant coverage.

---

Outside diff comments:
In `@src/shared/stripe-provider.ts`:
- Around line 114-115: Update the paymentReference assignment in the current
resolver to reuse asString(payment_intent), matching resolveWebhookSession,
instead of returning an empty-string fallback for non-string or missing values.

In `@test/integration/server/payments/purchase.test.ts`:
- Around line 27-30: Remove or update the stale Stripe client reset teardown
comments in the test setup for test/integration/server/payments/purchase.test.ts
lines 27-30 and test/integration/server/payments/success.test.ts lines 27-30;
ensure any remaining comments accurately describe the current code and do not
reference the deleted reset behavior.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bfee2243-8309-4d0e-bedc-b0bfd4a53b11

📥 Commits

Reviewing files that changed from the base of the PR and between d46d098 and 1da72dd.

📒 Files selected for processing (119)
  • .github/workflows/payment-sandbox-e2e.yml
  • e2e-payments/README.md
  • e2e-payments/src/browser.ts
  • e2e-payments/src/config.ts
  • e2e-payments/src/flow.ts
  • e2e-payments/src/main.ts
  • e2e-payments/src/providers/stripe.ts
  • e2e-payments/src/providers/types.ts
  • scripts/stripe-mock.ts
  • src/features/admin/settings-stripe.ts
  • src/features/api/request-schemas.ts
  • src/shared/bulk-email-targets.ts
  • src/shared/payment-helpers.ts
  • src/shared/stripe-provider.ts
  • src/shared/stripe.ts
  • src/shared/stripe/client.ts
  • src/shared/stripe/endpoints.ts
  • src/shared/stripe/mock.ts
  • src/shared/stripe/request.ts
  • src/shared/stripe/runtime.ts
  • src/shared/stripe/schemas.ts
  • src/shared/stripe/webhook.ts
  • src/shared/validation/string.ts
  • src/ui/static/style.scss
  • src/ui/templates/admin/attendee-form.tsx
  • test/e2e/accounting/drivers.ts
  • test/features/admin/settings-stripe.test.ts
  • test/features/api/request-schemas.test.ts
  • test/integration/bulk-email.test.ts
  • test/integration/code-quality.test.ts
  • test/integration/routes/renewal.test.ts
  • test/integration/routes/site-assignment-checkout.test.ts
  • test/integration/server/balance-get.test.ts
  • test/integration/server/balance-payment-replay.test.ts
  • test/integration/server/balance-post.test.ts
  • test/integration/server/balance-webhook.test.ts
  • test/integration/server/bulk-email/previous-bookings.test.ts
  • test/integration/server/package-children.test.ts
  • test/integration/server/payments-success-basic.test.ts
  • test/integration/server/payments-success-refunds.test.ts
  • test/integration/server/payments-success-replay.test.ts
  • test/integration/server/payments/cancel.test.ts
  • test/integration/server/payments/confirm.test.ts
  • test/integration/server/payments/purchase.test.ts
  • test/integration/server/payments/replay.test.ts
  • test/integration/server/payments/success.test.ts
  • test/integration/server/public/can-pay-more-multi.test.ts
  • test/integration/server/public/can-pay-more-single.test.ts
  • test/integration/server/public/daily-listings-multi.test.ts
  • test/integration/server/public/daily-listings-single.test.ts
  • test/integration/server/public/ticket-additional-coverage.test.ts
  • test/integration/server/public/ticket-csrf-and-capacity.test.ts
  • test/integration/server/public/ticket-paid-flow-edge-cases.test.ts
  • test/integration/server/public/ticket-reserved-and-edge-cases.test.ts
  • test/integration/server/reservation/deposit-basics.test.ts
  • test/integration/server/reservation/edge-cases.test.ts
  • test/integration/server/reservation/no-provider.test.ts
  • test/integration/server/reservation/promo-addons.test.ts
  • test/integration/server/reservation/public-default-modifiers.test.ts
  • test/integration/server/webhook-dual-path.test.ts
  • test/integration/server/webhooks/acknowledge-edge-cases.test.ts
  • test/integration/server/webhooks/already-processed-rollback.test.ts
  • test/integration/server/webhooks/can-pay-more-multi-ticket.test.ts
  • test/integration/server/webhooks/can-pay-more-single-ticket.test.ts
  • test/integration/server/webhooks/concurrent-processing.test.ts
  • test/integration/server/webhooks/custom-questions-multi.test.ts
  • test/integration/server/webhooks/custom-questions-single.test.ts
  • test/integration/server/webhooks/customisable-days-pricing.test.ts
  • test/integration/server/webhooks/extract-intent-redirect.test.ts
  • test/integration/server/webhooks/item-validation.test.ts
  • test/integration/server/webhooks/modifier-refunds.test.ts
  • test/integration/server/webhooks/modifiers.test.ts
  • test/integration/server/webhooks/multi-ticket-booking.test.ts
  • test/integration/server/webhooks/multi-ticket-refunds.test.ts
  • test/integration/server/webhooks/price-paid-calculation.test.ts
  • test/integration/server/webhooks/price-signature-hidden-and-standalone.test.ts
  • test/integration/server/webhooks/price-signature-package-overrides.test.ts
  • test/integration/server/webhooks/price-signature-post-commit-recovery.test.ts
  • test/integration/server/webhooks/price-signature-stored-refund-and-ignore.test.ts
  • test/integration/server/webhooks/price-signature-trusted-and-mismatch.test.ts
  • test/integration/server/webhooks/promo-codes.test.ts
  • test/integration/server/webhooks/refund-helper-functions.test.ts
  • test/integration/server/webhooks/refund-logging.test.ts
  • test/integration/server/webhooks/refund-skip-conditions.test.ts
  • test/integration/server/webhooks/registration-closed.test.ts
  • test/integration/server/webhooks/session-resolution.test.ts
  • test/integration/server/webhooks/signature-validation.test.ts
  • test/integration/server/webhooks/single-ticket-booking.test.ts
  • test/integration/server/webhooks/single-ticket-refunds.test.ts
  • test/integration/server/webhooks/sumup.test.ts
  • test/integration/server/webhooks/unrecognized-sessions.test.ts
  • test/integration/stripe-client-parity.test.ts
  • test/integration/stripe-mock-ports.test.ts
  • test/integration/stripe/checkout.test.ts
  • test/integration/stripe/connection.test.ts
  • test/integration/stripe/core.test.ts
  • test/integration/stripe/webhook-config.test.ts
  • test/integration/stripe/webhook-setup.test.ts
  • test/integration/stripe/webhook.test.ts
  • test/integration/test-utils/stubs-and-mocks.test.ts
  • test/lib/stripe/fixtures.ts
  • test/lib/stripe/harness.ts
  • test/lib/stripe/responses.ts
  • test/lib/stripe/webhook-mocks.ts
  • test/shared/bulk-email-targets.test.ts
  • test/shared/payment-helpers/dispatch.test.ts
  • test/shared/stripe-provider.test.ts
  • test/shared/stripe-provider/operations.test.ts
  • test/shared/stripe.test.ts
  • test/shared/stripe/client.test.ts
  • test/shared/stripe/endpoints.test.ts
  • test/shared/stripe/mock.test.ts
  • test/shared/stripe/request.test.ts
  • test/shared/stripe/runtime.test.ts
  • test/shared/stripe/schemas.test.ts
  • test/shared/stripe/webhook.test.ts
  • test/shared/validation/string.test.ts
  • test/test-utils/order-journey.ts
  • test/ui/templates/admin/attendee-form/quantity.test.ts
💤 Files with no reviewable changes (8)
  • test/test-utils/order-journey.ts
  • src/shared/stripe/webhook.ts
  • src/ui/templates/admin/attendee-form.tsx
  • test/integration/server/payments/confirm.test.ts
  • test/integration/server/public/ticket-additional-coverage.test.ts
  • test/integration/bulk-email.test.ts
  • test/integration/server/payments/cancel.test.ts
  • test/integration/code-quality.test.ts

Comment thread e2e-payments/src/main.ts
Comment thread src/shared/payment-helpers.ts Outdated
Comment thread src/shared/stripe/mock.ts
Comment thread src/shared/stripe/request.ts
Comment thread src/shared/stripe/runtime.ts Outdated
Comment thread test/integration/server/webhooks/modifier-refunds.test.ts
Comment thread test/shared/stripe-provider/operations.test.ts
Comment thread test/shared/stripe/request.test.ts Outdated
Comment thread test/ui/templates/admin/attendee-form/quantity.test.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aec331c6c3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/features/admin/settings-stripe.ts Outdated
value,
webhookUrl,
previousEndpointId,
keyChanged ? undefined : previousEndpointId,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the current webhook when rotating keys

When the new secret is another key for the same Stripe account, passing undefined here means setupWebhookEndpoint no longer knows which endpoint is currently live. In the endpoint-limit retry path it lists same-URL endpoints and deletes every ID except the one passed as existingEndpointId; with this value omitted, it can delete the current saved endpoint before the replacement is created and stored, leaving paid bookings without a working webhook if the retry or DB save then fails.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Resolved in ec23855. saveSecret now always passes previousEndpointId to setupWebhookEndpoint, including on a key rotation. Inside setupWebhookEndpoint the endpoint-limit retry path filters the listed same-URL endpoints by id !== existingEndpointId (src/shared/stripe/endpoints.ts:28), so the recorded endpoint stays live until the new one is created — the existing keeps the recorded endpoint live when the cap retry fails regression in test/integration/stripe/webhook-setup.test.ts:110 covers that safety. The post-activate cleanupOldWebhookEndpoints calls then delete the previous endpoint once the replacement is saved. If the new key is on a different Stripe account, passing the old id is a no-op — the list will not return it.

The pre-existing rotationCases test in test/features/admin/settings-stripe.test.ts now asserts existingEndpointId="we_old" for both rotation cases (the key-rotation case previously asserted undefined and would have allowed this bug to slip through), so a regression that drops the recorded id here would fail the test directly.

});

/** Build the versioned Stripe request transport with bounded retries. */
export const createStripeRequest = (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add an explicit transport return type

The repo's AGENTS.md requires exported functions to declare their return type, but this new exported factory infers the returned generic request function from its body. That leaves callers without a stable named contract and makes the checker re-derive this transport shape; add an explicit return type here, ideally via a named request-function type.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No change — leaving as inferred. I tried both the suggested refactor (: StripeRequest + inferred inline parameters) and a typed-const form (assigning transport: StripeRequest with arrow parameters implied by context). Both fail with TS7006: TypeScript cannot infer arrow-function parameter types from a generic contextual type. Annotating with : StripeRequest would force restating the parameter list inline, duplicating the type alias's parameter list verbatim and tripping jscpd's non-negotiable 0% threshold. The sibling ClientRunner<Client> annotation in src/shared/payment-helpers.ts avoids this only because guardedWithValue's impl uses a different type-parameter name (Value) than the alias's Client; no such divergence is possible here since both surfaces reference the same Method / StripeParams / ResponseSchema<T>. The inferred return shape is still a stable contract for callers (client.ts:109 and the request tests rely on it). If a future TypeScript version lifts the contextual-typing limitation for generic arrow functions, the named annotation can land cleanly without parameter-list duplication.

stefan-burke added a commit that referenced this pull request Jul 20, 2026
- Preserve the recorded webhook endpoint across a key-rotation limit-retry
  by always passing previousEndpointId to setupWebhookEndpoint, so a limit
  retry cannot delete the live webhook before its replacement is saved
  (regression test asserts the recorded endpoint id is forwarded in both
  rotation cases).
- Isolate each teardown/failure-report step in the e2e payments runner via
  .catch(() => {}), so a failed session.stop or tunnel.stop cannot leave the
  app-server child alive and hang CI instead of failing cleanly.
- Annotate createWithClient with an exported ClientRunner<Client> type so
  the steel-thread runs (stripeClientRuntime.run) hold a named contract
  instead of leaning on inference.
- Rename runtimeConfig to runtimeConfigOrNull to match the *OrNull naming
  convention for intentional nullable resolvers.
- Rename the inner port variable in stripe-mock port parser to avoid
  shadowing the enclosing function.
- Use #test/lib/server-webhooks/... alias instead of ../... relative path
  in the modifier webhook tests.
- Replace manual reassignment + try/finally with stub() in the
  stripe-provider operations test for consistency with the rest of the file.
- Strengthen the 'returns structured error fields' test by asserting the
  structured fields never carry Stripe's raw response message.
- Scope the CSP-safe-styling test's assertions to the quantity <input>
  element itself rather than the full page HTML.
- Remove the stale 'reset the client after each test' comment from
  success.test.ts (the reset was deleted with the SDK removal).
@stefan-burke

Copy link
Copy Markdown
Member Author

{"body":"Addressed the two "outside diff range" notes from the latest CodeRabbit review summary (these don't get their own inline threads, so following up here):\n\n**src/shared/stripe-provider.ts:114-115payment_intent ?? \"\"\nNo change. payment_intent is validated at the schema boundary by StripeCheckoutSessionSchema (src/shared/stripe/schemas.ts:32) as NonEmptyNullableStringSchema = v.nullable(NonEmptyTextSchema), so the field has already been narrowed to string | null (a non-empty string when present, null otherwise) before toValidatedSession ever runs. The ?? \"\" normalises a genuinely-expected null — a Stripe checkout session for a free / no_payment_required payment has no payment intent — into the canonical empty-string representation for ValidatedPaymentSession.paymentReference. This matches the same convention the codebase uses elsewhere for genuinely-optional boundary values (searchParams.get(key) ?? \"\" in src/features/url.ts, and extractSessionMetadata's get normalisation that turns an absent field into \"\"). The CodeRabbit suggestion to reuse asString(obj.payment_intent) doesn't apply here — that call doesn't exist in src/shared/stripe-provider.ts; resolveWebhookSession parses the session via StripeCheckoutSessionSchema and delegates through toValidatedSession, which uses the same ?? \"\" normalisation as the path that handles payment_intent directly.\n\ntest/integration/server/payments/purchase.test.ts#L27-L30 and success.test.ts#L27-L30 — stale "reset the client after each test" comment**\nResolved in ec23855 for success.test.ts. The stale three-line comment ("Some tests here configure Stripe without installing mocks (so no withMocks cleanup runs); reset the client after each so configuration never leaks into the next test.") lived at test/integration/server/payments/success.test.ts:27-29 below the describe(\"GET /payment/success\", () => { block; the suggested purchase.test.ts#L27-L30 location is unrelated setup code (thankYouUrl, unitPrice) and has no stale comment to remove — I verified by searching for reset | cleanup in that file. The Stripe client reset teardown was deleted with the SDK removal in this PR, so the comment was describing a no-longer-extant afterEach and is removed.\n\n---\n\nFor the inline threads: 9 of the 11 unresolved CodeRabbit items and 1 of the 2 Codex items are addressed in commit ec23855 with replies on each thread; the 1 remaining shared CodeRabbit + Codex item (the request for an explicit StripeRequest return type on createStripeRequest) is intentionally not applied — see the replies on those two threads for the TypeScript contextual-typing limitation and jscpd 0% threshold that prevent the annotation landing without parameter-list duplication. deno task precommit is green."
}

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ec23855fb3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread e2e-payments/src/providers/stripe.ts Outdated
const event = stripeCheckoutEvents(await response.json()).find(
({ checkoutId }) => checkoutId === sessionId,
);
return event?.pendingWebhooks === 0 ? event : null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid using account-wide webhook pending count

When the Stripe sandbox account has any stale enabled webhook endpoint from a canceled/failed run, this waits for pending_webhooks to become 0 even though Stripe defines that field as pending deliveries to all configured URLs, not just this run's endpoint (https://docs.stripe.com/api/events/object). Because the stale trycloudflare endpoints are only cleaned in finally after this check, an orphaned endpoint can keep the nightly Stripe leg failing even after the app's current webhook has processed the checkout.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Resolved in d6dd985. waitForStripeWebhook no longer gates on pending_webhooks === 0; it now waits for the checkout.session.completed event to exist for the session id. By the time this wait runs, the caller (assertPaidBookingConfirmed in main.ts:145) has already verified the booking is confirmed in the app, which means our own webhook endpoint has already received and processed the event. The previous pending_webhooks === 0 check was account-wide and could stall the nightly Stripe leg on a stale enabled endpoint left by an earlier cancelled/failed run — cleanup only deletes those stale endpoints in finally AFTER this wait, so the wait would never satisfy.

The removed pendingWebhooks field was also dead code on the type (only populated, never read after the check was dropped), so it was deleted from StripeCheckoutEvent and the parsing function per the house rule on dead code.

Note: the e2e-payments package is intentionally typecheck-only (no in-process test layer) per the codebase convention, so the only end-to-end surface for this code is the nightly sandbox run linked in the PR description's verification section.

const requested = response.headers.get("stripe-should-retry");
if (requested === "false") return false;
if (requested === "true") return true;
return response.status === 409 || response.status >= 500;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Retry Stripe lock timeouts

When Stripe returns HTTP 429 with code=lock_timeout during object contention, Stripe documents these as intermittent errors that should be retried and notes that its SDK retry mechanisms handle lock-timeout 429s (https://docs.stripe.com/rate-limits#object-lock-timeouts). This fallback only retries 409/5xx unless Stripe-Should-Retry is present, so replacing stripe-node can make a retryable refund or PaymentIntent lookup fail immediately instead of using the configured retry budget.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Resolved in d6dd985. The transport now retries 429 responses whose body has error.code === "lock_timeout" or error.type === "lock_timeout", per Stripe's rate-limits docs (https://docs.stripe.com/rate-limits#object-lock-timeouts). The check clones the response before reading the body, so the original body stays readable for the error path if the check returns false (no retry). Vanilla 429 rate-limits still only retry when Stripe asks via its stripe-should-retry: true header — compounding a real rate-limit with retries would make the limit worse.

Three regression tests cover the new path in test/shared/stripe/request.test.ts: (1) retries a 429 lock_timeout (object contention) mocks a 429 with error.code === "lock_timeout" then a 200, asserts the request retried once and succeeded; (2) does not retry a 429 rate-limit without lock_timeout mocks a 429 with error.code === "rate_limit", asserts a StripeApiError without retrying; (3) does not retry a 429 whose body cannot be parsed as JSON covers the catch branch in isLockTimeoutResponse, asserts a StripeProtocolError ("Invalid JSON received from the Stripe API"). deno task precommit (including 100% line/branch coverage) passes.

stefan-burke added a commit that referenced this pull request Jul 20, 2026
CodeRabbit follow-up:
- Guard notifyFailure(target) with .catch(() => {}) in the e2e payments
  runner so a future change to the helper can never replace the original
  journey error before rethrow.

Codex (new comments):
- Retry Stripe 429 lock_timeout responses (object contention) per Stripe's
  rate-limits docs. The transport now clones the response, parses the body
  on a 429, and retries if error.code or error.type is 'lock_timeout'.
  Vanilla 429 rate-limits still only retry when Stripe asks via its
  stripe-should-retry header. Regression tests cover the retryable
  lock_timeout, the non-retryable rate-limit, and a 429 with unparseable
  JSON body.
- Stop gating the e2e-payments Stripe webhook wait on
  pending_webhooks === 0. That counter is account-wide, so a stale enabled
  webhook endpoint left by an earlier canceled/failed run (only cleaned in
  finally after this wait) would keep the nightly Stripe leg failing even
  though the app's own webhook had already been delivered and the booking
  confirmed. The wait now gates on the checkout.session.completed event
  existing for the session id, which is the actual signal we need. Removed
  the now-dead pendingWebhooks field from StripeCheckoutEvent.

CodeRabbit follow-up (request.test.ts): narrow the structured-error-fields
comment to explicitly exclude error.message, which intentionally mirrors
Stripe's raw error.message for stripe-node parity; the privacy boundary
lives one layer up in sanitizeStripeError, which never logs it.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d6dd985eb0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread e2e-payments/src/providers/stripe.ts Outdated
Comment on lines +115 to +118
const event = stripeCheckoutEvents(await response.json()).find(
({ checkoutId }) => checkoutId === sessionId,
);
return event ?? null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Verify delivery, not just event creation

When the Stripe leg returns via /payment/success, handlePaymentSuccess processes the session directly when a session_id is present (src/features/api/webhooks.ts lines 212-216), so the preceding assertPaidBookingConfirmed can pass without any webhook POST being delivered. This poll only looks for the session's event in Stripe's Events API; every completed checkout creates that event even if our registered endpoint 404s or rejects the signature. As a result the nightly Stripe leg can report that webhook delivery/signature works while the actual webhook path is broken; check a delivery attempt to this run's endpoint, or an app-side webhook marker, rather than just event existence.

Useful? React with 👍 / 👎.

Comment thread scripts/mutation/equivalent-mutants.txt Outdated
# Stripe request null checks: both values are typed as string|null, never undefined,
# so strict and loose null equality agree for every possible input.
src/shared/stripe/request.ts:89:17 === → == # retryAfter is string|null; both comparisons are true only for null
src/shared/stripe/request.ts:123:15 === → == # Headers.get returns string|null; both comparisons are true only for null

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Move the Stripe ignore entry to its live line

When precommit:mutation mutates src/shared/stripe/request.ts, the ignore-list checker validates entries for that file against generated mutants and fails stale ones. This entry points at line 123, but the Headers.get null check it describes is now at line 151, so a mutation run for the newly added Stripe transport reports this as stale (and the actual survivor is not suppressed). Update the recorded location or add a test that kills the mutant.

Useful? React with 👍 / 👎.

Comment thread src/shared/stripe/endpoints.ts Outdated
existingEndpointId,
) => {
try {
const client = stripeClientRuntime.create(secretKey, 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use the Stripe retry budget for webhook setup

When saving Stripe settings hits a transient Stripe/network failure while creating the webhook endpoint, this 0 disables the retry handling implemented in the new transport for 409/5xx and connection errors. The replaced Stripe SDK client used the configured retry budget for this setup call, and the custom transport already reuses one idempotency key across POST retries, so a single retryable blip can now make credential setup fail instead of recovering. Let this use the default retry count unless a specific non-retryable setup path is required.

Useful? React with 👍 / 👎.

stefan-burke added a commit that referenced this pull request Jul 21, 2026
- Preserve the recorded webhook endpoint across a key-rotation limit-retry
  by always passing previousEndpointId to setupWebhookEndpoint, so a limit
  retry cannot delete the live webhook before its replacement is saved
  (regression test asserts the recorded endpoint id is forwarded in both
  rotation cases).
- Isolate each teardown/failure-report step in the e2e payments runner via
  .catch(() => {}), so a failed session.stop or tunnel.stop cannot leave the
  app-server child alive and hang CI instead of failing cleanly.
- Annotate createWithClient with an exported ClientRunner<Client> type so
  the steel-thread runs (stripeClientRuntime.run) hold a named contract
  instead of leaning on inference.
- Rename runtimeConfig to runtimeConfigOrNull to match the *OrNull naming
  convention for intentional nullable resolvers.
- Rename the inner port variable in stripe-mock port parser to avoid
  shadowing the enclosing function.
- Use #test/lib/server-webhooks/... alias instead of ../... relative path
  in the modifier webhook tests.
- Replace manual reassignment + try/finally with stub() in the
  stripe-provider operations test for consistency with the rest of the file.
- Strengthen the 'returns structured error fields' test by asserting the
  structured fields never carry Stripe's raw response message.
- Scope the CSP-safe-styling test's assertions to the quantity <input>
  element itself rather than the full page HTML.
- Remove the stale 'reset the client after each test' comment from
  success.test.ts (the reset was deleted with the SDK removal).
stefan-burke added a commit that referenced this pull request Jul 21, 2026
CodeRabbit follow-up:
- Guard notifyFailure(target) with .catch(() => {}) in the e2e payments
  runner so a future change to the helper can never replace the original
  journey error before rethrow.

Codex (new comments):
- Retry Stripe 429 lock_timeout responses (object contention) per Stripe's
  rate-limits docs. The transport now clones the response, parses the body
  on a 429, and retries if error.code or error.type is 'lock_timeout'.
  Vanilla 429 rate-limits still only retry when Stripe asks via its
  stripe-should-retry header. Regression tests cover the retryable
  lock_timeout, the non-retryable rate-limit, and a 429 with unparseable
  JSON body.
- Stop gating the e2e-payments Stripe webhook wait on
  pending_webhooks === 0. That counter is account-wide, so a stale enabled
  webhook endpoint left by an earlier canceled/failed run (only cleaned in
  finally after this wait) would keep the nightly Stripe leg failing even
  though the app's own webhook had already been delivered and the booking
  confirmed. The wait now gates on the checkout.session.completed event
  existing for the session id, which is the actual signal we need. Removed
  the now-dead pendingWebhooks field from StripeCheckoutEvent.

CodeRabbit follow-up (request.test.ts): narrow the structured-error-fields
comment to explicitly exclude error.message, which intentionally mirrors
Stripe's raw error.message for stripe-node parity; the privacy boundary
lives one layer up in sanitizeStripeError, which never logs it.
- Preserve the recorded webhook endpoint across a key-rotation limit-retry
  by always passing previousEndpointId to setupWebhookEndpoint, so a limit
  retry cannot delete the live webhook before its replacement is saved
  (regression test asserts the recorded endpoint id is forwarded in both
  rotation cases).
- Isolate each teardown/failure-report step in the e2e payments runner via
  .catch(() => {}), so a failed session.stop or tunnel.stop cannot leave the
  app-server child alive and hang CI instead of failing cleanly.
- Annotate createWithClient with an exported ClientRunner<Client> type so
  the steel-thread runs (stripeClientRuntime.run) hold a named contract
  instead of leaning on inference.
- Rename runtimeConfig to runtimeConfigOrNull to match the *OrNull naming
  convention for intentional nullable resolvers.
- Rename the inner port variable in stripe-mock port parser to avoid
  shadowing the enclosing function.
- Use #test/lib/server-webhooks/... alias instead of ../... relative path
  in the modifier webhook tests.
- Replace manual reassignment + try/finally with stub() in the
  stripe-provider operations test for consistency with the rest of the file.
- Strengthen the 'returns structured error fields' test by asserting the
  structured fields never carry Stripe's raw response message.
- Scope the CSP-safe-styling test's assertions to the quantity <input>
  element itself rather than the full page HTML.
- Remove the stale 'reset the client after each test' comment from
  success.test.ts (the reset was deleted with the SDK removal).
CodeRabbit follow-up:
- Guard notifyFailure(target) with .catch(() => {}) in the e2e payments
  runner so a future change to the helper can never replace the original
  journey error before rethrow.

Codex (new comments):
- Retry Stripe 429 lock_timeout responses (object contention) per Stripe's
  rate-limits docs. The transport now clones the response, parses the body
  on a 429, and retries if error.code or error.type is 'lock_timeout'.
  Vanilla 429 rate-limits still only retry when Stripe asks via its
  stripe-should-retry header. Regression tests cover the retryable
  lock_timeout, the non-retryable rate-limit, and a 429 with unparseable
  JSON body.
- Stop gating the e2e-payments Stripe webhook wait on
  pending_webhooks === 0. That counter is account-wide, so a stale enabled
  webhook endpoint left by an earlier canceled/failed run (only cleaned in
  finally after this wait) would keep the nightly Stripe leg failing even
  though the app's own webhook had already been delivered and the booking
  confirmed. The wait now gates on the checkout.session.completed event
  existing for the session id, which is the actual signal we need. Removed
  the now-dead pendingWebhooks field from StripeCheckoutEvent.

CodeRabbit follow-up (request.test.ts): narrow the structured-error-fields
comment to explicitly exclude error.message, which intentionally mirrors
Stripe's raw error.message for stripe-node parity; the privacy boundary
lives one layer up in sanitizeStripeError, which never logs it.
@stefan-burke
stefan-burke added this pull request to the merge queue Jul 21, 2026
Merged via the queue into main with commit 7563691 Jul 21, 2026
3 checks passed
@stefan-burke
stefan-burke deleted the replace-stripe-sdk branch July 21, 2026 16:11
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