@shware/purchase 6.0.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,18 +12,18 @@ signatures and calling store APIs stays in your app, with the official SDKs.
12
12
  ```sh
13
13
  pnpm add @shware/purchase zod
14
14
  # optional peers, only for the subpaths you use
15
- pnpm add stripe # @shware/purchase/stripe (^22.6.2 || ^23.0.0)
15
+ pnpm add stripe # @shware/purchase/stripe (^23.0.0)
16
16
  pnpm add @apple/app-store-server-library # @shware/purchase/app-store (^3.1.0)
17
17
  ```
18
18
 
19
19
  ## Entry points
20
20
 
21
- | Import path | Contents |
22
- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
23
- | `@shware/purchase` | Subscription model (`PLATFORMS`, `SUBSCRIPTION_STATUSES`, `AVAILABLE_STATUSES`, cancellation types) and the three configs |
24
- | `@shware/purchase/stripe` | `StripeEventHandler`, Stripe mappers, checkout schemas, `METADATA_KEYS`, analytics helpers, browser pending-checkout helpers |
25
- | `@shware/purchase/app-store` | `AppStoreNotificationHandler` for App Store Server Notifications V2, `mapCancellationDetails` |
26
- | `@shware/purchase/google-play` | Real-time developer notification (RTDN) types and enums, subscription state and cancellation mappers |
21
+ | Import path | Contents |
22
+ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
23
+ | `@shware/purchase` | Subscription model (`PLATFORMS`, `SUBSCRIPTION_STATUSES`, `AVAILABLE_STATUSES`, cancellation types) and the three configs |
24
+ | `@shware/purchase/stripe` | `StripeThinEventHandler`, `StripeEventHandler`, Stripe mappers, checkout schemas, `METADATA_KEYS`, analytics helpers, browser pending-checkout helpers |
25
+ | `@shware/purchase/app-store` | `AppStoreNotificationHandler` for App Store Server Notifications V2, `mapCancellationDetails` |
26
+ | `@shware/purchase/google-play` | Real-time developer notification (RTDN) types and enums, subscription state and cancellation mappers |
27
27
 
28
28
  ## Subscription model
29
29
 
@@ -131,14 +131,38 @@ so existing subscribers keep resolving.
131
131
 
132
132
  ### Webhooks
133
133
 
134
- Verify the signature with the Stripe SDK, then dispatch with `StripeEventHandler`. `on` is typed
135
- per event, so each handler receives the right `event.data.object`; `process` throws for an event
136
- type with no handler.
134
+ Two dispatchers, one per Stripe [event format](https://docs.stripe.com/events/how-events-work#event-formats).
135
+ Both are typed per event type, run every handler registered for a type, and throw from `process`
136
+ for a type with no handler.
137
+
138
+ | Handler | Event format | Verify with | Handlers receive |
139
+ | ------------------------ | ------------ | ------------------------------------------ | ---------------------------------------- |
140
+ | `StripeThinEventHandler` | thin | `stripe.parseEventNotificationAsync(...)` | the typed notification (`v1.*` types) |
141
+ | `StripeEventHandler` | snapshot | `stripe.webhooks.constructEventAsync(...)` | `event.data.object` for the event's type |
142
+
143
+ Which to use: Stripe recommends thin events for new integrations, and so do we. Use snapshot
144
+ events when you need the full payload as it was when the event fired, or
145
+ `data.previous_attributes`.
146
+
147
+ #### Thin events (recommended)
148
+
149
+ A thin event only says which object changed (`type`, `id`, `created`, `reason`, `related_object`),
150
+ so each handler reads current state from Stripe with `notification.fetchRelatedObject()` (or the
151
+ full event with `fetchEvent()`). The SDK never pre-fetches: some handlers only need
152
+ `related_object.id`. `on` is keyed by thin event type (`v1.customer.subscription.updated`, ...),
153
+ so `fetchRelatedObject()` resolves to the right Stripe object. Delivery order carries no meaning:
154
+ re-read state instead of applying payloads in order, and dedupe one-off facts (credits, payments,
155
+ refunds) with idempotency keys. `StripeThinEventNotificationOf<T>` types a handler shared across
156
+ several types.
157
+
158
+ Create the event destination with the thin payload style and the `v1.*` types you handle. A
159
+ Cloudflare Workers route with Hono (`parseEventNotificationAsync` uses Web Crypto there):
137
160
 
138
161
  ```ts
139
162
  import {
140
163
  METADATA_KEYS,
141
- StripeEventHandler,
164
+ StripeThinEventHandler,
165
+ type StripeThinEventNotificationOf,
142
166
  mapCancellationDetails,
143
167
  mapSubscriptionStatus,
144
168
  mapTime,
@@ -147,23 +171,31 @@ import Stripe from 'stripe';
147
171
 
148
172
  const stripe = new Stripe(env.STRIPE_SECRET_KEY);
149
173
 
150
- export async function processStripeEvent(signature: string, body: string) {
151
- const event = await stripe.webhooks.constructEventAsync(
152
- body,
153
- signature,
174
+ const handler = StripeThinEventHandler.create()
175
+ .on('v1.checkout.session.completed', handleCheckoutSessionCompleted)
176
+ .on('v1.invoice.paid', handleInvoicePaid)
177
+ .on('v1.customer.subscription.created', handleSubscriptionEvent)
178
+ .on('v1.customer.subscription.updated', handleSubscriptionEvent)
179
+ .on('v1.customer.subscription.deleted', handleSubscriptionEvent);
180
+
181
+ app.post('/webhooks/stripe', async (c) => {
182
+ const notification = await stripe.parseEventNotificationAsync(
183
+ await c.req.text(),
184
+ c.req.header('stripe-signature') ?? '',
154
185
  env.STRIPE_WEBHOOK_SECRET
155
186
  );
156
-
157
- await StripeEventHandler.create()
158
- .on('checkout.session.completed', handleCheckoutSessionCompleted)
159
- .on('invoice.payment_succeeded', handleInvoicePaymentSucceeded)
160
- .on('customer.subscription.created', handleSubscriptionEvent)
161
- .on('customer.subscription.updated', handleSubscriptionEvent)
162
- .on('customer.subscription.deleted', handleSubscriptionEvent)
163
- .process(event);
164
- }
165
-
166
- async function handleSubscriptionEvent(sub: Stripe.Subscription) {
187
+ await handler.process(notification);
188
+ return c.body(null, 204);
189
+ });
190
+
191
+ async function handleSubscriptionEvent(
192
+ notification: StripeThinEventNotificationOf<
193
+ | 'v1.customer.subscription.created'
194
+ | 'v1.customer.subscription.updated'
195
+ | 'v1.customer.subscription.deleted'
196
+ >
197
+ ) {
198
+ const sub = await notification.fetchRelatedObject(); // Stripe.Subscription, current state
167
199
  const userId = sub.metadata[METADATA_KEYS.USER_ID];
168
200
  const productId = sub.metadata[METADATA_KEYS.PRODUCT_ID];
169
201
  await saveSubscription({
@@ -178,6 +210,41 @@ async function handleSubscriptionEvent(sub: Stripe.Subscription) {
178
210
  }
179
211
  ```
180
212
 
213
+ #### Snapshot events
214
+
215
+ `on` is keyed by snapshot event type (`customer.subscription.updated`, ...) and each handler
216
+ receives that event's `data.object`, typed.
217
+
218
+ ```ts
219
+ import { StripeEventHandler } from '@shware/purchase/stripe';
220
+
221
+ export async function processStripeEvent(signature: string, body: string) {
222
+ const event = await stripe.webhooks.constructEventAsync(
223
+ body,
224
+ signature,
225
+ env.STRIPE_WEBHOOK_SECRET
226
+ );
227
+
228
+ await StripeEventHandler.create()
229
+ .on('invoice.payment_succeeded', handleInvoicePaymentSucceeded) // (invoice: Stripe.Invoice)
230
+ .on('customer.subscription.updated', handleSubscription) // (sub: Stripe.Subscription)
231
+ .process(event);
232
+ }
233
+ ```
234
+
235
+ #### Moving from snapshot to thin events
236
+
237
+ - Switch the event destination to the thin payload style; `parseEventNotificationAsync` rejects
238
+ snapshot payloads.
239
+ - Replace `constructEventAsync` with `parseEventNotificationAsync` and `StripeEventHandler` with
240
+ `StripeThinEventHandler`.
241
+ - Prefix every `on` type with `v1.` (`invoice.paid` becomes `v1.invoice.paid`).
242
+ - Handlers receive the notification instead of `event.data.object`: call
243
+ `await notification.fetchRelatedObject()`, or use `notification.related_object.id` when the id
244
+ is enough. The fetched object is current state, so handlers must not depend on delivery order.
245
+ - The mappers (`mapSubscriptionStatus`, `mapInvoice`, `mapCancellationDetails`, ...) take the
246
+ fetched objects unchanged.
247
+
181
248
  ### Checkout
182
249
 
183
250
  `createCheckoutSessionSchema(productIds)` is a `zod/mini` schema for the create-checkout request
@@ -2,12 +2,14 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_stripe_config = require("./config.cjs");
3
3
  const require_stripe_handler = require("./handler.cjs");
4
4
  const require_stripe_mapper = require("./mapper.cjs");
5
+ const require_stripe_thin_handler = require("./thin-handler.cjs");
5
6
  const require_stripe_schema = require("./schema.cjs");
6
7
  const require_stripe_metadata = require("./metadata.cjs");
7
8
  const require_stripe_pending_checkout = require("./pending-checkout.cjs");
8
9
  exports.METADATA_KEYS = require_stripe_metadata.METADATA_KEYS;
9
10
  exports.StripeConfig = require_stripe_config.StripeConfig;
10
11
  exports.StripeEventHandler = require_stripe_handler.StripeEventHandler;
12
+ exports.StripeThinEventHandler = require_stripe_thin_handler.StripeThinEventHandler;
11
13
  exports.addPendingCheckout = require_stripe_pending_checkout.addPendingCheckout;
12
14
  exports.cancellationDetailsSchema = require_stripe_schema.cancellationDetailsSchema;
13
15
  exports.createCheckoutSessionSchema = require_stripe_schema.createCheckoutSessionSchema;
@@ -2,8 +2,9 @@ import { CancellationDetails } from "../subscription/index.cjs";
2
2
  import { PriceId, StripeConfig, StripeMode } from "./config.cjs";
3
3
  import { StripeEventHandler } from "./handler.cjs";
4
4
  import { BeginCheckoutProperties, CheckoutSession, LineItem, PaymentStatus, Price, ProductPrice, PurchaseProperties, getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price } from "./mapper.cjs";
5
+ import { StripeThinEventHandler, StripeThinEventNotificationOf, StripeThinEventType } from "./thin-handler.cjs";
5
6
  import { AllowedOrigins, CreateCheckoutSessionDTO, CreateCheckoutSessionSchemaOptions, cancellationDetailsSchema, createCheckoutSessionSchema, returnUrlSchema } from "./schema.cjs";
6
7
  import { METADATA_KEYS } from "./metadata.cjs";
7
8
  import { addPendingCheckout, getPendingCheckouts, removePendingCheckout } from "./pending-checkout.cjs";
8
9
  import { Stripe } from "stripe";
9
- export { type AllowedOrigins, type BeginCheckoutProperties, type CancellationDetails, type CheckoutSession, type CreateCheckoutSessionDTO, type CreateCheckoutSessionSchemaOptions, type LineItem, METADATA_KEYS, type PaymentStatus, type Price, type PriceId, type ProductPrice, type PurchaseProperties, type Stripe, StripeConfig, StripeEventHandler, type StripeMode, addPendingCheckout, cancellationDetailsSchema, createCheckoutSessionSchema, getBeginCheckoutProperties, getPendingCheckouts, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price, removePendingCheckout, returnUrlSchema };
10
+ export { type AllowedOrigins, type BeginCheckoutProperties, type CancellationDetails, type CheckoutSession, type CreateCheckoutSessionDTO, type CreateCheckoutSessionSchemaOptions, type LineItem, METADATA_KEYS, type PaymentStatus, type Price, type PriceId, type ProductPrice, type PurchaseProperties, type Stripe, StripeConfig, StripeEventHandler, type StripeMode, StripeThinEventHandler, type StripeThinEventNotificationOf, type StripeThinEventType, addPendingCheckout, cancellationDetailsSchema, createCheckoutSessionSchema, getBeginCheckoutProperties, getPendingCheckouts, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price, removePendingCheckout, returnUrlSchema };
@@ -2,8 +2,9 @@ import { CancellationDetails } from "../subscription/index.mjs";
2
2
  import { PriceId, StripeConfig, StripeMode } from "./config.mjs";
3
3
  import { StripeEventHandler } from "./handler.mjs";
4
4
  import { BeginCheckoutProperties, CheckoutSession, LineItem, PaymentStatus, Price, ProductPrice, PurchaseProperties, getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price } from "./mapper.mjs";
5
+ import { StripeThinEventHandler, StripeThinEventNotificationOf, StripeThinEventType } from "./thin-handler.mjs";
5
6
  import { AllowedOrigins, CreateCheckoutSessionDTO, CreateCheckoutSessionSchemaOptions, cancellationDetailsSchema, createCheckoutSessionSchema, returnUrlSchema } from "./schema.mjs";
6
7
  import { METADATA_KEYS } from "./metadata.mjs";
7
8
  import { addPendingCheckout, getPendingCheckouts, removePendingCheckout } from "./pending-checkout.mjs";
8
9
  import { Stripe } from "stripe";
9
- export { type AllowedOrigins, type BeginCheckoutProperties, type CancellationDetails, type CheckoutSession, type CreateCheckoutSessionDTO, type CreateCheckoutSessionSchemaOptions, type LineItem, METADATA_KEYS, type PaymentStatus, type Price, type PriceId, type ProductPrice, type PurchaseProperties, type Stripe, StripeConfig, StripeEventHandler, type StripeMode, addPendingCheckout, cancellationDetailsSchema, createCheckoutSessionSchema, getBeginCheckoutProperties, getPendingCheckouts, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price, removePendingCheckout, returnUrlSchema };
10
+ export { type AllowedOrigins, type BeginCheckoutProperties, type CancellationDetails, type CheckoutSession, type CreateCheckoutSessionDTO, type CreateCheckoutSessionSchemaOptions, type LineItem, METADATA_KEYS, type PaymentStatus, type Price, type PriceId, type ProductPrice, type PurchaseProperties, type Stripe, StripeConfig, StripeEventHandler, type StripeMode, StripeThinEventHandler, type StripeThinEventNotificationOf, type StripeThinEventType, addPendingCheckout, cancellationDetailsSchema, createCheckoutSessionSchema, getBeginCheckoutProperties, getPendingCheckouts, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price, removePendingCheckout, returnUrlSchema };
@@ -1,7 +1,8 @@
1
1
  import { StripeConfig } from "./config.mjs";
2
2
  import { StripeEventHandler } from "./handler.mjs";
3
3
  import { getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price } from "./mapper.mjs";
4
+ import { StripeThinEventHandler } from "./thin-handler.mjs";
4
5
  import { cancellationDetailsSchema, createCheckoutSessionSchema, returnUrlSchema } from "./schema.mjs";
5
6
  import { METADATA_KEYS } from "./metadata.mjs";
6
7
  import { addPendingCheckout, getPendingCheckouts, removePendingCheckout } from "./pending-checkout.mjs";
7
- export { METADATA_KEYS, StripeConfig, StripeEventHandler, addPendingCheckout, cancellationDetailsSchema, createCheckoutSessionSchema, getBeginCheckoutProperties, getPendingCheckouts, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price, removePendingCheckout, returnUrlSchema };
8
+ export { METADATA_KEYS, StripeConfig, StripeEventHandler, StripeThinEventHandler, addPendingCheckout, cancellationDetailsSchema, createCheckoutSessionSchema, getBeginCheckoutProperties, getPendingCheckouts, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price, removePendingCheckout, returnUrlSchema };
@@ -0,0 +1,40 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/stripe/thin-handler.ts
3
+ /**
4
+ * Stripe thin event dispatcher keyed by event type (`v1.customer.subscription.updated`, ...). The
5
+ * caller verifies and parses the payload with `stripe.parseEventNotificationAsync` first and passes
6
+ * the notification in. A thin event only says which object changed, so each handler reads current
7
+ * state itself with `notification.fetchRelatedObject()` (or `fetchEvent()`). Nothing is pre-fetched
8
+ * here: some handlers only need `related_object.id`, and fetching inside the handler keeps the API
9
+ * call next to the code that uses its result. Delivery order carries no meaning: handlers re-read
10
+ * state instead of applying a payload, and dedupe one-off facts with idempotency keys.
11
+ *
12
+ * The thin-event counterpart of StripeEventHandler, which dispatches snapshot events. Thin event
13
+ * types and `parseEventNotificationAsync` need stripe-node 23+.
14
+ */
15
+ var StripeThinEventHandler = class StripeThinEventHandler {
16
+ handlers;
17
+ constructor() {
18
+ this.handlers = /* @__PURE__ */ new Map();
19
+ }
20
+ static create() {
21
+ return new StripeThinEventHandler();
22
+ }
23
+ on = (type, handler) => {
24
+ if (!this.handlers.has(type)) this.handlers.set(type, []);
25
+ this.handlers.get(type)?.push(handler);
26
+ return this;
27
+ };
28
+ process = async (notification) => {
29
+ const eventHandlers = this.handlers.get(notification.type);
30
+ if (eventHandlers) await Promise.all(eventHandlers.map((handler) => handler(notification)));
31
+ else {
32
+ console.error(`No stripe event handler found for event type: ${notification.type}`);
33
+ throw new Error(`No stripe event handler found for event type: ${notification.type}`);
34
+ }
35
+ };
36
+ };
37
+ //#endregion
38
+ exports.StripeThinEventHandler = StripeThinEventHandler;
39
+
40
+ //# sourceMappingURL=thin-handler.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thin-handler.cjs","names":[],"sources":["../../src/stripe/thin-handler.ts"],"sourcesContent":["import type { Stripe } from 'stripe';\n\ntype StripeEventNotification = Stripe.V2.Core.EventNotification;\n\nexport type StripeThinEventType = StripeEventNotification['type'];\n\nexport type StripeThinEventNotificationOf<T extends StripeThinEventType> = Extract<\n StripeEventNotification,\n { type: T }\n>;\n\ntype EventHandlerFn<T extends StripeThinEventType = StripeThinEventType> = (\n notification: StripeThinEventNotificationOf<T>\n) => Promise<void>;\n\n/**\n * Stripe thin event dispatcher keyed by event type (`v1.customer.subscription.updated`, ...). The\n * caller verifies and parses the payload with `stripe.parseEventNotificationAsync` first and passes\n * the notification in. A thin event only says which object changed, so each handler reads current\n * state itself with `notification.fetchRelatedObject()` (or `fetchEvent()`). Nothing is pre-fetched\n * here: some handlers only need `related_object.id`, and fetching inside the handler keeps the API\n * call next to the code that uses its result. Delivery order carries no meaning: handlers re-read\n * state instead of applying a payload, and dedupe one-off facts with idempotency keys.\n *\n * The thin-event counterpart of StripeEventHandler, which dispatches snapshot events. Thin event\n * types and `parseEventNotificationAsync` need stripe-node 23+.\n */\nexport class StripeThinEventHandler {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private readonly handlers: Map<StripeThinEventType, Array<EventHandlerFn<any>>>;\n\n private constructor() {\n this.handlers = new Map();\n }\n\n static create() {\n return new StripeThinEventHandler();\n }\n\n on = <T extends StripeThinEventType>(type: T, handler: EventHandlerFn<T>) => {\n if (!this.handlers.has(type)) {\n this.handlers.set(type, []);\n }\n\n this.handlers.get(type)?.push(handler);\n return this;\n };\n\n process = async (notification: StripeEventNotification): Promise<void> => {\n const eventHandlers = this.handlers.get(notification.type);\n if (eventHandlers) {\n await Promise.all(eventHandlers.map((handler) => handler(notification)));\n } else {\n console.error(`No stripe event handler found for event type: ${notification.type}`);\n throw new Error(`No stripe event handler found for event type: ${notification.type}`);\n }\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AA2BA,IAAa,yBAAb,MAAa,uBAAuB;CAElC;CAEA,cAAsB;EACpB,KAAK,2BAAW,IAAI,IAAI;CAC1B;CAEA,OAAO,SAAS;EACd,OAAO,IAAI,uBAAuB;CACpC;CAEA,MAAqC,MAAS,YAA+B;EAC3E,IAAI,CAAC,KAAK,SAAS,IAAI,IAAI,GACzB,KAAK,SAAS,IAAI,MAAM,CAAC,CAAC;EAG5B,KAAK,SAAS,IAAI,IAAI,CAAC,EAAE,KAAK,OAAO;EACrC,OAAO;CACT;CAEA,UAAU,OAAO,iBAAyD;EACxE,MAAM,gBAAgB,KAAK,SAAS,IAAI,aAAa,IAAI;EACzD,IAAI,eACF,MAAM,QAAQ,IAAI,cAAc,KAAK,YAAY,QAAQ,YAAY,CAAC,CAAC;OAClE;GACL,QAAQ,MAAM,iDAAiD,aAAa,MAAM;GAClF,MAAM,IAAI,MAAM,iDAAiD,aAAa,MAAM;EACtF;CACF;AACF"}
@@ -0,0 +1,30 @@
1
+ import { Stripe } from "stripe";
2
+ //#region src/stripe/thin-handler.d.ts
3
+ type StripeEventNotification = Stripe.V2.Core.EventNotification;
4
+ export type StripeThinEventType = StripeEventNotification['type'];
5
+ export type StripeThinEventNotificationOf<T extends StripeThinEventType> = Extract<StripeEventNotification, {
6
+ type: T;
7
+ }>;
8
+ type EventHandlerFn<T extends StripeThinEventType = StripeThinEventType> = (notification: StripeThinEventNotificationOf<T>) => Promise<void>;
9
+ /**
10
+ * Stripe thin event dispatcher keyed by event type (`v1.customer.subscription.updated`, ...). The
11
+ * caller verifies and parses the payload with `stripe.parseEventNotificationAsync` first and passes
12
+ * the notification in. A thin event only says which object changed, so each handler reads current
13
+ * state itself with `notification.fetchRelatedObject()` (or `fetchEvent()`). Nothing is pre-fetched
14
+ * here: some handlers only need `related_object.id`, and fetching inside the handler keeps the API
15
+ * call next to the code that uses its result. Delivery order carries no meaning: handlers re-read
16
+ * state instead of applying a payload, and dedupe one-off facts with idempotency keys.
17
+ *
18
+ * The thin-event counterpart of StripeEventHandler, which dispatches snapshot events. Thin event
19
+ * types and `parseEventNotificationAsync` need stripe-node 23+.
20
+ */
21
+ export declare class StripeThinEventHandler {
22
+ private readonly handlers;
23
+ private constructor();
24
+ static create(): StripeThinEventHandler;
25
+ on: <T extends StripeThinEventType>(type: T, handler: EventHandlerFn<T>) => this;
26
+ process: (notification: StripeEventNotification) => Promise<void>;
27
+ }
28
+ //#endregion
29
+ export {};
30
+ //# sourceMappingURL=thin-handler.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thin-handler.d.cts","names":[],"sources":["../../src/stripe/thin-handler.ts"],"mappings":";;KAEK,0BAA0B,OAAO,GAAG,KAAK;YAElC,sBAAsB;YAEtB,8BAA8B,UAAU,uBAAuB,QACzE;EACE,MAAM;;KAGL,eAAe,UAAU,sBAAsB,wBAClD,cAAc,8BAA8B,OACzC;;;;;;;;;;;;;qBAcQ;mBAEM;UAEV;SAIA,UAAM;EAIb,KAAM,UAAU,qBAAmB,MAAQ,GAAC,SAAW,eAAe;EAStE,UAAO,cAAwB,4BAA0B"}
@@ -0,0 +1,30 @@
1
+ import { Stripe } from "stripe";
2
+ //#region src/stripe/thin-handler.d.ts
3
+ type StripeEventNotification = Stripe.V2.Core.EventNotification;
4
+ export type StripeThinEventType = StripeEventNotification['type'];
5
+ export type StripeThinEventNotificationOf<T extends StripeThinEventType> = Extract<StripeEventNotification, {
6
+ type: T;
7
+ }>;
8
+ type EventHandlerFn<T extends StripeThinEventType = StripeThinEventType> = (notification: StripeThinEventNotificationOf<T>) => Promise<void>;
9
+ /**
10
+ * Stripe thin event dispatcher keyed by event type (`v1.customer.subscription.updated`, ...). The
11
+ * caller verifies and parses the payload with `stripe.parseEventNotificationAsync` first and passes
12
+ * the notification in. A thin event only says which object changed, so each handler reads current
13
+ * state itself with `notification.fetchRelatedObject()` (or `fetchEvent()`). Nothing is pre-fetched
14
+ * here: some handlers only need `related_object.id`, and fetching inside the handler keeps the API
15
+ * call next to the code that uses its result. Delivery order carries no meaning: handlers re-read
16
+ * state instead of applying a payload, and dedupe one-off facts with idempotency keys.
17
+ *
18
+ * The thin-event counterpart of StripeEventHandler, which dispatches snapshot events. Thin event
19
+ * types and `parseEventNotificationAsync` need stripe-node 23+.
20
+ */
21
+ export declare class StripeThinEventHandler {
22
+ private readonly handlers;
23
+ private constructor();
24
+ static create(): StripeThinEventHandler;
25
+ on: <T extends StripeThinEventType>(type: T, handler: EventHandlerFn<T>) => this;
26
+ process: (notification: StripeEventNotification) => Promise<void>;
27
+ }
28
+ //#endregion
29
+ export {};
30
+ //# sourceMappingURL=thin-handler.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thin-handler.d.mts","names":[],"sources":["../../src/stripe/thin-handler.ts"],"mappings":";;KAEK,0BAA0B,OAAO,GAAG,KAAK;YAElC,sBAAsB;YAEtB,8BAA8B,UAAU,uBAAuB,QACzE;EACE,MAAM;;KAGL,eAAe,UAAU,sBAAsB,wBAClD,cAAc,8BAA8B,OACzC;;;;;;;;;;;;;qBAcQ;mBAEM;UAEV;SAIA,UAAM;EAIb,KAAM,UAAU,qBAAmB,MAAQ,GAAC,SAAW,eAAe;EAStE,UAAO,cAAwB,4BAA0B"}
@@ -0,0 +1,39 @@
1
+ //#region src/stripe/thin-handler.ts
2
+ /**
3
+ * Stripe thin event dispatcher keyed by event type (`v1.customer.subscription.updated`, ...). The
4
+ * caller verifies and parses the payload with `stripe.parseEventNotificationAsync` first and passes
5
+ * the notification in. A thin event only says which object changed, so each handler reads current
6
+ * state itself with `notification.fetchRelatedObject()` (or `fetchEvent()`). Nothing is pre-fetched
7
+ * here: some handlers only need `related_object.id`, and fetching inside the handler keeps the API
8
+ * call next to the code that uses its result. Delivery order carries no meaning: handlers re-read
9
+ * state instead of applying a payload, and dedupe one-off facts with idempotency keys.
10
+ *
11
+ * The thin-event counterpart of StripeEventHandler, which dispatches snapshot events. Thin event
12
+ * types and `parseEventNotificationAsync` need stripe-node 23+.
13
+ */
14
+ var StripeThinEventHandler = class StripeThinEventHandler {
15
+ handlers;
16
+ constructor() {
17
+ this.handlers = /* @__PURE__ */ new Map();
18
+ }
19
+ static create() {
20
+ return new StripeThinEventHandler();
21
+ }
22
+ on = (type, handler) => {
23
+ if (!this.handlers.has(type)) this.handlers.set(type, []);
24
+ this.handlers.get(type)?.push(handler);
25
+ return this;
26
+ };
27
+ process = async (notification) => {
28
+ const eventHandlers = this.handlers.get(notification.type);
29
+ if (eventHandlers) await Promise.all(eventHandlers.map((handler) => handler(notification)));
30
+ else {
31
+ console.error(`No stripe event handler found for event type: ${notification.type}`);
32
+ throw new Error(`No stripe event handler found for event type: ${notification.type}`);
33
+ }
34
+ };
35
+ };
36
+ //#endregion
37
+ export { StripeThinEventHandler };
38
+
39
+ //# sourceMappingURL=thin-handler.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thin-handler.mjs","names":[],"sources":["../../src/stripe/thin-handler.ts"],"sourcesContent":["import type { Stripe } from 'stripe';\n\ntype StripeEventNotification = Stripe.V2.Core.EventNotification;\n\nexport type StripeThinEventType = StripeEventNotification['type'];\n\nexport type StripeThinEventNotificationOf<T extends StripeThinEventType> = Extract<\n StripeEventNotification,\n { type: T }\n>;\n\ntype EventHandlerFn<T extends StripeThinEventType = StripeThinEventType> = (\n notification: StripeThinEventNotificationOf<T>\n) => Promise<void>;\n\n/**\n * Stripe thin event dispatcher keyed by event type (`v1.customer.subscription.updated`, ...). The\n * caller verifies and parses the payload with `stripe.parseEventNotificationAsync` first and passes\n * the notification in. A thin event only says which object changed, so each handler reads current\n * state itself with `notification.fetchRelatedObject()` (or `fetchEvent()`). Nothing is pre-fetched\n * here: some handlers only need `related_object.id`, and fetching inside the handler keeps the API\n * call next to the code that uses its result. Delivery order carries no meaning: handlers re-read\n * state instead of applying a payload, and dedupe one-off facts with idempotency keys.\n *\n * The thin-event counterpart of StripeEventHandler, which dispatches snapshot events. Thin event\n * types and `parseEventNotificationAsync` need stripe-node 23+.\n */\nexport class StripeThinEventHandler {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private readonly handlers: Map<StripeThinEventType, Array<EventHandlerFn<any>>>;\n\n private constructor() {\n this.handlers = new Map();\n }\n\n static create() {\n return new StripeThinEventHandler();\n }\n\n on = <T extends StripeThinEventType>(type: T, handler: EventHandlerFn<T>) => {\n if (!this.handlers.has(type)) {\n this.handlers.set(type, []);\n }\n\n this.handlers.get(type)?.push(handler);\n return this;\n };\n\n process = async (notification: StripeEventNotification): Promise<void> => {\n const eventHandlers = this.handlers.get(notification.type);\n if (eventHandlers) {\n await Promise.all(eventHandlers.map((handler) => handler(notification)));\n } else {\n console.error(`No stripe event handler found for event type: ${notification.type}`);\n throw new Error(`No stripe event handler found for event type: ${notification.type}`);\n }\n };\n}\n"],"mappings":";;;;;;;;;;;;;AA2BA,IAAa,yBAAb,MAAa,uBAAuB;CAElC;CAEA,cAAsB;EACpB,KAAK,2BAAW,IAAI,IAAI;CAC1B;CAEA,OAAO,SAAS;EACd,OAAO,IAAI,uBAAuB;CACpC;CAEA,MAAqC,MAAS,YAA+B;EAC3E,IAAI,CAAC,KAAK,SAAS,IAAI,IAAI,GACzB,KAAK,SAAS,IAAI,MAAM,CAAC,CAAC;EAG5B,KAAK,SAAS,IAAI,IAAI,CAAC,EAAE,KAAK,OAAO;EACrC,OAAO;CACT;CAEA,UAAU,OAAO,iBAAyD;EACxE,MAAM,gBAAgB,KAAK,SAAS,IAAI,aAAa,IAAI;EACzD,IAAI,eACF,MAAM,QAAQ,IAAI,cAAc,KAAK,YAAY,QAAQ,YAAY,CAAC,CAAC;OAClE;GACL,QAAQ,MAAM,iDAAiD,aAAa,MAAM;GAClF,MAAM,IAAI,MAAM,iDAAiD,aAAa,MAAM;EACtF;CACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shware/purchase",
3
- "version": "6.0.0",
3
+ "version": "7.0.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -70,7 +70,7 @@
70
70
  },
71
71
  "peerDependencies": {
72
72
  "@apple/app-store-server-library": "^3.1.0",
73
- "stripe": "^22.6.2 || ^23.0.0",
73
+ "stripe": "^23.0.0",
74
74
  "zod": "^4.6.5"
75
75
  },
76
76
  "peerDependenciesMeta": {