Skip to content

feat(stripe): Stripe provider with 35 resources - #1280

Closed
Mkassabov wants to merge 1 commit into
mainfrom
claude/stripe-alchemy-resources-c2b5de
Closed

feat(stripe): Stripe provider with 35 resources#1280
Mkassabov wants to merge 1 commit into
mainfrom
claude/stripe-alchemy-resources-c2b5de

Conversation

@Mkassabov

Copy link
Copy Markdown
Contributor

Adds alchemy/Stripe — 35 resources with providers, tests, and generated API docs.

export default Alchemy.Stack(
  "Billing",
  { providers: Stripe.providers(), state: Alchemy.localState() },
  Effect.gen(function* () {
    const product = yield* Stripe.Product("pro", { name: "Pro" });
    const price = yield* Stripe.Price("pro-monthly", {
      productId: product.productId,
      currency: "usd",
      unitAmount: 2000,
      recurring: { interval: "month" },
    });
    const hook = yield* Stripe.WebhookEndpoint("events", {
      url: "https://api.example.com/stripe",
      enabledEvents: ["invoice.paid", "customer.subscription.deleted"],
    });
    return { priceId: price.priceId, secret: hook.secret };
  }),
);

Resources

Billing & catalog: Product, Price, Plan, Coupon, PromotionCode, TaxRate, ShippingRate, PaymentLink, Customer, CustomerTaxId, CreditGrant, Meter, Alert, Feature, ProductFeature, BillingPortalConfiguration, WebhookEndpoint, FileLink, AppsSecret

Payments & tax: PaymentMethodConfiguration, PaymentMethodDomain, ApplePayDomain, TaxRegistration, TaxSettings

Connect: Account, AccountPerson, AccountExternalAccount

Issuing: IssuingCardholder, IssuingCard, IssuingPersonalizationDesign

Terminal & Radar: TerminalLocation, TerminalConfiguration, TerminalReader, RadarValueList, RadarValueListItem

Ownership branding

Stripe has no tagging API, so ownership lives in each object's metadata map under reserved keys, which is how read re-adopts a resource whose state row was lost:

{ alchemy_stack: "Billing", alchemy_stage: "dev", alchemy_id: "pro-monthly" }

Metadata.ts centralizes this. metadataUpdate handles Stripe's unset idiom — a removed key must be posted as "", not omitted — and stripInternalMetadata keeps the reserved keys out of the user-facing attribute so a no-op deploy shows no drift.

Deleting is mostly archiving

Only 13 of the 35 objects have a real DELETE. The rest cannot be deleted, so delete performs the closest teardown Stripe offers and is idempotent:

  • archive (active: false, or a /deactivate endpoint) — Price, TaxRate, ShippingRate, PaymentLink, PromotionCode, BillingPortalConfiguration, Meter, Alert, Feature, PaymentMethodConfiguration, PaymentMethodDomain, IssuingCard, IssuingCardholder
  • expire / voidFileLink, CreditGrant, TaxRegistration
  • restore previous stateTaxSettings (an account-level singleton; the first reconcile captures the pre-existing settings so destroy can put them back)
  • nothing at allIssuingPersonalizationDesign has no delete, archive, or deactivate endpoint

Each such resource documents the residue in a caution note. Some sharp edges are permanent and worth knowing before running the suites: a Meter's event_name and a Feature's lookup_key are reserved account-wide forever, even after deactivation.

Auth

STRIPE_API_KEY, or an interactively-stored key in ~/.alchemy/credentials, via the standard AuthProvider shape. Registered in builtinAuth.

Not in this PR

  • Resources have not been run against the live API.
  • No distilled patches. The generated Stripe SDK types every operation's errors as [UnknownStripeError], and the protocol dispatches on the envelope's error.type before status — so a 404 for a missing object surfaces as InvalidRequestError with code === "resource_missing", never NotFound. Providers catch both tags in the meantime; ~130 patch candidates are recorded from the implementation pass.

Adds `alchemy/Stripe` — credentials/auth provider, metadata-based
ownership branding, and 35 resource providers with tests.

Resources are not yet live-tested and no distilled patches are written
yet; both are follow-up work.
@alchemy-version-bot

Copy link
Copy Markdown
Contributor

Install the packages built from this commit:

Alchemy

alchemy

bun add https://pkg.ing/alchemy/7328f43

@alchemy.run/better-auth

bun add https://pkg.ing/@alchemy.run/better-auth/7328f43

@alchemy.run/cloudflare-runtime

bun add https://pkg.ing/@alchemy.run/cloudflare-runtime/7328f43

@alchemy.run/frontend-frameworks

bun add https://pkg.ing/@alchemy.run/frontend-frameworks/7328f43

@alchemy.run/node-utils

bun add https://pkg.ing/@alchemy.run/node-utils/7328f43

@alchemy.run/pr-package

bun add https://pkg.ing/@alchemy.run/pr-package/7328f43

@alchemy.run/floci

bun add https://pkg.ing/@alchemy.run/floci/7328f43

Distilled

@distilled.cloud/core

bun add https://pkg.ing/@distilled.cloud/core/e70eb61

@distilled.cloud/aws

bun add https://pkg.ing/@distilled.cloud/aws/e70eb61

@distilled.cloud/axiom

bun add https://pkg.ing/@distilled.cloud/axiom/e70eb61

@distilled.cloud/cloudflare

bun add https://pkg.ing/@distilled.cloud/cloudflare/e70eb61

@distilled.cloud/hetzner

bun add https://pkg.ing/@distilled.cloud/hetzner/e70eb61

@distilled.cloud/neon

bun add https://pkg.ing/@distilled.cloud/neon/e70eb61

@distilled.cloud/planetscale

bun add https://pkg.ing/@distilled.cloud/planetscale/e70eb61

@sam-goodwin

Copy link
Copy Markdown
Contributor

Is there a companion PR for distilled or was the schema perfect?

@sam-goodwin

Copy link
Copy Markdown
Contributor

Looks like bindings are missing? Each operation that can target a resource needs a Binding.

E.g. in AWS, we have AWS/S3/GetObject.ts and AWS/S3/GetObjectHttp.ts where GetObjectHttp.ts implements the binding with distilled using HTTP. In Cloudflare, there'd be Cloudflare/R2/ReadBucketBinding.ts and Cloudflare/R2/ReadBucketHttp.ts where ReadBucketBinding is used in a Worker (using the native binding, hence the Binding suffix) and ReadBucketHttp.ts uses distilled over HTTP with an AccountApiToken for auth.

Each stripe operation should be encoded as a binding and the process of binding that (creating token, binding that token and resource ID to the host, etc>) should be packaged up. Can mostly look at the Http bindings in Cloudflare and AWS and repeat those patterns

@Mkassabov Mkassabov closed this Aug 24, 2026
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.

2 participants