@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.
- package/LICENSE.md +6 -0
- package/README.md +5 -2
- package/dist/index.d.mts +293 -526
- package/dist/index.d.ts +293 -526
- package/dist/index.mjs +1 -16
- package/dist/packem_shared/LunoraPaymentError-C8uAUIzQ.mjs +1 -0
- package/dist/packem_shared/MemoryPaymentStore-CkXqJEe2.mjs +1 -0
- package/dist/packem_shared/PAYMENT_TERMINAL_STATES-DVNbZxBy.mjs +1 -0
- package/dist/packem_shared/adapter.d-xBfAhqVC.d.mts +445 -0
- package/dist/packem_shared/adapter.d-xBfAhqVC.d.ts +445 -0
- package/dist/packem_shared/addMoney-BKfsSf0H.mjs +1 -0
- package/dist/packem_shared/applyWebhookAction-Bwvycl00.mjs +1 -0
- package/dist/packem_shared/constantTimeEqual-B4DP75aY.mjs +1 -0
- package/dist/packem_shared/createAdapterRegistry-SoXdsiQd.mjs +1 -0
- package/dist/packem_shared/createDatabasePaymentStore-DxChfP5X.mjs +1 -0
- package/dist/packem_shared/createPayment-Cj0tRPRZ.mjs +1 -0
- package/dist/packem_shared/entitlementsForReference-BbEOKu5d.mjs +1 -0
- package/dist/packem_shared/idempotencyKey-CfJKqcE3.mjs +1 -0
- package/dist/packem_shared/json-D85Nbd92.mjs +1 -0
- package/dist/packem_shared/lunoraDatabaseToPaymentDatabase-CW1VjW4D.mjs +1 -0
- package/dist/packem_shared/not-supported-BBbwyP_u.mjs +1 -0
- package/dist/packem_shared/observability-C9tz8mj_.mjs +1 -0
- package/dist/packem_shared/paymentTables-Be-_11ux.mjs +1 -0
- package/dist/packem_shared/reconcile-BjWV0mB0.mjs +1 -0
- package/dist/packem_shared/subscription-event-BosLshSM.mjs +1 -0
- package/dist/providers/autumn-features.d.mts +129 -0
- package/dist/providers/autumn-features.d.ts +129 -0
- package/dist/providers/autumn-features.mjs +1 -0
- package/dist/providers/autumn.d.mts +28 -0
- package/dist/providers/autumn.d.ts +28 -0
- package/dist/providers/autumn.mjs +1 -0
- package/dist/providers/creem.d.mts +26 -0
- package/dist/providers/creem.d.ts +26 -0
- package/dist/providers/creem.mjs +1 -0
- package/dist/providers/dodopayments.d.mts +30 -0
- package/dist/providers/dodopayments.d.ts +30 -0
- package/dist/providers/dodopayments.mjs +1 -0
- package/dist/providers/polar.d.mts +31 -0
- package/dist/providers/polar.d.ts +31 -0
- package/dist/providers/polar.mjs +1 -0
- package/dist/providers/stripe.d.mts +32 -0
- package/dist/providers/stripe.d.ts +32 -0
- package/dist/providers/stripe.mjs +1 -0
- package/package.json +45 -4
- package/dist/packem_shared/LunoraPaymentError-B3hEzXSs.mjs +0 -22
- package/dist/packem_shared/MemoryPaymentStore-DvgdWa3C.mjs +0 -72
- package/dist/packem_shared/PAYMENT_TERMINAL_STATES-DrxV0clv.mjs +0 -26
- package/dist/packem_shared/addMoney-bCcs1nyw.mjs +0 -60
- package/dist/packem_shared/applyWebhookAction-DpAqf3Lw.mjs +0 -177
- package/dist/packem_shared/constantTimeEqual-CfY0jYcL.mjs +0 -95
- package/dist/packem_shared/createAdapterRegistry-BuDHFCBc.mjs +0 -24
- package/dist/packem_shared/createDatabasePaymentStore-bYB_HUE6.mjs +0 -172
- package/dist/packem_shared/createPayment-BccfPGyw.mjs +0 -190
- package/dist/packem_shared/createPolarAdapter-BJtVGSlF.mjs +0 -217
- package/dist/packem_shared/createStripeAdapter-D40MVBXg.mjs +0 -270
- package/dist/packem_shared/entitlementsForReference-CzZGXPoZ.mjs +0 -51
- package/dist/packem_shared/idempotencyKey-BFzDCA7g.mjs +0 -3
- package/dist/packem_shared/json-Db337f36.mjs +0 -6
- package/dist/packem_shared/lunoraDatabaseToPaymentDatabase-RlKX3Kcd.mjs +0 -29
- package/dist/packem_shared/observability-CvhJ205g.mjs +0 -11
- package/dist/packem_shared/paymentTables-DccHwWr_.mjs +0 -127
- package/dist/packem_shared/reconcile-CI1ukJF9.mjs +0 -73
|
@@ -1,270 +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
|
-
incomplete: "trialing",
|
|
19
|
-
incomplete_expired: "canceled",
|
|
20
|
-
past_due: "past_due",
|
|
21
|
-
paused: "paused",
|
|
22
|
-
trialing: "trialing",
|
|
23
|
-
unpaid: "past_due"
|
|
24
|
-
};
|
|
25
|
-
const readReferenceId = (object) => readString(asRecord(object.metadata), "referenceId") ?? readString(object, "client_reference_id");
|
|
26
|
-
const firstItem = (object) => {
|
|
27
|
-
const items = asRecord(object.items);
|
|
28
|
-
const data = Array.isArray(items.data) ? items.data : [];
|
|
29
|
-
return asRecord(data[0]);
|
|
30
|
-
};
|
|
31
|
-
const firstPriceId = (object) => readString(asRecord(firstItem(object).price), "id");
|
|
32
|
-
const firstQuantity = (object) => readNumber(firstItem(object), "quantity");
|
|
33
|
-
const periodEndMs = (object) => {
|
|
34
|
-
const seconds = readNumber(object, "current_period_end");
|
|
35
|
-
return seconds === void 0 ? void 0 : seconds * 1e3;
|
|
36
|
-
};
|
|
37
|
-
const periodStartMs = (object) => {
|
|
38
|
-
const seconds = readNumber(object, "current_period_start");
|
|
39
|
-
return seconds === void 0 ? void 0 : seconds * 1e3;
|
|
40
|
-
};
|
|
41
|
-
const intentToSession = (intent) => {
|
|
42
|
-
const amount = money(BigInt(intent.amount), intent.currency);
|
|
43
|
-
const state = PAYMENT_STATE_BY_STRIPE_STATUS[intent.status] ?? "initiated";
|
|
44
|
-
const now = Date.now();
|
|
45
|
-
return {
|
|
46
|
-
amount,
|
|
47
|
-
capturedAmount: state === "captured" ? money(BigInt(intent.amount_received ?? intent.amount), intent.currency) : zeroMoney(intent.currency),
|
|
48
|
-
createdAt: now,
|
|
49
|
-
id: intent.id,
|
|
50
|
-
provider: "stripe",
|
|
51
|
-
referenceId: intent.metadata?.referenceId ?? "",
|
|
52
|
-
refundedAmount: zeroMoney(intent.currency),
|
|
53
|
-
state,
|
|
54
|
-
updatedAt: now
|
|
55
|
-
};
|
|
56
|
-
};
|
|
57
|
-
const subscriptionFromStripe = (subscription) => {
|
|
58
|
-
const now = Date.now();
|
|
59
|
-
return {
|
|
60
|
-
cancelAtPeriodEnd: subscription.cancel_at_period_end ?? false,
|
|
61
|
-
createdAt: now,
|
|
62
|
-
currentPeriodEnd: subscription.current_period_end ? subscription.current_period_end * 1e3 : void 0,
|
|
63
|
-
currentPeriodStart: subscription.current_period_start ? subscription.current_period_start * 1e3 : void 0,
|
|
64
|
-
id: subscription.id,
|
|
65
|
-
priceId: subscription.items?.data[0]?.price?.id ?? "",
|
|
66
|
-
provider: "stripe",
|
|
67
|
-
quantity: subscription.items?.data[0]?.quantity ?? 1,
|
|
68
|
-
referenceId: subscription.metadata?.referenceId ?? "",
|
|
69
|
-
state: SUBSCRIPTION_STATE_BY_STRIPE_STATUS[subscription.status] ?? "active",
|
|
70
|
-
updatedAt: now
|
|
71
|
-
};
|
|
72
|
-
};
|
|
73
|
-
const subscriptionEventType = (status) => {
|
|
74
|
-
const state = status ? SUBSCRIPTION_STATE_BY_STRIPE_STATUS[status] : void 0;
|
|
75
|
-
if (state === "canceled") {
|
|
76
|
-
return "subscription.canceled";
|
|
77
|
-
}
|
|
78
|
-
if (state === "past_due") {
|
|
79
|
-
return "subscription.past_due";
|
|
80
|
-
}
|
|
81
|
-
if (state === "paused") {
|
|
82
|
-
return "subscription.paused";
|
|
83
|
-
}
|
|
84
|
-
if (state === "active" || state === "trialing") {
|
|
85
|
-
return "subscription.active";
|
|
86
|
-
}
|
|
87
|
-
return "subscription.updated";
|
|
88
|
-
};
|
|
89
|
-
const mapEvent = (eventId, eventType, object) => {
|
|
90
|
-
const base = { eventId, provider: "stripe", raw: { object, type: eventType } };
|
|
91
|
-
const currency = readString(object, "currency") ?? "usd";
|
|
92
|
-
switch (eventType) {
|
|
93
|
-
case "charge.refunded": {
|
|
94
|
-
return {
|
|
95
|
-
...base,
|
|
96
|
-
amount: money(BigInt(readNumber(object, "amount_refunded") ?? 0), currency),
|
|
97
|
-
amountKind: "absolute",
|
|
98
|
-
referenceId: readReferenceId(object),
|
|
99
|
-
sessionId: readString(object, "payment_intent") ?? readString(object, "id"),
|
|
100
|
-
type: "payment.refunded"
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
case "checkout.session.completed": {
|
|
104
|
-
if (readString(object, "mode") === "subscription") {
|
|
105
|
-
return {
|
|
106
|
-
...base,
|
|
107
|
-
customerId: readString(object, "customer"),
|
|
108
|
-
referenceId: readReferenceId(object),
|
|
109
|
-
subscriptionId: readString(object, "subscription"),
|
|
110
|
-
type: "subscription.active"
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
const amountTotal = readNumber(object, "amount_total");
|
|
114
|
-
return {
|
|
115
|
-
...base,
|
|
116
|
-
amount: amountTotal === void 0 ? void 0 : money(BigInt(amountTotal), currency),
|
|
117
|
-
customerId: readString(object, "customer"),
|
|
118
|
-
referenceId: readReferenceId(object),
|
|
119
|
-
sessionId: readString(object, "payment_intent") ?? readString(object, "id"),
|
|
120
|
-
type: "payment.captured"
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
case "customer.subscription.created":
|
|
124
|
-
case "customer.subscription.updated": {
|
|
125
|
-
return {
|
|
126
|
-
...base,
|
|
127
|
-
cancelAtPeriodEnd: readBoolean(object, "cancel_at_period_end"),
|
|
128
|
-
currentPeriodEnd: periodEndMs(object),
|
|
129
|
-
currentPeriodStart: periodStartMs(object),
|
|
130
|
-
customerId: readString(object, "customer"),
|
|
131
|
-
priceId: firstPriceId(object),
|
|
132
|
-
quantity: firstQuantity(object),
|
|
133
|
-
referenceId: readReferenceId(object),
|
|
134
|
-
subscriptionId: readString(object, "id"),
|
|
135
|
-
type: subscriptionEventType(readString(object, "status"))
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
case "customer.subscription.deleted": {
|
|
139
|
-
return { ...base, referenceId: readReferenceId(object), subscriptionId: readString(object, "id"), type: "subscription.canceled" };
|
|
140
|
-
}
|
|
141
|
-
case "payment_intent.amount_capturable_updated": {
|
|
142
|
-
return {
|
|
143
|
-
...base,
|
|
144
|
-
amount: money(BigInt(readNumber(object, "amount") ?? 0), currency),
|
|
145
|
-
referenceId: readReferenceId(object),
|
|
146
|
-
sessionId: readString(object, "id"),
|
|
147
|
-
type: "payment.authorized"
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
case "payment_intent.payment_failed": {
|
|
151
|
-
return { ...base, referenceId: readReferenceId(object), sessionId: readString(object, "id"), type: "payment.failed" };
|
|
152
|
-
}
|
|
153
|
-
case "payment_intent.succeeded": {
|
|
154
|
-
return {
|
|
155
|
-
...base,
|
|
156
|
-
amount: money(BigInt(readNumber(object, "amount_received") ?? readNumber(object, "amount") ?? 0), currency),
|
|
157
|
-
customerId: readString(object, "customer"),
|
|
158
|
-
referenceId: readReferenceId(object),
|
|
159
|
-
sessionId: readString(object, "id"),
|
|
160
|
-
type: "payment.captured"
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
default: {
|
|
164
|
-
return { ...base, type: "unhandled" };
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
const createStripeAdapter = (options) => {
|
|
169
|
-
const { client, webhookSecret } = options;
|
|
170
|
-
return {
|
|
171
|
-
cancelPayment: async (sessionId, paymentOptions) => {
|
|
172
|
-
const intent = await client.paymentIntents.cancel(sessionId, void 0, { idempotencyKey: paymentOptions?.idempotencyKey });
|
|
173
|
-
return intentToSession(intent);
|
|
174
|
-
},
|
|
175
|
-
cancelSubscription: async (subscriptionId, cancelOptions) => {
|
|
176
|
-
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 });
|
|
177
|
-
return subscriptionFromStripe(subscription);
|
|
178
|
-
},
|
|
179
|
-
capabilities: { merchantOfRecord: false, portal: true, usageMetering: true },
|
|
180
|
-
capturePayment: async (input) => {
|
|
181
|
-
const intent = await client.paymentIntents.capture(
|
|
182
|
-
input.sessionId,
|
|
183
|
-
input.amount ? { amount_to_capture: Number(input.amount.minorUnits) } : void 0,
|
|
184
|
-
{ idempotencyKey: input.idempotencyKey }
|
|
185
|
-
);
|
|
186
|
-
return intentToSession(intent);
|
|
187
|
-
},
|
|
188
|
-
createCheckout: async (input) => {
|
|
189
|
-
const session = await client.checkout.sessions.create(
|
|
190
|
-
{
|
|
191
|
-
cancel_url: input.cancelUrl,
|
|
192
|
-
client_reference_id: input.referenceId,
|
|
193
|
-
customer: input.customerId,
|
|
194
|
-
line_items: [{ price: input.priceId, quantity: input.quantity ?? 1 }],
|
|
195
|
-
// Pin the framework-controlled `referenceId` LAST so caller metadata can never override it.
|
|
196
|
-
metadata: { ...input.metadata, referenceId: input.referenceId },
|
|
197
|
-
mode: input.mode,
|
|
198
|
-
subscription_data: input.mode === "subscription" ? { metadata: { referenceId: input.referenceId } } : void 0,
|
|
199
|
-
success_url: input.successUrl
|
|
200
|
-
},
|
|
201
|
-
{ idempotencyKey: input.idempotencyKey }
|
|
202
|
-
);
|
|
203
|
-
return { id: session.id, provider: "stripe", url: session.url ?? "" };
|
|
204
|
-
},
|
|
205
|
-
createPortalSession: async (input) => {
|
|
206
|
-
const session = await client.billingPortal.sessions.create({ customer: input.customerId, return_url: input.returnUrl });
|
|
207
|
-
return { url: session.url };
|
|
208
|
-
},
|
|
209
|
-
getOrCreateCustomer: async (ref) => {
|
|
210
|
-
const customer = await client.customers.create(
|
|
211
|
-
{ email: ref.email, metadata: { ...ref.metadata, referenceId: ref.referenceId } },
|
|
212
|
-
{ idempotencyKey: idempotencyKey("customer", "stripe", ref.referenceId) }
|
|
213
|
-
);
|
|
214
|
-
return { createdAt: Date.now(), email: customer.email ?? void 0, id: customer.id, provider: "stripe", referenceId: ref.referenceId };
|
|
215
|
-
},
|
|
216
|
-
getPaymentStatus: async (sessionId) => intentToSession(await client.paymentIntents.retrieve(sessionId)),
|
|
217
|
-
getSubscriptionStatus: async (subscriptionId) => subscriptionFromStripe(await client.subscriptions.retrieve(subscriptionId)),
|
|
218
|
-
identifier: "stripe",
|
|
219
|
-
parseWebhook: async ({ headers, payload }) => {
|
|
220
|
-
const signatureHeader = headers.get("stripe-signature") ?? "";
|
|
221
|
-
await verifyStripeSignature({ payload, secret: webhookSecret, signatureHeader, toleranceSeconds: options.webhookToleranceSeconds });
|
|
222
|
-
const event = asRecord(JSON.parse(payload));
|
|
223
|
-
const object = asRecord(asRecord(event.data).object);
|
|
224
|
-
return mapEvent(readString(event, "id") ?? "", readString(event, "type") ?? "", object);
|
|
225
|
-
},
|
|
226
|
-
refundPayment: async (input) => {
|
|
227
|
-
await client.refunds.create(
|
|
228
|
-
{
|
|
229
|
-
amount: input.amount ? Number(input.amount.minorUnits) : void 0,
|
|
230
|
-
payment_intent: input.sessionId,
|
|
231
|
-
reason: input.reason
|
|
232
|
-
},
|
|
233
|
-
{ idempotencyKey: input.idempotencyKey }
|
|
234
|
-
);
|
|
235
|
-
const intent = await client.paymentIntents.retrieve(input.sessionId);
|
|
236
|
-
const session = intentToSession(intent);
|
|
237
|
-
const refundedAmount = input.amount ?? session.capturedAmount;
|
|
238
|
-
const partial = input.amount !== void 0 && compareMoney(input.amount, session.capturedAmount) < 0;
|
|
239
|
-
return { ...session, refundedAmount, state: partial ? "partially_refunded" : "refunded" };
|
|
240
|
-
},
|
|
241
|
-
reportUsage: async (input) => {
|
|
242
|
-
await client.billing.meterEvents.create(
|
|
243
|
-
{
|
|
244
|
-
event_name: input.featureId,
|
|
245
|
-
identifier: input.idempotencyKey,
|
|
246
|
-
payload: { stripe_customer_id: input.customerId, value: String(input.quantity) },
|
|
247
|
-
timestamp: input.timestamp === void 0 ? void 0 : Math.floor(input.timestamp / 1e3)
|
|
248
|
-
},
|
|
249
|
-
{ idempotencyKey: input.idempotencyKey }
|
|
250
|
-
);
|
|
251
|
-
},
|
|
252
|
-
resumeSubscription: async (subscriptionId) => {
|
|
253
|
-
const subscription = await client.subscriptions.update(subscriptionId, { cancel_at_period_end: false });
|
|
254
|
-
return subscriptionFromStripe(subscription);
|
|
255
|
-
},
|
|
256
|
-
updateSubscription: async (subscriptionId, patch) => {
|
|
257
|
-
const parameters = {};
|
|
258
|
-
if (patch.quantity !== void 0) {
|
|
259
|
-
parameters.quantity = patch.quantity;
|
|
260
|
-
}
|
|
261
|
-
if (patch.priceId) {
|
|
262
|
-
parameters.items = [{ price: patch.priceId }];
|
|
263
|
-
}
|
|
264
|
-
const subscription = await client.subscriptions.update(subscriptionId, parameters);
|
|
265
|
-
return subscriptionFromStripe(subscription);
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
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,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,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 };
|