Provider compatibility

Read one version-pinned SDK workflow, its negative case, and the exact claim that workflow supports.

Install the client version exercised by this repository:

pnpm add stripe@22.5.0

Point the official client at one instance-scoped URL. This adapter is the complete first-party example; maxNetworkRetries: 0 keeps retry behavior outside the claim.

src/local-stripe.ts
import { createStripeSdkFetch } from "@localhost2137/stripe";
import Stripe from "stripe";

export interface LocalStripeConnection {
	readonly apiUrl: string;
	readonly secretKey: string;
}

/** Builds the official Stripe SDK against one localhost2137 instance-scoped account. */
export function createLocalStripe(connection: LocalStripeConnection): Stripe {
	return new Stripe(connection.secretKey, {
		httpClient: Stripe.createFetchHttpClient(createStripeSdkFetch(connection.apiUrl)),
		maxNetworkRetries: 0,
	});
}

The checked workflow uses normal SDK calls for a success, a provider-shaped error, and a later state transition:

test/subscription.test.ts
import { createTestRuntime } from "localhost2137/testing";
import { afterEach, describe, expect, it } from "vitest";
import config from "../localhost.config.js";
import { createLocalStripe } from "../src/local-stripe.js";

const runtimes: Array<Awaited<ReturnType<typeof createTestRuntime>>> = [];

afterEach(async () => {
	await Promise.all(runtimes.splice(0).map((runtime) => runtime.close()));
});

describe("official Stripe SDK", () => {
	it("creates and renews a subscription through normal SDK calls", async () => {
		const runtime = await createTestRuntime({ config, port: 0, storage: "temporary" });
		runtimes.push(runtime);
		const instance = await runtime.createInstance();
		try {
			const product = await instance.stripe.createProduct({ name: "Pro" });
			const price = await instance.stripe.createPrice({
				productId: product.id,
				unitAmount: 2_500,
			});
			const client = createLocalStripe(instance.stripe.connection);

			const customer = await client.customers.create({
				email: "ada@example.test",
				name: "Ada",
			});
			await expect(client.customers.retrieve("cus_missing")).rejects.toMatchObject({
				code: "customer_missing",
				statusCode: 404,
				type: "StripeInvalidRequestError",
			});
			const subscription = await client.subscriptions.create({
				customer: customer.id,
				items: [{ price: price.id }],
			});
			expect(subscription).toMatchObject({
				customer: customer.id,
				id: "sub_000001",
				latest_invoice: "in_000001",
				status: "active",
			});

			await instance.clock.advance("30d");
			const invoices = await client.invoices.list({
				limit: 10,
				subscription: subscription.id,
			});
			expect(invoices.data.map(({ id }) => id)).toEqual(["in_000001", "in_000002"]);
			expect(invoices.data[1]).toMatchObject({
				amount_paid: 2_500,
				customer: customer.id,
				paid: true,
				subscription: subscription.id,
			});
		} finally {
			await instance.destroy();
		}
	});
});
pnpm --filter @localhost2137/example-stripe-sdk test

The exact claim

With Stripe Node 22.5.0, this checked path establishes all of the following together:

  • the SDK can create a customer and an active subscription through the local adapter;
  • retrieving one missing customer becomes a 404 StripeInvalidRequestError with the documented local customer_missing code;
  • advancing the instance clock by 30 days produces one additional paid invoice;
  • the SDK can filter invoices by that subscription and parse the returned objects.

The test uses privileged operations only to arrange a product and price. It does not claim that the application-facing Stripe API created those two resources.

What this does not establish

The workflow says nothing about customer listing, subscription cancellation, another Stripe SDK version, every field on the returned objects, webhook delivery, retry policy, pagination, external account configuration, or behavior on Stripe's servers.

Do not turn “unverified” into “unsupported.” Unsupported behavior is deliberately rejected or omitted. Unverified behavior simply has no claim here. Check the Stripe support reference and add an application-level test for every extra path your code depends on.

Read compatibility in layers

LayerEvidence to look for
Routing and transportExact methods, paths, query or body encoding, authentication, status, and error envelope
DataFields, defaults, IDs, validation, ordering, and pagination used by the application
StateThe sequence connecting one request to later reads or writes
Time and deliveryClock transitions, event identity, signatures, retries, duplicates, and ordering
ClientThe locked SDK or adapter version that exercised the path

An endpoint name is not a compatibility claim. A passing happy path does not establish its errors. A correct error envelope does not establish retry behavior. Keep each statement no broader than its executable evidence.

The generic plugin contract verifies lifecycle, isolation, persistence, routing, and runtime integration. Provider fidelity comes from plugin-specific protocol, SDK, state, event, and negative tests. Operations and emulated APIs explains why control operations and provider routes are separate evidence; Choose the right test boundary explains which claims still need provider-facing checks.