@itzsudhan/creem-expo 0.1.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.
Files changed (39) hide show
  1. package/README.md +45 -0
  2. package/app.plugin.js +202 -0
  3. package/dist/chunk-BF74I2QD.mjs +857 -0
  4. package/dist/express-BAx2zfw7.d.mts +84 -0
  5. package/dist/express-jb3wXcce.d.ts +84 -0
  6. package/dist/index.d.mts +164 -0
  7. package/dist/index.d.ts +164 -0
  8. package/dist/index.js +1132 -0
  9. package/dist/index.mjs +1078 -0
  10. package/dist/server/express.d.mts +5 -0
  11. package/dist/server/express.d.ts +5 -0
  12. package/dist/server/express.js +888 -0
  13. package/dist/server/express.mjs +10 -0
  14. package/dist/server/index.d.mts +15 -0
  15. package/dist/server/index.d.ts +15 -0
  16. package/dist/server/index.js +898 -0
  17. package/dist/server/index.mjs +25 -0
  18. package/dist/types-NcFyNrWp.d.mts +271 -0
  19. package/dist/types-NcFyNrWp.d.ts +271 -0
  20. package/package.json +106 -0
  21. package/src/client/apiClient.ts +195 -0
  22. package/src/client/components/CreemCheckoutButton.tsx +91 -0
  23. package/src/client/components/CreemCheckoutModal.tsx +81 -0
  24. package/src/client/components/CreemManageSubscriptionButton.tsx +58 -0
  25. package/src/client/context.tsx +57 -0
  26. package/src/client/hooks/useCreemCheckout.ts +478 -0
  27. package/src/client/hooks/useCreemSubscription.ts +194 -0
  28. package/src/client/utils/checkoutState.ts +99 -0
  29. package/src/client/utils/linking.ts +232 -0
  30. package/src/errors.ts +61 -0
  31. package/src/index.ts +19 -0
  32. package/src/server/core.ts +815 -0
  33. package/src/server/createCreemClient.ts +16 -0
  34. package/src/server/express.ts +187 -0
  35. package/src/server/fetchHandlers.ts +191 -0
  36. package/src/server/index.ts +6 -0
  37. package/src/server/json.ts +44 -0
  38. package/src/server/signatures.ts +18 -0
  39. package/src/types.ts +402 -0
@@ -0,0 +1,857 @@
1
+ // src/server/express.ts
2
+ import express, {
3
+ Router
4
+ } from "express";
5
+
6
+ // src/server/json.ts
7
+ var CreemHttpError = class extends Error {
8
+ status;
9
+ details;
10
+ constructor(status, message, details) {
11
+ super(message);
12
+ this.status = status;
13
+ this.details = details;
14
+ }
15
+ };
16
+ var json = (body, init) => new Response(JSON.stringify(body), {
17
+ ...init,
18
+ headers: {
19
+ "content-type": "application/json; charset=utf-8",
20
+ ...init?.headers ?? {}
21
+ }
22
+ });
23
+ var errorResponse = (error) => {
24
+ if (error instanceof CreemHttpError) {
25
+ return json(
26
+ {
27
+ error: {
28
+ message: error.message,
29
+ details: error.details ?? null
30
+ }
31
+ },
32
+ { status: error.status }
33
+ );
34
+ }
35
+ return json(
36
+ {
37
+ error: {
38
+ message: error instanceof Error ? error.message : "An unexpected Creem server error occurred."
39
+ }
40
+ },
41
+ { status: 500 }
42
+ );
43
+ };
44
+
45
+ // src/server/signatures.ts
46
+ import { createHmac, timingSafeEqual } from "crypto";
47
+ var verifyWebhookSignature = (rawBody, signature, secret) => {
48
+ const computedSignature = createHmac("sha256", secret).update(rawBody).digest("hex");
49
+ const providedBuffer = Buffer.from(signature, "hex");
50
+ const computedBuffer = Buffer.from(computedSignature, "hex");
51
+ if (providedBuffer.length !== computedBuffer.length) {
52
+ return false;
53
+ }
54
+ return timingSafeEqual(providedBuffer, computedBuffer);
55
+ };
56
+
57
+ // src/server/core.ts
58
+ var KNOWN_WEBHOOK_HANDLER_KEYS = {
59
+ "checkout.completed": "onCheckoutCompleted",
60
+ "dispute.created": "onDisputeCreated",
61
+ "refund.created": "onRefundCreated",
62
+ "subscription.active": "onSubscriptionActive",
63
+ "subscription.canceled": "onSubscriptionCanceled",
64
+ "subscription.expired": "onSubscriptionExpired",
65
+ "subscription.paid": "onSubscriptionPaid",
66
+ "subscription.past_due": "onSubscriptionPastDue",
67
+ "subscription.paused": "onSubscriptionPaused",
68
+ "subscription.trialing": "onSubscriptionTrialing",
69
+ "subscription.unpaid": "onSubscriptionUnpaid",
70
+ "subscription.update": "onSubscriptionUpdate"
71
+ };
72
+ var CREEM_USER_ID_KEY = "creemExpoUserId";
73
+ var nowIso = (clock) => (clock ? clock() : /* @__PURE__ */ new Date()).toISOString();
74
+ var asRecord = (value) => typeof value === "object" && value !== null ? value : {};
75
+ var asOptionalJsonObject = (value) => typeof value === "object" && value !== null ? value : void 0;
76
+ var extractId = (value) => {
77
+ if (typeof value === "string") {
78
+ return value;
79
+ }
80
+ if (value && typeof value === "object" && "id" in value) {
81
+ return typeof value.id === "string" ? value.id : void 0;
82
+ }
83
+ return void 0;
84
+ };
85
+ var extractCustomerEmail = (customer) => {
86
+ if (!customer || typeof customer !== "object" || !("email" in customer)) {
87
+ return void 0;
88
+ }
89
+ return typeof customer.email === "string" ? customer.email : void 0;
90
+ };
91
+ var mergeMetadata = (metadata, identity) => ({
92
+ ...metadata ?? {},
93
+ [CREEM_USER_ID_KEY]: identity.userId
94
+ });
95
+ var isHttpUrl = (value) => {
96
+ if (!value) {
97
+ return false;
98
+ }
99
+ try {
100
+ const protocol = new URL(value).protocol;
101
+ return protocol === "http:" || protocol === "https:";
102
+ } catch {
103
+ return false;
104
+ }
105
+ };
106
+ var mapCheckoutEntity = (checkout) => ({
107
+ checkoutId: checkout.id,
108
+ checkoutUrl: checkout.checkoutUrl ?? "",
109
+ productId: extractId(checkout.product),
110
+ requestId: checkout.requestId,
111
+ successUrl: checkout.successUrl ?? null,
112
+ status: checkout.status,
113
+ mode: checkout.mode,
114
+ customerId: extractId(checkout.customer) ?? null,
115
+ subscriptionId: extractId(checkout.subscription) ?? null,
116
+ rawCheckout: checkout
117
+ });
118
+ var mapPortalEntity = (portal, customerId) => ({
119
+ customerId,
120
+ customerPortalLink: portal.customerPortalLink,
121
+ rawPortal: portal
122
+ });
123
+ var isEntitledFromStatus = (status) => status === "active" || status === "trialing" || status === "paid" || status === "scheduled_cancel";
124
+ var isVerifiedCheckoutStatus = (status) => status === "complete" || status === "completed" || status === "paid" || status === "success" || status === "succeeded";
125
+ var buildCheckoutVerification = (checkoutId, checkout, userState) => {
126
+ const matchesLatestCheckout = userState?.latestCheckoutId === checkoutId;
127
+ const source = checkout ? "checkout" : matchesLatestCheckout ? "subscription" : "none";
128
+ const verified = isVerifiedCheckoutStatus(checkout?.status) || Boolean(
129
+ (matchesLatestCheckout || checkout) && (checkout?.orderId ?? checkout?.subscriptionId ?? userState?.latestOrderId ?? userState?.subscriptionId ?? (userState?.entitlementActive ? "entitled" : null))
130
+ );
131
+ return {
132
+ checkoutId,
133
+ verified,
134
+ source,
135
+ checkoutStatus: checkout?.status ?? null,
136
+ customerId: checkout?.customerId ?? userState?.customerId ?? null,
137
+ subscriptionId: checkout?.subscriptionId ?? userState?.subscriptionId ?? null,
138
+ orderId: checkout?.orderId ?? userState?.latestOrderId ?? null,
139
+ productId: checkout?.productId ?? userState?.latestProductId ?? null,
140
+ entitlementActive: userState?.entitlementActive ?? false,
141
+ subscriptionStatus: userState?.subscriptionStatus ?? null,
142
+ updatedAt: checkout?.updatedAt ?? userState?.updatedAt ?? null
143
+ };
144
+ };
145
+ var normalizeWebhookEvent = (payload) => {
146
+ const record = asRecord(payload);
147
+ const object = asRecord(record.object);
148
+ if (typeof record.id !== "string" || typeof record.eventType !== "string") {
149
+ throw new CreemHttpError(400, "Creem webhook payload is missing required fields.");
150
+ }
151
+ const createdAt = typeof record.created_at === "number" ? record.created_at : typeof record.createdAt === "number" ? record.createdAt : Date.now();
152
+ return {
153
+ id: record.id,
154
+ eventType: record.eventType,
155
+ createdAt,
156
+ object
157
+ };
158
+ };
159
+ var buildUserState = (currentState, patch, clock) => ({
160
+ userId: patch.userId ?? currentState?.userId ?? "",
161
+ email: patch.email ?? currentState?.email ?? null,
162
+ customerId: patch.customerId ?? currentState?.customerId ?? null,
163
+ subscriptionId: patch.subscriptionId ?? currentState?.subscriptionId ?? null,
164
+ latestCheckoutId: patch.latestCheckoutId ?? currentState?.latestCheckoutId ?? null,
165
+ latestOrderId: patch.latestOrderId ?? currentState?.latestOrderId ?? null,
166
+ latestProductId: patch.latestProductId ?? currentState?.latestProductId ?? null,
167
+ entitlementActive: patch.entitlementActive ?? currentState?.entitlementActive ?? false,
168
+ subscriptionStatus: patch.subscriptionStatus ?? currentState?.subscriptionStatus ?? null,
169
+ metadata: patch.metadata ?? currentState?.metadata ?? null,
170
+ subscription: patch.subscription ?? currentState?.subscription ?? null,
171
+ updatedAt: nowIso(clock)
172
+ });
173
+ var buildCheckoutRecord = (session, userId, input, clock) => {
174
+ const timestamp = nowIso(clock);
175
+ return {
176
+ checkoutId: session.checkoutId,
177
+ userId,
178
+ productId: session.productId ?? input.productId,
179
+ requestId: session.requestId ?? input.requestId,
180
+ status: session.status,
181
+ checkoutUrl: session.checkoutUrl,
182
+ successUrl: session.successUrl ?? input.returnUrl ?? null,
183
+ customerId: session.customerId ?? input.customer?.id ?? null,
184
+ subscriptionId: session.subscriptionId ?? null,
185
+ orderId: null,
186
+ metadata: input.metadata ?? null,
187
+ createdAt: timestamp,
188
+ updatedAt: timestamp
189
+ };
190
+ };
191
+ var resolveIdentityFromWebhook = async (store, event) => {
192
+ const metadata = asRecord(event.object.metadata);
193
+ const explicitUserId = metadata[CREEM_USER_ID_KEY];
194
+ if (typeof explicitUserId === "string") {
195
+ return store.getUserState(explicitUserId);
196
+ }
197
+ const subscriptionId = extractId(event.object.subscription);
198
+ if (subscriptionId) {
199
+ const fromSubscription = await store.getUserStateBySubscriptionId(subscriptionId);
200
+ if (fromSubscription) {
201
+ return fromSubscription;
202
+ }
203
+ }
204
+ const customerId = extractId(event.object.customer);
205
+ if (customerId) {
206
+ const fromCustomer = await store.getUserStateByCustomerId(customerId);
207
+ if (fromCustomer) {
208
+ return fromCustomer;
209
+ }
210
+ }
211
+ const checkoutId = extractId(event.object);
212
+ if (checkoutId) {
213
+ const checkout = await store.getCheckout(checkoutId);
214
+ if (checkout) {
215
+ return store.getUserState(checkout.userId);
216
+ }
217
+ }
218
+ return null;
219
+ };
220
+ var buildSubscriptionSnapshot = (userId, state, subscription) => ({
221
+ userId,
222
+ email: state?.email ?? null,
223
+ customerId: state?.customerId ?? null,
224
+ subscriptionId: state?.subscriptionId ?? null,
225
+ latestCheckoutId: state?.latestCheckoutId ?? null,
226
+ latestOrderId: state?.latestOrderId ?? null,
227
+ latestProductId: state?.latestProductId ?? null,
228
+ entitlementActive: state?.entitlementActive ?? false,
229
+ subscriptionStatus: subscription?.status ?? state?.subscriptionStatus ?? null,
230
+ source: subscription ? "creem" : state ? "store" : "none",
231
+ subscription: subscription ?? state?.subscription ?? null,
232
+ metadata: state?.metadata ?? null,
233
+ updatedAt: state?.updatedAt ?? null
234
+ });
235
+ var buildSubscriptionStatePatch = ({
236
+ userState,
237
+ identity,
238
+ subscription,
239
+ fallbackProductId
240
+ }) => ({
241
+ userId: identity.userId,
242
+ email: identity.email ?? userState?.email ?? null,
243
+ customerId: extractId(subscription.customer) ?? userState?.customerId ?? identity.customerId ?? null,
244
+ subscriptionId: subscription.id,
245
+ latestProductId: extractId(subscription.product) ?? fallbackProductId ?? userState?.latestProductId ?? null,
246
+ subscriptionStatus: subscription.status,
247
+ entitlementActive: isEntitledFromStatus(subscription.status),
248
+ subscription
249
+ });
250
+ var createCreemServerCore = ({
251
+ creem,
252
+ store,
253
+ webhookSecret,
254
+ resolveIdentity,
255
+ buildSuccessUrl,
256
+ onWebhookEvent,
257
+ webhookHandlers,
258
+ now,
259
+ ...namedWebhookHandlers
260
+ }) => {
261
+ const resolveRequestIdentity = async ({
262
+ request,
263
+ platformContext
264
+ }) => resolveIdentity({
265
+ request,
266
+ platformContext
267
+ });
268
+ const dispatchWebhookHandlers = async (context) => {
269
+ const handlerFromMap = webhookHandlers?.[context.event.eventType];
270
+ if (handlerFromMap) {
271
+ await handlerFromMap(context);
272
+ }
273
+ const handlerKey = KNOWN_WEBHOOK_HANDLER_KEYS[context.event.eventType];
274
+ const namedHandler = handlerKey ? namedWebhookHandlers[handlerKey] : void 0;
275
+ if (namedHandler) {
276
+ await namedHandler(context);
277
+ }
278
+ if (onWebhookEvent) {
279
+ await onWebhookEvent(context);
280
+ }
281
+ };
282
+ return {
283
+ async createCheckoutSession({
284
+ request,
285
+ input,
286
+ platformContext
287
+ }) {
288
+ const identity = await resolveRequestIdentity({ request, platformContext });
289
+ const userState = await store.getUserState(identity.userId);
290
+ const successUrl = isHttpUrl(input.returnUrl) ? input.returnUrl : await buildSuccessUrl({
291
+ request,
292
+ platformContext,
293
+ identity,
294
+ input,
295
+ userState
296
+ });
297
+ const checkout = await creem.checkouts.create({
298
+ productId: input.productId,
299
+ requestId: input.requestId,
300
+ units: input.units,
301
+ discountCode: input.discountCode,
302
+ customer: {
303
+ id: input.customer?.id ?? userState?.customerId ?? identity.customerId ?? void 0,
304
+ email: input.customer?.email ?? identity.email ?? userState?.email ?? void 0
305
+ },
306
+ customFields: input.customFields,
307
+ successUrl,
308
+ metadata: mergeMetadata(input.metadata, identity)
309
+ });
310
+ const session = mapCheckoutEntity(checkout);
311
+ await store.saveCheckout(buildCheckoutRecord(session, identity.userId, input, now));
312
+ const nextState = buildUserState(
313
+ userState,
314
+ {
315
+ userId: identity.userId,
316
+ email: input.customer?.email ?? identity.email ?? userState?.email ?? null,
317
+ customerId: session.customerId ?? userState?.customerId ?? identity.customerId ?? null,
318
+ latestCheckoutId: session.checkoutId,
319
+ latestProductId: session.productId ?? input.productId,
320
+ metadata: mergeMetadata(input.metadata, identity)
321
+ },
322
+ now
323
+ );
324
+ await store.saveUserState(nextState);
325
+ return session;
326
+ },
327
+ async getSubscriptionSnapshot({
328
+ request,
329
+ platformContext
330
+ }) {
331
+ const identity = await resolveRequestIdentity({ request, platformContext });
332
+ const userState = await store.getUserState(identity.userId);
333
+ if (!userState?.subscriptionId) {
334
+ return buildSubscriptionSnapshot(identity.userId, userState, null);
335
+ }
336
+ try {
337
+ const subscription = await creem.subscriptions.get(userState.subscriptionId);
338
+ const nextState = buildUserState(
339
+ userState,
340
+ buildSubscriptionStatePatch({
341
+ userState,
342
+ identity,
343
+ subscription
344
+ }),
345
+ now
346
+ );
347
+ await store.saveUserState(nextState);
348
+ return buildSubscriptionSnapshot(identity.userId, nextState, subscription);
349
+ } catch {
350
+ return buildSubscriptionSnapshot(identity.userId, userState, null);
351
+ }
352
+ },
353
+ async verifyCheckoutSession({
354
+ request,
355
+ input,
356
+ platformContext
357
+ }) {
358
+ const identity = await resolveRequestIdentity({ request, platformContext });
359
+ const userState = await store.getUserState(identity.userId);
360
+ const checkout = await store.getCheckout(input.checkoutId);
361
+ const checkoutForUser = checkout && checkout.userId === identity.userId ? checkout : null;
362
+ return buildCheckoutVerification(input.checkoutId, checkoutForUser, userState);
363
+ },
364
+ async cancelSubscription({
365
+ request,
366
+ input,
367
+ platformContext
368
+ }) {
369
+ const identity = await resolveRequestIdentity({ request, platformContext });
370
+ const userState = await store.getUserState(identity.userId);
371
+ const subscriptionId = input.subscriptionId ?? userState?.subscriptionId;
372
+ if (!subscriptionId) {
373
+ throw new CreemHttpError(
374
+ 400,
375
+ "No Creem subscription is linked to the current user yet."
376
+ );
377
+ }
378
+ const subscription = await creem.subscriptions.cancel(subscriptionId, {
379
+ mode: input.mode,
380
+ onExecute: input.onExecute
381
+ });
382
+ const nextState = buildUserState(
383
+ userState,
384
+ buildSubscriptionStatePatch({
385
+ userState,
386
+ identity,
387
+ subscription
388
+ }),
389
+ now
390
+ );
391
+ await store.saveUserState(nextState);
392
+ return buildSubscriptionSnapshot(identity.userId, nextState, subscription);
393
+ },
394
+ async pauseSubscription({
395
+ request,
396
+ input,
397
+ platformContext
398
+ }) {
399
+ const identity = await resolveRequestIdentity({ request, platformContext });
400
+ const userState = await store.getUserState(identity.userId);
401
+ const subscriptionId = input.subscriptionId ?? userState?.subscriptionId;
402
+ if (!subscriptionId) {
403
+ throw new CreemHttpError(
404
+ 400,
405
+ "No Creem subscription is linked to the current user yet."
406
+ );
407
+ }
408
+ const subscription = await creem.subscriptions.pause(subscriptionId);
409
+ const nextState = buildUserState(
410
+ userState,
411
+ buildSubscriptionStatePatch({
412
+ userState,
413
+ identity,
414
+ subscription
415
+ }),
416
+ now
417
+ );
418
+ await store.saveUserState(nextState);
419
+ return buildSubscriptionSnapshot(identity.userId, nextState, subscription);
420
+ },
421
+ async resumeSubscription({
422
+ request,
423
+ input,
424
+ platformContext
425
+ }) {
426
+ const identity = await resolveRequestIdentity({ request, platformContext });
427
+ const userState = await store.getUserState(identity.userId);
428
+ const subscriptionId = input.subscriptionId ?? userState?.subscriptionId;
429
+ if (!subscriptionId) {
430
+ throw new CreemHttpError(
431
+ 400,
432
+ "No Creem subscription is linked to the current user yet."
433
+ );
434
+ }
435
+ const subscription = await creem.subscriptions.resume(subscriptionId);
436
+ const nextState = buildUserState(
437
+ userState,
438
+ buildSubscriptionStatePatch({
439
+ userState,
440
+ identity,
441
+ subscription
442
+ }),
443
+ now
444
+ );
445
+ await store.saveUserState(nextState);
446
+ return buildSubscriptionSnapshot(identity.userId, nextState, subscription);
447
+ },
448
+ async upgradeSubscription({
449
+ request,
450
+ input,
451
+ platformContext
452
+ }) {
453
+ const identity = await resolveRequestIdentity({ request, platformContext });
454
+ const userState = await store.getUserState(identity.userId);
455
+ const subscriptionId = input.subscriptionId ?? userState?.subscriptionId;
456
+ if (!subscriptionId) {
457
+ throw new CreemHttpError(
458
+ 400,
459
+ "No Creem subscription is linked to the current user yet."
460
+ );
461
+ }
462
+ if (!input.productId) {
463
+ throw new CreemHttpError(400, "`productId` is required to upgrade.");
464
+ }
465
+ const subscription = await creem.subscriptions.upgrade(subscriptionId, {
466
+ productId: input.productId,
467
+ updateBehavior: input.updateBehavior
468
+ });
469
+ const nextState = buildUserState(
470
+ userState,
471
+ buildSubscriptionStatePatch({
472
+ userState,
473
+ identity,
474
+ subscription,
475
+ fallbackProductId: input.productId
476
+ }),
477
+ now
478
+ );
479
+ await store.saveUserState(nextState);
480
+ return buildSubscriptionSnapshot(identity.userId, nextState, subscription);
481
+ },
482
+ async createCustomerPortalLink({
483
+ request,
484
+ platformContext
485
+ }) {
486
+ const identity = await resolveRequestIdentity({ request, platformContext });
487
+ const userState = await store.getUserState(identity.userId);
488
+ const customerId = userState?.customerId ?? identity.customerId;
489
+ if (!customerId) {
490
+ throw new CreemHttpError(
491
+ 400,
492
+ "No customer is linked to the current user yet. Complete a checkout first."
493
+ );
494
+ }
495
+ const portal = await creem.customers.generateBillingLinks({
496
+ customerId
497
+ });
498
+ return mapPortalEntity(portal, customerId);
499
+ },
500
+ async handleWebhook({
501
+ request,
502
+ rawBody,
503
+ signature,
504
+ platformContext
505
+ }) {
506
+ if (!verifyWebhookSignature(rawBody, signature, webhookSecret)) {
507
+ throw new CreemHttpError(401, "Invalid Creem webhook signature.");
508
+ }
509
+ const event = normalizeWebhookEvent(JSON.parse(rawBody));
510
+ if (await store.hasProcessedWebhook(event.id)) {
511
+ return {
512
+ ok: true,
513
+ duplicated: true,
514
+ eventId: event.id
515
+ };
516
+ }
517
+ const existingUserState = await resolveIdentityFromWebhook(store, event);
518
+ let resolvedUserState = existingUserState;
519
+ const checkoutId = extractId(event.object);
520
+ const existingCheckout = checkoutId ? await store.getCheckout(checkoutId) : null;
521
+ const eventMetadata = asOptionalJsonObject(event.object.metadata);
522
+ const subscriptionId = extractId(event.object.subscription);
523
+ const customerId = extractId(event.object.customer);
524
+ const orderId = extractId(event.object.order);
525
+ const productId = extractId(event.object.product);
526
+ const subscriptionStatus = typeof event.object.status === "string" ? event.object.status : null;
527
+ const userId = existingUserState?.userId ?? existingCheckout?.userId ?? (typeof asRecord(eventMetadata)[CREEM_USER_ID_KEY] === "string" ? asRecord(eventMetadata)[CREEM_USER_ID_KEY] : null);
528
+ if (userId) {
529
+ const nextState = buildUserState(
530
+ existingUserState,
531
+ {
532
+ userId,
533
+ email: extractCustomerEmail(event.object.customer) ?? existingUserState?.email ?? null,
534
+ customerId: customerId ?? existingUserState?.customerId ?? null,
535
+ subscriptionId: subscriptionId ?? existingUserState?.subscriptionId ?? null,
536
+ latestCheckoutId: checkoutId ?? existingUserState?.latestCheckoutId ?? null,
537
+ latestOrderId: orderId ?? existingUserState?.latestOrderId ?? null,
538
+ latestProductId: productId ?? existingUserState?.latestProductId ?? null,
539
+ subscriptionStatus,
540
+ entitlementActive: isEntitledFromStatus(subscriptionStatus),
541
+ metadata: eventMetadata ?? existingUserState?.metadata ?? null,
542
+ subscription: "collectionMethod" in event.object ? event.object : existingUserState?.subscription ?? null
543
+ },
544
+ now
545
+ );
546
+ await store.saveUserState(nextState);
547
+ resolvedUserState = nextState;
548
+ if (checkoutId) {
549
+ await store.saveCheckout({
550
+ checkoutId,
551
+ userId,
552
+ productId: productId ?? existingCheckout?.productId,
553
+ requestId: typeof event.object.request_id === "string" ? event.object.request_id : existingCheckout?.requestId,
554
+ status: typeof event.object.status === "string" ? event.object.status : existingCheckout?.status ?? "completed",
555
+ checkoutUrl: existingCheckout?.checkoutUrl ?? "",
556
+ successUrl: existingCheckout?.successUrl ?? null,
557
+ customerId: customerId ?? existingCheckout?.customerId ?? null,
558
+ subscriptionId: subscriptionId ?? existingCheckout?.subscriptionId ?? null,
559
+ orderId: orderId ?? existingCheckout?.orderId ?? null,
560
+ metadata: eventMetadata ?? existingCheckout?.metadata ?? null,
561
+ createdAt: existingCheckout?.createdAt ?? nowIso(now),
562
+ updatedAt: nowIso(now)
563
+ });
564
+ }
565
+ }
566
+ await dispatchWebhookHandlers({
567
+ request,
568
+ platformContext,
569
+ event,
570
+ userState: resolvedUserState
571
+ });
572
+ await store.markWebhookProcessed({
573
+ eventId: event.id,
574
+ eventType: event.eventType,
575
+ processedAt: nowIso(now)
576
+ });
577
+ return {
578
+ ok: true,
579
+ duplicated: false,
580
+ eventId: event.id
581
+ };
582
+ }
583
+ };
584
+ };
585
+
586
+ // src/server/fetchHandlers.ts
587
+ var createCreemFetchHandlers = (options) => {
588
+ const core = createCreemServerCore(options);
589
+ const readOptionalJson = async (request) => {
590
+ const rawBody = await request.text();
591
+ return rawBody ? JSON.parse(rawBody) : {};
592
+ };
593
+ return {
594
+ async createCheckoutSession(request, platformContext) {
595
+ try {
596
+ const input = await request.json();
597
+ if (!input?.productId) {
598
+ throw new CreemHttpError(400, "`productId` is required.");
599
+ }
600
+ const session = await core.createCheckoutSession({
601
+ request,
602
+ platformContext,
603
+ input
604
+ });
605
+ return json(session, { status: 200 });
606
+ } catch (error) {
607
+ return errorResponse(error);
608
+ }
609
+ },
610
+ async verifyCheckoutSession(request, platformContext) {
611
+ try {
612
+ const input = await readOptionalJson(request);
613
+ if (!input?.checkoutId) {
614
+ throw new CreemHttpError(400, "`checkoutId` is required.");
615
+ }
616
+ const verification = await core.verifyCheckoutSession({
617
+ request,
618
+ platformContext,
619
+ input
620
+ });
621
+ return json(verification, { status: 200 });
622
+ } catch (error) {
623
+ return errorResponse(error);
624
+ }
625
+ },
626
+ async getSubscription(request, platformContext) {
627
+ try {
628
+ const snapshot = await core.getSubscriptionSnapshot({
629
+ request,
630
+ platformContext
631
+ });
632
+ return json(snapshot, { status: 200 });
633
+ } catch (error) {
634
+ return errorResponse(error);
635
+ }
636
+ },
637
+ async cancelSubscription(request, platformContext) {
638
+ try {
639
+ const input = await readOptionalJson(request);
640
+ const snapshot = await core.cancelSubscription({
641
+ request,
642
+ platformContext,
643
+ input
644
+ });
645
+ return json(snapshot, { status: 200 });
646
+ } catch (error) {
647
+ return errorResponse(error);
648
+ }
649
+ },
650
+ async pauseSubscription(request, platformContext) {
651
+ try {
652
+ const input = await readOptionalJson(request);
653
+ const snapshot = await core.pauseSubscription({
654
+ request,
655
+ platformContext,
656
+ input
657
+ });
658
+ return json(snapshot, { status: 200 });
659
+ } catch (error) {
660
+ return errorResponse(error);
661
+ }
662
+ },
663
+ async resumeSubscription(request, platformContext) {
664
+ try {
665
+ const input = await readOptionalJson(request);
666
+ const snapshot = await core.resumeSubscription({
667
+ request,
668
+ platformContext,
669
+ input
670
+ });
671
+ return json(snapshot, { status: 200 });
672
+ } catch (error) {
673
+ return errorResponse(error);
674
+ }
675
+ },
676
+ async upgradeSubscription(request, platformContext) {
677
+ try {
678
+ const input = await readOptionalJson(request);
679
+ const snapshot = await core.upgradeSubscription({
680
+ request,
681
+ platformContext,
682
+ input
683
+ });
684
+ return json(snapshot, { status: 200 });
685
+ } catch (error) {
686
+ return errorResponse(error);
687
+ }
688
+ },
689
+ async createCustomerPortal(request, platformContext) {
690
+ try {
691
+ const portal = await core.createCustomerPortalLink({
692
+ request,
693
+ platformContext
694
+ });
695
+ return json(portal, { status: 200 });
696
+ } catch (error) {
697
+ return errorResponse(error);
698
+ }
699
+ },
700
+ async handleWebhook(request, platformContext) {
701
+ try {
702
+ const signature = request.headers.get("creem-signature");
703
+ if (!signature) {
704
+ throw new CreemHttpError(401, "Missing Creem webhook signature.");
705
+ }
706
+ const rawBody = await request.text();
707
+ const result = await core.handleWebhook({
708
+ request,
709
+ platformContext,
710
+ rawBody,
711
+ signature
712
+ });
713
+ return json(result, { status: 200 });
714
+ } catch (error) {
715
+ return errorResponse(error);
716
+ }
717
+ }
718
+ };
719
+ };
720
+
721
+ // src/server/express.ts
722
+ var firstHeaderValue = (value) => value?.split(",")[0]?.trim();
723
+ var getRequestOrigin = (request) => {
724
+ const protocol = firstHeaderValue(request.get("x-forwarded-proto")) ?? request.protocol;
725
+ const host = firstHeaderValue(request.get("x-forwarded-host")) ?? request.get("host") ?? "localhost";
726
+ return `${protocol}://${host}`;
727
+ };
728
+ var appendHeaders = (headers, source) => {
729
+ for (const [key, value] of Object.entries(source)) {
730
+ if (typeof value === "undefined") {
731
+ continue;
732
+ }
733
+ if (Array.isArray(value)) {
734
+ for (const item of value) {
735
+ headers.append(key, item);
736
+ }
737
+ continue;
738
+ }
739
+ headers.set(key, value);
740
+ }
741
+ return headers;
742
+ };
743
+ var serializeBody = (body) => {
744
+ if (typeof body === "undefined" || body === null) {
745
+ return void 0;
746
+ }
747
+ if (Buffer.isBuffer(body)) {
748
+ return new Uint8Array(body);
749
+ }
750
+ if (typeof body === "string") {
751
+ return body;
752
+ }
753
+ return JSON.stringify(body);
754
+ };
755
+ var toWebRequest = (request) => {
756
+ const origin = getRequestOrigin(request);
757
+ const url = new URL(request.originalUrl || request.url, origin);
758
+ const headers = appendHeaders(new Headers(), request.headers);
759
+ const init = {
760
+ method: request.method,
761
+ headers
762
+ };
763
+ if (!["GET", "HEAD"].includes(request.method.toUpperCase())) {
764
+ const body = serializeBody(request.body);
765
+ if (typeof body !== "undefined") {
766
+ init.body = body;
767
+ }
768
+ }
769
+ return new Request(url.toString(), init);
770
+ };
771
+ var sendWebResponse = async (response, webResponse) => {
772
+ response.status(webResponse.status);
773
+ webResponse.headers.forEach((value, key) => {
774
+ if (key.toLowerCase() === "transfer-encoding") {
775
+ return;
776
+ }
777
+ response.setHeader(key, value);
778
+ });
779
+ response.send(await webResponse.text());
780
+ };
781
+ var __internal = {
782
+ firstHeaderValue,
783
+ getRequestOrigin,
784
+ appendHeaders,
785
+ serializeBody,
786
+ toWebRequest,
787
+ sendWebResponse
788
+ };
789
+ var createCreemExpressRouter = ({
790
+ getPlatformContext,
791
+ ...options
792
+ }) => {
793
+ const router = Router();
794
+ const handlers = createCreemFetchHandlers(options);
795
+ const runHandler = (handler) => {
796
+ return async (request, response) => {
797
+ const webRequest = toWebRequest(request);
798
+ const platformContext = getPlatformContext ? getPlatformContext(request, response) : request;
799
+ const webResponse = await handler(webRequest, platformContext);
800
+ await sendWebResponse(response, webResponse);
801
+ };
802
+ };
803
+ router.post(
804
+ "/creem/checkout-sessions",
805
+ express.json(),
806
+ runHandler(handlers.createCheckoutSession)
807
+ );
808
+ router.post(
809
+ "/creem/checkout/verify",
810
+ express.json(),
811
+ runHandler(handlers.verifyCheckoutSession)
812
+ );
813
+ router.get("/creem/subscription", runHandler(handlers.getSubscription));
814
+ router.post(
815
+ "/creem/subscription/cancel",
816
+ express.json(),
817
+ runHandler(handlers.cancelSubscription)
818
+ );
819
+ router.post(
820
+ "/creem/subscription/pause",
821
+ express.json(),
822
+ runHandler(handlers.pauseSubscription)
823
+ );
824
+ router.post(
825
+ "/creem/subscription/resume",
826
+ express.json(),
827
+ runHandler(handlers.resumeSubscription)
828
+ );
829
+ router.post(
830
+ "/creem/subscription/upgrade",
831
+ express.json(),
832
+ runHandler(handlers.upgradeSubscription)
833
+ );
834
+ router.post(
835
+ "/creem/customer-portal",
836
+ express.json(),
837
+ runHandler(handlers.createCustomerPortal)
838
+ );
839
+ router.post(
840
+ "/creem/webhooks",
841
+ express.raw({
842
+ type: "*/*"
843
+ }),
844
+ runHandler(handlers.handleWebhook)
845
+ );
846
+ return router;
847
+ };
848
+ var createCreemRouter = createCreemExpressRouter;
849
+
850
+ export {
851
+ verifyWebhookSignature,
852
+ createCreemServerCore,
853
+ createCreemFetchHandlers,
854
+ __internal,
855
+ createCreemExpressRouter,
856
+ createCreemRouter
857
+ };