feat(stripe-plugin)!: modernize for Stripe SDK 22 / dahlia API (OSS-555 items 1-3, 6)#36
feat(stripe-plugin)!: modernize for Stripe SDK 22 / dahlia API (OSS-555 items 1-3, 6)#36michaelbromley wants to merge 12 commits into
Conversation
Bumps the stripe peer + dev dependency to ^22 and fixes the TypeScript fallout. v22's CJS+NodeNext type exports are currently broken for namespace access (Stripe.PaymentIntent etc. resolve to a stub StripeConstructor namespace; see stripe/stripe-node#2683 / #2725), so a local stripe-types.ts shim extracts the relevant types from the SDK's method signatures. Once #2725 ships in a release we pin, the shim can be deleted and the rest of the plugin can return to direct namespace access.
The Stripe client now defaults to the SDK's pinned API version (currently 2026-05-27.dahlia) instead of sending no version header. This keeps the API responses aligned with the SDK's TypeScript types. A new `apiVersion` plugin option lets merchants override this: pass a specific version string to pin to it, or `null` to fall back to the Stripe account's default version (the pre-2.0.0 behaviour). Existing integrations that relied on the account default need to set `apiVersion: null` explicitly. BREAKING CHANGE: The default Stripe API version is now the SDK's pinned version rather than the account default. Set `apiVersion: null` to restore the previous behaviour.
The Vendure refund input's `reason` is now passed through to Stripe: mapped to the structured `reason` field when it matches one of Stripe's accepted enum values (`duplicate`, `fraudulent`, `requested_by_customer`), otherwise stored on the refund metadata as `vendureRefundReason` so it isn't lost. `refunds.create` is now also called with an idempotency key derived from the payment intent ID and refund amount, matching the parity already in place for payment intent creation.
Two changes that together let the existing 18 tests pass on the new SDK + mocking stack: 1. Switch VendureStripeClient to Stripe.createFetchHttpClient() rather than the default NodeHttpClient. The default client waits for the socket's secureConnect event before writing the request body; nock@14 (via @mswjs/interceptors) only emits secureConnect once a request has been matched, so body and reply deadlock. The fetch client side-steps the dance entirely. 2. e2e/stripe-payment.e2e-spec.ts switches to the default Stripe import and drops the hard-coded apiVersion: '2023-08-16' on the webhook helper. The mock payloads still send api_version: '2022-11-15' in their body — that's opaque to the controller.
…t path works
The dev server imported @vendure/admin-ui-plugin (not present in this
workspace) and old codegen exports (CREATE_PAYMENT_METHOD,
ADD_ITEM_TO_ORDER, CREATE_*_STRIPE_PAYMENT_INTENT) that no longer
exist on this branch — the operations are now gql.tada documents in
e2e/graphql/.
Switch to:
- testConfig from e2e-common (it's a factory function, unlike the
@vendure/testing re-export which is a static value),
- the *Document exports from e2e/graphql/*,
- GraphiqlPlugin in place of AdminUiPlugin so the manual walkthrough
has a query surface,
- an inline createCustomStripePaymentIntentDocument for the test
plugin's mutation (no helper exists for it).
Unblocks the OSS-555 manual walkthrough against a real Stripe test
account.
…hatch Default behaviour is unchanged — the plugin still configures Stripe with FetchHttpClient. The new `httpClient` plugin option lets users inject a NodeHttpClient with a custom http.Agent for proxying or keep-alive tuning, which the FetchHttpClient default cannot express. This is the production safety valve called out in the PR's confidence plan against the HTTP transport switch.
While writing the apiVersion confidence test I confirmed that stripe-node
(both v13 and v22) coerces a null/undefined apiVersion to its own pinned
default via `props.apiVersion || DEFAULT_API_VERSION` in stripe.core.js.
There is no way on stripe-node v22 to instruct the SDK to omit the
Stripe-Version header and fall back to the Stripe account's default API
version — which is what the previous "// Use accounts default version"
comment in stripe-client.ts incorrectly claimed.
Reflect reality in the API:
- apiVersion is typed `StripeLatestApiVersion | undefined`, not nullable.
- The CHANGELOG now states the pre-2.0.0 behaviour was *already* the SDK
pinned version (just an older one), and the only escape hatch is
pinning to a specific older API version string.
Also export StripePluginOptions, StripeHttpClient, and StripeLatestApiVersion
from the public entry point so consumers can type their config without
reaching into the package internals.
BREAKING CHANGE: The apiVersion option no longer accepts null. If you
previously set `apiVersion: null`, drop it (same effect) or pin to a
specific older version string.
Three new test groups, none requiring a real Stripe account:
1. VendureStripeClient configuration:
- Stripe-Version header is the SDK's pinned version by default.
- Stripe-Version header takes an explicit override string.
2. createRefund:
- Stripe-enum reason ("requested_by_customer") goes to the structured
`reason` field.
- Non-enum reason goes to `metadata.vendureRefundReason`.
- No reason → neither field is set.
- Idempotency-Key header equals `refund_{paymentIntentId}_{amount}`.
These are the confidence tests listed in the PR's pre-merge plan and
pin the behaviour change introduced by `feat(stripe-plugin): forward
refund reason and add idempotency key`.
src/__type-checks.ts uses compile-time `Extends<>` assertions to pin the specific fields the plugin reads/writes against the shimmed types in stripe-types.ts. If a future stripe-node release reshapes the underlying method signatures (e.g. by tightening overloads), the shimmed types extracted via `Parameters<>` / `ReturnType<>` can drift silently — this file makes that drift fail the build instead of the runtime. Verified the tripwire fires by temporarily asserting StripeLatestApiVersion extends `number`; tsc errored with "Type 'false' does not satisfy the constraint 'true'" pointing at the right line. Delete this file when stripe/stripe-node#2725 ships and the shim itself can be deleted.
Pre-merge 5 — stripe@13.11.0 vs stripe@22.2.0 d.ts diff for fields the plugin reads/writesPulled
The only real change worth a note
( ConclusionThe "default API version moves 2023-08-16 → 2026-05-27.dahlia" risk listed in the confidence plan is materially smaller than feared: the type shapes the plugin reads/writes are unchanged across the entire SDK and API-version span, modulo one narrowed enum the plugin doesn't use. |
CI enforces no-floating-promises, no-non-null-assertion, and
no-unnecessary-type-assertion as errors; my local pre-flight only
treated them as warnings.
- dev-server: wrap top-level IIFE with `void`, swap
`process.env.STRIPE_*\!` for `?? ''` (Stripe will throw at first
call if the env is missing, which is the same failure mode).
- spec: drop the unnecessary `\!` on `orders.items[0]`.
- __type-checks: move the `Stripe` value import to the head of the
file alongside the type imports.
Remaining changes are eslint --fix auto-applied import-order
reshuffles in stripe.service.ts and the gql.tada graphql-{admin,shop}
files. All commits prior to this one stay green; the full monorepo
lint now passes end-to-end.
Closes OSS-555 — scoped to items 1, 2, 3, 6 (pure modernization). Items 4, 5, 7 (async/processing webhook, dashboard refund + dispute sync, SetupIntents) are deferred to follow-up issues.
What's in this PR
bdc903513.x→^22.0.0peer + dev. Localsrc/stripe-types.tsshim works around stripe-node#2683 (CJS NodeNext default import doesn't expose namespace types).e58cc1bapiVersiondefaults to the SDK pinned version (2026-05-27.dahlia). New plugin option lets callers pin a specific version or setnullto fall back to the Stripe account default (pre-2.0.0 behaviour).392a493nock13 → 14,rimraf5 → 6.typescriptleft at 5.8.2 (monorepo-pinned).6115123createRefundforwards Vendure's refundreasonto Stripe (mapped to the enum where possible, otherwise stored onmetadata.vendureRefundReason) and adds an idempotency key.e0a6606VendureStripeClienttoStripe.createFetchHttpClient(). nock 14 (via@mswjs/interceptors) deadlocks Stripe'sNodeHttpClient— see test plan below.a1381fcfeat!on the apiVersion commit triggers the 2.0.0 major bump via the release flow.Verified
bun run buildclean across all 8 packages.bun run lintclean (0 errors, 12 pre-existing warnings).bun run e2einstripe-plugin: 18/18 pass.Not test-covered — needs reviewer eyeball
These three behavioural changes are not covered by the existing e2e suite (the suite mocks Stripe entirely via nock):
node:http(s)tofetch. Production users who inject a customhttp.Agent(proxy, keep-alive tuning) will silently lose it. AnhttpClientescape hatch is planned as a follow-up commit on this PR.dahlia. Stripe API responses on this account will now be shaped per the 2026-05-27 API. Per the Stripe changelog the corePaymentIntent/Refund/Charge/Customershapes are stable between2023-08-16anddahlia, but this is unverified against a live account.metadata.vendureRefundReason, not the structuredreasonfield.Confidence plan
1. Risk × evidence matrix
2023-08-16→2026-05-27.dahlia(~3 years of evolution)2. Pre-merge actions (in order)
3. Post-merge
4. Remaining assumptions after this plan
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.