stripe 20.4.1 -> 22.4.0, with the API version pinned first - #244
Open
Jan0707 wants to merge 2 commits into
Open
Conversation
`new Stripe(key)` sends whichever API version the SDK happens to bundle, so the version this app talks to has been an invisible property of a transitive default rather than a decision. That matters for the pending 20.4.1 -> 22.4.0 upgrade: SDK 20.4.1 bundles 2026-02-25.clover and 22.4.0 bundles 2026-07-29.dahlia, so bumping the library would silently move checkout and webhooks onto a different Stripe API version — a payment-behaviour change disguised as a dependency bump. Pinning the current value first makes that separable. Verified as a no-op: the SDK's own default is read from node_modules and matches the pinned string exactly, so nothing about today's behaviour changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JkKMceYAzAYLrSyC42FWTf
The API version is pinned in the previous commit, so this bump changes the library and provably nothing on the wire. Without that, it would have moved checkout and webhooks from 2026-02-25.clover to 2026-07-29.dahlia — a different Stripe API major — with nothing in the diff saying so. The pin now needs a cast: SDK 22 types apiVersion as the single literal it bundles, so holding clover cannot type-check. That TS2322 was the *only* type error the upgrade produced, which is the useful part — our code is already dahlia-compatible. The cost is recorded in the file: TypeScript no longer rejects a nonsense version string on that line. Checked dahlia's breaking changes against the params we actually send, not the changelog summary. dynamic_tax_rates and the twint enum are both unused; everything else is in Issuing, PaymentRecord, Radar and V2 Core, which this app never calls. SDK 22's own breaking changes are clear too: we already use new Stripe(), pass params as a single first argument, and use no callbacks, host overrides or dropped type aliases. Adds test/stripeWebhook.test.ts (9 cases). SDK 21 added "throw an error when using the wrong webhook parsing method", and the webhook is the one silent failure in this app: if verification breaks, the site keeps taking money and stops issuing tickets, with nothing a gate or a visitor would notice. The tests sign real payloads with Stripe's own helper and capture the outgoing request with a fake fetch — no network, no keys. One is a negative control proving the pin does work: an unpinned client sends a different version. Also probed the built bundle under NODE_ENV=production, because Pinia 4 passed every gate and failed only on a real request. A forged signature returns 400, a valid one 200. 100 tests pass, lint 0 errors, ratchet steady at 263, audit 0 vulnerabilities, build exit 0, all on Node 24.19.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JkKMceYAzAYLrSyC42FWTf
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
There was a problem hiding this comment.
Pull request overview
This PR upgrades the Nuxt app’s Stripe SDK from 20.x to 22.4.0 while explicitly pinning the Stripe API version to prevent a silent API-version migration during the dependency bump. It also adds focused Vitest coverage around webhook signature verification and Checkout Session request construction to guard against the primary “silent failure” mode in the payment path.
Changes:
- Pin Stripe API version via
apiVersionwhen constructing the Stripe client. - Upgrade
stripedependency to^22.4.0(with corresponding lockfile updates). - Add a new Vitest suite to validate webhook signature verification behavior and to assert the pinned
Stripe-Versionheader is sent on checkout requests.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
nuxt-app/server/utils/stripe.ts |
Pins Stripe API version and centralizes Stripe client construction. |
nuxt-app/test/stripeWebhook.test.ts |
Adds tests for webhook signature verification and checkout request/header behavior under Stripe SDK 22. |
nuxt-app/package.json |
Bumps stripe dependency to ^22.4.0. |
nuxt-app/package-lock.json |
Updates lockfile for the Stripe bump and transitive dependency metadata changes. |
docs/dependency-upgrade-plan.md |
Documents the Stripe upgrade approach and follow-up plan for clover → dahlia. |
Files not reviewed (1)
- nuxt-app/package-lock.json: Generated file
Suppressed comments (1)
nuxt-app/test/stripeWebhook.test.ts:178
- This assertion checks the decoded request body, so what it really guarantees is that
{CHECKOUT_SESSION_ID}is preserved after form decoding (i.e. not double-URL-encoded). The current test name/comment reads like it is asserting the raw wire encoding, which is confusing forapplication/x-www-form-urlencoded.
it('keeps the {CHECKOUT_SESSION_ID} placeholder unescaped for Stripe to substitute', async () => {
const { sent } = await capture(PARAMS)
expect(sent.body).toContain('success_url=https://example.com/success?session_id={CHECKOUT_SESSION_ID}')
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+69
to
+71
| it('accepts a Buffer body, which is what the raw request provides', () => { | ||
| const buffer = Buffer.from(EVENT, 'utf8') | ||
| const event = stripe.webhooks.constructEvent(buffer, sign(EVENT), SECRET) |
| // bundles, so pinning any other version cannot type-check — the SDK is on `2026-07-29.dahlia` while we | ||
| // deliberately still talk `2026-02-25.clover`. The cost is that TypeScript no longer rejects a | ||
| // nonsense version string here, so treat this line as hand-checked against Stripe's changelog. | ||
| export const STRIPE_API_VERSION = '2026-02-25.clover' as Stripe.LatestApiVersion |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes Phase 5. This was the last item, and it was waiting on a payment-path review — which pinning the API version largely removes the need for. See the last section.
The plan item said "check the pinned API version". There wasn't one
server/utils/stripe.tscallednew Stripe(config.stripeSecretKey)with no options, so the SDK sent whichever version it bundled:2026-02-25.clover2026-07-29.dahliaSo this was never "a library upgrade that happens to touch types" — it would have silently moved checkout and webhooks onto a different Stripe API major, with nothing in the diff saying so.
Two commits, and the order is the point
Pin the Stripe API version explicitly— setsapiVersion: '2026-02-25.clover'. Verified as a no-op by reading the SDK's own default out ofnode_modulesand comparing: identical strings, so nothing about today's behaviour changes.stripe 20.4.1 -> 22.4.0— the bump, with the wire held constant by the pin.What is left for a reviewer is now one line,
clover→dahlia, rather than a version bump with an API migration hidden inside it.The cast is the price of that split
SDK 22 types
apiVersionas the single literal it bundles, so holding clover cannot type-check:That was the only type error the entire upgrade produced — which is itself the useful finding: our code is already dahlia-compatible. Resolved with
as Stripe.LatestApiVersion, and the cost is written into the file: TypeScript no longer rejects a nonsense version string there.Named honestly: types now describe dahlia while the wire speaks clover. That cannot break existing calls, but it would let TypeScript accept a dahlia-only field that clover rejects at runtime. It is the argument for doing the dahlia move soon rather than eventually.
Checked against the API surface, not the changelog summary
The app's entire Stripe surface is
checkout.sessions.create,webhooks.constructEvent, and theStripe.Event/Stripe.Checkout.Sessiontypes. Each dahlia breaking change against the paramsbuildStripeSessionParamsactually sends:dynamic_tax_ratesremoved fromline_items[]twint.setup_future_usageenum widenedSDK 22's own breaking changes are clear too: we already use
new Stripe(), pass params as a single first argument, and use no callbacks, no per-request host override, and none of the dropped type aliases.Nine new tests, because the webhook is the one silent failure
tickets/webhook.post.tsis how a paid order becomes a fulfilled order. If verification breaks, the site keeps taking money and stops issuing tickets — and no gate and no visitor would see it. SDK 21 added "throw an error when using the wrong webhook parsing method", so that was the change to worry about.test/stripeWebhook.test.ts, no network and no real keys (Stripe's owngenerateTestHeaderStringandcreateFetchHttpClient):metadata.order_idBufferbody works, which is whatreadRawBodyprovidesStripe-Version: 2026-02-25.clover— proof the pin reaches the wiremetadata[order_id]intact under SDK 22's rewritten argument parsing{CHECKOUT_SESSION_ID}survives unescaped for Stripe to substituteThe signature tests are mutually validating rather than individually trusted: the same payload and secret verify, and each single perturbation fails, so the signing helper cannot be a no-op.
And a runtime probe, because of Pinia
Pinia 4 passed every local gate and failed only on a real request under
NODE_ENV=production, because bundling changed how it initialised. SDK 22 reworked its entry points and madeStripea true ES6 class — same class of risk, and a green build says nothing about it.Booted
.output/server/index.mjsunderNODE_ENV=productionwith dummy credentials and posted to/api/tickets/webhook:t=1,v1=deadbeefInvalid signature{"received":true}Both were needed — an endpoint that rejects everything passes the first, one that verifies nothing passes the second.
Verification
npm test100 passing / 11 files (was 91/10) ·prettier:checkclean ·lint0 errors, 127 warnings · typecheck ratchet steady at 263 · build exit 0 ·npm audit0 vulnerabilities · plus the production-bundle probe. All on Node 24.19.0.What still wants your colleague
Moving
clover→dahlia. It is now one line, it deletes the cast, and it removes the types/wire mismatch. The evidence above says it is a no-op for this app's surface — but "the changelog says our fields are untouched" is not the same as someone who knows the checkout flow confirming it. That is the review this item was waiting for, now reduced to a single decision instead of being bundled with an SDK major. Logged in the backlog with a note that two tests namingcloverare designed to fail when it moves.🤖 Generated with Claude Code
https://claude.ai/code/session_01JkKMceYAzAYLrSyC42FWTf