@lunora/payment 1.0.0-alpha.16 → 1.0.0-alpha.18

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 (33) hide show
  1. package/LICENSE.md +6 -0
  2. package/README.md +3 -2
  3. package/dist/index.d.mts +17 -456
  4. package/dist/index.d.ts +17 -456
  5. package/dist/index.mjs +3 -5
  6. package/dist/packem_shared/adapter.d-iFA55DzU.d.mts +312 -0
  7. package/dist/packem_shared/adapter.d-iFA55DzU.d.ts +312 -0
  8. package/dist/packem_shared/{constantTimeEqual-BN5Hm31G.mjs → constantTimeEqual-BQjoW85H.mjs} +13 -37
  9. package/dist/packem_shared/{createPayment-DP_LO7jc.mjs → createPayment-DzX-ji34.mjs} +15 -3
  10. package/dist/packem_shared/json-DhcPm8EO.mjs +11 -0
  11. package/dist/packem_shared/{lunoraDatabaseToPaymentDatabase-CQQlnRCJ.mjs → lunoraDatabaseToPaymentDatabase-CL2vdAXq.mjs} +1 -1
  12. package/dist/packem_shared/not-supported-C0onRyia.mjs +7 -0
  13. package/dist/packem_shared/subscription-event-CwEsWRCK.mjs +17 -0
  14. package/dist/providers/autumn-features.d.mts +94 -0
  15. package/dist/providers/autumn-features.d.ts +94 -0
  16. package/dist/providers/autumn-features.mjs +95 -0
  17. package/dist/providers/autumn.d.mts +19 -0
  18. package/dist/providers/autumn.d.ts +19 -0
  19. package/dist/providers/autumn.mjs +294 -0
  20. package/dist/providers/creem.d.mts +17 -0
  21. package/dist/providers/creem.d.ts +17 -0
  22. package/dist/providers/creem.mjs +205 -0
  23. package/dist/providers/dodopayments.d.mts +21 -0
  24. package/dist/providers/dodopayments.d.ts +21 -0
  25. package/dist/providers/dodopayments.mjs +259 -0
  26. package/dist/providers/polar.d.mts +22 -0
  27. package/dist/providers/polar.d.ts +22 -0
  28. package/dist/{packem_shared/createPolarAdapter-BovYiWCX.mjs → providers/polar.mjs} +47 -46
  29. package/dist/providers/stripe.d.mts +23 -0
  30. package/dist/providers/stripe.d.ts +23 -0
  31. package/dist/{packem_shared/createStripeAdapter-4a7fej3t.mjs → providers/stripe.mjs} +45 -53
  32. package/package.json +45 -5
  33. package/dist/packem_shared/json-Db337f36.mjs +0 -6
@@ -0,0 +1,259 @@
1
+ import { LunoraPaymentError } from '../packem_shared/LunoraPaymentError-BeQlhkZj.mjs';
2
+ import idempotencyKey from '../packem_shared/idempotencyKey-BFzDCA7g.mjs';
3
+ import { a as asRecord, b as readNumber, r as readString, d as referenceFromMetadata, p as parseTimestamp, c as readBoolean } from '../packem_shared/json-DhcPm8EO.mjs';
4
+ import { money, zeroMoney } from '../packem_shared/addMoney-jSh_7TfF.mjs';
5
+ import { verifyStandardWebhook } from '../packem_shared/constantTimeEqual-BQjoW85H.mjs';
6
+ import { m as makeNotSupported } from '../packem_shared/not-supported-C0onRyia.mjs';
7
+ import { s as stateToEventType } from '../packem_shared/subscription-event-CwEsWRCK.mjs';
8
+
9
+ const PAYMENT_STATE_BY_DODO_STATUS = {
10
+ cancelled: "canceled",
11
+ failed: "failed",
12
+ partially_captured: "captured",
13
+ partially_captured_and_capturable: "captured",
14
+ processing: "initiated",
15
+ requires_capture: "authorized",
16
+ requires_confirmation: "initiated",
17
+ requires_customer_action: "authorized",
18
+ requires_merchant_action: "authorized",
19
+ requires_payment_method: "initiated",
20
+ succeeded: "captured"
21
+ };
22
+ const SUBSCRIPTION_STATE_BY_DODO_STATUS = {
23
+ active: "active",
24
+ cancelled: "canceled",
25
+ expired: "canceled",
26
+ // SECURITY: `pending` (first payment not completed), `failed`, and `on_hold` (dunning) must not
27
+ // map to an entitling state — see the equivalent `incomplete` note in the Stripe/Polar adapters.
28
+ // Treat them as non-entitling `past_due`; Dodo has no trial status in this enum.
29
+ failed: "past_due",
30
+ on_hold: "past_due",
31
+ // A paused subscription is non-entitling until it resumes, but distinct from dunning — it maps to
32
+ // the `paused` state so a `subscription.paused` webhook routes to `subscription.paused` (not the
33
+ // generic `subscription.updated`) and stays consistent with `subscriptionFromDodo`.
34
+ paused: "paused",
35
+ pending: "past_due"
36
+ };
37
+ const notSupported = makeNotSupported("dodopayments (merchant-of-record)");
38
+ const customerIdOf = (object) => readString(asRecord(object.customer), "customer_id") ?? readString(object, "customer_id");
39
+ const subscriptionFromDodo = (input) => {
40
+ const subscription = asRecord(input);
41
+ const now = Date.now();
42
+ const status = readString(subscription, "status") ?? "";
43
+ return {
44
+ cancelAtPeriodEnd: readBoolean(subscription, "cancel_at_next_billing_date") ?? false,
45
+ createdAt: now,
46
+ currentPeriodEnd: parseTimestamp(readString(subscription, "next_billing_date")),
47
+ currentPeriodStart: parseTimestamp(readString(subscription, "previous_billing_date")),
48
+ id: readString(subscription, "subscription_id") ?? "",
49
+ priceId: readString(subscription, "product_id") ?? "",
50
+ provider: "dodopayments",
51
+ quantity: readNumber(subscription, "quantity") ?? 1,
52
+ referenceId: referenceFromMetadata(subscription) ?? customerIdOf(subscription) ?? "",
53
+ // Fail closed: an unrecognized Dodo status is treated as non-entitling `past_due`.
54
+ state: SUBSCRIPTION_STATE_BY_DODO_STATUS[status] ?? "past_due",
55
+ updatedAt: now
56
+ };
57
+ };
58
+ const paymentFromDodo = (input) => {
59
+ const payment = asRecord(input);
60
+ const now = Date.now();
61
+ const currency = readString(payment, "currency") ?? "usd";
62
+ const amount = money(BigInt(Math.round(readNumber(payment, "total_amount") ?? 0)), currency);
63
+ const state = PAYMENT_STATE_BY_DODO_STATUS[readString(payment, "status") ?? ""] ?? "initiated";
64
+ return {
65
+ amount,
66
+ capturedAmount: state === "captured" ? amount : zeroMoney(currency),
67
+ createdAt: now,
68
+ id: readString(payment, "payment_id") ?? "",
69
+ provider: "dodopayments",
70
+ referenceId: referenceFromMetadata(payment) ?? "",
71
+ refundedAmount: zeroMoney(currency),
72
+ state,
73
+ updatedAt: now
74
+ };
75
+ };
76
+ const mapEvent = (eventId, eventType, object) => {
77
+ const base = { eventId, provider: "dodopayments", raw: { object, type: eventType } };
78
+ const currency = readString(object, "currency") ?? "usd";
79
+ switch (eventType) {
80
+ // A lost chargeback (`dispute.lost`) is a definitive funds reversal carrying `amount` +
81
+ // `payment_id` — economically a refund, so it shares the refund mapping (record it so a customer
82
+ // who charges back doesn't stay entitled). Provisional dispute stages move no money and stay
83
+ // `unhandled`; a won dispute needs no transition.
84
+ case "dispute.lost":
85
+ case "refund.succeeded": {
86
+ return {
87
+ ...base,
88
+ amount: money(BigInt(Math.round(readNumber(object, "amount") ?? 0)), currency),
89
+ referenceId: referenceFromMetadata(object),
90
+ sessionId: readString(object, "payment_id"),
91
+ type: "payment.refunded"
92
+ };
93
+ }
94
+ // A cancelled payment never settled — record it as a non-entitling failure (there is no
95
+ // dedicated `payment.canceled` action; `failed` is the closest terminal, non-entitling state).
96
+ case "payment.cancelled":
97
+ case "payment.failed": {
98
+ return { ...base, referenceId: referenceFromMetadata(object), sessionId: readString(object, "payment_id"), type: "payment.failed" };
99
+ }
100
+ case "payment.succeeded": {
101
+ return {
102
+ ...base,
103
+ amount: money(BigInt(Math.round(readNumber(object, "total_amount") ?? 0)), currency),
104
+ customerId: customerIdOf(object),
105
+ referenceId: referenceFromMetadata(object),
106
+ sessionId: readString(object, "payment_id"),
107
+ subscriptionId: readString(object, "subscription_id"),
108
+ type: "payment.captured"
109
+ };
110
+ }
111
+ case "subscription.active":
112
+ case "subscription.cancelled":
113
+ case "subscription.expired":
114
+ case "subscription.failed":
115
+ case "subscription.on_hold":
116
+ case "subscription.paused":
117
+ case "subscription.plan_changed":
118
+ case "subscription.renewed":
119
+ case "subscription.updated": {
120
+ const status = readString(object, "status");
121
+ return {
122
+ ...base,
123
+ cancelAtPeriodEnd: readBoolean(object, "cancel_at_next_billing_date"),
124
+ currentPeriodEnd: parseTimestamp(readString(object, "next_billing_date")),
125
+ currentPeriodStart: parseTimestamp(readString(object, "previous_billing_date")),
126
+ customerId: customerIdOf(object),
127
+ priceId: readString(object, "product_id"),
128
+ quantity: readNumber(object, "quantity"),
129
+ referenceId: referenceFromMetadata(object) ?? customerIdOf(object),
130
+ subscriptionId: readString(object, "subscription_id"),
131
+ type: stateToEventType(SUBSCRIPTION_STATE_BY_DODO_STATUS[status ?? ""])
132
+ };
133
+ }
134
+ default: {
135
+ return { ...base, type: "unhandled" };
136
+ }
137
+ }
138
+ };
139
+ const createDodoPaymentsAdapter = (options) => {
140
+ const { webhookSecret } = options;
141
+ const client = options.client;
142
+ return {
143
+ // Dodo is a Merchant-of-Record: it moves the money, so there is no manual payment-intent
144
+ // authorize/capture/cancel. Refunds, however, are supported below.
145
+ cancelPayment: () => notSupported("manual payment cancellation"),
146
+ cancelSubscription: async (subscriptionId, cancelOptions) => {
147
+ const body = cancelOptions?.atPeriodEnd ? { cancel_at_next_billing_date: true } : { status: "cancelled" };
148
+ return subscriptionFromDodo(await client.subscriptions.update(subscriptionId, body));
149
+ },
150
+ capabilities: { merchantOfRecord: true, portal: true, usageMetering: true },
151
+ capturePayment: (_input) => notSupported("manual capture"),
152
+ createCheckout: async (input) => {
153
+ const session = asRecord(
154
+ await client.checkoutSessions.create({
155
+ // An existing Dodo customer is attached by id; otherwise Dodo collects one at checkout.
156
+ customer: input.customerId ? { customer_id: input.customerId } : void 0,
157
+ // Pin the framework-controlled `referenceId` LAST so caller metadata can never override it.
158
+ metadata: { ...input.metadata, referenceId: input.referenceId },
159
+ product_cart: [{ product_id: input.priceId, quantity: input.quantity ?? 1 }],
160
+ return_url: input.successUrl
161
+ })
162
+ );
163
+ return { id: readString(session, "session_id") ?? "", provider: "dodopayments", url: readString(session, "checkout_url") ?? "" };
164
+ },
165
+ createPortalSession: async (input) => {
166
+ const session = asRecord(await client.customers.customerPortal.create(input.customerId, { return_url: input.returnUrl }));
167
+ return { url: readString(session, "link") ?? readString(session, "url") ?? "" };
168
+ },
169
+ getOrCreateCustomer: async (ref) => {
170
+ const customer = asRecord(
171
+ await client.customers.create(
172
+ { email: ref.email ?? "", name: ref.metadata?.name ?? ref.referenceId },
173
+ { idempotencyKey: idempotencyKey("customer", "dodopayments", ref.referenceId) }
174
+ )
175
+ );
176
+ return {
177
+ createdAt: Date.now(),
178
+ email: readString(customer, "email") ?? ref.email,
179
+ id: readString(customer, "customer_id") ?? "",
180
+ provider: "dodopayments",
181
+ referenceId: ref.referenceId
182
+ };
183
+ },
184
+ getPaymentStatus: async (sessionId) => paymentFromDodo(await client.payments.retrieve(sessionId)),
185
+ getSubscriptionStatus: async (subscriptionId) => subscriptionFromDodo(await client.subscriptions.retrieve(subscriptionId)),
186
+ identifier: "dodopayments",
187
+ parseWebhook: async ({ headers, payload }) => {
188
+ const webhookId = headers.get("webhook-id") ?? "";
189
+ await verifyStandardWebhook({
190
+ payload,
191
+ secret: webhookSecret,
192
+ toleranceSeconds: options.webhookToleranceSeconds,
193
+ webhookId,
194
+ webhookSignature: headers.get("webhook-signature") ?? "",
195
+ webhookTimestamp: headers.get("webhook-timestamp") ?? ""
196
+ });
197
+ const event = asRecord(JSON.parse(payload));
198
+ return mapEvent(webhookId, readString(event, "type") ?? "", asRecord(event.data));
199
+ },
200
+ refundPayment: async (input) => {
201
+ if (input.amount !== void 0) {
202
+ throw new LunoraPaymentError(
203
+ "PROVIDER_ERROR",
204
+ "dodopayments refunds a payment in full; partial refunds require line items and aren't supported here"
205
+ );
206
+ }
207
+ const refund = asRecord(await client.refunds.create({ payment_id: input.sessionId, reason: input.reason }));
208
+ const currency = readString(refund, "currency") ?? "usd";
209
+ const refundedAmount = money(BigInt(Math.round(readNumber(refund, "amount") ?? 0)), currency);
210
+ let state = "captured";
211
+ if (readString(refund, "status") === "succeeded") {
212
+ state = "refunded";
213
+ }
214
+ return {
215
+ amount: refundedAmount,
216
+ capturedAmount: refundedAmount,
217
+ createdAt: Date.now(),
218
+ id: input.sessionId,
219
+ provider: "dodopayments",
220
+ referenceId: "",
221
+ refundedAmount,
222
+ state,
223
+ updatedAt: Date.now()
224
+ };
225
+ },
226
+ reportUsage: async (input) => {
227
+ await client.usageEvents.ingest({
228
+ events: [
229
+ {
230
+ customer_id: input.customerId ?? input.referenceId,
231
+ event_id: input.idempotencyKey,
232
+ event_name: input.featureId,
233
+ metadata: { value: input.quantity },
234
+ timestamp: input.timestamp === void 0 ? void 0 : new Date(input.timestamp).toISOString()
235
+ }
236
+ ]
237
+ });
238
+ },
239
+ resumeSubscription: async (subscriptionId) => {
240
+ const subscription = await client.subscriptions.update(subscriptionId, { cancel_at_next_billing_date: false });
241
+ return subscriptionFromDodo(subscription);
242
+ },
243
+ updateSubscription: async (subscriptionId, patch) => {
244
+ if (patch.priceId !== void 0 || patch.quantity !== void 0) {
245
+ const current = asRecord(
246
+ patch.priceId !== void 0 && patch.quantity !== void 0 ? void 0 : await client.subscriptions.retrieve(subscriptionId)
247
+ );
248
+ await client.subscriptions.changePlan(subscriptionId, {
249
+ product_id: patch.priceId ?? readString(current, "product_id") ?? "",
250
+ proration_billing_mode: "prorated_immediately",
251
+ quantity: patch.quantity ?? readNumber(current, "quantity") ?? 1
252
+ });
253
+ }
254
+ return subscriptionFromDodo(await client.subscriptions.retrieve(subscriptionId));
255
+ }
256
+ };
257
+ };
258
+
259
+ export { createDodoPaymentsAdapter };
@@ -0,0 +1,22 @@
1
+ import { P as PaymentAdapter } from "../packem_shared/adapter.d-iFA55DzU.mjs";
2
+ /**
3
+ * The `@polar-sh/sdk` surface the adapter uses, as a structural type — a real `Polar` instance
4
+ * satisfies it without a cast. Resources are `unknown` (the adapter re-types the client as the real
5
+ * `Polar` internally); this keeps the SDK's full type out of the published declarations.
6
+ */
7
+ interface PolarClientLike {
8
+ readonly checkouts: unknown;
9
+ readonly customers: unknown;
10
+ readonly customerSessions: unknown;
11
+ readonly events: unknown;
12
+ readonly orders: unknown;
13
+ readonly refunds: unknown;
14
+ readonly subscriptions: unknown;
15
+ }
16
+ interface PolarAdapterOptions {
17
+ readonly client: PolarClientLike;
18
+ readonly webhookSecret: string;
19
+ readonly webhookToleranceSeconds?: number;
20
+ }
21
+ declare const createPolarAdapter: (options: PolarAdapterOptions) => PaymentAdapter;
22
+ export { type PolarAdapterOptions, type PolarClientLike, createPolarAdapter };
@@ -0,0 +1,22 @@
1
+ import { P as PaymentAdapter } from "../packem_shared/adapter.d-iFA55DzU.js";
2
+ /**
3
+ * The `@polar-sh/sdk` surface the adapter uses, as a structural type — a real `Polar` instance
4
+ * satisfies it without a cast. Resources are `unknown` (the adapter re-types the client as the real
5
+ * `Polar` internally); this keeps the SDK's full type out of the published declarations.
6
+ */
7
+ interface PolarClientLike {
8
+ readonly checkouts: unknown;
9
+ readonly customers: unknown;
10
+ readonly customerSessions: unknown;
11
+ readonly events: unknown;
12
+ readonly orders: unknown;
13
+ readonly refunds: unknown;
14
+ readonly subscriptions: unknown;
15
+ }
16
+ interface PolarAdapterOptions {
17
+ readonly client: PolarClientLike;
18
+ readonly webhookSecret: string;
19
+ readonly webhookToleranceSeconds?: number;
20
+ }
21
+ declare const createPolarAdapter: (options: PolarAdapterOptions) => PaymentAdapter;
22
+ export { type PolarAdapterOptions, type PolarClientLike, createPolarAdapter };
@@ -1,13 +1,22 @@
1
- import { LunoraPaymentError } from './LunoraPaymentError-BeQlhkZj.mjs';
2
- import { a as asRecord, r as readString, b as readBoolean, c as readNumber } from './json-Db337f36.mjs';
3
- import { money, zeroMoney } from './addMoney-jSh_7TfF.mjs';
4
- import { verifyStandardWebhook } from './constantTimeEqual-BN5Hm31G.mjs';
1
+ import { a as asRecord, r as readString, d as referenceFromMetadata, c as readBoolean, p as parseTimestamp, b as readNumber } from '../packem_shared/json-DhcPm8EO.mjs';
2
+ import { money, zeroMoney } from '../packem_shared/addMoney-jSh_7TfF.mjs';
3
+ import { verifyStandardWebhook } 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';
5
6
 
7
+ const toEpochMs = (value) => {
8
+ if (value instanceof Date) {
9
+ return value.getTime();
10
+ }
11
+ return parseTimestamp(typeof value === "string" ? value : void 0);
12
+ };
6
13
  const PAYMENT_STATE_BY_POLAR_ORDER_STATUS = {
14
+ draft: "initiated",
7
15
  paid: "captured",
8
16
  partially_refunded: "partially_refunded",
9
17
  pending: "initiated",
10
- refunded: "refunded"
18
+ refunded: "refunded",
19
+ void: "canceled"
11
20
  };
12
21
  const SUBSCRIPTION_STATE_BY_POLAR_STATUS = {
13
22
  active: "active",
@@ -21,40 +30,36 @@ const SUBSCRIPTION_STATE_BY_POLAR_STATUS = {
21
30
  trialing: "trialing",
22
31
  unpaid: "past_due"
23
32
  };
24
- const notSupported = (operation) => {
25
- throw new LunoraPaymentError("PROVIDER_ERROR", `polar (merchant-of-record) does not support ${operation}`);
26
- };
27
- const parseTimestamp = (value) => {
28
- const parsed = typeof value === "string" ? Date.parse(value) : Number.NaN;
29
- return Number.isNaN(parsed) ? void 0 : parsed;
30
- };
31
- const subscriptionFromPolar = (subscription) => {
33
+ const notSupported = makeNotSupported("polar (merchant-of-record)");
34
+ const subscriptionFromPolar = (input) => {
35
+ const subscription = asRecord(input);
32
36
  const now = Date.now();
33
37
  return {
34
- cancelAtPeriodEnd: subscription.cancelAtPeriodEnd ?? false,
38
+ cancelAtPeriodEnd: readBoolean(subscription, "cancelAtPeriodEnd") ?? false,
35
39
  createdAt: now,
36
- currentPeriodEnd: parseTimestamp(subscription.currentPeriodEnd),
37
- currentPeriodStart: parseTimestamp(subscription.currentPeriodStart),
38
- id: subscription.id,
39
- priceId: subscription.productId ?? "",
40
+ currentPeriodEnd: toEpochMs(subscription.currentPeriodEnd),
41
+ currentPeriodStart: toEpochMs(subscription.currentPeriodStart),
42
+ id: readString(subscription, "id") ?? "",
43
+ priceId: readString(subscription, "productId") ?? "",
40
44
  provider: "polar",
41
45
  quantity: 1,
42
- referenceId: subscription.metadata?.referenceId ?? "",
43
- state: SUBSCRIPTION_STATE_BY_POLAR_STATUS[subscription.status] ?? "active",
46
+ referenceId: referenceFromMetadata(subscription) ?? "",
47
+ state: SUBSCRIPTION_STATE_BY_POLAR_STATUS[readString(subscription, "status") ?? ""] ?? "active",
44
48
  updatedAt: now
45
49
  };
46
50
  };
47
- const orderToSession = (order) => {
51
+ const orderToSession = (input) => {
52
+ const order = asRecord(input);
48
53
  const now = Date.now();
49
- const currency = order.currency ?? "usd";
50
- const amount = money(BigInt(order.totalAmount ?? order.amount ?? 0), currency);
51
- const state = PAYMENT_STATE_BY_POLAR_ORDER_STATUS[order.status] ?? "initiated";
54
+ const currency = readString(order, "currency") ?? "usd";
55
+ const amount = money(BigInt(Math.round(readNumber(order, "totalAmount") ?? readNumber(order, "amount") ?? 0)), currency);
56
+ const state = PAYMENT_STATE_BY_POLAR_ORDER_STATUS[readString(order, "status") ?? ""] ?? "initiated";
52
57
  const settled = state === "captured" || state === "partially_refunded" || state === "refunded";
53
58
  return {
54
59
  amount,
55
60
  capturedAmount: settled ? amount : zeroMoney(currency),
56
61
  createdAt: now,
57
- id: order.id,
62
+ id: readString(order, "id") ?? "",
58
63
  provider: "polar",
59
64
  referenceId: "",
60
65
  refundedAmount: state === "refunded" ? amount : zeroMoney(currency),
@@ -62,26 +67,15 @@ const orderToSession = (order) => {
62
67
  updatedAt: now
63
68
  };
64
69
  };
65
- const subscriptionEventType = (status) => {
66
- const state = status ? SUBSCRIPTION_STATE_BY_POLAR_STATUS[status] : void 0;
67
- if (state === "canceled") {
68
- return "subscription.canceled";
69
- }
70
- if (state === "past_due") {
71
- return "subscription.past_due";
72
- }
73
- if (state === "active" || state === "trialing") {
74
- return "subscription.active";
75
- }
76
- return "subscription.updated";
77
- };
78
- const referenceFromMetadata = (object) => readString(asRecord(object.metadata), "referenceId");
79
70
  const mapEvent = (eventId, eventType, object) => {
80
71
  const base = { eventId, provider: "polar", raw: { object, type: eventType } };
81
72
  const currency = readString(object, "currency") ?? "usd";
82
73
  switch (eventType) {
83
74
  case "order.created":
84
75
  case "order.paid": {
76
+ if (eventType === "order.created" && PAYMENT_STATE_BY_POLAR_ORDER_STATUS[readString(object, "status") ?? ""] !== "captured") {
77
+ return { ...base, type: "unhandled" };
78
+ }
85
79
  return {
86
80
  ...base,
87
81
  amount: money(BigInt(readNumber(object, "total_amount") ?? readNumber(object, "amount") ?? 0), currency),
@@ -105,8 +99,9 @@ const mapEvent = (eventId, eventType, object) => {
105
99
  case "subscription.canceled":
106
100
  case "subscription.created":
107
101
  case "subscription.revoked":
102
+ case "subscription.uncanceled":
108
103
  case "subscription.updated": {
109
- const type = eventType === "subscription.revoked" ? "subscription.canceled" : subscriptionEventType(readString(object, "status"));
104
+ const type = eventType === "subscription.revoked" ? "subscription.canceled" : stateToEventType(SUBSCRIPTION_STATE_BY_POLAR_STATUS[readString(object, "status") ?? ""]);
110
105
  return {
111
106
  ...base,
112
107
  cancelAtPeriodEnd: readBoolean(object, "cancel_at_period_end"),
@@ -125,7 +120,8 @@ const mapEvent = (eventId, eventType, object) => {
125
120
  }
126
121
  };
127
122
  const createPolarAdapter = (options) => {
128
- const { client, webhookSecret } = options;
123
+ const { webhookSecret } = options;
124
+ const client = options.client;
129
125
  return {
130
126
  cancelPayment: () => notSupported("manual payment cancellation"),
131
127
  cancelSubscription: async (subscriptionId, cancelOptions) => {
@@ -150,9 +146,10 @@ const createPolarAdapter = (options) => {
150
146
  },
151
147
  getOrCreateCustomer: async (ref) => {
152
148
  const customer = await client.customers.create({
153
- email: ref.email,
149
+ email: ref.email ?? "",
154
150
  externalId: ref.referenceId,
155
- metadata: { ...ref.metadata, referenceId: ref.referenceId }
151
+ metadata: { ...ref.metadata, referenceId: ref.referenceId },
152
+ type: "individual"
156
153
  });
157
154
  return { createdAt: Date.now(), email: customer.email ?? void 0, id: customer.id, provider: "polar", referenceId: ref.referenceId };
158
155
  },
@@ -173,12 +170,16 @@ const createPolarAdapter = (options) => {
173
170
  return mapEvent(webhookId, readString(event, "type") ?? "", asRecord(event.data));
174
171
  },
175
172
  refundPayment: async (input) => {
173
+ const order = input.amount ? void 0 : await client.orders.get({ id: input.sessionId });
174
+ const currency = input.amount?.currency ?? order?.currency ?? "usd";
175
+ const amountMinor = input.amount ? Number(input.amount.minorUnits) : order?.totalAmount ?? 0;
176
176
  await client.refunds.create({
177
- amount: input.amount ? Number(input.amount.minorUnits) : void 0,
177
+ amount: amountMinor,
178
178
  orderId: input.sessionId,
179
+ // `reason` is an open enum; a caller-supplied string is cast onto it (defaults to customer_request).
179
180
  reason: input.reason ?? "customer_request"
180
181
  });
181
- const refundedAmount = input.amount ?? money(0, "usd");
182
+ const refundedAmount = input.amount ?? money(BigInt(Math.round(amountMinor)), currency);
182
183
  return {
183
184
  amount: refundedAmount,
184
185
  capturedAmount: refundedAmount,
@@ -198,7 +199,7 @@ const createPolarAdapter = (options) => {
198
199
  externalCustomerId: input.referenceId,
199
200
  metadata: { value: input.quantity },
200
201
  name: input.featureId,
201
- timestamp: input.timestamp === void 0 ? void 0 : new Date(input.timestamp).toISOString()
202
+ timestamp: input.timestamp === void 0 ? void 0 : new Date(input.timestamp)
202
203
  }
203
204
  ]
204
205
  });
@@ -0,0 +1,23 @@
1
+ import { P as PaymentAdapter } from "../packem_shared/adapter.d-iFA55DzU.mjs";
2
+ /**
3
+ * The Stripe SDK surface the adapter uses, as a structural type — a real `Stripe` instance satisfies
4
+ * it without a cast. Resources are `unknown` here (the adapter re-types the client as the real
5
+ * `Stripe` internally); this keeps the SDK's full type out of the published declaration files.
6
+ */
7
+ interface StripeClientLike {
8
+ readonly billing: unknown;
9
+ readonly billingPortal: unknown;
10
+ readonly checkout: unknown;
11
+ readonly customers: unknown;
12
+ readonly paymentIntents: unknown;
13
+ readonly refunds: unknown;
14
+ readonly subscriptions: unknown;
15
+ readonly webhooks: unknown;
16
+ }
17
+ interface StripeAdapterOptions {
18
+ readonly client: StripeClientLike;
19
+ readonly webhookSecret: string;
20
+ readonly webhookToleranceSeconds?: number;
21
+ }
22
+ declare const createStripeAdapter: (options: StripeAdapterOptions) => PaymentAdapter;
23
+ export { type StripeAdapterOptions, type StripeClientLike, createStripeAdapter };
@@ -0,0 +1,23 @@
1
+ import { P as PaymentAdapter } from "../packem_shared/adapter.d-iFA55DzU.js";
2
+ /**
3
+ * The Stripe SDK surface the adapter uses, as a structural type — a real `Stripe` instance satisfies
4
+ * it without a cast. Resources are `unknown` here (the adapter re-types the client as the real
5
+ * `Stripe` internally); this keeps the SDK's full type out of the published declaration files.
6
+ */
7
+ interface StripeClientLike {
8
+ readonly billing: unknown;
9
+ readonly billingPortal: unknown;
10
+ readonly checkout: unknown;
11
+ readonly customers: unknown;
12
+ readonly paymentIntents: unknown;
13
+ readonly refunds: unknown;
14
+ readonly subscriptions: unknown;
15
+ readonly webhooks: unknown;
16
+ }
17
+ interface StripeAdapterOptions {
18
+ readonly client: StripeClientLike;
19
+ readonly webhookSecret: string;
20
+ readonly webhookToleranceSeconds?: number;
21
+ }
22
+ declare const createStripeAdapter: (options: StripeAdapterOptions) => PaymentAdapter;
23
+ export { type StripeAdapterOptions, type StripeClientLike, createStripeAdapter };