@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,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,57 +1,65 @@
1
- import { LunoraPaymentError } from './LunoraPaymentError-B3hEzXSs.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-bCcs1nyw.mjs';
4
- import { verifyStandardWebhook } from './constantTimeEqual-CfY0jYcL.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",
14
23
  canceled: "canceled",
15
- incomplete: "trialing",
24
+ // SECURITY: `incomplete` (first payment not completed) must not map to an
25
+ // entitling state — see the equivalent note in the Stripe adapter. Treat it as
26
+ // non-entitling `past_due`; reserve `trialing` for a genuine trial.
27
+ incomplete: "past_due",
16
28
  incomplete_expired: "canceled",
17
29
  past_due: "past_due",
18
30
  trialing: "trialing",
19
31
  unpaid: "past_due"
20
32
  };
21
- const notSupported = (operation) => {
22
- throw new LunoraPaymentError("PROVIDER_ERROR", `polar (merchant-of-record) does not support ${operation}`);
23
- };
24
- const parseTimestamp = (value) => {
25
- const parsed = typeof value === "string" ? Date.parse(value) : Number.NaN;
26
- return Number.isNaN(parsed) ? void 0 : parsed;
27
- };
28
- const subscriptionFromPolar = (subscription) => {
33
+ const notSupported = makeNotSupported("polar (merchant-of-record)");
34
+ const subscriptionFromPolar = (input) => {
35
+ const subscription = asRecord(input);
29
36
  const now = Date.now();
30
37
  return {
31
- cancelAtPeriodEnd: subscription.cancelAtPeriodEnd ?? false,
38
+ cancelAtPeriodEnd: readBoolean(subscription, "cancelAtPeriodEnd") ?? false,
32
39
  createdAt: now,
33
- currentPeriodEnd: parseTimestamp(subscription.currentPeriodEnd),
34
- currentPeriodStart: parseTimestamp(subscription.currentPeriodStart),
35
- id: subscription.id,
36
- priceId: subscription.productId ?? "",
40
+ currentPeriodEnd: toEpochMs(subscription.currentPeriodEnd),
41
+ currentPeriodStart: toEpochMs(subscription.currentPeriodStart),
42
+ id: readString(subscription, "id") ?? "",
43
+ priceId: readString(subscription, "productId") ?? "",
37
44
  provider: "polar",
38
45
  quantity: 1,
39
- referenceId: subscription.metadata?.referenceId ?? "",
40
- state: SUBSCRIPTION_STATE_BY_POLAR_STATUS[subscription.status] ?? "active",
46
+ referenceId: referenceFromMetadata(subscription) ?? "",
47
+ state: SUBSCRIPTION_STATE_BY_POLAR_STATUS[readString(subscription, "status") ?? ""] ?? "active",
41
48
  updatedAt: now
42
49
  };
43
50
  };
44
- const orderToSession = (order) => {
51
+ const orderToSession = (input) => {
52
+ const order = asRecord(input);
45
53
  const now = Date.now();
46
- const currency = order.currency ?? "usd";
47
- const amount = money(BigInt(order.totalAmount ?? order.amount ?? 0), currency);
48
- 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";
49
57
  const settled = state === "captured" || state === "partially_refunded" || state === "refunded";
50
58
  return {
51
59
  amount,
52
60
  capturedAmount: settled ? amount : zeroMoney(currency),
53
61
  createdAt: now,
54
- id: order.id,
62
+ id: readString(order, "id") ?? "",
55
63
  provider: "polar",
56
64
  referenceId: "",
57
65
  refundedAmount: state === "refunded" ? amount : zeroMoney(currency),
@@ -59,26 +67,15 @@ const orderToSession = (order) => {
59
67
  updatedAt: now
60
68
  };
61
69
  };
62
- const subscriptionEventType = (status) => {
63
- const state = status ? SUBSCRIPTION_STATE_BY_POLAR_STATUS[status] : void 0;
64
- if (state === "canceled") {
65
- return "subscription.canceled";
66
- }
67
- if (state === "past_due") {
68
- return "subscription.past_due";
69
- }
70
- if (state === "active" || state === "trialing") {
71
- return "subscription.active";
72
- }
73
- return "subscription.updated";
74
- };
75
- const referenceFromMetadata = (object) => readString(asRecord(object.metadata), "referenceId");
76
70
  const mapEvent = (eventId, eventType, object) => {
77
71
  const base = { eventId, provider: "polar", raw: { object, type: eventType } };
78
72
  const currency = readString(object, "currency") ?? "usd";
79
73
  switch (eventType) {
80
74
  case "order.created":
81
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
+ }
82
79
  return {
83
80
  ...base,
84
81
  amount: money(BigInt(readNumber(object, "total_amount") ?? readNumber(object, "amount") ?? 0), currency),
@@ -102,8 +99,9 @@ const mapEvent = (eventId, eventType, object) => {
102
99
  case "subscription.canceled":
103
100
  case "subscription.created":
104
101
  case "subscription.revoked":
102
+ case "subscription.uncanceled":
105
103
  case "subscription.updated": {
106
- 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") ?? ""]);
107
105
  return {
108
106
  ...base,
109
107
  cancelAtPeriodEnd: readBoolean(object, "cancel_at_period_end"),
@@ -122,7 +120,8 @@ const mapEvent = (eventId, eventType, object) => {
122
120
  }
123
121
  };
124
122
  const createPolarAdapter = (options) => {
125
- const { client, webhookSecret } = options;
123
+ const { webhookSecret } = options;
124
+ const client = options.client;
126
125
  return {
127
126
  cancelPayment: () => notSupported("manual payment cancellation"),
128
127
  cancelSubscription: async (subscriptionId, cancelOptions) => {
@@ -147,9 +146,10 @@ const createPolarAdapter = (options) => {
147
146
  },
148
147
  getOrCreateCustomer: async (ref) => {
149
148
  const customer = await client.customers.create({
150
- email: ref.email,
149
+ email: ref.email ?? "",
151
150
  externalId: ref.referenceId,
152
- metadata: { ...ref.metadata, referenceId: ref.referenceId }
151
+ metadata: { ...ref.metadata, referenceId: ref.referenceId },
152
+ type: "individual"
153
153
  });
154
154
  return { createdAt: Date.now(), email: customer.email ?? void 0, id: customer.id, provider: "polar", referenceId: ref.referenceId };
155
155
  },
@@ -170,12 +170,16 @@ const createPolarAdapter = (options) => {
170
170
  return mapEvent(webhookId, readString(event, "type") ?? "", asRecord(event.data));
171
171
  },
172
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;
173
176
  await client.refunds.create({
174
- amount: input.amount ? Number(input.amount.minorUnits) : void 0,
177
+ amount: amountMinor,
175
178
  orderId: input.sessionId,
179
+ // `reason` is an open enum; a caller-supplied string is cast onto it (defaults to customer_request).
176
180
  reason: input.reason ?? "customer_request"
177
181
  });
178
- const refundedAmount = input.amount ?? money(0, "usd");
182
+ const refundedAmount = input.amount ?? money(BigInt(Math.round(amountMinor)), currency);
179
183
  return {
180
184
  amount: refundedAmount,
181
185
  capturedAmount: refundedAmount,
@@ -195,7 +199,7 @@ const createPolarAdapter = (options) => {
195
199
  externalCustomerId: input.referenceId,
196
200
  metadata: { value: input.quantity },
197
201
  name: input.featureId,
198
- timestamp: input.timestamp === void 0 ? void 0 : new Date(input.timestamp).toISOString()
202
+ timestamp: input.timestamp === void 0 ? void 0 : new Date(input.timestamp)
199
203
  }
200
204
  ]
201
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 };