paddle-checkout-accelerator 2.3.0 → 2.5.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 +550 -164
- package/package.json +1 -1
|
@@ -5,134 +5,206 @@ import path from "node:path";
|
|
|
5
5
|
import prompts from "prompts";
|
|
6
6
|
|
|
7
7
|
const cwd = process.cwd();
|
|
8
|
+
const args = new Set(process.argv.slice(2));
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
|
|
10
|
+
const command =
|
|
11
|
+
process.argv[2];
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const force =
|
|
14
|
+
args.has("--force");
|
|
15
|
+
|
|
16
|
+
const interactive =
|
|
17
|
+
args.has("--interactive") ||
|
|
18
|
+
args.has("-i");
|
|
19
|
+
|
|
20
|
+
const minimal =
|
|
21
|
+
args.has("--minimal");
|
|
22
|
+
|
|
23
|
+
function exists(filePath) {
|
|
24
|
+
return fs.existsSync(
|
|
25
|
+
path.join(cwd, filePath)
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function detectAdapter() {
|
|
30
|
+
if (
|
|
31
|
+
exists("prisma/schema.prisma") ||
|
|
32
|
+
exists("src/lib/prisma.ts") ||
|
|
33
|
+
exists("lib/prisma.ts")
|
|
34
|
+
) {
|
|
35
|
+
return "prisma";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return "memory";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function writeFileSafe(filePath, content) {
|
|
42
|
+
const absolutePath =
|
|
43
|
+
path.join(cwd, filePath);
|
|
44
|
+
|
|
45
|
+
const existed =
|
|
46
|
+
fs.existsSync(absolutePath);
|
|
47
|
+
|
|
48
|
+
fs.mkdirSync(
|
|
49
|
+
path.dirname(absolutePath),
|
|
50
|
+
{ recursive: true }
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (existed && !force) {
|
|
54
|
+
console.log(
|
|
55
|
+
`Skipped ${filePath} because it already exists`
|
|
56
|
+
);
|
|
15
57
|
return;
|
|
16
58
|
}
|
|
17
59
|
|
|
18
|
-
fs.writeFileSync(
|
|
19
|
-
|
|
60
|
+
fs.writeFileSync(
|
|
61
|
+
absolutePath,
|
|
62
|
+
content
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
console.log(
|
|
66
|
+
`${existed ? "Updated" : "Created"} ${filePath}`
|
|
67
|
+
);
|
|
20
68
|
}
|
|
21
69
|
|
|
22
70
|
function appendEnvSafe(values) {
|
|
23
|
-
const envPath =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
71
|
+
const envPath =
|
|
72
|
+
path.join(cwd, ".env.local");
|
|
73
|
+
|
|
74
|
+
const content =
|
|
75
|
+
Object.entries(values)
|
|
76
|
+
.map(
|
|
77
|
+
([key, value]) =>
|
|
78
|
+
`${key}=${value ?? ""}`
|
|
79
|
+
)
|
|
80
|
+
.join("\n");
|
|
27
81
|
|
|
28
82
|
if (!fs.existsSync(envPath)) {
|
|
29
|
-
fs.writeFileSync(
|
|
83
|
+
fs.writeFileSync(
|
|
84
|
+
envPath,
|
|
85
|
+
content + "\n"
|
|
86
|
+
);
|
|
87
|
+
|
|
30
88
|
console.log("Created .env.local");
|
|
31
89
|
return;
|
|
32
90
|
}
|
|
33
91
|
|
|
34
|
-
const current =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
92
|
+
const current =
|
|
93
|
+
fs.readFileSync(envPath, "utf8");
|
|
94
|
+
|
|
95
|
+
const lines =
|
|
96
|
+
content
|
|
97
|
+
.split("\n")
|
|
98
|
+
.filter((line) => {
|
|
99
|
+
const key =
|
|
100
|
+
line.split("=")[0];
|
|
101
|
+
|
|
102
|
+
return !current.includes(
|
|
103
|
+
`${key}=`
|
|
104
|
+
);
|
|
105
|
+
});
|
|
41
106
|
|
|
42
107
|
if (lines.length) {
|
|
43
|
-
fs.appendFileSync(
|
|
108
|
+
fs.appendFileSync(
|
|
109
|
+
envPath,
|
|
110
|
+
"\n" + lines.join("\n") + "\n"
|
|
111
|
+
);
|
|
112
|
+
|
|
44
113
|
console.log("Updated .env.local");
|
|
45
114
|
} else {
|
|
46
|
-
console.log(
|
|
115
|
+
console.log(
|
|
116
|
+
"Skipped .env.local because Paddle keys already exist"
|
|
117
|
+
);
|
|
47
118
|
}
|
|
48
119
|
}
|
|
49
120
|
|
|
50
|
-
async function
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
121
|
+
async function getConfig() {
|
|
122
|
+
const detectedAdapter =
|
|
123
|
+
detectAdapter();
|
|
124
|
+
|
|
125
|
+
if (!interactive) {
|
|
126
|
+
return {
|
|
127
|
+
adapter: detectedAdapter,
|
|
128
|
+
routes: true,
|
|
129
|
+
env: true,
|
|
130
|
+
schema:
|
|
131
|
+
detectedAdapter === "prisma",
|
|
132
|
+
ui: !minimal,
|
|
133
|
+
};
|
|
61
134
|
}
|
|
62
135
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
136
|
+
const answers =
|
|
137
|
+
await prompts([
|
|
138
|
+
{
|
|
139
|
+
type: "select",
|
|
140
|
+
name: "adapter",
|
|
141
|
+
message:
|
|
142
|
+
"Which storage adapter do you want?",
|
|
143
|
+
choices: [
|
|
144
|
+
{
|
|
145
|
+
title: "Auto-detect",
|
|
146
|
+
value: detectedAdapter,
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
title: "Memory demo adapter",
|
|
150
|
+
value: "memory",
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
title: "Prisma production adapter",
|
|
154
|
+
value: "prisma",
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
initial: 0,
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
type: "toggle",
|
|
161
|
+
name: "routes",
|
|
162
|
+
message:
|
|
163
|
+
"Generate Paddle API routes?",
|
|
164
|
+
initial: true,
|
|
165
|
+
active: "yes",
|
|
166
|
+
inactive: "no",
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
type: "toggle",
|
|
170
|
+
name: "env",
|
|
171
|
+
message:
|
|
172
|
+
"Create/update .env.local placeholders?",
|
|
173
|
+
initial: true,
|
|
174
|
+
active: "yes",
|
|
175
|
+
inactive: "no",
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
type: "toggle",
|
|
179
|
+
name: "schema",
|
|
180
|
+
message:
|
|
181
|
+
"Generate Prisma schema example?",
|
|
182
|
+
initial:
|
|
183
|
+
detectedAdapter === "prisma",
|
|
184
|
+
active: "yes",
|
|
185
|
+
inactive: "no",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
type: "toggle",
|
|
189
|
+
name: "ui",
|
|
190
|
+
message:
|
|
191
|
+
"Generate billing UI pages?",
|
|
192
|
+
initial: true,
|
|
193
|
+
active: "yes",
|
|
194
|
+
inactive: "no",
|
|
195
|
+
},
|
|
196
|
+
]);
|
|
112
197
|
|
|
113
198
|
if (!answers.adapter) {
|
|
114
199
|
console.log("Install cancelled.");
|
|
115
200
|
process.exit(0);
|
|
116
201
|
}
|
|
117
202
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
"src/lib/billing.ts",
|
|
121
|
-
`import {
|
|
122
|
-
configureBilling,
|
|
123
|
-
memoryBillingAdapter,
|
|
124
|
-
} from "paddle-checkout-accelerator";
|
|
125
|
-
|
|
126
|
-
export const billing =
|
|
127
|
-
configureBilling({
|
|
128
|
-
adapter: memoryBillingAdapter,
|
|
129
|
-
});
|
|
130
|
-
`,
|
|
131
|
-
answers.force
|
|
132
|
-
);
|
|
133
|
-
}
|
|
203
|
+
return answers;
|
|
204
|
+
}
|
|
134
205
|
|
|
135
|
-
|
|
206
|
+
function writeBillingFile(adapter) {
|
|
207
|
+
if (adapter === "prisma") {
|
|
136
208
|
writeFileSafe(
|
|
137
209
|
"src/lib/billing.ts",
|
|
138
210
|
`import {
|
|
@@ -149,8 +221,7 @@ export const billing =
|
|
|
149
221
|
configureBilling({
|
|
150
222
|
adapter,
|
|
151
223
|
});
|
|
152
|
-
|
|
153
|
-
answers.force
|
|
224
|
+
`
|
|
154
225
|
);
|
|
155
226
|
|
|
156
227
|
writeFileSafe(
|
|
@@ -169,14 +240,31 @@ export const prisma =
|
|
|
169
240
|
if (process.env.NODE_ENV !== "production") {
|
|
170
241
|
globalForPrisma.prisma = prisma;
|
|
171
242
|
}
|
|
172
|
-
|
|
173
|
-
answers.force
|
|
243
|
+
`
|
|
174
244
|
);
|
|
175
245
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
writeFileSafe(
|
|
250
|
+
"src/lib/billing.ts",
|
|
251
|
+
`import {
|
|
252
|
+
configureBilling,
|
|
253
|
+
memoryBillingAdapter,
|
|
254
|
+
} from "paddle-checkout-accelerator";
|
|
255
|
+
|
|
256
|
+
export const billing =
|
|
257
|
+
configureBilling({
|
|
258
|
+
adapter: memoryBillingAdapter,
|
|
259
|
+
});
|
|
260
|
+
`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function writePrismaSchema() {
|
|
265
|
+
writeFileSafe(
|
|
266
|
+
"prisma/paddle-accelerator.schema.prisma",
|
|
267
|
+
`model Subscription {
|
|
180
268
|
userId String @id
|
|
181
269
|
plan String
|
|
182
270
|
status String
|
|
@@ -210,16 +298,206 @@ model BillingEvent {
|
|
|
210
298
|
createdAt DateTime
|
|
211
299
|
metadata Json?
|
|
212
300
|
}
|
|
213
|
-
|
|
214
|
-
|
|
301
|
+
`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function writeUiPages() {
|
|
306
|
+
writeFileSafe(
|
|
307
|
+
"src/app/billing/pricing/page.tsx",
|
|
308
|
+
`import {
|
|
309
|
+
PricingTable,
|
|
310
|
+
} from "paddle-checkout-accelerator";
|
|
311
|
+
|
|
312
|
+
export default function BillingPricingPage() {
|
|
313
|
+
return (
|
|
314
|
+
<main className="mx-auto max-w-5xl p-10">
|
|
315
|
+
<h1 className="text-4xl font-bold">
|
|
316
|
+
Choose your plan
|
|
317
|
+
</h1>
|
|
318
|
+
|
|
319
|
+
<p className="mt-3 text-gray-500">
|
|
320
|
+
Upgrade your account with Paddle billing.
|
|
321
|
+
</p>
|
|
322
|
+
|
|
323
|
+
<div className="mt-8">
|
|
324
|
+
<PricingTable />
|
|
325
|
+
</div>
|
|
326
|
+
</main>
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
`
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
writeFileSafe(
|
|
333
|
+
"src/app/billing/dashboard/page.tsx",
|
|
334
|
+
`import {
|
|
335
|
+
BillingHistory,
|
|
336
|
+
CustomerPortalButton,
|
|
337
|
+
SubscriptionCard,
|
|
338
|
+
TrialBanner,
|
|
339
|
+
UsageMeter,
|
|
340
|
+
} from "paddle-checkout-accelerator";
|
|
341
|
+
|
|
342
|
+
export default function BillingDashboardPage() {
|
|
343
|
+
return (
|
|
344
|
+
<main className="mx-auto max-w-5xl space-y-6 p-10">
|
|
345
|
+
<TrialBanner daysRemaining={5} />
|
|
346
|
+
|
|
347
|
+
<SubscriptionCard
|
|
348
|
+
plan="Pro"
|
|
349
|
+
status="Active"
|
|
350
|
+
/>
|
|
351
|
+
|
|
352
|
+
<UsageMeter
|
|
353
|
+
current={37}
|
|
354
|
+
limit={50}
|
|
355
|
+
/>
|
|
356
|
+
|
|
357
|
+
<CustomerPortalButton />
|
|
358
|
+
|
|
359
|
+
<BillingHistory />
|
|
360
|
+
</main>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
`
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
writeFileSafe(
|
|
367
|
+
"src/app/billing/customer-repair/page.tsx",
|
|
368
|
+
`"use client";
|
|
369
|
+
|
|
370
|
+
import { useState } from "react";
|
|
371
|
+
|
|
372
|
+
export default function CustomerRepairPage() {
|
|
373
|
+
const [email, setEmail] =
|
|
374
|
+
useState("");
|
|
375
|
+
const [result, setResult] =
|
|
376
|
+
useState("");
|
|
377
|
+
|
|
378
|
+
async function repair() {
|
|
379
|
+
const response =
|
|
380
|
+
await fetch(
|
|
381
|
+
"/api/paddle/repair-by-email",
|
|
382
|
+
{
|
|
383
|
+
method: "POST",
|
|
384
|
+
headers: {
|
|
385
|
+
"Content-Type":
|
|
386
|
+
"application/json",
|
|
387
|
+
},
|
|
388
|
+
body: JSON.stringify({
|
|
389
|
+
email,
|
|
390
|
+
}),
|
|
391
|
+
}
|
|
215
392
|
);
|
|
216
|
-
|
|
393
|
+
|
|
394
|
+
const data =
|
|
395
|
+
await response.json();
|
|
396
|
+
|
|
397
|
+
setResult(
|
|
398
|
+
JSON.stringify(data, null, 2)
|
|
399
|
+
);
|
|
217
400
|
}
|
|
218
401
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
402
|
+
return (
|
|
403
|
+
<main className="mx-auto max-w-3xl space-y-6 p-10">
|
|
404
|
+
<div>
|
|
405
|
+
<h1 className="text-4xl font-bold">
|
|
406
|
+
Repair Customer Access
|
|
407
|
+
</h1>
|
|
408
|
+
|
|
409
|
+
<p className="mt-3 text-gray-500">
|
|
410
|
+
Use this when a customer paid but their account did not unlock.
|
|
411
|
+
</p>
|
|
412
|
+
</div>
|
|
413
|
+
|
|
414
|
+
<input
|
|
415
|
+
value={email}
|
|
416
|
+
onChange={(event) =>
|
|
417
|
+
setEmail(event.target.value)
|
|
418
|
+
}
|
|
419
|
+
placeholder="customer@example.com"
|
|
420
|
+
className="w-full rounded-xl border p-4"
|
|
421
|
+
/>
|
|
422
|
+
|
|
423
|
+
<button
|
|
424
|
+
onClick={repair}
|
|
425
|
+
className="rounded-xl bg-black px-6 py-3 text-white"
|
|
426
|
+
>
|
|
427
|
+
Repair Subscription
|
|
428
|
+
</button>
|
|
429
|
+
|
|
430
|
+
{result && (
|
|
431
|
+
<pre className="overflow-auto rounded-xl border bg-slate-50 p-5 text-sm">
|
|
432
|
+
{result}
|
|
433
|
+
</pre>
|
|
434
|
+
)}
|
|
435
|
+
</main>
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
`
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
writeFileSafe(
|
|
442
|
+
"src/app/billing/protected-api/page.tsx",
|
|
443
|
+
`"use client";
|
|
444
|
+
|
|
445
|
+
import { useState } from "react";
|
|
446
|
+
|
|
447
|
+
export default function ProtectedApiPage() {
|
|
448
|
+
const [result, setResult] =
|
|
449
|
+
useState("");
|
|
450
|
+
|
|
451
|
+
async function run() {
|
|
452
|
+
const response =
|
|
453
|
+
await fetch(
|
|
454
|
+
"/api/demo/protected-generation",
|
|
455
|
+
{
|
|
456
|
+
method: "POST",
|
|
457
|
+
}
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
const data =
|
|
461
|
+
await response.json();
|
|
462
|
+
|
|
463
|
+
setResult(
|
|
464
|
+
JSON.stringify(data, null, 2)
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return (
|
|
469
|
+
<main className="mx-auto max-w-3xl space-y-6 p-10">
|
|
470
|
+
<h1 className="text-4xl font-bold">
|
|
471
|
+
Protected API Demo
|
|
472
|
+
</h1>
|
|
473
|
+
|
|
474
|
+
<p className="text-gray-500">
|
|
475
|
+
Test paid-route protection, feature checks, and usage limits.
|
|
476
|
+
</p>
|
|
477
|
+
|
|
478
|
+
<button
|
|
479
|
+
onClick={run}
|
|
480
|
+
className="rounded-xl bg-black px-6 py-3 text-white"
|
|
481
|
+
>
|
|
482
|
+
Run Protected Action
|
|
483
|
+
</button>
|
|
484
|
+
|
|
485
|
+
{result && (
|
|
486
|
+
<pre className="overflow-auto rounded-xl border bg-slate-50 p-5 text-sm">
|
|
487
|
+
{result}
|
|
488
|
+
</pre>
|
|
489
|
+
)}
|
|
490
|
+
</main>
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
`
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function writeRoutes() {
|
|
498
|
+
writeFileSafe(
|
|
499
|
+
"src/app/api/paddle/webhook/route.ts",
|
|
500
|
+
`import { NextRequest, NextResponse } from "next/server";
|
|
223
501
|
import {
|
|
224
502
|
syncPaddleEvent,
|
|
225
503
|
verifyPaddleWebhook,
|
|
@@ -230,94 +508,181 @@ export async function POST(request: NextRequest) {
|
|
|
230
508
|
const signature = request.headers.get("paddle-signature");
|
|
231
509
|
|
|
232
510
|
if (!signature) {
|
|
233
|
-
return NextResponse.json(
|
|
511
|
+
return NextResponse.json(
|
|
512
|
+
{ error: "Missing Paddle signature" },
|
|
513
|
+
{ status: 400 }
|
|
514
|
+
);
|
|
234
515
|
}
|
|
235
516
|
|
|
236
517
|
try {
|
|
237
|
-
const event =
|
|
518
|
+
const event =
|
|
519
|
+
await verifyPaddleWebhook(
|
|
520
|
+
rawBody,
|
|
521
|
+
signature
|
|
522
|
+
);
|
|
523
|
+
|
|
238
524
|
await syncPaddleEvent(event);
|
|
239
|
-
|
|
525
|
+
|
|
526
|
+
return NextResponse.json({
|
|
527
|
+
success: true,
|
|
528
|
+
});
|
|
240
529
|
} catch (error) {
|
|
241
530
|
return NextResponse.json(
|
|
242
|
-
{
|
|
531
|
+
{
|
|
532
|
+
error:
|
|
533
|
+
error instanceof Error
|
|
534
|
+
? error.message
|
|
535
|
+
: "Webhook verification failed",
|
|
536
|
+
},
|
|
243
537
|
{ status: 401 }
|
|
244
538
|
);
|
|
245
539
|
}
|
|
246
540
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
);
|
|
541
|
+
`
|
|
542
|
+
);
|
|
250
543
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
import {
|
|
544
|
+
writeFileSafe(
|
|
545
|
+
"src/app/api/paddle/portal-session/route.ts",
|
|
546
|
+
`import { NextRequest, NextResponse } from "next/server";
|
|
547
|
+
import {
|
|
548
|
+
createCustomerPortalSession,
|
|
549
|
+
} from "paddle-checkout-accelerator";
|
|
255
550
|
|
|
256
551
|
export async function POST(request: NextRequest) {
|
|
257
|
-
const body =
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
552
|
+
const body =
|
|
553
|
+
(await request.json()) as {
|
|
554
|
+
customerId?: string;
|
|
555
|
+
returnUrl?: string;
|
|
556
|
+
};
|
|
261
557
|
|
|
262
558
|
if (!body.customerId) {
|
|
263
|
-
return NextResponse.json(
|
|
559
|
+
return NextResponse.json(
|
|
560
|
+
{ error: "Missing customerId" },
|
|
561
|
+
{ status: 400 }
|
|
562
|
+
);
|
|
264
563
|
}
|
|
265
564
|
|
|
266
|
-
const session =
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
565
|
+
const session =
|
|
566
|
+
await createCustomerPortalSession({
|
|
567
|
+
customerId: body.customerId,
|
|
568
|
+
returnUrl: body.returnUrl,
|
|
569
|
+
});
|
|
270
570
|
|
|
271
571
|
return NextResponse.json(session);
|
|
272
572
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
);
|
|
573
|
+
`
|
|
574
|
+
);
|
|
276
575
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
import {
|
|
576
|
+
writeFileSafe(
|
|
577
|
+
"src/app/api/paddle/refresh-subscription/route.ts",
|
|
578
|
+
`import { NextRequest, NextResponse } from "next/server";
|
|
579
|
+
import {
|
|
580
|
+
refreshSubscriptionFromPaddle,
|
|
581
|
+
} from "paddle-checkout-accelerator";
|
|
281
582
|
|
|
282
583
|
export async function POST(request: NextRequest) {
|
|
283
|
-
const body =
|
|
284
|
-
|
|
285
|
-
|
|
584
|
+
const body =
|
|
585
|
+
(await request.json()) as {
|
|
586
|
+
subscriptionId?: string;
|
|
587
|
+
};
|
|
286
588
|
|
|
287
589
|
if (!body.subscriptionId) {
|
|
288
|
-
return NextResponse.json(
|
|
590
|
+
return NextResponse.json(
|
|
591
|
+
{ error: "Missing subscriptionId" },
|
|
592
|
+
{ status: 400 }
|
|
593
|
+
);
|
|
289
594
|
}
|
|
290
595
|
|
|
291
|
-
const subscription =
|
|
596
|
+
const subscription =
|
|
597
|
+
await refreshSubscriptionFromPaddle(
|
|
598
|
+
body.subscriptionId
|
|
599
|
+
);
|
|
600
|
+
|
|
292
601
|
return NextResponse.json(subscription);
|
|
293
602
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
);
|
|
603
|
+
`
|
|
604
|
+
);
|
|
297
605
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
import {
|
|
606
|
+
writeFileSafe(
|
|
607
|
+
"src/app/api/paddle/repair-by-email/route.ts",
|
|
608
|
+
`import { NextRequest, NextResponse } from "next/server";
|
|
609
|
+
import {
|
|
610
|
+
repairSubscriptionByEmail,
|
|
611
|
+
} from "paddle-checkout-accelerator";
|
|
302
612
|
|
|
303
613
|
export async function POST(request: NextRequest) {
|
|
304
|
-
const body =
|
|
305
|
-
|
|
306
|
-
|
|
614
|
+
const body =
|
|
615
|
+
(await request.json()) as {
|
|
616
|
+
email?: string;
|
|
617
|
+
};
|
|
307
618
|
|
|
308
619
|
if (!body.email) {
|
|
309
|
-
return NextResponse.json(
|
|
620
|
+
return NextResponse.json(
|
|
621
|
+
{ error: "Missing email" },
|
|
622
|
+
{ status: 400 }
|
|
623
|
+
);
|
|
310
624
|
}
|
|
311
625
|
|
|
312
|
-
const result =
|
|
626
|
+
const result =
|
|
627
|
+
await repairSubscriptionByEmail(
|
|
628
|
+
body.email
|
|
629
|
+
);
|
|
630
|
+
|
|
313
631
|
return NextResponse.json(result);
|
|
314
632
|
}
|
|
315
|
-
|
|
316
|
-
|
|
633
|
+
`
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
async function main() {
|
|
638
|
+
if (
|
|
639
|
+
!command ||
|
|
640
|
+
command === "--help" ||
|
|
641
|
+
command === "-h"
|
|
642
|
+
) {
|
|
643
|
+
console.log(`
|
|
644
|
+
Paddle Checkout Accelerator
|
|
645
|
+
|
|
646
|
+
Usage:
|
|
647
|
+
npx paddle-checkout-accelerator init
|
|
648
|
+
npx paddle-checkout-accelerator init --interactive
|
|
649
|
+
npx paddle-checkout-accelerator init --force
|
|
650
|
+
npx paddle-checkout-accelerator init --minimal
|
|
651
|
+
`);
|
|
652
|
+
process.exit(0);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (command !== "init") {
|
|
656
|
+
console.error(
|
|
657
|
+
`Unknown command: ${command}`
|
|
317
658
|
);
|
|
659
|
+
process.exit(1);
|
|
318
660
|
}
|
|
319
661
|
|
|
320
|
-
|
|
662
|
+
const config =
|
|
663
|
+
await getConfig();
|
|
664
|
+
|
|
665
|
+
console.log("");
|
|
666
|
+
console.log(
|
|
667
|
+
`Detected adapter: ${config.adapter}`
|
|
668
|
+
);
|
|
669
|
+
console.log("");
|
|
670
|
+
|
|
671
|
+
writeBillingFile(config.adapter);
|
|
672
|
+
|
|
673
|
+
if (config.schema) {
|
|
674
|
+
writePrismaSchema();
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (config.routes) {
|
|
678
|
+
writeRoutes();
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (config.ui) {
|
|
682
|
+
writeUiPages();
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (config.env) {
|
|
321
686
|
appendEnvSafe({
|
|
322
687
|
NEXT_PUBLIC_PADDLE_CLIENT_TOKEN: "",
|
|
323
688
|
PADDLE_API_KEY: "",
|
|
@@ -326,12 +691,33 @@ export async function POST(request: NextRequest) {
|
|
|
326
691
|
}
|
|
327
692
|
|
|
328
693
|
console.log("");
|
|
329
|
-
console.log(
|
|
694
|
+
console.log(
|
|
695
|
+
"✅ Paddle Checkout Accelerator installed."
|
|
696
|
+
);
|
|
330
697
|
console.log("");
|
|
331
698
|
console.log("Next:");
|
|
332
|
-
console.log(
|
|
333
|
-
|
|
334
|
-
|
|
699
|
+
console.log(
|
|
700
|
+
"1. Add your Paddle keys to .env.local"
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
if (config.adapter === "prisma") {
|
|
704
|
+
console.log(
|
|
705
|
+
"2. Copy generated Prisma models into your main prisma/schema.prisma"
|
|
706
|
+
);
|
|
707
|
+
console.log(
|
|
708
|
+
"3. Run your Prisma migration"
|
|
709
|
+
);
|
|
710
|
+
console.log(
|
|
711
|
+
"4. Configure Paddle webhook URL: /api/paddle/webhook"
|
|
712
|
+
);
|
|
713
|
+
} else {
|
|
714
|
+
console.log(
|
|
715
|
+
"2. Use Prisma adapter for production"
|
|
716
|
+
);
|
|
717
|
+
console.log(
|
|
718
|
+
"3. Configure Paddle webhook URL: /api/paddle/webhook"
|
|
719
|
+
);
|
|
720
|
+
}
|
|
335
721
|
}
|
|
336
722
|
|
|
337
723
|
main().catch((error) => {
|