@odla-ai/stripe
v0.1.1
Published
A typed, app-local Stripe integration for products, prices, billing, webhooks, Connect account scoping, and bidirectional catalog reconciliation.
Downloads
769
Maintainers
Readme
@odla-ai/stripe
App-local Stripe billing and catalog integration for odla applications. It is a fetch-based TypeScript library, not a hosted payment service or central key custodian.
The zero-runtime-dependency package provides:
- typed Products and Prices with cursor pagination and idempotency;
- safe immutable Price replacement plus mutable updates and archival;
- Customers, Subscriptions, Charges, Refunds, hosted sessions, and Connect;
- Stripe-Account scoping for connected accounts;
- webhook HMAC verification with a bounded replay window;
- managed SKU catalog pull, drift in both directions, and explicit apply;
- authorization-gated, bounded Product and Price routes.
The host resolves Stripe credentials from its own app vault and injects them. Credentials never move to another app or a central hub.
Client
import { initStripe } from "@odla-ai/stripe";
const stripe = initStripe({
secretKey: env.STRIPE_SECRET_KEY,
account: env.STRIPE_ACCOUNT_ID, // optional Connect scope
});
const product = await stripe.createProduct({
name: "Founding member",
metadata: { owner: "membership" },
}, "product:founding-member");
const price = await stripe.createPrice({
productId: product.id,
currency: "usd",
unitAmount: 2500,
recurring: { interval: "month" },
}, "price:founding-member:monthly");Stripe does not permit changing a Price amount, currency, or recurrence in place. replacePrice creates the replacement first, optionally moves the Product default, and only then archives the old Price. StripePartialFailureError.completed identifies a replacement created before a later safety step failed.
Bidirectional catalogs
Products participate only with odla_sku=<canonical sku>. Prices also require odla_price_key=<canonical price key>. Everything else is returned in the unmanaged id lists and is never archived by the planner.
const stripeState = await pullStripeCatalog(stripe);
const plan = planStripeCatalog(canonicalSkus, stripeState);
console.log(plan.toStripe); // app -> Stripe
console.log(plan.toLocal); // Stripe -> app
const applied = await applyStripeCatalogPlan(stripe, plan, {
idempotencyKeyPrefix: "catalog:approved-revision-42",
});
await savePublishedIds(applied.productIds, applied.priceIds);This supports a hybrid catalog: a hub can own canonical SKU definitions while each chapter publishes the live Stripe Product and Price ids it actually offers. The package reports drift; the application chooses authority.
Management routes and webhooks
createStripeCatalogRoutes({ authorize, client }) authorizes before resolving credentials or reading a body, caps mutation bodies at 32 KiB by default, and answers only its mount. It provides catalog pull plus Product/Price create, update, archive, and Price replacement. The host still owns authorization and local tier/SKU writes.
constructStripeEvent(payload, signature, secret) accepts rotating v1 signatures, compares HMACs in constant time, and rejects payloads outside the five-minute default tolerance.
stripeIntegration declares app-local Stripe secrets, optional Connect scope, provisioning guidance, and smoke checks. The host resolves and injects all credentials.
