paddle-checkout-accelerator 2.1.0 → 2.2.0
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/bin/paddle-checkout-accelerator.js +247 -0
- package/dist/index.cjs +1150 -0
- package/dist/index.d.cts +290 -0
- package/dist/index.d.ts +290 -0
- package/dist/index.js +1079 -0
- package/package.json +14 -7
- package/recipes/nextjs/app/billing.ts +1 -1
- package/dist/package/index.d.ts +0 -28
- package/dist/package/index.js +0 -28
- package/dist/src/components/paddle/BillingHistory.d.ts +0 -1
- package/dist/src/components/paddle/BillingHistory.js +0 -17
- package/dist/src/components/paddle/CustomerPortal.d.ts +0 -1
- package/dist/src/components/paddle/CustomerPortal.js +0 -5
- package/dist/src/components/paddle/CustomerPortalButton.d.ts +0 -1
- package/dist/src/components/paddle/CustomerPortalButton.js +0 -31
- package/dist/src/components/paddle/InlineCheckout.d.ts +0 -5
- package/dist/src/components/paddle/InlineCheckout.js +0 -13
- package/dist/src/components/paddle/PricingTable.d.ts +0 -1
- package/dist/src/components/paddle/PricingTable.js +0 -27
- package/dist/src/components/paddle/SubscriptionCard.d.ts +0 -6
- package/dist/src/components/paddle/SubscriptionCard.js +0 -5
- package/dist/src/components/paddle/TrialBanner.d.ts +0 -5
- package/dist/src/components/paddle/TrialBanner.js +0 -5
- package/dist/src/components/paddle/UpgradeModal.d.ts +0 -11
- package/dist/src/components/paddle/UpgradeModal.js +0 -35
- package/dist/src/components/paddle/UsageMeter.d.ts +0 -6
- package/dist/src/components/paddle/UsageMeter.js +0 -8
- package/dist/src/components/paddle/gates/SubscriptionGate.d.ts +0 -7
- package/dist/src/components/paddle/gates/SubscriptionGate.js +0 -8
- package/dist/src/lib/billing/adapters/index.d.ts +0 -3
- package/dist/src/lib/billing/adapters/index.js +0 -3
- package/dist/src/lib/billing/adapters/memory.d.ts +0 -2
- package/dist/src/lib/billing/adapters/memory.js +0 -41
- package/dist/src/lib/billing/adapters/prisma/index.d.ts +0 -28
- package/dist/src/lib/billing/adapters/prisma/index.js +0 -80
- package/dist/src/lib/billing/adapters/types.d.ts +0 -13
- package/dist/src/lib/billing/adapters/types.js +0 -1
- package/dist/src/lib/billing/configure.d.ts +0 -6
- package/dist/src/lib/billing/configure.js +0 -13
- package/dist/src/lib/billing/customer-repair.d.ts +0 -4
- package/dist/src/lib/billing/customer-repair.js +0 -19
- package/dist/src/lib/billing/demo-seed.d.ts +0 -1
- package/dist/src/lib/billing/demo-seed.js +0 -13
- package/dist/src/lib/billing/entitlements.d.ts +0 -3
- package/dist/src/lib/billing/entitlements.js +0 -16
- package/dist/src/lib/billing/events.d.ts +0 -12
- package/dist/src/lib/billing/events.js +0 -20
- package/dist/src/lib/billing/plans.d.ts +0 -9
- package/dist/src/lib/billing/plans.js +0 -41
- package/dist/src/lib/billing/protection.d.ts +0 -6
- package/dist/src/lib/billing/protection.js +0 -16
- package/dist/src/lib/billing/refresh.d.ts +0 -1
- package/dist/src/lib/billing/refresh.js +0 -32
- package/dist/src/lib/billing/subscriptions.d.ts +0 -16
- package/dist/src/lib/billing/subscriptions.js +0 -36
- package/dist/src/lib/billing/teams.d.ts +0 -42
- package/dist/src/lib/billing/teams.js +0 -104
- package/dist/src/lib/billing/usage.d.ts +0 -17
- package/dist/src/lib/billing/usage.js +0 -40
- package/dist/src/lib/billing/webhook-sync.d.ts +0 -26
- package/dist/src/lib/billing/webhook-sync.js +0 -39
- package/dist/src/lib/paddle/api.d.ts +0 -1
- package/dist/src/lib/paddle/api.js +0 -21
- package/dist/src/lib/paddle/client.d.ts +0 -1
- package/dist/src/lib/paddle/client.js +0 -13
- package/dist/src/lib/paddle/customers.d.ts +0 -15
- package/dist/src/lib/paddle/customers.js +0 -14
- package/dist/src/lib/paddle/events.d.ts +0 -10
- package/dist/src/lib/paddle/events.js +0 -11
- package/dist/src/lib/paddle/hooks.d.ts +0 -4
- package/dist/src/lib/paddle/hooks.js +0 -11
- package/dist/src/lib/paddle/portal.d.ts +0 -8
- package/dist/src/lib/paddle/portal.js +0 -28
- package/dist/src/lib/paddle/subscriptions.d.ts +0 -20
- package/dist/src/lib/paddle/subscriptions.js +0 -10
- package/dist/src/lib/paddle/types.d.ts +0 -8
- package/dist/src/lib/paddle/types.js +0 -1
- package/dist/src/lib/paddle/webhook.d.ts +0 -1
- package/dist/src/lib/paddle/webhook.js +0 -8
- package/dist/src/lib/utils.d.ts +0 -2
- package/dist/src/lib/utils.js +0 -5
package/dist/index.js
ADDED
|
@@ -0,0 +1,1079 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
|
|
21
|
+
// src/components/paddle/UpgradeModal.tsx
|
|
22
|
+
import { useState } from "react";
|
|
23
|
+
import {
|
|
24
|
+
ArrowRight,
|
|
25
|
+
Check,
|
|
26
|
+
Loader2,
|
|
27
|
+
X
|
|
28
|
+
} from "lucide-react";
|
|
29
|
+
|
|
30
|
+
// src/lib/paddle/client.ts
|
|
31
|
+
import { initializePaddle } from "@paddle/paddle-js";
|
|
32
|
+
var paddleInstance = null;
|
|
33
|
+
async function getPaddle() {
|
|
34
|
+
if (paddleInstance) {
|
|
35
|
+
return paddleInstance;
|
|
36
|
+
}
|
|
37
|
+
paddleInstance = await initializePaddle({
|
|
38
|
+
token: process.env.NEXT_PUBLIC_PADDLE_CLIENT_TOKEN
|
|
39
|
+
});
|
|
40
|
+
return paddleInstance;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/lib/paddle/hooks.ts
|
|
44
|
+
async function openCheckout(items) {
|
|
45
|
+
const paddle = await getPaddle();
|
|
46
|
+
if (!paddle) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
"Paddle failed to initialize"
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
paddle.Checkout.open({
|
|
52
|
+
items
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/components/paddle/UpgradeModal.tsx
|
|
57
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
58
|
+
function UpgradeModal({
|
|
59
|
+
open,
|
|
60
|
+
onClose,
|
|
61
|
+
currentPlan,
|
|
62
|
+
targetPlan,
|
|
63
|
+
targetPriceId,
|
|
64
|
+
yearlySavings,
|
|
65
|
+
onUpgradeSuccess
|
|
66
|
+
}) {
|
|
67
|
+
const [loading, setLoading] = useState(false);
|
|
68
|
+
const [error, setError] = useState(null);
|
|
69
|
+
if (!open) return null;
|
|
70
|
+
async function handleUpgrade() {
|
|
71
|
+
try {
|
|
72
|
+
setLoading(true);
|
|
73
|
+
setError(null);
|
|
74
|
+
await openCheckout([
|
|
75
|
+
{
|
|
76
|
+
priceId: targetPriceId,
|
|
77
|
+
quantity: 1
|
|
78
|
+
}
|
|
79
|
+
]);
|
|
80
|
+
onUpgradeSuccess == null ? void 0 : onUpgradeSuccess({
|
|
81
|
+
targetPlan
|
|
82
|
+
});
|
|
83
|
+
} catch (err) {
|
|
84
|
+
setError(
|
|
85
|
+
err instanceof Error ? err.message : "Unable to launch checkout"
|
|
86
|
+
);
|
|
87
|
+
} finally {
|
|
88
|
+
setLoading(false);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-lg rounded-3xl border bg-white shadow-2xl", children: [
|
|
92
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b p-6", children: [
|
|
93
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
94
|
+
/* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold", children: "Upgrade Plan" }),
|
|
95
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-slate-500", children: "Unlock additional features" })
|
|
96
|
+
] }),
|
|
97
|
+
/* @__PURE__ */ jsx(
|
|
98
|
+
"button",
|
|
99
|
+
{
|
|
100
|
+
onClick: onClose,
|
|
101
|
+
className: "rounded-lg p-2 hover:bg-slate-100",
|
|
102
|
+
children: /* @__PURE__ */ jsx(X, { size: 18 })
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
] }),
|
|
106
|
+
/* @__PURE__ */ jsxs("div", { className: "p-6", children: [
|
|
107
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-2xl border p-5", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
108
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
109
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm text-slate-500", children: "Current" }),
|
|
110
|
+
/* @__PURE__ */ jsx("div", { className: "mt-1 font-semibold", children: currentPlan })
|
|
111
|
+
] }),
|
|
112
|
+
/* @__PURE__ */ jsx(
|
|
113
|
+
ArrowRight,
|
|
114
|
+
{
|
|
115
|
+
className: "text-slate-400",
|
|
116
|
+
size: 20
|
|
117
|
+
}
|
|
118
|
+
),
|
|
119
|
+
/* @__PURE__ */ jsxs("div", { className: "text-right", children: [
|
|
120
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm text-slate-500", children: "Upgrade To" }),
|
|
121
|
+
/* @__PURE__ */ jsx("div", { className: "mt-1 font-semibold", children: targetPlan })
|
|
122
|
+
] })
|
|
123
|
+
] }) }),
|
|
124
|
+
/* @__PURE__ */ jsx("div", { className: "mt-6 rounded-2xl bg-slate-50 p-5", children: /* @__PURE__ */ jsxs("ul", { className: "space-y-3", children: [
|
|
125
|
+
/* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3", children: [
|
|
126
|
+
/* @__PURE__ */ jsx(Check, { size: 16 }),
|
|
127
|
+
"Unlimited projects"
|
|
128
|
+
] }),
|
|
129
|
+
/* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3", children: [
|
|
130
|
+
/* @__PURE__ */ jsx(Check, { size: 16 }),
|
|
131
|
+
"Advanced billing"
|
|
132
|
+
] }),
|
|
133
|
+
/* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3", children: [
|
|
134
|
+
/* @__PURE__ */ jsx(Check, { size: 16 }),
|
|
135
|
+
"Priority support"
|
|
136
|
+
] }),
|
|
137
|
+
/* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3", children: [
|
|
138
|
+
/* @__PURE__ */ jsx(Check, { size: 16 }),
|
|
139
|
+
"Team access"
|
|
140
|
+
] })
|
|
141
|
+
] }) }),
|
|
142
|
+
yearlySavings && /* @__PURE__ */ jsxs("div", { className: "mt-4 rounded-xl border border-emerald-200 bg-emerald-50 p-3 text-sm", children: [
|
|
143
|
+
"Save ",
|
|
144
|
+
yearlySavings
|
|
145
|
+
] }),
|
|
146
|
+
error && /* @__PURE__ */ jsx("div", { className: "mt-4 rounded-xl border border-red-200 bg-red-50 p-3 text-sm text-red-700", children: error }),
|
|
147
|
+
/* @__PURE__ */ jsx(
|
|
148
|
+
"button",
|
|
149
|
+
{
|
|
150
|
+
onClick: handleUpgrade,
|
|
151
|
+
disabled: loading,
|
|
152
|
+
className: "mt-6 flex w-full items-center justify-center gap-2 rounded-2xl bg-black px-5 py-4 text-white transition hover:opacity-90 disabled:opacity-50",
|
|
153
|
+
children: loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
154
|
+
/* @__PURE__ */ jsx(
|
|
155
|
+
Loader2,
|
|
156
|
+
{
|
|
157
|
+
size: 18,
|
|
158
|
+
className: "animate-spin"
|
|
159
|
+
}
|
|
160
|
+
),
|
|
161
|
+
"Launching Checkout..."
|
|
162
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
163
|
+
"Upgrade Now",
|
|
164
|
+
/* @__PURE__ */ jsx(ArrowRight, { size: 18 })
|
|
165
|
+
] })
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
] })
|
|
169
|
+
] }) });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/components/paddle/UsageMeter.tsx
|
|
173
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
174
|
+
function UsageMeter({
|
|
175
|
+
current,
|
|
176
|
+
limit
|
|
177
|
+
}) {
|
|
178
|
+
const percentage = Math.min(
|
|
179
|
+
100,
|
|
180
|
+
current / limit * 100
|
|
181
|
+
);
|
|
182
|
+
return /* @__PURE__ */ jsxs2("div", { className: "rounded-xl border p-5", children: [
|
|
183
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex justify-between", children: [
|
|
184
|
+
/* @__PURE__ */ jsx2("span", { children: "Usage" }),
|
|
185
|
+
/* @__PURE__ */ jsxs2("span", { children: [
|
|
186
|
+
current,
|
|
187
|
+
"/",
|
|
188
|
+
limit
|
|
189
|
+
] })
|
|
190
|
+
] }),
|
|
191
|
+
/* @__PURE__ */ jsx2("div", { className: "mt-3 h-3 rounded-full bg-gray-100", children: /* @__PURE__ */ jsx2(
|
|
192
|
+
"div",
|
|
193
|
+
{
|
|
194
|
+
className: "h-3 rounded-full bg-black",
|
|
195
|
+
style: {
|
|
196
|
+
width: `${percentage}%`
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
) })
|
|
200
|
+
] });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/components/paddle/TrialBanner.tsx
|
|
204
|
+
import { jsxs as jsxs3 } from "react/jsx-runtime";
|
|
205
|
+
function TrialBanner({
|
|
206
|
+
daysRemaining
|
|
207
|
+
}) {
|
|
208
|
+
return /* @__PURE__ */ jsxs3("div", { className: "rounded-xl border border-yellow-300 bg-yellow-50 p-4", children: [
|
|
209
|
+
"Trial expires in",
|
|
210
|
+
" ",
|
|
211
|
+
/* @__PURE__ */ jsxs3("strong", { children: [
|
|
212
|
+
daysRemaining,
|
|
213
|
+
" days"
|
|
214
|
+
] }),
|
|
215
|
+
"."
|
|
216
|
+
] });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/components/paddle/BillingHistory.tsx
|
|
220
|
+
import { jsx as jsx3, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
221
|
+
var invoices = [
|
|
222
|
+
{
|
|
223
|
+
id: "INV-001",
|
|
224
|
+
amount: "$79",
|
|
225
|
+
status: "Paid"
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: "INV-002",
|
|
229
|
+
amount: "$79",
|
|
230
|
+
status: "Paid"
|
|
231
|
+
}
|
|
232
|
+
];
|
|
233
|
+
function BillingHistory() {
|
|
234
|
+
return /* @__PURE__ */ jsx3("div", { className: "rounded-xl border", children: invoices.map((invoice) => /* @__PURE__ */ jsxs4(
|
|
235
|
+
"div",
|
|
236
|
+
{
|
|
237
|
+
className: "flex justify-between border-b p-4",
|
|
238
|
+
children: [
|
|
239
|
+
/* @__PURE__ */ jsx3("div", { children: invoice.id }),
|
|
240
|
+
/* @__PURE__ */ jsx3("div", { children: invoice.amount }),
|
|
241
|
+
/* @__PURE__ */ jsx3("div", { children: invoice.status })
|
|
242
|
+
]
|
|
243
|
+
},
|
|
244
|
+
invoice.id
|
|
245
|
+
)) });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/components/paddle/CustomerPortalButton.tsx
|
|
249
|
+
import { useState as useState2 } from "react";
|
|
250
|
+
import { jsx as jsx4, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
251
|
+
function CustomerPortalButton() {
|
|
252
|
+
const [loading, setLoading] = useState2(false);
|
|
253
|
+
const [error, setError] = useState2(null);
|
|
254
|
+
async function launchPortal() {
|
|
255
|
+
var _a2;
|
|
256
|
+
try {
|
|
257
|
+
setLoading(true);
|
|
258
|
+
setError(null);
|
|
259
|
+
const response = await fetch(
|
|
260
|
+
"/api/paddle/portal-session",
|
|
261
|
+
{ method: "POST" }
|
|
262
|
+
);
|
|
263
|
+
const data = await response.json();
|
|
264
|
+
if (!response.ok || !data.success || !data.url) {
|
|
265
|
+
throw new Error(
|
|
266
|
+
(_a2 = data.error) != null ? _a2 : "Unable to open billing portal"
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
window.location.href = data.url;
|
|
270
|
+
} catch (err) {
|
|
271
|
+
setError(
|
|
272
|
+
err instanceof Error ? err.message : "Unable to open billing portal"
|
|
273
|
+
);
|
|
274
|
+
} finally {
|
|
275
|
+
setLoading(false);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return /* @__PURE__ */ jsxs5("div", { className: "space-y-3", children: [
|
|
279
|
+
/* @__PURE__ */ jsx4(
|
|
280
|
+
"button",
|
|
281
|
+
{
|
|
282
|
+
onClick: launchPortal,
|
|
283
|
+
disabled: loading,
|
|
284
|
+
className: "rounded-xl bg-black px-4 py-3 text-white disabled:opacity-60",
|
|
285
|
+
children: loading ? "Opening Billing..." : "Manage Billing"
|
|
286
|
+
}
|
|
287
|
+
),
|
|
288
|
+
error && /* @__PURE__ */ jsx4("div", { className: "rounded-xl border border-red-200 bg-red-50 p-3 text-sm text-red-700", children: error })
|
|
289
|
+
] });
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/components/paddle/SubscriptionCard.tsx
|
|
293
|
+
import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
294
|
+
function SubscriptionCard({
|
|
295
|
+
plan,
|
|
296
|
+
status
|
|
297
|
+
}) {
|
|
298
|
+
return /* @__PURE__ */ jsxs6("div", { className: "rounded-2xl border p-6", children: [
|
|
299
|
+
/* @__PURE__ */ jsx5("div", { className: "text-sm text-gray-500", children: "Current Plan" }),
|
|
300
|
+
/* @__PURE__ */ jsx5("div", { className: "mt-2 text-3xl font-bold", children: plan }),
|
|
301
|
+
/* @__PURE__ */ jsxs6("div", { className: "mt-4", children: [
|
|
302
|
+
"Status: ",
|
|
303
|
+
status
|
|
304
|
+
] })
|
|
305
|
+
] });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/components/paddle/gates/SubscriptionGate.tsx
|
|
309
|
+
import { Fragment as Fragment2, jsx as jsx6 } from "react/jsx-runtime";
|
|
310
|
+
function SubscriptionGate({
|
|
311
|
+
active,
|
|
312
|
+
children,
|
|
313
|
+
fallback
|
|
314
|
+
}) {
|
|
315
|
+
if (!active) {
|
|
316
|
+
return fallback != null ? fallback : /* @__PURE__ */ jsx6("div", { className: "rounded-xl border p-6", children: "Upgrade required." });
|
|
317
|
+
}
|
|
318
|
+
return /* @__PURE__ */ jsx6(Fragment2, { children });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/lib/paddle/webhook.ts
|
|
322
|
+
import { Webhook } from "svix";
|
|
323
|
+
async function verifyPaddleWebhook(rawBody, signature) {
|
|
324
|
+
const secret = process.env.PADDLE_WEBHOOK_SECRET;
|
|
325
|
+
const wh = new Webhook(secret);
|
|
326
|
+
return wh.verify(rawBody, {
|
|
327
|
+
"paddle-signature": signature
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/lib/billing/plans.ts
|
|
332
|
+
var plans = {
|
|
333
|
+
free: {
|
|
334
|
+
name: "Free",
|
|
335
|
+
monthlyLimit: 25,
|
|
336
|
+
seatLimit: 1,
|
|
337
|
+
features: ["checkout"]
|
|
338
|
+
},
|
|
339
|
+
starter: {
|
|
340
|
+
name: "Starter",
|
|
341
|
+
monthlyLimit: 500,
|
|
342
|
+
seatLimit: 1,
|
|
343
|
+
features: ["checkout", "customer_portal"]
|
|
344
|
+
},
|
|
345
|
+
pro: {
|
|
346
|
+
name: "Pro",
|
|
347
|
+
monthlyLimit: 5e3,
|
|
348
|
+
seatLimit: 5,
|
|
349
|
+
features: [
|
|
350
|
+
"checkout",
|
|
351
|
+
"customer_portal",
|
|
352
|
+
"usage_limits",
|
|
353
|
+
"api_access"
|
|
354
|
+
]
|
|
355
|
+
},
|
|
356
|
+
business: {
|
|
357
|
+
name: "Business",
|
|
358
|
+
monthlyLimit: 5e4,
|
|
359
|
+
seatLimit: 25,
|
|
360
|
+
features: [
|
|
361
|
+
"checkout",
|
|
362
|
+
"customer_portal",
|
|
363
|
+
"usage_limits",
|
|
364
|
+
"team_members",
|
|
365
|
+
"api_access",
|
|
366
|
+
"priority_support"
|
|
367
|
+
]
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
function getPlanSeatLimit(plan) {
|
|
371
|
+
return plans[plan].seatLimit;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/lib/billing/adapters/memory.ts
|
|
375
|
+
var subscriptions = /* @__PURE__ */ new Map();
|
|
376
|
+
var usage = /* @__PURE__ */ new Map();
|
|
377
|
+
var teams = /* @__PURE__ */ new Map();
|
|
378
|
+
var billingEvents = /* @__PURE__ */ new Map();
|
|
379
|
+
function usageKey(userId, key, period) {
|
|
380
|
+
return `${userId}:${key}:${period}`;
|
|
381
|
+
}
|
|
382
|
+
var memoryBillingAdapter = {
|
|
383
|
+
async getSubscription(userId) {
|
|
384
|
+
var _a2;
|
|
385
|
+
return (_a2 = subscriptions.get(userId)) != null ? _a2 : null;
|
|
386
|
+
},
|
|
387
|
+
async upsertSubscription(record) {
|
|
388
|
+
subscriptions.set(
|
|
389
|
+
record.userId,
|
|
390
|
+
record
|
|
391
|
+
);
|
|
392
|
+
return record;
|
|
393
|
+
},
|
|
394
|
+
async getUsage(userId, key, period) {
|
|
395
|
+
var _a2;
|
|
396
|
+
return (_a2 = usage.get(
|
|
397
|
+
usageKey(userId, key, period)
|
|
398
|
+
)) != null ? _a2 : 0;
|
|
399
|
+
},
|
|
400
|
+
async incrementUsage(userId, key, period, amount) {
|
|
401
|
+
var _a2;
|
|
402
|
+
const id = usageKey(userId, key, period);
|
|
403
|
+
const next = ((_a2 = usage.get(id)) != null ? _a2 : 0) + amount;
|
|
404
|
+
usage.set(id, next);
|
|
405
|
+
return next;
|
|
406
|
+
},
|
|
407
|
+
async getTeam(teamId) {
|
|
408
|
+
var _a2;
|
|
409
|
+
return (_a2 = teams.get(teamId)) != null ? _a2 : null;
|
|
410
|
+
},
|
|
411
|
+
async upsertTeam(team) {
|
|
412
|
+
teams.set(
|
|
413
|
+
team.teamId,
|
|
414
|
+
team
|
|
415
|
+
);
|
|
416
|
+
return team;
|
|
417
|
+
},
|
|
418
|
+
async recordBillingEvent(event) {
|
|
419
|
+
var _a2;
|
|
420
|
+
const current = (_a2 = billingEvents.get(event.userId)) != null ? _a2 : [];
|
|
421
|
+
current.unshift(event);
|
|
422
|
+
billingEvents.set(
|
|
423
|
+
event.userId,
|
|
424
|
+
current
|
|
425
|
+
);
|
|
426
|
+
return event;
|
|
427
|
+
},
|
|
428
|
+
async getBillingEvents(userId) {
|
|
429
|
+
var _a2;
|
|
430
|
+
return (_a2 = billingEvents.get(userId)) != null ? _a2 : [];
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
// src/lib/billing/adapters/prisma/index.ts
|
|
435
|
+
function createPrismaBillingAdapter(prisma) {
|
|
436
|
+
return {
|
|
437
|
+
async getSubscription(userId) {
|
|
438
|
+
return prisma.subscription.findUnique({
|
|
439
|
+
where: { userId }
|
|
440
|
+
});
|
|
441
|
+
},
|
|
442
|
+
async upsertSubscription(record) {
|
|
443
|
+
return prisma.subscription.upsert({
|
|
444
|
+
where: {
|
|
445
|
+
userId: record.userId
|
|
446
|
+
},
|
|
447
|
+
create: record,
|
|
448
|
+
update: record
|
|
449
|
+
});
|
|
450
|
+
},
|
|
451
|
+
async getUsage(userId, key, period) {
|
|
452
|
+
var _a2;
|
|
453
|
+
const usage2 = await prisma.usageEvent.findUnique({
|
|
454
|
+
where: {
|
|
455
|
+
userId_key_period: {
|
|
456
|
+
userId,
|
|
457
|
+
key,
|
|
458
|
+
period
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
return (_a2 = usage2 == null ? void 0 : usage2.count) != null ? _a2 : 0;
|
|
463
|
+
},
|
|
464
|
+
async incrementUsage(userId, key, period, amount) {
|
|
465
|
+
const usage2 = await prisma.usageEvent.upsert({
|
|
466
|
+
where: {
|
|
467
|
+
userId_key_period: {
|
|
468
|
+
userId,
|
|
469
|
+
key,
|
|
470
|
+
period
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
create: {
|
|
474
|
+
userId,
|
|
475
|
+
key,
|
|
476
|
+
period,
|
|
477
|
+
count: amount
|
|
478
|
+
},
|
|
479
|
+
update: {
|
|
480
|
+
count: {
|
|
481
|
+
increment: amount
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
return usage2.count;
|
|
486
|
+
},
|
|
487
|
+
async getTeam(teamId) {
|
|
488
|
+
return prisma.team.findUnique({
|
|
489
|
+
where: { teamId }
|
|
490
|
+
});
|
|
491
|
+
},
|
|
492
|
+
async upsertTeam(team) {
|
|
493
|
+
return prisma.team.upsert({
|
|
494
|
+
where: {
|
|
495
|
+
teamId: team.teamId
|
|
496
|
+
},
|
|
497
|
+
create: team,
|
|
498
|
+
update: team
|
|
499
|
+
});
|
|
500
|
+
},
|
|
501
|
+
async recordBillingEvent(event) {
|
|
502
|
+
return prisma.billingEvent.create({
|
|
503
|
+
data: event
|
|
504
|
+
});
|
|
505
|
+
},
|
|
506
|
+
async getBillingEvents(userId) {
|
|
507
|
+
return prisma.billingEvent.findMany({
|
|
508
|
+
where: { userId },
|
|
509
|
+
orderBy: {
|
|
510
|
+
createdAt: "desc"
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// src/lib/billing/subscriptions.ts
|
|
518
|
+
var adapter = memoryBillingAdapter;
|
|
519
|
+
function setBillingAdapter(nextAdapter) {
|
|
520
|
+
adapter = nextAdapter;
|
|
521
|
+
}
|
|
522
|
+
async function getSubscription(userId) {
|
|
523
|
+
var _a2;
|
|
524
|
+
return (_a2 = await adapter.getSubscription(
|
|
525
|
+
userId
|
|
526
|
+
)) != null ? _a2 : {
|
|
527
|
+
userId,
|
|
528
|
+
plan: "free",
|
|
529
|
+
status: "none"
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
async function upsertSubscription(record) {
|
|
533
|
+
return adapter.upsertSubscription(
|
|
534
|
+
record
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
function isSubscriptionActive(subscription) {
|
|
538
|
+
return subscription.status === "active" || subscription.status === "trialing";
|
|
539
|
+
}
|
|
540
|
+
async function requireSubscription(userId, minimumPlan = "starter") {
|
|
541
|
+
const subscription = await getSubscription(userId);
|
|
542
|
+
if (!isSubscriptionActive(subscription)) {
|
|
543
|
+
throw new Error(
|
|
544
|
+
"Active subscription required"
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
const order = [
|
|
548
|
+
"free",
|
|
549
|
+
"starter",
|
|
550
|
+
"pro",
|
|
551
|
+
"business"
|
|
552
|
+
];
|
|
553
|
+
if (order.indexOf(subscription.plan) < order.indexOf(minimumPlan)) {
|
|
554
|
+
throw new Error(
|
|
555
|
+
`${minimumPlan} plan required`
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
return subscription;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// src/lib/billing/entitlements.ts
|
|
562
|
+
async function hasFeature(userId, feature) {
|
|
563
|
+
const subscription = await getSubscription(userId);
|
|
564
|
+
if (!isSubscriptionActive(subscription)) {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
return plans[subscription.plan].features.includes(feature);
|
|
568
|
+
}
|
|
569
|
+
async function requireFeature(userId, feature) {
|
|
570
|
+
const allowed = await hasFeature(userId, feature);
|
|
571
|
+
if (!allowed) {
|
|
572
|
+
throw new Error(
|
|
573
|
+
`Feature not available: ${feature}`
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// src/lib/billing/usage.ts
|
|
580
|
+
var adapter2 = memoryBillingAdapter;
|
|
581
|
+
function setUsageAdapter(nextAdapter) {
|
|
582
|
+
adapter2 = nextAdapter;
|
|
583
|
+
}
|
|
584
|
+
function currentPeriod() {
|
|
585
|
+
const now = /* @__PURE__ */ new Date();
|
|
586
|
+
return `${now.getUTCFullYear()}-${String(
|
|
587
|
+
now.getUTCMonth() + 1
|
|
588
|
+
).padStart(2, "0")}`;
|
|
589
|
+
}
|
|
590
|
+
async function getUsage(userId, key) {
|
|
591
|
+
return adapter2.getUsage(
|
|
592
|
+
userId,
|
|
593
|
+
key,
|
|
594
|
+
currentPeriod()
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
async function getUsageLimit(userId) {
|
|
598
|
+
const subscription = await getSubscription(userId);
|
|
599
|
+
return plans[subscription.plan].monthlyLimit;
|
|
600
|
+
}
|
|
601
|
+
async function canUse(userId, key) {
|
|
602
|
+
const [used, limit] = await Promise.all([
|
|
603
|
+
getUsage(userId, key),
|
|
604
|
+
getUsageLimit(userId)
|
|
605
|
+
]);
|
|
606
|
+
return {
|
|
607
|
+
allowed: used < limit,
|
|
608
|
+
used,
|
|
609
|
+
limit,
|
|
610
|
+
remaining: Math.max(
|
|
611
|
+
0,
|
|
612
|
+
limit - used
|
|
613
|
+
)
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
async function incrementUsage(userId, key, amount = 1) {
|
|
617
|
+
return adapter2.incrementUsage(
|
|
618
|
+
userId,
|
|
619
|
+
key,
|
|
620
|
+
currentPeriod(),
|
|
621
|
+
amount
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
async function requireUsage(userId, key) {
|
|
625
|
+
const usage2 = await canUse(userId, key);
|
|
626
|
+
if (!usage2.allowed) {
|
|
627
|
+
throw new Error(
|
|
628
|
+
"Usage limit reached"
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
return usage2;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/lib/billing/webhook-sync.ts
|
|
635
|
+
async function syncPaddleEvent(event) {
|
|
636
|
+
var _a2, _b, _c, _d, _e;
|
|
637
|
+
const data = event.data;
|
|
638
|
+
const userId = (_a2 = data == null ? void 0 : data.custom_data) == null ? void 0 : _a2.userId;
|
|
639
|
+
if (!userId) {
|
|
640
|
+
return {
|
|
641
|
+
synced: false,
|
|
642
|
+
reason: "missing userId in custom_data"
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
const plan = (_c = (_b = data == null ? void 0 : data.custom_data) == null ? void 0 : _b.plan) != null ? _c : "starter";
|
|
646
|
+
if ((_d = event.event_type) == null ? void 0 : _d.startsWith(
|
|
647
|
+
"subscription."
|
|
648
|
+
)) {
|
|
649
|
+
await upsertSubscription({
|
|
650
|
+
userId,
|
|
651
|
+
plan,
|
|
652
|
+
status: (data == null ? void 0 : data.status) === "active" ? "active" : (data == null ? void 0 : data.status) === "trialing" ? "trialing" : (data == null ? void 0 : data.status) === "paused" ? "paused" : (data == null ? void 0 : data.status) === "canceled" ? "canceled" : "none",
|
|
653
|
+
paddleCustomerId: data == null ? void 0 : data.customer_id,
|
|
654
|
+
paddleSubscriptionId: data == null ? void 0 : data.id,
|
|
655
|
+
currentPeriodEnd: (_e = data == null ? void 0 : data.current_billing_period) == null ? void 0 : _e.ends_at
|
|
656
|
+
});
|
|
657
|
+
return {
|
|
658
|
+
synced: true,
|
|
659
|
+
type: "subscription"
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
return {
|
|
663
|
+
synced: false,
|
|
664
|
+
reason: "unhandled event type"
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// src/lib/billing/protection.ts
|
|
669
|
+
async function protectPlan(userId, plan) {
|
|
670
|
+
return requireSubscription(
|
|
671
|
+
userId,
|
|
672
|
+
plan
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
async function protectFeature(userId, feature) {
|
|
676
|
+
return requireFeature(
|
|
677
|
+
userId,
|
|
678
|
+
feature
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
async function protectUsage(userId, usageKey2, amount = 1) {
|
|
682
|
+
await requireUsage(
|
|
683
|
+
userId,
|
|
684
|
+
usageKey2
|
|
685
|
+
);
|
|
686
|
+
await incrementUsage(
|
|
687
|
+
userId,
|
|
688
|
+
usageKey2,
|
|
689
|
+
amount
|
|
690
|
+
);
|
|
691
|
+
return {
|
|
692
|
+
allowed: true
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// src/lib/billing/demo-seed.ts
|
|
697
|
+
var seeded = false;
|
|
698
|
+
async function seedDemoBilling() {
|
|
699
|
+
if (seeded) {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
await upsertSubscription({
|
|
703
|
+
userId: "demo-user-1",
|
|
704
|
+
plan: "pro",
|
|
705
|
+
status: "active"
|
|
706
|
+
});
|
|
707
|
+
seeded = true;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// src/lib/paddle/portal.ts
|
|
711
|
+
async function createCustomerPortalSession({
|
|
712
|
+
customerId,
|
|
713
|
+
returnUrl
|
|
714
|
+
}) {
|
|
715
|
+
var _a2, _b;
|
|
716
|
+
const apiKey = process.env.PADDLE_API_KEY;
|
|
717
|
+
if (!apiKey) {
|
|
718
|
+
throw new Error("Missing PADDLE_API_KEY");
|
|
719
|
+
}
|
|
720
|
+
const response = await fetch(
|
|
721
|
+
"https://api.paddle.com/customer-portal-sessions",
|
|
722
|
+
{
|
|
723
|
+
method: "POST",
|
|
724
|
+
headers: {
|
|
725
|
+
Authorization: `Bearer ${apiKey}`,
|
|
726
|
+
"Content-Type": "application/json"
|
|
727
|
+
},
|
|
728
|
+
body: JSON.stringify({
|
|
729
|
+
customer_id: customerId,
|
|
730
|
+
urls: returnUrl ? { return_url: returnUrl } : void 0
|
|
731
|
+
})
|
|
732
|
+
}
|
|
733
|
+
);
|
|
734
|
+
if (!response.ok) {
|
|
735
|
+
throw new Error(
|
|
736
|
+
"Failed to create Paddle customer portal session"
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
const data = await response.json();
|
|
740
|
+
const url = (_b = (_a2 = data.data) == null ? void 0 : _a2.urls) == null ? void 0 : _b.general;
|
|
741
|
+
if (!url) {
|
|
742
|
+
throw new Error(
|
|
743
|
+
"Paddle portal URL missing from response"
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
return { url };
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// src/lib/paddle/api.ts
|
|
750
|
+
var _a;
|
|
751
|
+
var PADDLE_API_BASE = (_a = process.env.PADDLE_API_BASE) != null ? _a : "https://api.paddle.com";
|
|
752
|
+
async function paddleApi(path, options = {}) {
|
|
753
|
+
const apiKey = process.env.PADDLE_API_KEY;
|
|
754
|
+
if (!apiKey) {
|
|
755
|
+
throw new Error("Missing PADDLE_API_KEY");
|
|
756
|
+
}
|
|
757
|
+
const response = await fetch(
|
|
758
|
+
`${PADDLE_API_BASE}${path}`,
|
|
759
|
+
__spreadProps(__spreadValues({}, options), {
|
|
760
|
+
headers: __spreadValues({
|
|
761
|
+
Authorization: `Bearer ${apiKey}`,
|
|
762
|
+
"Content-Type": "application/json"
|
|
763
|
+
}, options.headers)
|
|
764
|
+
})
|
|
765
|
+
);
|
|
766
|
+
if (!response.ok) {
|
|
767
|
+
const text = await response.text();
|
|
768
|
+
throw new Error(
|
|
769
|
+
`Paddle API failed: ${response.status} ${text}`
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
return response.json();
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// src/lib/paddle/subscriptions.ts
|
|
776
|
+
async function fetchPaddleSubscription(subscriptionId) {
|
|
777
|
+
return paddleApi(
|
|
778
|
+
`/subscriptions/${subscriptionId}`
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
async function fetchPaddleSubscriptionsForCustomer(customerId) {
|
|
782
|
+
const params = new URLSearchParams({
|
|
783
|
+
customer_id: customerId
|
|
784
|
+
});
|
|
785
|
+
return paddleApi(
|
|
786
|
+
`/subscriptions?${params.toString()}`
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// src/lib/billing/refresh.ts
|
|
791
|
+
function mapStatus(status) {
|
|
792
|
+
if (status === "active") return "active";
|
|
793
|
+
if (status === "trialing") return "trialing";
|
|
794
|
+
if (status === "paused") return "paused";
|
|
795
|
+
if (status === "canceled") return "canceled";
|
|
796
|
+
if (status === "past_due") return "past_due";
|
|
797
|
+
return "none";
|
|
798
|
+
}
|
|
799
|
+
async function refreshSubscriptionFromPaddle(subscriptionId) {
|
|
800
|
+
var _a2, _b, _c, _d;
|
|
801
|
+
const response = await fetchPaddleSubscription(
|
|
802
|
+
subscriptionId
|
|
803
|
+
);
|
|
804
|
+
const sub = response.data;
|
|
805
|
+
const userId = (_a2 = sub == null ? void 0 : sub.custom_data) == null ? void 0 : _a2.userId;
|
|
806
|
+
if (!sub || !userId) {
|
|
807
|
+
throw new Error(
|
|
808
|
+
"Paddle subscription missing custom_data.userId"
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
const plan = (_c = (_b = sub.custom_data) == null ? void 0 : _b.plan) != null ? _c : "starter";
|
|
812
|
+
return upsertSubscription({
|
|
813
|
+
userId,
|
|
814
|
+
plan,
|
|
815
|
+
status: mapStatus(sub.status),
|
|
816
|
+
paddleCustomerId: sub.customer_id,
|
|
817
|
+
paddleSubscriptionId: sub.id,
|
|
818
|
+
currentPeriodEnd: (_d = sub.current_billing_period) == null ? void 0 : _d.ends_at
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// src/lib/paddle/customers.ts
|
|
823
|
+
async function fetchPaddleCustomer(customerId) {
|
|
824
|
+
return paddleApi(
|
|
825
|
+
`/customers/${customerId}`
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
async function findPaddleCustomersByEmail(email) {
|
|
829
|
+
const params = new URLSearchParams({
|
|
830
|
+
email
|
|
831
|
+
});
|
|
832
|
+
return paddleApi(
|
|
833
|
+
`/customers?${params.toString()}`
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
async function findFirstPaddleCustomerByEmail(email) {
|
|
837
|
+
var _a2, _b;
|
|
838
|
+
const response = await findPaddleCustomersByEmail(email);
|
|
839
|
+
return (_b = (_a2 = response.data) == null ? void 0 : _a2[0]) != null ? _b : null;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// src/lib/billing/customer-repair.ts
|
|
843
|
+
async function repairSubscriptionByEmail(email) {
|
|
844
|
+
var _a2;
|
|
845
|
+
const customer = await findFirstPaddleCustomerByEmail(email);
|
|
846
|
+
if (!customer) {
|
|
847
|
+
throw new Error(
|
|
848
|
+
"No Paddle customer found for email"
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
const subscriptions2 = await fetchPaddleSubscriptionsForCustomer(
|
|
852
|
+
customer.id
|
|
853
|
+
);
|
|
854
|
+
const activeSubscription = (_a2 = subscriptions2.data) == null ? void 0 : _a2.find(
|
|
855
|
+
(sub) => ["active", "trialing", "past_due"].includes(
|
|
856
|
+
sub.status
|
|
857
|
+
)
|
|
858
|
+
);
|
|
859
|
+
if (!activeSubscription) {
|
|
860
|
+
throw new Error(
|
|
861
|
+
"No active Paddle subscription found for customer"
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
const repaired = await refreshSubscriptionFromPaddle(
|
|
865
|
+
activeSubscription.id
|
|
866
|
+
);
|
|
867
|
+
return {
|
|
868
|
+
customer,
|
|
869
|
+
subscription: repaired
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// src/lib/billing/teams.ts
|
|
874
|
+
var adapter3 = memoryBillingAdapter;
|
|
875
|
+
function setTeamAdapter(nextAdapter) {
|
|
876
|
+
adapter3 = nextAdapter;
|
|
877
|
+
}
|
|
878
|
+
function requireTeamAdapter() {
|
|
879
|
+
if (!adapter3.getTeam || !adapter3.upsertTeam) {
|
|
880
|
+
throw new Error(
|
|
881
|
+
"Billing adapter does not support team operations"
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
return adapter3;
|
|
885
|
+
}
|
|
886
|
+
async function createTeam({
|
|
887
|
+
teamId,
|
|
888
|
+
name,
|
|
889
|
+
ownerId,
|
|
890
|
+
plan = "free"
|
|
891
|
+
}) {
|
|
892
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
893
|
+
const team = {
|
|
894
|
+
teamId,
|
|
895
|
+
name,
|
|
896
|
+
ownerId,
|
|
897
|
+
plan,
|
|
898
|
+
members: [
|
|
899
|
+
{
|
|
900
|
+
userId: ownerId,
|
|
901
|
+
role: "owner",
|
|
902
|
+
joinedAt: now
|
|
903
|
+
}
|
|
904
|
+
]
|
|
905
|
+
};
|
|
906
|
+
return requireTeamAdapter().upsertTeam(team);
|
|
907
|
+
}
|
|
908
|
+
async function getTeam(teamId) {
|
|
909
|
+
return requireTeamAdapter().getTeam(teamId);
|
|
910
|
+
}
|
|
911
|
+
async function updateTeamPlan(teamId, plan) {
|
|
912
|
+
const team = await getTeam(teamId);
|
|
913
|
+
if (!team) {
|
|
914
|
+
throw new Error("Team not found");
|
|
915
|
+
}
|
|
916
|
+
team.plan = plan;
|
|
917
|
+
return requireTeamAdapter().upsertTeam(team);
|
|
918
|
+
}
|
|
919
|
+
async function getSeatUsage(teamId) {
|
|
920
|
+
const team = await getTeam(teamId);
|
|
921
|
+
if (!team) {
|
|
922
|
+
throw new Error("Team not found");
|
|
923
|
+
}
|
|
924
|
+
const limit = getPlanSeatLimit(team.plan);
|
|
925
|
+
const used = team.members.length;
|
|
926
|
+
return {
|
|
927
|
+
used,
|
|
928
|
+
limit,
|
|
929
|
+
remaining: Math.max(0, limit - used),
|
|
930
|
+
allowed: used < limit
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
async function canInviteTeamMember(teamId) {
|
|
934
|
+
const usage2 = await getSeatUsage(teamId);
|
|
935
|
+
return usage2.allowed;
|
|
936
|
+
}
|
|
937
|
+
async function addTeamMember({
|
|
938
|
+
teamId,
|
|
939
|
+
userId,
|
|
940
|
+
email,
|
|
941
|
+
role = "member"
|
|
942
|
+
}) {
|
|
943
|
+
const team = await getTeam(teamId);
|
|
944
|
+
if (!team) {
|
|
945
|
+
throw new Error("Team not found");
|
|
946
|
+
}
|
|
947
|
+
const alreadyMember = team.members.some(
|
|
948
|
+
(member) => member.userId === userId
|
|
949
|
+
);
|
|
950
|
+
if (alreadyMember) {
|
|
951
|
+
return team;
|
|
952
|
+
}
|
|
953
|
+
const usage2 = await getSeatUsage(teamId);
|
|
954
|
+
if (!usage2.allowed) {
|
|
955
|
+
throw new Error(
|
|
956
|
+
"Seat limit reached"
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
const nextTeam = __spreadProps(__spreadValues({}, team), {
|
|
960
|
+
members: [
|
|
961
|
+
...team.members,
|
|
962
|
+
{
|
|
963
|
+
userId,
|
|
964
|
+
email,
|
|
965
|
+
role,
|
|
966
|
+
joinedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
967
|
+
}
|
|
968
|
+
]
|
|
969
|
+
});
|
|
970
|
+
return requireTeamAdapter().upsertTeam(nextTeam);
|
|
971
|
+
}
|
|
972
|
+
async function removeTeamMember({
|
|
973
|
+
teamId,
|
|
974
|
+
userId
|
|
975
|
+
}) {
|
|
976
|
+
const team = await getTeam(teamId);
|
|
977
|
+
if (!team) {
|
|
978
|
+
throw new Error("Team not found");
|
|
979
|
+
}
|
|
980
|
+
if (team.ownerId === userId) {
|
|
981
|
+
throw new Error(
|
|
982
|
+
"Cannot remove team owner"
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
const nextTeam = __spreadProps(__spreadValues({}, team), {
|
|
986
|
+
members: team.members.filter(
|
|
987
|
+
(member) => member.userId !== userId
|
|
988
|
+
)
|
|
989
|
+
});
|
|
990
|
+
return requireTeamAdapter().upsertTeam(nextTeam);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// src/lib/billing/events.ts
|
|
994
|
+
var adapter4 = memoryBillingAdapter;
|
|
995
|
+
function setBillingEventAdapter(nextAdapter) {
|
|
996
|
+
adapter4 = nextAdapter;
|
|
997
|
+
}
|
|
998
|
+
function requireEventAdapter() {
|
|
999
|
+
if (!adapter4.recordBillingEvent || !adapter4.getBillingEvents) {
|
|
1000
|
+
throw new Error(
|
|
1001
|
+
"Billing adapter does not support event operations"
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
return adapter4;
|
|
1005
|
+
}
|
|
1006
|
+
async function recordBillingEvent(event) {
|
|
1007
|
+
return requireEventAdapter().recordBillingEvent(event);
|
|
1008
|
+
}
|
|
1009
|
+
async function getBillingEvents(userId) {
|
|
1010
|
+
return requireEventAdapter().getBillingEvents(userId);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// src/lib/billing/configure.ts
|
|
1014
|
+
function configureBilling({
|
|
1015
|
+
adapter: adapter5
|
|
1016
|
+
}) {
|
|
1017
|
+
setBillingAdapter(adapter5);
|
|
1018
|
+
setUsageAdapter(adapter5);
|
|
1019
|
+
setTeamAdapter(adapter5);
|
|
1020
|
+
setBillingEventAdapter(adapter5);
|
|
1021
|
+
return {
|
|
1022
|
+
adapterConfigured: true
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
export {
|
|
1026
|
+
BillingHistory,
|
|
1027
|
+
CustomerPortalButton,
|
|
1028
|
+
SubscriptionCard,
|
|
1029
|
+
SubscriptionGate,
|
|
1030
|
+
TrialBanner,
|
|
1031
|
+
UpgradeModal,
|
|
1032
|
+
UsageMeter,
|
|
1033
|
+
addTeamMember,
|
|
1034
|
+
canInviteTeamMember,
|
|
1035
|
+
canUse,
|
|
1036
|
+
configureBilling,
|
|
1037
|
+
createCustomerPortalSession,
|
|
1038
|
+
createPrismaBillingAdapter,
|
|
1039
|
+
createTeam,
|
|
1040
|
+
fetchPaddleCustomer,
|
|
1041
|
+
fetchPaddleSubscription,
|
|
1042
|
+
fetchPaddleSubscriptionsForCustomer,
|
|
1043
|
+
findFirstPaddleCustomerByEmail,
|
|
1044
|
+
findPaddleCustomersByEmail,
|
|
1045
|
+
getBillingEvents,
|
|
1046
|
+
getPaddle,
|
|
1047
|
+
getPlanSeatLimit,
|
|
1048
|
+
getSeatUsage,
|
|
1049
|
+
getSubscription,
|
|
1050
|
+
getTeam,
|
|
1051
|
+
getUsage,
|
|
1052
|
+
getUsageLimit,
|
|
1053
|
+
hasFeature,
|
|
1054
|
+
incrementUsage,
|
|
1055
|
+
isSubscriptionActive,
|
|
1056
|
+
memoryBillingAdapter,
|
|
1057
|
+
openCheckout,
|
|
1058
|
+
paddleApi,
|
|
1059
|
+
plans,
|
|
1060
|
+
protectFeature,
|
|
1061
|
+
protectPlan,
|
|
1062
|
+
protectUsage,
|
|
1063
|
+
recordBillingEvent,
|
|
1064
|
+
refreshSubscriptionFromPaddle,
|
|
1065
|
+
removeTeamMember,
|
|
1066
|
+
repairSubscriptionByEmail,
|
|
1067
|
+
requireFeature,
|
|
1068
|
+
requireSubscription,
|
|
1069
|
+
requireUsage,
|
|
1070
|
+
seedDemoBilling,
|
|
1071
|
+
setBillingAdapter,
|
|
1072
|
+
setBillingEventAdapter,
|
|
1073
|
+
setTeamAdapter,
|
|
1074
|
+
setUsageAdapter,
|
|
1075
|
+
syncPaddleEvent,
|
|
1076
|
+
updateTeamPlan,
|
|
1077
|
+
upsertSubscription,
|
|
1078
|
+
verifyPaddleWebhook
|
|
1079
|
+
};
|