@lunora/payment 1.0.0-alpha.13 → 1.0.0-alpha.130

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 +8 -4
  3. package/dist/index.d.mts +407 -530
  4. package/dist/index.d.ts +407 -530
  5. package/dist/index.mjs +1 -16
  6. package/dist/packem_shared/LunoraPaymentError-BSxyhWgu.mjs +1 -0
  7. package/dist/packem_shared/MemoryPaymentStore-CZbrrEb1.mjs +1 -0
  8. package/dist/packem_shared/PAYMENT_TERMINAL_STATES-C-ICZkun.mjs +1 -0
  9. package/dist/packem_shared/adapter.d-DytKm_IF.d.mts +513 -0
  10. package/dist/packem_shared/adapter.d-DytKm_IF.d.ts +513 -0
  11. package/dist/packem_shared/addMoney-B4ufWAnD.mjs +1 -0
  12. package/dist/packem_shared/applyWebhookAction-CvLpZjKP.mjs +1 -0
  13. package/dist/packem_shared/constantTimeEqual-ggrwGvRa.mjs +1 -0
  14. package/dist/packem_shared/createAdapterRegistry-DyCsry1Q.mjs +1 -0
  15. package/dist/packem_shared/createDatabasePaymentStore-BGcHX1uq.mjs +1 -0
  16. package/dist/packem_shared/createPayment-Dimez2zY.mjs +1 -0
  17. package/dist/packem_shared/entitlementsForReference-PMmhAOWa.mjs +1 -0
  18. package/dist/packem_shared/idempotencyKey-qQJLcEQX.mjs +1 -0
  19. package/dist/packem_shared/json-BJJPJVYj.mjs +1 -0
  20. package/dist/packem_shared/lunoraDatabaseToPaymentDatabase-CFPYSEu0.mjs +1 -0
  21. package/dist/packem_shared/not-supported-Cl0brzhn.mjs +1 -0
  22. package/dist/packem_shared/observability-BteZ2dJS.mjs +1 -0
  23. package/dist/packem_shared/paymentTables-Ve_lJxAt.mjs +1 -0
  24. package/dist/packem_shared/reconcile-fbxX8j9k.mjs +1 -0
  25. package/dist/packem_shared/subscription-event-C8GiQSFM.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 +34 -0
  42. package/dist/providers/stripe.d.ts +34 -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-CgErk9zo.mjs +0 -220
  55. package/dist/packem_shared/createStripeAdapter-oDvMhpeX.mjs +0 -279
  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,279 +0,0 @@
1
- import idempotencyKey from './idempotencyKey-BFzDCA7g.mjs';
2
- import { a as asRecord, r as readString, c as readNumber, b as readBoolean } from './json-Db337f36.mjs';
3
- import { compareMoney, money, zeroMoney } from './addMoney-bCcs1nyw.mjs';
4
- import { verifyStripeSignature } from './constantTimeEqual-CfY0jYcL.mjs';
5
-
6
- const PAYMENT_STATE_BY_STRIPE_STATUS = {
7
- canceled: "canceled",
8
- processing: "authorized",
9
- requires_action: "authorized",
10
- requires_capture: "authorized",
11
- requires_confirmation: "initiated",
12
- requires_payment_method: "initiated",
13
- succeeded: "captured"
14
- };
15
- const SUBSCRIPTION_STATE_BY_STRIPE_STATUS = {
16
- active: "active",
17
- canceled: "canceled",
18
- // SECURITY: `incomplete` means the FIRST payment has not succeeded (SCA
19
- // `requires_action` or a failed initial charge). It must NOT map to an
20
- // entitling state — Stripe's recommended `payment_behavior: "default_incomplete"`
21
- // makes `incomplete` the initial status of every new subscription, so mapping
22
- // it to `trialing` (which is in ACTIVE_STATES) would grant paid entitlements
23
- // before any payment. Reserve `trialing` for a genuine Stripe trial (status
24
- // `trialing`, mapped below); treat `incomplete` as non-entitling `past_due`.
25
- incomplete: "past_due",
26
- incomplete_expired: "canceled",
27
- past_due: "past_due",
28
- paused: "paused",
29
- trialing: "trialing",
30
- unpaid: "past_due"
31
- };
32
- const readReferenceId = (object) => readString(asRecord(object.metadata), "referenceId") ?? readString(object, "client_reference_id");
33
- const firstItem = (object) => {
34
- const items = asRecord(object.items);
35
- const data = Array.isArray(items.data) ? items.data : [];
36
- return asRecord(data[0]);
37
- };
38
- const firstPriceId = (object) => readString(asRecord(firstItem(object).price), "id");
39
- const firstQuantity = (object) => readNumber(firstItem(object), "quantity");
40
- const periodEndMs = (object) => {
41
- const seconds = readNumber(object, "current_period_end");
42
- return seconds === void 0 ? void 0 : seconds * 1e3;
43
- };
44
- const periodStartMs = (object) => {
45
- const seconds = readNumber(object, "current_period_start");
46
- return seconds === void 0 ? void 0 : seconds * 1e3;
47
- };
48
- const intentToSession = (intent) => {
49
- const amount = money(BigInt(intent.amount), intent.currency);
50
- const state = PAYMENT_STATE_BY_STRIPE_STATUS[intent.status] ?? "initiated";
51
- const now = Date.now();
52
- return {
53
- amount,
54
- capturedAmount: state === "captured" ? money(BigInt(intent.amount_received ?? intent.amount), intent.currency) : zeroMoney(intent.currency),
55
- createdAt: now,
56
- id: intent.id,
57
- provider: "stripe",
58
- referenceId: intent.metadata?.referenceId ?? "",
59
- refundedAmount: zeroMoney(intent.currency),
60
- state,
61
- updatedAt: now
62
- };
63
- };
64
- const subscriptionFromStripe = (subscription) => {
65
- const now = Date.now();
66
- return {
67
- cancelAtPeriodEnd: subscription.cancel_at_period_end ?? false,
68
- createdAt: now,
69
- currentPeriodEnd: subscription.current_period_end ? subscription.current_period_end * 1e3 : void 0,
70
- currentPeriodStart: subscription.current_period_start ? subscription.current_period_start * 1e3 : void 0,
71
- id: subscription.id,
72
- priceId: subscription.items?.data[0]?.price?.id ?? "",
73
- provider: "stripe",
74
- quantity: subscription.items?.data[0]?.quantity ?? 1,
75
- referenceId: subscription.metadata?.referenceId ?? "",
76
- state: SUBSCRIPTION_STATE_BY_STRIPE_STATUS[subscription.status] ?? "active",
77
- updatedAt: now
78
- };
79
- };
80
- const subscriptionEventType = (status) => {
81
- const state = status ? SUBSCRIPTION_STATE_BY_STRIPE_STATUS[status] : void 0;
82
- if (state === "canceled") {
83
- return "subscription.canceled";
84
- }
85
- if (state === "past_due") {
86
- return "subscription.past_due";
87
- }
88
- if (state === "paused") {
89
- return "subscription.paused";
90
- }
91
- if (state === "active" || state === "trialing") {
92
- return "subscription.active";
93
- }
94
- return "subscription.updated";
95
- };
96
- const mapEvent = (eventId, eventType, object) => {
97
- const base = { eventId, provider: "stripe", raw: { object, type: eventType } };
98
- const currency = readString(object, "currency") ?? "usd";
99
- switch (eventType) {
100
- case "charge.refunded": {
101
- return {
102
- ...base,
103
- amount: money(BigInt(readNumber(object, "amount_refunded") ?? 0), currency),
104
- amountKind: "absolute",
105
- referenceId: readReferenceId(object),
106
- sessionId: readString(object, "payment_intent") ?? readString(object, "id"),
107
- type: "payment.refunded"
108
- };
109
- }
110
- case "checkout.session.completed": {
111
- if (readString(object, "mode") === "subscription") {
112
- const paymentStatus = readString(object, "payment_status");
113
- const paid = paymentStatus === "paid" || paymentStatus === "no_payment_required";
114
- return {
115
- ...base,
116
- customerId: readString(object, "customer"),
117
- referenceId: readReferenceId(object),
118
- subscriptionId: readString(object, "subscription"),
119
- type: paid ? "subscription.active" : "subscription.updated"
120
- };
121
- }
122
- const amountTotal = readNumber(object, "amount_total");
123
- return {
124
- ...base,
125
- amount: amountTotal === void 0 ? void 0 : money(BigInt(amountTotal), currency),
126
- customerId: readString(object, "customer"),
127
- referenceId: readReferenceId(object),
128
- sessionId: readString(object, "payment_intent") ?? readString(object, "id"),
129
- type: "payment.captured"
130
- };
131
- }
132
- case "customer.subscription.created":
133
- case "customer.subscription.updated": {
134
- return {
135
- ...base,
136
- cancelAtPeriodEnd: readBoolean(object, "cancel_at_period_end"),
137
- currentPeriodEnd: periodEndMs(object),
138
- currentPeriodStart: periodStartMs(object),
139
- customerId: readString(object, "customer"),
140
- priceId: firstPriceId(object),
141
- quantity: firstQuantity(object),
142
- referenceId: readReferenceId(object),
143
- subscriptionId: readString(object, "id"),
144
- type: subscriptionEventType(readString(object, "status"))
145
- };
146
- }
147
- case "customer.subscription.deleted": {
148
- return { ...base, referenceId: readReferenceId(object), subscriptionId: readString(object, "id"), type: "subscription.canceled" };
149
- }
150
- case "payment_intent.amount_capturable_updated": {
151
- return {
152
- ...base,
153
- amount: money(BigInt(readNumber(object, "amount") ?? 0), currency),
154
- referenceId: readReferenceId(object),
155
- sessionId: readString(object, "id"),
156
- type: "payment.authorized"
157
- };
158
- }
159
- case "payment_intent.payment_failed": {
160
- return { ...base, referenceId: readReferenceId(object), sessionId: readString(object, "id"), type: "payment.failed" };
161
- }
162
- case "payment_intent.succeeded": {
163
- return {
164
- ...base,
165
- amount: money(BigInt(readNumber(object, "amount_received") ?? readNumber(object, "amount") ?? 0), currency),
166
- customerId: readString(object, "customer"),
167
- referenceId: readReferenceId(object),
168
- sessionId: readString(object, "id"),
169
- type: "payment.captured"
170
- };
171
- }
172
- default: {
173
- return { ...base, type: "unhandled" };
174
- }
175
- }
176
- };
177
- const createStripeAdapter = (options) => {
178
- const { client, webhookSecret } = options;
179
- return {
180
- cancelPayment: async (sessionId, paymentOptions) => {
181
- const intent = await client.paymentIntents.cancel(sessionId, void 0, { idempotencyKey: paymentOptions?.idempotencyKey });
182
- return intentToSession(intent);
183
- },
184
- cancelSubscription: async (subscriptionId, cancelOptions) => {
185
- const subscription = cancelOptions?.atPeriodEnd ? await client.subscriptions.update(subscriptionId, { cancel_at_period_end: true }, { idempotencyKey: cancelOptions.idempotencyKey }) : await client.subscriptions.cancel(subscriptionId, void 0, { idempotencyKey: cancelOptions?.idempotencyKey });
186
- return subscriptionFromStripe(subscription);
187
- },
188
- capabilities: { merchantOfRecord: false, portal: true, usageMetering: true },
189
- capturePayment: async (input) => {
190
- const intent = await client.paymentIntents.capture(
191
- input.sessionId,
192
- input.amount ? { amount_to_capture: Number(input.amount.minorUnits) } : void 0,
193
- { idempotencyKey: input.idempotencyKey }
194
- );
195
- return intentToSession(intent);
196
- },
197
- createCheckout: async (input) => {
198
- const session = await client.checkout.sessions.create(
199
- {
200
- cancel_url: input.cancelUrl,
201
- client_reference_id: input.referenceId,
202
- customer: input.customerId,
203
- line_items: [{ price: input.priceId, quantity: input.quantity ?? 1 }],
204
- // Pin the framework-controlled `referenceId` LAST so caller metadata can never override it.
205
- metadata: { ...input.metadata, referenceId: input.referenceId },
206
- mode: input.mode,
207
- subscription_data: input.mode === "subscription" ? { metadata: { referenceId: input.referenceId } } : void 0,
208
- success_url: input.successUrl
209
- },
210
- { idempotencyKey: input.idempotencyKey }
211
- );
212
- return { id: session.id, provider: "stripe", url: session.url ?? "" };
213
- },
214
- createPortalSession: async (input) => {
215
- const session = await client.billingPortal.sessions.create({ customer: input.customerId, return_url: input.returnUrl });
216
- return { url: session.url };
217
- },
218
- getOrCreateCustomer: async (ref) => {
219
- const customer = await client.customers.create(
220
- { email: ref.email, metadata: { ...ref.metadata, referenceId: ref.referenceId } },
221
- { idempotencyKey: idempotencyKey("customer", "stripe", ref.referenceId) }
222
- );
223
- return { createdAt: Date.now(), email: customer.email ?? void 0, id: customer.id, provider: "stripe", referenceId: ref.referenceId };
224
- },
225
- getPaymentStatus: async (sessionId) => intentToSession(await client.paymentIntents.retrieve(sessionId)),
226
- getSubscriptionStatus: async (subscriptionId) => subscriptionFromStripe(await client.subscriptions.retrieve(subscriptionId)),
227
- identifier: "stripe",
228
- parseWebhook: async ({ headers, payload }) => {
229
- const signatureHeader = headers.get("stripe-signature") ?? "";
230
- await verifyStripeSignature({ payload, secret: webhookSecret, signatureHeader, toleranceSeconds: options.webhookToleranceSeconds });
231
- const event = asRecord(JSON.parse(payload));
232
- const object = asRecord(asRecord(event.data).object);
233
- return mapEvent(readString(event, "id") ?? "", readString(event, "type") ?? "", object);
234
- },
235
- refundPayment: async (input) => {
236
- await client.refunds.create(
237
- {
238
- amount: input.amount ? Number(input.amount.minorUnits) : void 0,
239
- payment_intent: input.sessionId,
240
- reason: input.reason
241
- },
242
- { idempotencyKey: input.idempotencyKey }
243
- );
244
- const intent = await client.paymentIntents.retrieve(input.sessionId);
245
- const session = intentToSession(intent);
246
- const refundedAmount = input.amount ?? session.capturedAmount;
247
- const partial = input.amount !== void 0 && compareMoney(input.amount, session.capturedAmount) < 0;
248
- return { ...session, refundedAmount, state: partial ? "partially_refunded" : "refunded" };
249
- },
250
- reportUsage: async (input) => {
251
- await client.billing.meterEvents.create(
252
- {
253
- event_name: input.featureId,
254
- identifier: input.idempotencyKey,
255
- payload: { stripe_customer_id: input.customerId, value: String(input.quantity) },
256
- timestamp: input.timestamp === void 0 ? void 0 : Math.floor(input.timestamp / 1e3)
257
- },
258
- { idempotencyKey: input.idempotencyKey }
259
- );
260
- },
261
- resumeSubscription: async (subscriptionId) => {
262
- const subscription = await client.subscriptions.update(subscriptionId, { cancel_at_period_end: false });
263
- return subscriptionFromStripe(subscription);
264
- },
265
- updateSubscription: async (subscriptionId, patch) => {
266
- const parameters = {};
267
- if (patch.quantity !== void 0) {
268
- parameters.quantity = patch.quantity;
269
- }
270
- if (patch.priceId) {
271
- parameters.items = [{ price: patch.priceId }];
272
- }
273
- const subscription = await client.subscriptions.update(subscriptionId, parameters);
274
- return subscriptionFromStripe(subscription);
275
- }
276
- };
277
- };
278
-
279
- export { createStripeAdapter };
@@ -1,51 +0,0 @@
1
- const ACTIVE_STATES = /* @__PURE__ */ new Set(["active", "trialing"]);
2
- const usagePeriodStart = (subscriptions) => {
3
- let start = 0;
4
- for (const subscription of subscriptions) {
5
- if (ACTIVE_STATES.has(subscription.state) && subscription.currentPeriodStart !== void 0) {
6
- start = Math.max(start, subscription.currentPeriodStart);
7
- }
8
- }
9
- return start;
10
- };
11
- const featureNames = (config) => {
12
- const names = /* @__PURE__ */ new Set();
13
- for (const plan of Object.values(config.plans)) {
14
- for (const feature of plan.features ?? []) {
15
- names.add(feature);
16
- }
17
- for (const key of Object.keys(plan.limits ?? {})) {
18
- names.add(key);
19
- }
20
- }
21
- return [...names].toSorted((a, b) => a.localeCompare(b));
22
- };
23
- const hasActivePrice = (subscriptions, priceId) => subscriptions.some((subscription) => subscription.priceId === priceId && ACTIVE_STATES.has(subscription.state));
24
- const resolveEntitlements = (config, subscriptions) => {
25
- const activePriceIds = new Set(subscriptions.filter((subscription) => ACTIVE_STATES.has(subscription.state)).map((subscription) => subscription.priceId));
26
- const plans = [];
27
- const features = /* @__PURE__ */ new Set();
28
- const limits = /* @__PURE__ */ new Map();
29
- for (const [name, plan] of Object.entries(config.plans)) {
30
- if (!plan.priceIds.some((id) => activePriceIds.has(id))) {
31
- continue;
32
- }
33
- plans.push(name);
34
- for (const feature of plan.features ?? []) {
35
- features.add(feature);
36
- }
37
- for (const [key, value] of Object.entries(plan.limits ?? {})) {
38
- const current = limits.get(key);
39
- limits.set(key, current === void 0 ? value : Math.max(current, value));
40
- }
41
- }
42
- return {
43
- features,
44
- has: (feature) => features.has(feature),
45
- limit: (key) => limits.get(key),
46
- plans
47
- };
48
- };
49
- const entitlementsForReference = async (store, config, referenceId) => resolveEntitlements(config, await store.listSubscriptionsByReference(referenceId));
50
-
51
- export { entitlementsForReference, featureNames, hasActivePrice, resolveEntitlements, usagePeriodStart };
@@ -1,3 +0,0 @@
1
- const idempotencyKey = (operation, ...parts) => [operation, ...parts.map(String)].join(":");
2
-
3
- export { idempotencyKey as default };
@@ -1,6 +0,0 @@
1
- const asRecord = (value) => typeof value === "object" && value !== null ? value : {};
2
- const readString = (object, key) => typeof object[key] === "string" ? object[key] : void 0;
3
- const readNumber = (object, key) => typeof object[key] === "number" ? object[key] : void 0;
4
- const readBoolean = (object, key) => typeof object[key] === "boolean" ? object[key] : void 0;
5
-
6
- export { asRecord as a, readBoolean as b, readNumber as c, readString as r };
@@ -1,29 +0,0 @@
1
- import { createPayment } from './createPayment-BccfPGyw.mjs';
2
- import { createDatabasePaymentStore } from './createDatabasePaymentStore-bYB_HUE6.mjs';
3
-
4
- const lunoraDatabaseToPaymentDatabase = (database) => {
5
- return {
6
- delete: async (id) => database.delete(id),
7
- findFirst: async (table, where) => await database.findFirst(table, { where }),
8
- findMany: async (table, where) => {
9
- const result = await database.findMany(table, { where });
10
- return result.page;
11
- },
12
- insert: async (table, document) => database.insert(table, document),
13
- patch: async (id, patch) => database.patch(id, patch)
14
- };
15
- };
16
- const paymentsFromContext = (context, options) => {
17
- const userId = context.auth?.userId ?? void 0;
18
- return createPayment({
19
- adapter: options.adapter,
20
- // The default authorizer fails closed on an empty/whitespace reference: a missing identity or a
21
- // blank reference (e.g. webhook-orphaned rows with `referenceId: ""`) is never authorized.
22
- authorize: options.authorize ?? ((referenceId) => referenceId.trim() !== "" && userId !== void 0 && referenceId === userId),
23
- entitlements: options.entitlements,
24
- observability: options.observability,
25
- store: createDatabasePaymentStore(lunoraDatabaseToPaymentDatabase(context.db))
26
- });
27
- };
28
-
29
- export { lunoraDatabaseToPaymentDatabase, paymentsFromContext };
@@ -1,11 +0,0 @@
1
- const notifyObserver = (observer, event) => {
2
- if (!observer) {
3
- return;
4
- }
5
- try {
6
- observer(event);
7
- } catch {
8
- }
9
- };
10
-
11
- export { notifyObserver as n };
@@ -1,127 +0,0 @@
1
- import { defineTable } from '@lunora/server';
2
- import { v } from '@lunora/values';
3
-
4
- const products = defineTable({
5
- description: v.optional(v.string()),
6
- name: v.string(),
7
- provider: v.string(),
8
- providerProductId: v.string()
9
- }).index("by_provider_product", ["provider", "providerProductId"], { unique: true });
10
- const prices = defineTable({
11
- active: v.boolean(),
12
- amountMinor: v.bigint(),
13
- currency: v.string(),
14
- interval: v.optional(v.string()),
15
- provider: v.string(),
16
- providerPriceId: v.string(),
17
- providerProductId: v.string()
18
- }).index("by_provider_price", ["provider", "providerPriceId"], { unique: true });
19
- const customers = defineTable({
20
- createdAt: v.number(),
21
- email: v.optional(v.string()),
22
- provider: v.string(),
23
- providerCustomerId: v.string(),
24
- referenceId: v.string()
25
- }).index("by_provider_customer", ["provider", "providerCustomerId"], { unique: true }).index("by_reference", ["referenceId"]);
26
- const subscriptions = defineTable({
27
- cancelAtPeriodEnd: v.boolean(),
28
- createdAt: v.number(),
29
- currentPeriodEnd: v.optional(v.number()),
30
- currentPeriodStart: v.optional(v.number()),
31
- priceId: v.string(),
32
- provider: v.string(),
33
- providerSubscriptionId: v.string(),
34
- quantity: v.number(),
35
- referenceId: v.string(),
36
- state: v.string(),
37
- updatedAt: v.number()
38
- }).index("by_provider_subscription", ["provider", "providerSubscriptionId"], { unique: true }).index("by_reference", ["referenceId"]);
39
- const checkouts = defineTable({
40
- createdAt: v.number(),
41
- mode: v.string(),
42
- priceId: v.string(),
43
- provider: v.string(),
44
- providerCheckoutId: v.string(),
45
- referenceId: v.string(),
46
- url: v.string()
47
- }).index("by_provider_checkout", ["provider", "providerCheckoutId"], { unique: true });
48
- const paymentSessions = defineTable({
49
- amountMinor: v.bigint(),
50
- capturedMinor: v.bigint(),
51
- createdAt: v.number(),
52
- currency: v.string(),
53
- provider: v.string(),
54
- providerSessionId: v.string(),
55
- referenceId: v.string(),
56
- refundedMinor: v.bigint(),
57
- state: v.string(),
58
- updatedAt: v.number()
59
- }).index("by_provider_session", ["provider", "providerSessionId"], { unique: true }).index("by_reference", ["referenceId"]);
60
- const payments = defineTable({
61
- amountMinor: v.bigint(),
62
- createdAt: v.number(),
63
- currency: v.string(),
64
- provider: v.string(),
65
- providerPaymentId: v.string(),
66
- referenceId: v.string(),
67
- sessionId: v.string(),
68
- status: v.string()
69
- }).index("by_provider_payment", ["provider", "providerPaymentId"], { unique: true });
70
- const captures = defineTable({
71
- amountMinor: v.bigint(),
72
- createdAt: v.number(),
73
- currency: v.string(),
74
- provider: v.string(),
75
- providerCaptureId: v.string(),
76
- sessionId: v.string()
77
- }).index("by_session", ["sessionId"]);
78
- const refunds = defineTable({
79
- amountMinor: v.bigint(),
80
- createdAt: v.number(),
81
- currency: v.string(),
82
- provider: v.string(),
83
- providerRefundId: v.string(),
84
- reason: v.optional(v.string()),
85
- sessionId: v.string()
86
- }).index("by_session", ["sessionId"]);
87
- const invoices = defineTable({
88
- amountMinor: v.bigint(),
89
- createdAt: v.number(),
90
- currency: v.string(),
91
- provider: v.string(),
92
- providerInvoiceId: v.string(),
93
- referenceId: v.string(),
94
- status: v.string(),
95
- subscriptionId: v.optional(v.string())
96
- }).index("by_provider_invoice", ["provider", "providerInvoiceId"], { unique: true });
97
- const events = defineTable({
98
- processedAt: v.number(),
99
- provider: v.string(),
100
- providerEventId: v.string(),
101
- type: v.string()
102
- }).index("by_provider_event", ["provider", "providerEventId"], { unique: true });
103
- const usageEvents = defineTable({
104
- createdAt: v.number(),
105
- featureId: v.string(),
106
- idempotencyKey: v.string(),
107
- provider: v.string(),
108
- quantity: v.number(),
109
- referenceId: v.string(),
110
- reportedToProvider: v.boolean()
111
- }).index("by_idempotency", ["provider", "idempotencyKey"], { unique: true }).index("by_reference_feature", ["referenceId", "featureId"]);
112
- const paymentTables = {
113
- captures,
114
- checkouts,
115
- customers,
116
- events,
117
- invoices,
118
- paymentSessions,
119
- payments,
120
- prices,
121
- products,
122
- refunds,
123
- subscriptions,
124
- usageEvents
125
- };
126
-
127
- export { paymentTables as default };
@@ -1,73 +0,0 @@
1
- import { compareMoney } from './addMoney-bCcs1nyw.mjs';
2
- import { n as notifyObserver } from './observability-CvhJ205g.mjs';
3
-
4
- const sameCurrencyAmount = (a, b) => a.currency === b.currency && compareMoney(a, b) === 0;
5
- const subscriptionDrifted = (existing, current) => existing?.state !== current.state || existing.cancelAtPeriodEnd !== current.cancelAtPeriodEnd || existing.currentPeriodEnd !== current.currentPeriodEnd || existing.priceId !== current.priceId || existing.quantity !== current.quantity;
6
- const paymentDrifted = (existing, current) => existing?.state !== current.state || !sameCurrencyAmount(existing.capturedAmount, current.capturedAmount) || !sameCurrencyAmount(existing.refundedAmount, current.refundedAmount);
7
- const reconcileSubscription = async (adapter, store, id, observer) => {
8
- const current = await adapter.getSubscriptionStatus(id);
9
- const existing = await store.getSubscription(adapter.identifier, id);
10
- if (!subscriptionDrifted(existing, current)) {
11
- return false;
12
- }
13
- await store.upsertSubscription({ ...current, createdAt: existing?.createdAt ?? current.createdAt });
14
- notifyObserver(observer, { id, kind: "subscription", provider: adapter.identifier, type: "reconcile.drift" });
15
- return true;
16
- };
17
- const reconcilePayment = async (adapter, store, id, observer) => {
18
- const current = await adapter.getPaymentStatus(id);
19
- const existing = await store.getPaymentSession(adapter.identifier, id);
20
- if (!paymentDrifted(existing, current)) {
21
- return false;
22
- }
23
- await store.upsertPaymentSession({ ...current, createdAt: existing?.createdAt ?? current.createdAt });
24
- notifyObserver(observer, { id, kind: "payment", provider: adapter.identifier, type: "reconcile.drift" });
25
- return true;
26
- };
27
- const sweep = async (ids, kind, reconcileOne, adapter, observer) => {
28
- const settled = await Promise.allSettled(ids.map((id) => reconcileOne(id)));
29
- let updated = 0;
30
- let failed = 0;
31
- for (const [index, result] of settled.entries()) {
32
- if (result.status === "fulfilled") {
33
- if (result.value) {
34
- updated += 1;
35
- }
36
- } else {
37
- failed += 1;
38
- notifyObserver(observer, { error: result.reason, id: ids[index] ?? "", kind, provider: adapter.identifier, type: "reconcile.error" });
39
- }
40
- }
41
- return { failed, updated };
42
- };
43
- const reconcile = async (input) => {
44
- const { adapter, observability, store } = input;
45
- const subscriptionIds = input.subscriptionIds ?? [];
46
- const paymentSessionIds = input.paymentSessionIds ?? [];
47
- const subscriptionCounts = await sweep(
48
- subscriptionIds,
49
- "subscription",
50
- (id) => reconcileSubscription(adapter, store, id, observability),
51
- adapter,
52
- observability
53
- );
54
- const paymentCounts = await sweep(paymentSessionIds, "payment", (id) => reconcilePayment(adapter, store, id, observability), adapter, observability);
55
- notifyObserver(observability, {
56
- failedPayments: paymentCounts.failed,
57
- failedSubscriptions: subscriptionCounts.failed,
58
- provider: adapter.identifier,
59
- type: "reconcile.completed",
60
- updatedPayments: paymentCounts.updated,
61
- updatedSubscriptions: subscriptionCounts.updated
62
- });
63
- return {
64
- checkedPayments: paymentSessionIds.length,
65
- checkedSubscriptions: subscriptionIds.length,
66
- failedPayments: paymentCounts.failed,
67
- failedSubscriptions: subscriptionCounts.failed,
68
- updatedPayments: paymentCounts.updated,
69
- updatedSubscriptions: subscriptionCounts.updated
70
- };
71
- };
72
-
73
- export { reconcile };