@lunora/payment 1.0.0-alpha.7 → 1.0.0-alpha.71

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 (62) hide show
  1. package/LICENSE.md +6 -0
  2. package/README.md +5 -2
  3. package/dist/index.d.mts +293 -526
  4. package/dist/index.d.ts +293 -526
  5. package/dist/index.mjs +1 -16
  6. package/dist/packem_shared/LunoraPaymentError-C8uAUIzQ.mjs +1 -0
  7. package/dist/packem_shared/MemoryPaymentStore-CkXqJEe2.mjs +1 -0
  8. package/dist/packem_shared/PAYMENT_TERMINAL_STATES-DVNbZxBy.mjs +1 -0
  9. package/dist/packem_shared/adapter.d-xBfAhqVC.d.mts +445 -0
  10. package/dist/packem_shared/adapter.d-xBfAhqVC.d.ts +445 -0
  11. package/dist/packem_shared/addMoney-BKfsSf0H.mjs +1 -0
  12. package/dist/packem_shared/applyWebhookAction-Bwvycl00.mjs +1 -0
  13. package/dist/packem_shared/constantTimeEqual-B4DP75aY.mjs +1 -0
  14. package/dist/packem_shared/createAdapterRegistry-SoXdsiQd.mjs +1 -0
  15. package/dist/packem_shared/createDatabasePaymentStore-DxChfP5X.mjs +1 -0
  16. package/dist/packem_shared/createPayment-Cj0tRPRZ.mjs +1 -0
  17. package/dist/packem_shared/entitlementsForReference-BbEOKu5d.mjs +1 -0
  18. package/dist/packem_shared/idempotencyKey-CfJKqcE3.mjs +1 -0
  19. package/dist/packem_shared/json-D85Nbd92.mjs +1 -0
  20. package/dist/packem_shared/lunoraDatabaseToPaymentDatabase-CW1VjW4D.mjs +1 -0
  21. package/dist/packem_shared/not-supported-BBbwyP_u.mjs +1 -0
  22. package/dist/packem_shared/observability-C9tz8mj_.mjs +1 -0
  23. package/dist/packem_shared/paymentTables-Be-_11ux.mjs +1 -0
  24. package/dist/packem_shared/reconcile-BjWV0mB0.mjs +1 -0
  25. package/dist/packem_shared/subscription-event-BosLshSM.mjs +1 -0
  26. package/dist/providers/autumn-features.d.mts +129 -0
  27. package/dist/providers/autumn-features.d.ts +129 -0
  28. package/dist/providers/autumn-features.mjs +1 -0
  29. package/dist/providers/autumn.d.mts +28 -0
  30. package/dist/providers/autumn.d.ts +28 -0
  31. package/dist/providers/autumn.mjs +1 -0
  32. package/dist/providers/creem.d.mts +26 -0
  33. package/dist/providers/creem.d.ts +26 -0
  34. package/dist/providers/creem.mjs +1 -0
  35. package/dist/providers/dodopayments.d.mts +30 -0
  36. package/dist/providers/dodopayments.d.ts +30 -0
  37. package/dist/providers/dodopayments.mjs +1 -0
  38. package/dist/providers/polar.d.mts +31 -0
  39. package/dist/providers/polar.d.ts +31 -0
  40. package/dist/providers/polar.mjs +1 -0
  41. package/dist/providers/stripe.d.mts +32 -0
  42. package/dist/providers/stripe.d.ts +32 -0
  43. package/dist/providers/stripe.mjs +1 -0
  44. package/package.json +45 -4
  45. package/dist/packem_shared/LunoraPaymentError-B3hEzXSs.mjs +0 -22
  46. package/dist/packem_shared/MemoryPaymentStore-DvgdWa3C.mjs +0 -72
  47. package/dist/packem_shared/PAYMENT_TERMINAL_STATES-DrxV0clv.mjs +0 -26
  48. package/dist/packem_shared/addMoney-bCcs1nyw.mjs +0 -60
  49. package/dist/packem_shared/applyWebhookAction-DpAqf3Lw.mjs +0 -177
  50. package/dist/packem_shared/constantTimeEqual-CfY0jYcL.mjs +0 -95
  51. package/dist/packem_shared/createAdapterRegistry-BuDHFCBc.mjs +0 -24
  52. package/dist/packem_shared/createDatabasePaymentStore-bYB_HUE6.mjs +0 -172
  53. package/dist/packem_shared/createPayment-BccfPGyw.mjs +0 -190
  54. package/dist/packem_shared/createPolarAdapter-BJtVGSlF.mjs +0 -217
  55. package/dist/packem_shared/createStripeAdapter-D40MVBXg.mjs +0 -270
  56. package/dist/packem_shared/entitlementsForReference-CzZGXPoZ.mjs +0 -51
  57. package/dist/packem_shared/idempotencyKey-BFzDCA7g.mjs +0 -3
  58. package/dist/packem_shared/json-Db337f36.mjs +0 -6
  59. package/dist/packem_shared/lunoraDatabaseToPaymentDatabase-RlKX3Kcd.mjs +0 -29
  60. package/dist/packem_shared/observability-CvhJ205g.mjs +0 -11
  61. package/dist/packem_shared/paymentTables-DccHwWr_.mjs +0 -127
  62. package/dist/packem_shared/reconcile-CI1ukJF9.mjs +0 -73
@@ -1,172 +0,0 @@
1
- import { money } from './addMoney-bCcs1nyw.mjs';
2
-
3
- const readString = (row, key) => typeof row[key] === "string" ? row[key] : "";
4
- const readOptionalString = (row, key) => typeof row[key] === "string" ? row[key] : void 0;
5
- const readNumber = (row, key) => typeof row[key] === "number" ? row[key] : 0;
6
- const readOptionalNumber = (row, key) => typeof row[key] === "number" ? row[key] : void 0;
7
- const readBoolean = (row, key) => row[key] === true;
8
- const readBigint = (row, key) => {
9
- const value = row[key];
10
- if (typeof value === "bigint") {
11
- return value;
12
- }
13
- return typeof value === "number" || typeof value === "string" ? BigInt(value) : 0n;
14
- };
15
- const customerToRow = (customer) => {
16
- return {
17
- createdAt: customer.createdAt,
18
- email: customer.email,
19
- provider: customer.provider,
20
- providerCustomerId: customer.id,
21
- referenceId: customer.referenceId
22
- };
23
- };
24
- const rowToCustomer = (row) => {
25
- return {
26
- createdAt: readNumber(row, "createdAt"),
27
- email: readOptionalString(row, "email"),
28
- id: readString(row, "providerCustomerId"),
29
- provider: readString(row, "provider"),
30
- referenceId: readString(row, "referenceId")
31
- };
32
- };
33
- const sessionToRow = (session) => {
34
- return {
35
- amountMinor: session.amount.minorUnits,
36
- capturedMinor: session.capturedAmount.minorUnits,
37
- createdAt: session.createdAt,
38
- currency: session.amount.currency,
39
- provider: session.provider,
40
- providerSessionId: session.id,
41
- referenceId: session.referenceId,
42
- refundedMinor: session.refundedAmount.minorUnits,
43
- state: session.state,
44
- updatedAt: session.updatedAt
45
- };
46
- };
47
- const rowToSession = (row) => {
48
- const currency = readString(row, "currency");
49
- return {
50
- amount: money(readBigint(row, "amountMinor"), currency),
51
- capturedAmount: money(readBigint(row, "capturedMinor"), currency),
52
- createdAt: readNumber(row, "createdAt"),
53
- id: readString(row, "providerSessionId"),
54
- provider: readString(row, "provider"),
55
- referenceId: readString(row, "referenceId"),
56
- refundedAmount: money(readBigint(row, "refundedMinor"), currency),
57
- state: readString(row, "state"),
58
- updatedAt: readNumber(row, "updatedAt")
59
- };
60
- };
61
- const subscriptionToRow = (subscription) => {
62
- return {
63
- cancelAtPeriodEnd: subscription.cancelAtPeriodEnd,
64
- createdAt: subscription.createdAt,
65
- currentPeriodEnd: subscription.currentPeriodEnd,
66
- currentPeriodStart: subscription.currentPeriodStart,
67
- priceId: subscription.priceId,
68
- provider: subscription.provider,
69
- providerSubscriptionId: subscription.id,
70
- quantity: subscription.quantity,
71
- referenceId: subscription.referenceId,
72
- state: subscription.state,
73
- updatedAt: subscription.updatedAt
74
- };
75
- };
76
- const rowToSubscription = (row) => {
77
- return {
78
- cancelAtPeriodEnd: readBoolean(row, "cancelAtPeriodEnd"),
79
- createdAt: readNumber(row, "createdAt"),
80
- currentPeriodEnd: readOptionalNumber(row, "currentPeriodEnd"),
81
- currentPeriodStart: readOptionalNumber(row, "currentPeriodStart"),
82
- id: readString(row, "providerSubscriptionId"),
83
- priceId: readString(row, "priceId"),
84
- provider: readString(row, "provider"),
85
- quantity: readNumber(row, "quantity"),
86
- referenceId: readString(row, "referenceId"),
87
- state: readString(row, "state"),
88
- updatedAt: readNumber(row, "updatedAt")
89
- };
90
- };
91
- const usageEventToRow = (event) => {
92
- return {
93
- createdAt: event.createdAt,
94
- featureId: event.featureId,
95
- idempotencyKey: event.idempotencyKey,
96
- provider: event.provider,
97
- quantity: event.quantity,
98
- referenceId: event.referenceId,
99
- reportedToProvider: event.reportedToProvider
100
- };
101
- };
102
- const createDatabasePaymentStore = (database) => {
103
- const upsert = async (table, where, row) => {
104
- const existing = await database.findFirst(table, where);
105
- if (existing) {
106
- await database.patch(existing._id, row);
107
- return;
108
- }
109
- await database.insert(table, row);
110
- };
111
- return {
112
- getCustomerByReference: async (provider, referenceId) => {
113
- const row = await database.findFirst("customers", { provider, referenceId });
114
- return row ? rowToCustomer(row) : void 0;
115
- },
116
- getPaymentSession: async (provider, id) => {
117
- const row = await database.findFirst("paymentSessions", { provider, providerSessionId: id });
118
- return row ? rowToSession(row) : void 0;
119
- },
120
- getSubscription: async (provider, id) => {
121
- const row = await database.findFirst("subscriptions", { provider, providerSubscriptionId: id });
122
- return row ? rowToSubscription(row) : void 0;
123
- },
124
- listSubscriptionsByReference: async (referenceId) => {
125
- const rows = await database.findMany("subscriptions", { referenceId });
126
- return rows.map((row) => rowToSubscription(row));
127
- },
128
- markEventProcessed: async (provider, eventId) => {
129
- const existing = await database.findFirst("events", { provider, providerEventId: eventId });
130
- if (existing) {
131
- return false;
132
- }
133
- await database.insert("events", { processedAt: Date.now(), provider, providerEventId: eventId, type: "" });
134
- return true;
135
- },
136
- releaseEvent: async (provider, eventId) => {
137
- const existing = await database.findFirst("events", { provider, providerEventId: eventId });
138
- if (existing) {
139
- await database.delete(existing._id);
140
- }
141
- },
142
- markUsageReported: async (provider, idempotencyKey) => {
143
- const existing = await database.findFirst("usageEvents", { idempotencyKey, provider });
144
- if (existing) {
145
- await database.patch(existing._id, { reportedToProvider: true });
146
- }
147
- },
148
- recordUsage: async (event) => {
149
- const existing = await database.findFirst("usageEvents", { idempotencyKey: event.idempotencyKey, provider: event.provider });
150
- if (existing) {
151
- return false;
152
- }
153
- await database.insert("usageEvents", usageEventToRow(event));
154
- return true;
155
- },
156
- sumUsage: async (referenceId, featureId, since) => {
157
- const rows = await database.findMany("usageEvents", { featureId, referenceId });
158
- let total = 0;
159
- for (const row of rows) {
160
- if (readNumber(row, "createdAt") >= since) {
161
- total += readNumber(row, "quantity");
162
- }
163
- }
164
- return total;
165
- },
166
- upsertCustomer: async (customer) => upsert("customers", { provider: customer.provider, providerCustomerId: customer.id }, customerToRow(customer)),
167
- upsertPaymentSession: async (session) => upsert("paymentSessions", { provider: session.provider, providerSessionId: session.id }, sessionToRow(session)),
168
- upsertSubscription: async (subscription) => upsert("subscriptions", { provider: subscription.provider, providerSubscriptionId: subscription.id }, subscriptionToRow(subscription))
169
- };
170
- };
171
-
172
- export { createDatabasePaymentStore };
@@ -1,190 +0,0 @@
1
- import { usagePeriodStart, resolveEntitlements, featureNames, hasActivePrice } from './entitlementsForReference-CzZGXPoZ.mjs';
2
- import { LunoraPaymentError } from './LunoraPaymentError-B3hEzXSs.mjs';
3
- import idempotencyKey from './idempotencyKey-BFzDCA7g.mjs';
4
- import { n as notifyObserver } from './observability-CvhJ205g.mjs';
5
- import applyWebhookAction from './applyWebhookAction-DpAqf3Lw.mjs';
6
-
7
- const jsonResponse = (body, status) => Response.json(body, { headers: { "content-type": "application/json" }, status });
8
- const stripReferenceId = (metadata) => metadata && "referenceId" in metadata ? Object.fromEntries(Object.entries(metadata).filter(([key]) => key !== "referenceId")) : metadata;
9
- const createPayment = (options) => {
10
- const { adapter, store } = options;
11
- const ensureAuthorized = async (referenceId) => {
12
- if (!options.authorize) {
13
- return;
14
- }
15
- let allowed;
16
- try {
17
- allowed = await options.authorize(referenceId);
18
- } catch {
19
- throw new LunoraPaymentError("FORBIDDEN", `caller not authorized for reference "${referenceId}"`);
20
- }
21
- if (!allowed) {
22
- throw new LunoraPaymentError("FORBIDDEN", `caller not authorized for reference "${referenceId}"`);
23
- }
24
- };
25
- const startCheckout = async (input) => {
26
- await ensureAuthorized(input.referenceId);
27
- const metadata = stripReferenceId(input.metadata);
28
- let { customerId } = input;
29
- if (!customerId) {
30
- const existing = await store.getCustomerByReference(adapter.identifier, input.referenceId);
31
- if (existing) {
32
- customerId = existing.id;
33
- } else {
34
- const customer = await adapter.getOrCreateCustomer({ referenceId: input.referenceId });
35
- customerId = customer.id;
36
- await store.upsertCustomer(customer);
37
- }
38
- }
39
- const key = input.idempotencyKey ?? idempotencyKey(
40
- "checkout",
41
- adapter.identifier,
42
- input.referenceId,
43
- input.priceId,
44
- input.mode,
45
- String(input.quantity ?? 1),
46
- input.successUrl,
47
- input.cancelUrl,
48
- metadata ? JSON.stringify(metadata) : ""
49
- );
50
- return adapter.createCheckout({ ...input, customerId, idempotencyKey: key, metadata });
51
- };
52
- const evaluateFeature = async (entitlements, subscriptions, referenceId, featureId, need) => {
53
- const limit = entitlements.limit(featureId);
54
- if (limit !== void 0) {
55
- const used = await store.sumUsage(referenceId, featureId, usagePeriodStart(subscriptions));
56
- const balance = limit - used;
57
- return { allowed: balance >= need, balance, limit, unlimited: false, used };
58
- }
59
- return { allowed: entitlements.has(featureId), unlimited: entitlements.has(featureId) };
60
- };
61
- return {
62
- adapter,
63
- attach: async (input) => startCheckout({ ...input, mode: input.mode ?? "subscription" }),
64
- cancelSubscription: async (subscriptionId, cancelOptions) => {
65
- const existing = await store.getSubscription(adapter.identifier, subscriptionId);
66
- if (!existing) {
67
- throw new LunoraPaymentError("NOT_FOUND", `subscription "${subscriptionId}" not found`);
68
- }
69
- try {
70
- await ensureAuthorized(existing.referenceId);
71
- } catch {
72
- throw new LunoraPaymentError("NOT_FOUND", `subscription "${subscriptionId}" not found`);
73
- }
74
- const key = cancelOptions?.idempotencyKey ?? idempotencyKey("cancel_subscription", adapter.identifier, subscriptionId);
75
- const updated = await adapter.cancelSubscription(subscriptionId, { ...cancelOptions, idempotencyKey: key });
76
- await store.upsertSubscription(updated);
77
- return updated;
78
- },
79
- check: async (input) => {
80
- await ensureAuthorized(input.referenceId);
81
- const subscriptions = await store.listSubscriptionsByReference(input.referenceId);
82
- if (input.priceId !== void 0) {
83
- return { allowed: hasActivePrice(subscriptions, input.priceId), unlimited: false };
84
- }
85
- if (input.featureId === void 0) {
86
- throw new LunoraPaymentError("CONFIG_INVALID", "check() requires a featureId or priceId");
87
- }
88
- if (!options.entitlements) {
89
- throw new LunoraPaymentError("CONFIG_INVALID", "check() requires `entitlements` to be configured");
90
- }
91
- const entitlements = resolveEntitlements(options.entitlements, subscriptions);
92
- return evaluateFeature(entitlements, subscriptions, input.referenceId, input.featureId, input.quantity ?? 1);
93
- },
94
- createCheckout: async (input) => startCheckout(input),
95
- createPortalSession: async (referenceId, returnUrl) => {
96
- await ensureAuthorized(referenceId);
97
- const customer = await store.getCustomerByReference(adapter.identifier, referenceId);
98
- if (!customer) {
99
- throw new LunoraPaymentError("NOT_FOUND", `no customer for reference "${referenceId}"`);
100
- }
101
- return adapter.createPortalSession({ customerId: customer.id, returnUrl });
102
- },
103
- handleWebhook: async (request) => {
104
- let action;
105
- try {
106
- const payload = await request.text();
107
- action = await adapter.parseWebhook({ headers: request.headers, payload });
108
- } catch (error) {
109
- if (error instanceof LunoraPaymentError) {
110
- return jsonResponse({ error: error.message }, error.status);
111
- }
112
- return jsonResponse({ error: "webhook error" }, 400);
113
- }
114
- const result = await applyWebhookAction(store, action, options.observability);
115
- return jsonResponse({ applied: result.applied, reason: result.reason }, 200);
116
- },
117
- listBalances: async (referenceId) => {
118
- await ensureAuthorized(referenceId);
119
- if (!options.entitlements) {
120
- throw new LunoraPaymentError("CONFIG_INVALID", "listBalances() requires `entitlements` to be configured");
121
- }
122
- const subscriptions = await store.listSubscriptionsByReference(referenceId);
123
- const entitlements = resolveEntitlements(options.entitlements, subscriptions);
124
- return Promise.all(
125
- featureNames(options.entitlements).map(async (featureId) => {
126
- return {
127
- featureId,
128
- ...await evaluateFeature(entitlements, subscriptions, referenceId, featureId, 1)
129
- };
130
- })
131
- );
132
- },
133
- listSubscriptions: async (referenceId) => {
134
- await ensureAuthorized(referenceId);
135
- return store.listSubscriptionsByReference(referenceId);
136
- },
137
- store,
138
- track: async (input) => {
139
- await ensureAuthorized(input.referenceId);
140
- const target = input.quantity ?? 1;
141
- const key = input.idempotencyKey ?? crypto.randomUUID();
142
- let delta = target;
143
- if (input.mode === "set") {
144
- const subscriptions = await store.listSubscriptionsByReference(input.referenceId);
145
- const current = await store.sumUsage(input.referenceId, input.featureId, usagePeriodStart(subscriptions));
146
- delta = target - current;
147
- }
148
- if (delta === 0) {
149
- return { recorded: false, reportedToProvider: false };
150
- }
151
- const recorded = await store.recordUsage({
152
- createdAt: Date.now(),
153
- featureId: input.featureId,
154
- idempotencyKey: key,
155
- provider: adapter.identifier,
156
- quantity: delta,
157
- referenceId: input.referenceId,
158
- reportedToProvider: false
159
- });
160
- if (!recorded) {
161
- return { recorded: false, reportedToProvider: false };
162
- }
163
- if (delta <= 0 || !adapter.capabilities.usageMetering || !adapter.reportUsage) {
164
- return { recorded: true, reportedToProvider: false };
165
- }
166
- try {
167
- const customer = await store.getCustomerByReference(adapter.identifier, input.referenceId);
168
- await adapter.reportUsage({
169
- customerId: customer?.id,
170
- featureId: input.featureId,
171
- idempotencyKey: key,
172
- quantity: delta,
173
- referenceId: input.referenceId
174
- });
175
- await store.markUsageReported(adapter.identifier, key);
176
- return { recorded: true, reportedToProvider: true };
177
- } catch {
178
- notifyObserver(options.observability, {
179
- featureId: input.featureId,
180
- provider: adapter.identifier,
181
- referenceId: input.referenceId,
182
- type: "usage.report_failed"
183
- });
184
- return { recorded: true, reportedToProvider: false };
185
- }
186
- }
187
- };
188
- };
189
-
190
- export { createPayment };
@@ -1,217 +0,0 @@
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';
5
-
6
- const PAYMENT_STATE_BY_POLAR_ORDER_STATUS = {
7
- paid: "captured",
8
- partially_refunded: "partially_refunded",
9
- pending: "initiated",
10
- refunded: "refunded"
11
- };
12
- const SUBSCRIPTION_STATE_BY_POLAR_STATUS = {
13
- active: "active",
14
- canceled: "canceled",
15
- incomplete: "trialing",
16
- incomplete_expired: "canceled",
17
- past_due: "past_due",
18
- trialing: "trialing",
19
- unpaid: "past_due"
20
- };
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) => {
29
- const now = Date.now();
30
- return {
31
- cancelAtPeriodEnd: subscription.cancelAtPeriodEnd ?? false,
32
- createdAt: now,
33
- currentPeriodEnd: parseTimestamp(subscription.currentPeriodEnd),
34
- currentPeriodStart: parseTimestamp(subscription.currentPeriodStart),
35
- id: subscription.id,
36
- priceId: subscription.productId ?? "",
37
- provider: "polar",
38
- quantity: 1,
39
- referenceId: subscription.metadata?.referenceId ?? "",
40
- state: SUBSCRIPTION_STATE_BY_POLAR_STATUS[subscription.status] ?? "active",
41
- updatedAt: now
42
- };
43
- };
44
- const orderToSession = (order) => {
45
- 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";
49
- const settled = state === "captured" || state === "partially_refunded" || state === "refunded";
50
- return {
51
- amount,
52
- capturedAmount: settled ? amount : zeroMoney(currency),
53
- createdAt: now,
54
- id: order.id,
55
- provider: "polar",
56
- referenceId: "",
57
- refundedAmount: state === "refunded" ? amount : zeroMoney(currency),
58
- state,
59
- updatedAt: now
60
- };
61
- };
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
- const mapEvent = (eventId, eventType, object) => {
77
- const base = { eventId, provider: "polar", raw: { object, type: eventType } };
78
- const currency = readString(object, "currency") ?? "usd";
79
- switch (eventType) {
80
- case "order.created":
81
- case "order.paid": {
82
- return {
83
- ...base,
84
- amount: money(BigInt(readNumber(object, "total_amount") ?? readNumber(object, "amount") ?? 0), currency),
85
- customerId: readString(object, "customer_id"),
86
- referenceId: referenceFromMetadata(object),
87
- sessionId: readString(object, "id"),
88
- subscriptionId: readString(object, "subscription_id"),
89
- type: "payment.captured"
90
- };
91
- }
92
- case "refund.created": {
93
- return {
94
- ...base,
95
- amount: money(BigInt(readNumber(object, "amount") ?? 0), currency),
96
- referenceId: referenceFromMetadata(object),
97
- sessionId: readString(object, "order_id") ?? readString(object, "id"),
98
- type: "payment.refunded"
99
- };
100
- }
101
- case "subscription.active":
102
- case "subscription.canceled":
103
- case "subscription.created":
104
- case "subscription.revoked":
105
- case "subscription.updated": {
106
- const type = eventType === "subscription.revoked" ? "subscription.canceled" : subscriptionEventType(readString(object, "status"));
107
- return {
108
- ...base,
109
- cancelAtPeriodEnd: readBoolean(object, "cancel_at_period_end"),
110
- currentPeriodEnd: parseTimestamp(readString(object, "current_period_end")),
111
- currentPeriodStart: parseTimestamp(readString(object, "current_period_start")),
112
- customerId: readString(object, "customer_id"),
113
- priceId: readString(object, "product_id"),
114
- referenceId: referenceFromMetadata(object),
115
- subscriptionId: readString(object, "id"),
116
- type
117
- };
118
- }
119
- default: {
120
- return { ...base, type: "unhandled" };
121
- }
122
- }
123
- };
124
- const createPolarAdapter = (options) => {
125
- const { client, webhookSecret } = options;
126
- return {
127
- cancelPayment: () => notSupported("manual payment cancellation"),
128
- cancelSubscription: async (subscriptionId, cancelOptions) => {
129
- const subscription = cancelOptions?.atPeriodEnd ? await client.subscriptions.update({ id: subscriptionId, subscriptionUpdate: { cancelAtPeriodEnd: true } }) : await client.subscriptions.revoke({ id: subscriptionId });
130
- return subscriptionFromPolar(subscription);
131
- },
132
- capabilities: { merchantOfRecord: true, portal: true, usageMetering: true },
133
- capturePayment: (_input) => notSupported("manual capture"),
134
- createCheckout: async (input) => {
135
- const checkout = await client.checkouts.create({
136
- customerEmail: void 0,
137
- // Pin the framework-controlled `referenceId` LAST so caller metadata can never override it.
138
- metadata: { ...input.metadata, referenceId: input.referenceId },
139
- products: [input.priceId],
140
- successUrl: input.successUrl
141
- });
142
- return { id: checkout.id, provider: "polar", url: checkout.url };
143
- },
144
- createPortalSession: async (input) => {
145
- const session = await client.customerSessions.create({ customerId: input.customerId });
146
- return { url: session.customerPortalUrl };
147
- },
148
- getOrCreateCustomer: async (ref) => {
149
- const customer = await client.customers.create({
150
- email: ref.email,
151
- externalId: ref.referenceId,
152
- metadata: { ...ref.metadata, referenceId: ref.referenceId }
153
- });
154
- return { createdAt: Date.now(), email: customer.email ?? void 0, id: customer.id, provider: "polar", referenceId: ref.referenceId };
155
- },
156
- getPaymentStatus: async (sessionId) => orderToSession(await client.orders.get({ id: sessionId })),
157
- getSubscriptionStatus: async (subscriptionId) => subscriptionFromPolar(await client.subscriptions.get({ id: subscriptionId })),
158
- identifier: "polar",
159
- parseWebhook: async ({ headers, payload }) => {
160
- const webhookId = headers.get("webhook-id") ?? "";
161
- await verifyStandardWebhook({
162
- payload,
163
- secret: webhookSecret,
164
- toleranceSeconds: options.webhookToleranceSeconds,
165
- webhookId,
166
- webhookSignature: headers.get("webhook-signature") ?? "",
167
- webhookTimestamp: headers.get("webhook-timestamp") ?? ""
168
- });
169
- const event = asRecord(JSON.parse(payload));
170
- return mapEvent(webhookId, readString(event, "type") ?? "", asRecord(event.data));
171
- },
172
- refundPayment: async (input) => {
173
- await client.refunds.create({
174
- amount: input.amount ? Number(input.amount.minorUnits) : void 0,
175
- orderId: input.sessionId,
176
- reason: input.reason ?? "customer_request"
177
- });
178
- const refundedAmount = input.amount ?? money(0, "usd");
179
- return {
180
- amount: refundedAmount,
181
- capturedAmount: refundedAmount,
182
- createdAt: Date.now(),
183
- id: input.sessionId,
184
- provider: "polar",
185
- referenceId: "",
186
- refundedAmount,
187
- state: "refunded",
188
- updatedAt: Date.now()
189
- };
190
- },
191
- reportUsage: async (input) => {
192
- await client.events.ingest({
193
- events: [
194
- {
195
- externalCustomerId: input.referenceId,
196
- metadata: { value: input.quantity },
197
- name: input.featureId,
198
- timestamp: input.timestamp === void 0 ? void 0 : new Date(input.timestamp).toISOString()
199
- }
200
- ]
201
- });
202
- },
203
- resumeSubscription: async (subscriptionId) => {
204
- const subscription = await client.subscriptions.update({ id: subscriptionId, subscriptionUpdate: { cancelAtPeriodEnd: false } });
205
- return subscriptionFromPolar(subscription);
206
- },
207
- updateSubscription: async (subscriptionId, patch) => {
208
- const subscription = await client.subscriptions.update({
209
- id: subscriptionId,
210
- subscriptionUpdate: patch.priceId ? { productId: patch.priceId } : {}
211
- });
212
- return subscriptionFromPolar(subscription);
213
- }
214
- };
215
- };
216
-
217
- export { createPolarAdapter };