@lunora/payment 1.0.0-alpha.2 → 1.0.0-alpha.21

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 (40) hide show
  1. package/LICENSE.md +6 -0
  2. package/README.md +3 -2
  3. package/__assets__/package-og.svg +1 -1
  4. package/dist/index.d.mts +20 -459
  5. package/dist/index.d.ts +20 -459
  6. package/dist/index.mjs +9 -11
  7. package/dist/packem_shared/{LunoraPaymentError-B3hEzXSs.mjs → LunoraPaymentError-BeQlhkZj.mjs} +4 -7
  8. package/dist/packem_shared/adapter.d-iFA55DzU.d.mts +312 -0
  9. package/dist/packem_shared/adapter.d-iFA55DzU.d.ts +312 -0
  10. package/dist/packem_shared/{addMoney-bCcs1nyw.mjs → addMoney-jSh_7TfF.mjs} +1 -1
  11. package/dist/packem_shared/{applyWebhookAction-DpAqf3Lw.mjs → applyWebhookAction-CLAx4svt.mjs} +1 -1
  12. package/dist/packem_shared/{constantTimeEqual-CfY0jYcL.mjs → constantTimeEqual-BQjoW85H.mjs} +14 -38
  13. package/dist/packem_shared/{createAdapterRegistry-BuDHFCBc.mjs → createAdapterRegistry-Ds7bx_TK.mjs} +1 -1
  14. package/dist/packem_shared/{createDatabasePaymentStore-bYB_HUE6.mjs → createDatabasePaymentStore-C0NUCj1H.mjs} +1 -1
  15. package/dist/packem_shared/{createPayment-BccfPGyw.mjs → createPayment-DzX-ji34.mjs} +17 -5
  16. package/dist/packem_shared/json-DhcPm8EO.mjs +11 -0
  17. package/dist/packem_shared/{lunoraDatabaseToPaymentDatabase-RlKX3Kcd.mjs → lunoraDatabaseToPaymentDatabase-CL2vdAXq.mjs} +2 -2
  18. package/dist/packem_shared/not-supported-C0onRyia.mjs +7 -0
  19. package/dist/packem_shared/{reconcile-CI1ukJF9.mjs → reconcile-Dtn_ErbJ.mjs} +1 -1
  20. package/dist/packem_shared/subscription-event-CwEsWRCK.mjs +17 -0
  21. package/dist/providers/autumn-features.d.mts +94 -0
  22. package/dist/providers/autumn-features.d.ts +94 -0
  23. package/dist/providers/autumn-features.mjs +95 -0
  24. package/dist/providers/autumn.d.mts +19 -0
  25. package/dist/providers/autumn.d.ts +19 -0
  26. package/dist/providers/autumn.mjs +294 -0
  27. package/dist/providers/creem.d.mts +17 -0
  28. package/dist/providers/creem.d.ts +17 -0
  29. package/dist/providers/creem.mjs +205 -0
  30. package/dist/providers/dodopayments.d.mts +21 -0
  31. package/dist/providers/dodopayments.d.ts +21 -0
  32. package/dist/providers/dodopayments.mjs +259 -0
  33. package/dist/providers/polar.d.mts +22 -0
  34. package/dist/providers/polar.d.ts +22 -0
  35. package/dist/{packem_shared/createPolarAdapter-BJtVGSlF.mjs → providers/polar.mjs} +51 -47
  36. package/dist/providers/stripe.d.mts +23 -0
  37. package/dist/providers/stripe.d.ts +23 -0
  38. package/dist/{packem_shared/createStripeAdapter-D40MVBXg.mjs → providers/stripe.mjs} +56 -55
  39. package/package.json +46 -5
  40. package/dist/packem_shared/json-Db337f36.mjs +0 -6
@@ -0,0 +1,294 @@
1
+ import { LunoraPaymentError } from '../packem_shared/LunoraPaymentError-BeQlhkZj.mjs';
2
+ import { a as asRecord, c as readBoolean, r as readString, b as readNumber } from '../packem_shared/json-DhcPm8EO.mjs';
3
+ import { money } from '../packem_shared/addMoney-jSh_7TfF.mjs';
4
+ import { verifyStandardWebhook } from '../packem_shared/constantTimeEqual-BQjoW85H.mjs';
5
+ import { s as stateToEventType } from '../packem_shared/subscription-event-CwEsWRCK.mjs';
6
+
7
+ const SUBSCRIPTION_STATE_BY_AUTUMN_STATUS = {
8
+ active: "active",
9
+ canceled: "canceled",
10
+ expired: "canceled",
11
+ past_due: "past_due",
12
+ // A scheduled product has not started yet — non-entitling until it activates.
13
+ scheduled: "paused",
14
+ trialing: "trialing"
15
+ };
16
+ const SUBSCRIPTION_STATE_BY_AUTUMN_ACTION = {
17
+ activated: "active",
18
+ canceled: "canceled",
19
+ cancelled: "canceled",
20
+ expired: "canceled",
21
+ scheduled: "paused"
22
+ };
23
+ const SUBSCRIPTION_ID_SEPARATOR = "::";
24
+ const notSupported = (operation) => {
25
+ throw new LunoraPaymentError("PROVIDER_ERROR", `autumn manages billing through the underlying processor and does not support ${operation}`);
26
+ };
27
+ const autumnSubscriptionId = (customerId, productId) => `${customerId}${SUBSCRIPTION_ID_SEPARATOR}${productId}`;
28
+ const parseAutumnSubscriptionId = (subscriptionId) => {
29
+ const index = subscriptionId.lastIndexOf(SUBSCRIPTION_ID_SEPARATOR);
30
+ if (index === -1) {
31
+ throw new LunoraPaymentError("PROVIDER_ERROR", `malformed autumn subscription id "${subscriptionId}" (expected "<customerId>::<productId>")`);
32
+ }
33
+ return { customerId: subscriptionId.slice(0, index), productId: subscriptionId.slice(index + SUBSCRIPTION_ID_SEPARATOR.length) };
34
+ };
35
+ const readAny = (object, ...keys) => {
36
+ for (const key of keys) {
37
+ const value = readString(object, key);
38
+ if (value !== void 0) {
39
+ return value;
40
+ }
41
+ }
42
+ return void 0;
43
+ };
44
+ const readAnyNumber = (object, ...keys) => {
45
+ for (const key of keys) {
46
+ const value = readNumber(object, key);
47
+ if (value !== void 0) {
48
+ return value;
49
+ }
50
+ }
51
+ return void 0;
52
+ };
53
+ const isCanceling = (product) => readAnyNumber(product, "canceled_at", "canceledAt") !== void 0 || readAny(product, "status") === "scheduled";
54
+ const productToSubscription = (customerId, product) => {
55
+ const now = Date.now();
56
+ const productId = readAny(product, "id", "product_id", "productId", "plan_id", "planId") ?? "";
57
+ const status = readAny(product, "status") ?? "active";
58
+ const pastDue = readBoolean(product, "past_due") ?? readBoolean(product, "pastDue") ?? false;
59
+ return {
60
+ cancelAtPeriodEnd: isCanceling(product),
61
+ createdAt: now,
62
+ currentPeriodEnd: readAnyNumber(product, "current_period_end", "currentPeriodEnd") ?? void 0,
63
+ currentPeriodStart: readAnyNumber(product, "current_period_start", "currentPeriodStart") ?? void 0,
64
+ id: autumnSubscriptionId(customerId, productId),
65
+ priceId: productId,
66
+ provider: "autumn",
67
+ quantity: readAnyNumber(product, "quantity") ?? 1,
68
+ referenceId: customerId,
69
+ // Fail closed: a past-due flag, or an unrecognized Autumn status, is non-entitling `past_due`.
70
+ state: pastDue ? "past_due" : SUBSCRIPTION_STATE_BY_AUTUMN_STATUS[status] ?? "past_due",
71
+ updatedAt: now
72
+ };
73
+ };
74
+ const asRecordList = (value) => Array.isArray(value) ? value.map((entry) => asRecord(entry)) : [];
75
+ const findProduct = (customer, productId) => {
76
+ const rows = [...asRecordList(customer.products), ...asRecordList(customer.subscriptions)];
77
+ return rows.find((entry) => (readAny(entry, "id", "product_id", "productId", "plan_id", "planId") ?? "") === productId);
78
+ };
79
+ const balanceFields = (balance) => {
80
+ return {
81
+ balance: readAnyNumber(balance, "remaining", "balance"),
82
+ limit: readAnyNumber(balance, "granted", "included_usage", "limit"),
83
+ unlimited: readBoolean(balance, "unlimited") ?? false,
84
+ used: readAnyNumber(balance, "usage", "used")
85
+ };
86
+ };
87
+ const constructedSubscription = (customerId, productId, state, cancelAtPeriodEnd) => {
88
+ const now = Date.now();
89
+ return {
90
+ cancelAtPeriodEnd,
91
+ createdAt: now,
92
+ id: autumnSubscriptionId(customerId, productId),
93
+ priceId: productId,
94
+ provider: "autumn",
95
+ quantity: 1,
96
+ referenceId: customerId,
97
+ state,
98
+ updatedAt: now
99
+ };
100
+ };
101
+ const readSubscription = async (client, customerId, productId) => {
102
+ const customer = asRecord(await client.customers.get({ customerId }));
103
+ const product = findProduct(customer, productId);
104
+ return product ? productToSubscription(customerId, product) : constructedSubscription(customerId, productId, "canceled", false);
105
+ };
106
+ const referenceFromEvent = (object) => readAny(object, "customer_id", "customerId");
107
+ const mapBillingUpdated = (eventId, object) => {
108
+ const base = { eventId, provider: "autumn", raw: { object, type: "billing.updated" } };
109
+ const customerId = referenceFromEvent(object);
110
+ const change = asRecordList(object.plan_changes)[0] ?? object;
111
+ const subscription = change.subscription ? asRecord(change.subscription) : change;
112
+ const planId = readAny(subscription, "plan_id", "planId", "product_id", "productId", "id");
113
+ const status = readAny(subscription, "status");
114
+ const action = readAny(change, "action");
115
+ const pastDue = readBoolean(subscription, "past_due") ?? readBoolean(subscription, "pastDue") ?? false;
116
+ const fromStatus = status === void 0 ? void 0 : SUBSCRIPTION_STATE_BY_AUTUMN_STATUS[status];
117
+ const fromAction = action === void 0 ? void 0 : SUBSCRIPTION_STATE_BY_AUTUMN_ACTION[action];
118
+ const state = pastDue ? "past_due" : fromStatus ?? fromAction;
119
+ return {
120
+ ...base,
121
+ cancelAtPeriodEnd: readBoolean(subscription, "cancel_at_period_end") ?? isCanceling(subscription),
122
+ currentPeriodEnd: readAnyNumber(subscription, "current_period_end", "currentPeriodEnd"),
123
+ currentPeriodStart: readAnyNumber(subscription, "current_period_start", "currentPeriodStart"),
124
+ customerId,
125
+ priceId: planId,
126
+ referenceId: customerId,
127
+ subscriptionId: customerId === void 0 || planId === void 0 ? void 0 : autumnSubscriptionId(customerId, planId),
128
+ type: stateToEventType(state)
129
+ };
130
+ };
131
+ const mapEvent = (eventId, eventType, object) => {
132
+ const base = { eventId, provider: "autumn", raw: { object, type: eventType } };
133
+ const currency = readAny(object, "currency") ?? "usd";
134
+ switch (eventType) {
135
+ // Auto-topup settles a real invoice — surface it as a captured payment.
136
+ case "billing.auto_topup_succeeded": {
137
+ const invoice = asRecord(object.invoice);
138
+ const amount = readAnyNumber(invoice, "total", "amount") ?? readAnyNumber(object, "total", "amount");
139
+ const invoiceCurrency = readAny(invoice, "currency") ?? currency;
140
+ return {
141
+ ...base,
142
+ amount: amount === void 0 ? void 0 : money(BigInt(Math.round(amount)), invoiceCurrency),
143
+ customerId: referenceFromEvent(object),
144
+ referenceId: referenceFromEvent(object),
145
+ sessionId: readAny(invoice, "id", "stripe_id", "invoice_id") ?? readAny(object, "id"),
146
+ type: "payment.captured"
147
+ };
148
+ }
149
+ case "billing.updated": {
150
+ return mapBillingUpdated(eventId, object);
151
+ }
152
+ // Product lifecycle — the entitling truth. `data` is the product row (or wraps it).
153
+ case "customer.product.added":
154
+ case "customer.product.canceled":
155
+ case "customer.product.expired":
156
+ case "customer.product.updated":
157
+ case "product.attached": {
158
+ const product = object.product ? asRecord(object.product) : object;
159
+ const status = eventType === "customer.product.canceled" || eventType === "customer.product.expired" ? "canceled" : readAny(product, "status");
160
+ const customerId = referenceFromEvent(object) ?? referenceFromEvent(product);
161
+ return {
162
+ ...base,
163
+ cancelAtPeriodEnd: readBoolean(product, "cancel_at_period_end") ?? isCanceling(product),
164
+ currentPeriodEnd: readAnyNumber(product, "current_period_end", "currentPeriodEnd"),
165
+ currentPeriodStart: readAnyNumber(product, "current_period_start", "currentPeriodStart"),
166
+ customerId,
167
+ priceId: readAny(product, "id", "product_id", "productId"),
168
+ referenceId: customerId,
169
+ subscriptionId: customerId === void 0 ? void 0 : autumnSubscriptionId(customerId, readAny(product, "id", "product_id", "productId") ?? ""),
170
+ type: stateToEventType(SUBSCRIPTION_STATE_BY_AUTUMN_STATUS[status ?? ""])
171
+ };
172
+ }
173
+ // Money movement — Autumn surfaces settled invoices/payments.
174
+ case "invoice.paid":
175
+ case "payment.succeeded": {
176
+ const amount = readAnyNumber(object, "total", "amount", "amount_paid");
177
+ return {
178
+ ...base,
179
+ // Autumn amounts are assumed integer minor units, but the event catalog is unverified —
180
+ // round defensively so a provider-sent decimal can't throw a RangeError out of
181
+ // `parseWebhook` (which would 400 the endpoint and wedge Autumn into infinite retries).
182
+ amount: amount === void 0 ? void 0 : money(BigInt(Math.round(amount)), currency),
183
+ customerId: referenceFromEvent(object),
184
+ referenceId: referenceFromEvent(object),
185
+ sessionId: readAny(object, "id", "invoice_id", "stripe_id"),
186
+ type: "payment.captured"
187
+ };
188
+ }
189
+ default: {
190
+ return { ...base, type: "unhandled" };
191
+ }
192
+ }
193
+ };
194
+ const checkoutUrlFrom = (result) => readAny(result, "checkout_url", "checkoutUrl", "payment_url", "paymentUrl", "url") ?? "";
195
+ const createAutumnAdapter = (options) => {
196
+ const { webhookSecret } = options;
197
+ const client = options.client;
198
+ return {
199
+ // Autumn abstracts Stripe money movement; there is no payment intent to cancel/capture/refund.
200
+ cancelPayment: () => notSupported("manual payment cancellation"),
201
+ cancelSubscription: async (subscriptionId, cancelOptions) => {
202
+ const { customerId, productId } = parseAutumnSubscriptionId(subscriptionId);
203
+ await client.billing.update({
204
+ cancelAction: cancelOptions?.atPeriodEnd ? "cancel_end_of_cycle" : "cancel_immediately",
205
+ customerId,
206
+ planId: productId
207
+ });
208
+ if (cancelOptions?.atPeriodEnd) {
209
+ return { ...await readSubscription(client, customerId, productId), cancelAtPeriodEnd: true };
210
+ }
211
+ return constructedSubscription(customerId, productId, "canceled", false);
212
+ },
213
+ capabilities: { merchantOfRecord: false, portal: true, usageMetering: true },
214
+ capturePayment: (_input) => notSupported("manual capture"),
215
+ checkEntitlement: async (input) => {
216
+ if (input.featureId === void 0) {
217
+ const customer = asRecord(await client.customers.get({ customerId: input.referenceId }));
218
+ const product = findProduct(customer, input.priceId ?? "");
219
+ const state = product ? productToSubscription(input.referenceId, product).state : void 0;
220
+ return { allowed: state === "active" || state === "trialing", unlimited: false };
221
+ }
222
+ const result = asRecord(await client.check({ customerId: input.referenceId, featureId: input.featureId, requiredBalance: input.quantity ?? 1 }));
223
+ const allowed = readBoolean(result, "allowed") ?? false;
224
+ const rawBalance = result.balance;
225
+ const balance = typeof rawBalance === "object" && rawBalance !== null ? asRecord(rawBalance) : result;
226
+ return { allowed, ...balanceFields(balance) };
227
+ },
228
+ createCheckout: async (input) => {
229
+ const result = asRecord(await client.billing.attach({ customerId: input.referenceId, planId: input.priceId }));
230
+ return { id: autumnSubscriptionId(input.referenceId, input.priceId), provider: "autumn", url: checkoutUrlFrom(result) };
231
+ },
232
+ createPortalSession: async (input) => {
233
+ const result = asRecord(await client.billing.openCustomerPortal({ customerId: input.customerId }));
234
+ return { url: readAny(result, "url") ?? "" };
235
+ },
236
+ getBalances: async (referenceId) => {
237
+ const customer = asRecord(await client.customers.get({ customerId: referenceId }));
238
+ const balances = asRecord(customer.balances ?? customer.features);
239
+ return Object.entries(balances).map(([key, raw]) => {
240
+ const balance = asRecord(raw);
241
+ const fields = balanceFields(balance);
242
+ const allowed = fields.unlimited || (fields.balance ?? 0) > 0;
243
+ return { allowed, featureId: readAny(balance, "featureId", "feature_id") ?? key, ...fields };
244
+ });
245
+ },
246
+ getOrCreateCustomer: async (ref) => {
247
+ const customer = asRecord(await client.customers.getOrCreate({ customerId: ref.referenceId, email: ref.email, name: ref.metadata?.name }));
248
+ return {
249
+ createdAt: Date.now(),
250
+ email: readAny(customer, "email") ?? ref.email,
251
+ id: readAny(customer, "id") ?? ref.referenceId,
252
+ provider: "autumn",
253
+ referenceId: ref.referenceId
254
+ };
255
+ },
256
+ // Autumn is entitlement-centric; it exposes no standalone one-time payment-session lookup.
257
+ getPaymentStatus: () => notSupported("payment-session reconciliation"),
258
+ getSubscriptionStatus: async (subscriptionId) => {
259
+ const { customerId, productId } = parseAutumnSubscriptionId(subscriptionId);
260
+ return readSubscription(client, customerId, productId);
261
+ },
262
+ identifier: "autumn",
263
+ parseWebhook: async ({ headers, payload }) => {
264
+ const webhookId = headers.get("svix-id") ?? headers.get("webhook-id") ?? "";
265
+ await verifyStandardWebhook({
266
+ payload,
267
+ secret: webhookSecret,
268
+ toleranceSeconds: options.webhookToleranceSeconds,
269
+ webhookId,
270
+ webhookSignature: headers.get("svix-signature") ?? headers.get("webhook-signature") ?? "",
271
+ webhookTimestamp: headers.get("svix-timestamp") ?? headers.get("webhook-timestamp") ?? ""
272
+ });
273
+ const event = asRecord(JSON.parse(payload));
274
+ return mapEvent(webhookId, readString(event, "type") ?? "", asRecord(event.data));
275
+ },
276
+ refundPayment: () => notSupported("refunds"),
277
+ reportUsage: async (input) => {
278
+ await client.track({ customerId: input.referenceId, featureId: input.featureId, value: input.quantity });
279
+ },
280
+ resumeSubscription: async (subscriptionId) => {
281
+ const { customerId, productId } = parseAutumnSubscriptionId(subscriptionId);
282
+ await client.billing.update({ cancelAction: "uncancel", customerId, planId: productId });
283
+ return { ...await readSubscription(client, customerId, productId), cancelAtPeriodEnd: false };
284
+ },
285
+ updateSubscription: async (subscriptionId, patch) => {
286
+ const { customerId, productId } = parseAutumnSubscriptionId(subscriptionId);
287
+ const targetProduct = patch.priceId ?? productId;
288
+ await client.billing.attach({ customerId, planId: targetProduct });
289
+ return readSubscription(client, customerId, targetProduct);
290
+ }
291
+ };
292
+ };
293
+
294
+ export { createAutumnAdapter };
@@ -0,0 +1,17 @@
1
+ import { P as PaymentAdapter } from "../packem_shared/adapter.d-iFA55DzU.mjs";
2
+ /**
3
+ * The `creem` SDK surface the adapter uses, as a structural type — a real `Creem` instance satisfies
4
+ * it without a cast. Resources are `unknown` (the adapter re-types the client as the real `Creem`
5
+ * internally); this keeps the SDK's full type out of the published declarations.
6
+ */
7
+ interface CreemClientLike {
8
+ readonly checkouts: unknown;
9
+ readonly customers: unknown;
10
+ readonly subscriptions: unknown;
11
+ }
12
+ interface CreemAdapterOptions {
13
+ readonly client: CreemClientLike;
14
+ readonly webhookSecret: string;
15
+ }
16
+ declare const createCreemAdapter: (options: CreemAdapterOptions) => PaymentAdapter;
17
+ export { type CreemAdapterOptions, type CreemClientLike, createCreemAdapter };
@@ -0,0 +1,17 @@
1
+ import { P as PaymentAdapter } from "../packem_shared/adapter.d-iFA55DzU.js";
2
+ /**
3
+ * The `creem` SDK surface the adapter uses, as a structural type — a real `Creem` instance satisfies
4
+ * it without a cast. Resources are `unknown` (the adapter re-types the client as the real `Creem`
5
+ * internally); this keeps the SDK's full type out of the published declarations.
6
+ */
7
+ interface CreemClientLike {
8
+ readonly checkouts: unknown;
9
+ readonly customers: unknown;
10
+ readonly subscriptions: unknown;
11
+ }
12
+ interface CreemAdapterOptions {
13
+ readonly client: CreemClientLike;
14
+ readonly webhookSecret: string;
15
+ }
16
+ declare const createCreemAdapter: (options: CreemAdapterOptions) => PaymentAdapter;
17
+ export { type CreemAdapterOptions, type CreemClientLike, createCreemAdapter };
@@ -0,0 +1,205 @@
1
+ import { a as asRecord, r as readString, d as referenceFromMetadata, b as readNumber, p as parseTimestamp, c as readBoolean } from '../packem_shared/json-DhcPm8EO.mjs';
2
+ import { money, zeroMoney } from '../packem_shared/addMoney-jSh_7TfF.mjs';
3
+ import { verifyCreemSignature } from '../packem_shared/constantTimeEqual-BQjoW85H.mjs';
4
+ import { m as makeNotSupported } from '../packem_shared/not-supported-C0onRyia.mjs';
5
+ import { s as stateToEventType } from '../packem_shared/subscription-event-CwEsWRCK.mjs';
6
+
7
+ const PAYMENT_STATE_BY_CREEM_STATUS = {
8
+ canceled: "canceled",
9
+ completed: "captured",
10
+ expired: "canceled",
11
+ paid: "captured",
12
+ partially_refunded: "partially_refunded",
13
+ pending: "initiated",
14
+ refunded: "refunded"
15
+ };
16
+ const SUBSCRIPTION_STATE_BY_CREEM_STATUS = {
17
+ active: "active",
18
+ canceled: "canceled",
19
+ cancelled: "canceled",
20
+ expired: "canceled",
21
+ // SECURITY: `incomplete`/`unpaid`/`past_due` (first or a renewal payment not settled) must not map
22
+ // to an entitling state — see the equivalent `incomplete` note in the Stripe/Polar adapters. A
23
+ // `scheduled_cancel` subscription is still active until period end, so it entitles (the pending
24
+ // cancellation surfaces via `cancelAtPeriodEnd`).
25
+ incomplete: "past_due",
26
+ paid: "active",
27
+ past_due: "past_due",
28
+ paused: "paused",
29
+ scheduled_cancel: "active",
30
+ trialing: "trialing",
31
+ unpaid: "past_due"
32
+ };
33
+ const notSupported = makeNotSupported("creem (merchant-of-record)");
34
+ const idOf = (value) => typeof value === "string" ? value : readString(asRecord(value), "id");
35
+ const readCheckoutUrl = (checkout) => readString(checkout, "checkout_url") ?? readString(checkout, "checkoutUrl") ?? "";
36
+ const isCanceling = (subscription) => readString(subscription, "canceled_at") !== void 0 || readString(subscription, "canceledAt") !== void 0 || readString(subscription, "status") === "scheduled_cancel";
37
+ const subscriptionFromCreem = (input) => {
38
+ const subscription = asRecord(input);
39
+ const now = Date.now();
40
+ const status = readString(subscription, "status") ?? "";
41
+ return {
42
+ cancelAtPeriodEnd: isCanceling(subscription),
43
+ createdAt: now,
44
+ currentPeriodEnd: parseTimestamp(readString(subscription, "current_period_end_date") ?? readString(subscription, "currentPeriodEndDate")),
45
+ currentPeriodStart: parseTimestamp(readString(subscription, "current_period_start_date") ?? readString(subscription, "currentPeriodStartDate")),
46
+ id: readString(subscription, "id") ?? "",
47
+ priceId: idOf(subscription.product) ?? "",
48
+ provider: "creem",
49
+ quantity: readNumber(subscription, "units") ?? 1,
50
+ referenceId: referenceFromMetadata(subscription) ?? idOf(subscription.customer) ?? "",
51
+ // Fail closed: an unrecognized Creem status is treated as non-entitling `past_due`.
52
+ state: SUBSCRIPTION_STATE_BY_CREEM_STATUS[status] ?? "past_due",
53
+ updatedAt: now
54
+ };
55
+ };
56
+ const checkoutToSession = (input) => {
57
+ const checkout = asRecord(input);
58
+ const now = Date.now();
59
+ const order = asRecord(checkout.order);
60
+ const currency = readString(order, "currency") ?? readString(checkout, "currency") ?? "usd";
61
+ const amount = money(BigInt(Math.round(readNumber(order, "amount") ?? readNumber(checkout, "amount") ?? 0)), currency);
62
+ const state = PAYMENT_STATE_BY_CREEM_STATUS[readString(order, "status") ?? readString(checkout, "status") ?? ""] ?? "initiated";
63
+ const settled = state === "captured" || state === "partially_refunded" || state === "refunded";
64
+ return {
65
+ amount,
66
+ capturedAmount: settled ? amount : zeroMoney(currency),
67
+ createdAt: now,
68
+ id: readString(checkout, "id") ?? "",
69
+ provider: "creem",
70
+ referenceId: referenceFromMetadata(checkout) ?? "",
71
+ refundedAmount: state === "refunded" ? amount : zeroMoney(currency),
72
+ state,
73
+ updatedAt: now
74
+ };
75
+ };
76
+ const mapEvent = (eventId, eventType, object) => {
77
+ const base = { eventId, provider: "creem", raw: { object, type: eventType } };
78
+ const order = asRecord(object.order);
79
+ const currency = readString(order, "currency") ?? readString(object, "currency") ?? "usd";
80
+ switch (eventType) {
81
+ case "checkout.completed": {
82
+ const amount = readNumber(order, "amount") ?? readNumber(object, "amount");
83
+ return {
84
+ ...base,
85
+ // Round before BigInt: Creem documents integer minor units, but a stray fractional amount
86
+ // would throw a RangeError out of `parseWebhook` (a 400 → provider retry loop). Match Autumn.
87
+ amount: amount === void 0 ? void 0 : money(BigInt(Math.round(amount)), currency),
88
+ customerId: idOf(object.customer),
89
+ referenceId: referenceFromMetadata(object),
90
+ sessionId: readString(object, "id"),
91
+ subscriptionId: idOf(object.subscription),
92
+ type: "payment.captured"
93
+ };
94
+ }
95
+ case "refund.created": {
96
+ const amount = readNumber(object, "refund_amount") ?? readNumber(object, "refundAmount") ?? readNumber(object, "amount") ?? readNumber(order, "amount");
97
+ const refundCurrency = readString(object, "refund_currency") ?? readString(object, "refundCurrency") ?? currency;
98
+ return {
99
+ ...base,
100
+ amount: amount === void 0 ? void 0 : money(BigInt(Math.round(amount)), refundCurrency),
101
+ referenceId: referenceFromMetadata(object),
102
+ sessionId: idOf(object.transaction) ?? idOf(object.subscription) ?? idOf(object.order) ?? idOf(object.checkout) ?? readString(object, "id"),
103
+ type: "payment.refunded"
104
+ };
105
+ }
106
+ case "subscription.active":
107
+ case "subscription.canceled":
108
+ case "subscription.expired":
109
+ case "subscription.paid":
110
+ case "subscription.past_due":
111
+ case "subscription.paused":
112
+ case "subscription.scheduled_cancel":
113
+ case "subscription.trialing":
114
+ case "subscription.unpaid":
115
+ case "subscription.update": {
116
+ const status = eventType === "subscription.scheduled_cancel" ? "scheduled_cancel" : readString(object, "status");
117
+ return {
118
+ ...base,
119
+ cancelAtPeriodEnd: readBoolean(object, "cancel_at_period_end") ?? isCanceling(object),
120
+ currentPeriodEnd: parseTimestamp(readString(object, "current_period_end_date") ?? readString(object, "currentPeriodEndDate")),
121
+ currentPeriodStart: parseTimestamp(readString(object, "current_period_start_date") ?? readString(object, "currentPeriodStartDate")),
122
+ customerId: idOf(object.customer),
123
+ priceId: idOf(object.product),
124
+ referenceId: referenceFromMetadata(object) ?? idOf(object.customer),
125
+ subscriptionId: readString(object, "id"),
126
+ type: stateToEventType(SUBSCRIPTION_STATE_BY_CREEM_STATUS[status ?? ""])
127
+ };
128
+ }
129
+ default: {
130
+ return { ...base, type: "unhandled" };
131
+ }
132
+ }
133
+ };
134
+ const createCreemAdapter = (options) => {
135
+ const { webhookSecret } = options;
136
+ const client = options.client;
137
+ return {
138
+ // Creem is a Merchant-of-Record: it moves the money, so there is no manual payment-intent flow.
139
+ cancelPayment: () => notSupported("manual payment cancellation"),
140
+ cancelSubscription: async (subscriptionId, cancelOptions) => (
141
+ // Creem supports both immediate and period-end cancellation via `mode`; omitting it defers
142
+ // to the store's configured default, so pass it explicitly. Default (no `atPeriodEnd`) is
143
+ // immediate, matching the other adapters. Creem reports the resulting state, which we return.
144
+ subscriptionFromCreem(await client.subscriptions.cancel(subscriptionId, { mode: cancelOptions?.atPeriodEnd === true ? "scheduled" : "immediate" }))
145
+ ),
146
+ capabilities: { merchantOfRecord: true, portal: true, usageMetering: false },
147
+ capturePayment: (_input) => notSupported("manual capture"),
148
+ createCheckout: async (input) => {
149
+ const checkout = await client.checkouts.create({
150
+ customer: input.customerId ? { id: input.customerId } : void 0,
151
+ // Pin the framework-controlled `referenceId` LAST so caller metadata can never override it.
152
+ metadata: { ...input.metadata, referenceId: input.referenceId },
153
+ productId: input.priceId,
154
+ requestId: input.idempotencyKey,
155
+ successUrl: input.successUrl,
156
+ units: input.quantity
157
+ });
158
+ return { id: readString(checkout, "id") ?? "", provider: "creem", url: readCheckoutUrl(checkout) };
159
+ },
160
+ createPortalSession: async (input) => {
161
+ const link = await client.customers.generateBillingLinks({ customerId: input.customerId });
162
+ return { url: readString(link, "customer_portal_link") ?? readString(link, "customerPortalLink") ?? "" };
163
+ },
164
+ getOrCreateCustomer: async (ref) => {
165
+ const toCustomer = (record) => {
166
+ return {
167
+ createdAt: Date.now(),
168
+ email: readString(record, "email") ?? ref.email,
169
+ id: readString(record, "id") ?? "",
170
+ provider: "creem",
171
+ referenceId: ref.referenceId
172
+ };
173
+ };
174
+ try {
175
+ return toCustomer(asRecord(await client.customers.create({ email: ref.email ?? "", name: ref.metadata?.name ?? ref.referenceId })));
176
+ } catch (error) {
177
+ if (ref.email !== void 0) {
178
+ return toCustomer(asRecord(await client.customers.retrieve(void 0, ref.email)));
179
+ }
180
+ throw error;
181
+ }
182
+ },
183
+ getPaymentStatus: async (sessionId) => checkoutToSession(await client.checkouts.retrieve(sessionId)),
184
+ getSubscriptionStatus: async (subscriptionId) => subscriptionFromCreem(await client.subscriptions.get(subscriptionId)),
185
+ identifier: "creem",
186
+ parseWebhook: async ({ headers, payload }) => {
187
+ await verifyCreemSignature({ payload, secret: webhookSecret, signature: headers.get("creem-signature") ?? "" });
188
+ const event = asRecord(JSON.parse(payload));
189
+ return mapEvent(readString(event, "id") ?? "", readString(event, "eventType") ?? readString(event, "type") ?? "", asRecord(event.object));
190
+ },
191
+ // Creem refunds are issued from the dashboard; there is no SDK endpoint to initiate one.
192
+ refundPayment: () => notSupported("programmatic refunds"),
193
+ resumeSubscription: async (subscriptionId) => subscriptionFromCreem(await client.subscriptions.resume(subscriptionId)),
194
+ updateSubscription: async (subscriptionId, patch) => {
195
+ if (patch.priceId) {
196
+ return subscriptionFromCreem(
197
+ await client.subscriptions.upgrade(subscriptionId, { productId: patch.priceId, updateBehavior: "proration-charge-immediately" })
198
+ );
199
+ }
200
+ return subscriptionFromCreem(await client.subscriptions.get(subscriptionId));
201
+ }
202
+ };
203
+ };
204
+
205
+ export { createCreemAdapter };
@@ -0,0 +1,21 @@
1
+ import { P as PaymentAdapter } from "../packem_shared/adapter.d-iFA55DzU.mjs";
2
+ /**
3
+ * The `dodopayments` SDK surface the adapter uses, as a structural type — a real `DodoPayments`
4
+ * instance satisfies it without a cast. Resources are `unknown` (the adapter re-types the client as
5
+ * the real `DodoPayments` internally); this keeps the SDK's full type out of the published declarations.
6
+ */
7
+ interface DodoPaymentsClientLike {
8
+ readonly checkoutSessions: unknown;
9
+ readonly customers: unknown;
10
+ readonly payments: unknown;
11
+ readonly refunds: unknown;
12
+ readonly subscriptions: unknown;
13
+ readonly usageEvents: unknown;
14
+ }
15
+ interface DodoPaymentsAdapterOptions {
16
+ readonly client: DodoPaymentsClientLike;
17
+ readonly webhookSecret: string;
18
+ readonly webhookToleranceSeconds?: number;
19
+ }
20
+ declare const createDodoPaymentsAdapter: (options: DodoPaymentsAdapterOptions) => PaymentAdapter;
21
+ export { type DodoPaymentsAdapterOptions, type DodoPaymentsClientLike, createDodoPaymentsAdapter };
@@ -0,0 +1,21 @@
1
+ import { P as PaymentAdapter } from "../packem_shared/adapter.d-iFA55DzU.js";
2
+ /**
3
+ * The `dodopayments` SDK surface the adapter uses, as a structural type — a real `DodoPayments`
4
+ * instance satisfies it without a cast. Resources are `unknown` (the adapter re-types the client as
5
+ * the real `DodoPayments` internally); this keeps the SDK's full type out of the published declarations.
6
+ */
7
+ interface DodoPaymentsClientLike {
8
+ readonly checkoutSessions: unknown;
9
+ readonly customers: unknown;
10
+ readonly payments: unknown;
11
+ readonly refunds: unknown;
12
+ readonly subscriptions: unknown;
13
+ readonly usageEvents: unknown;
14
+ }
15
+ interface DodoPaymentsAdapterOptions {
16
+ readonly client: DodoPaymentsClientLike;
17
+ readonly webhookSecret: string;
18
+ readonly webhookToleranceSeconds?: number;
19
+ }
20
+ declare const createDodoPaymentsAdapter: (options: DodoPaymentsAdapterOptions) => PaymentAdapter;
21
+ export { type DodoPaymentsAdapterOptions, type DodoPaymentsClientLike, createDodoPaymentsAdapter };