paddle-checkout-accelerator 2.4.0 → 2.6.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.
|
@@ -17,6 +17,9 @@ const interactive =
|
|
|
17
17
|
args.has("--interactive") ||
|
|
18
18
|
args.has("-i");
|
|
19
19
|
|
|
20
|
+
const minimal =
|
|
21
|
+
args.has("--minimal");
|
|
22
|
+
|
|
20
23
|
function exists(filePath) {
|
|
21
24
|
return fs.existsSync(
|
|
22
25
|
path.join(cwd, filePath)
|
|
@@ -115,6 +118,120 @@ function appendEnvSafe(values) {
|
|
|
115
118
|
}
|
|
116
119
|
}
|
|
117
120
|
|
|
121
|
+
function readEnvFile() {
|
|
122
|
+
const envPath =
|
|
123
|
+
path.join(cwd, ".env.local");
|
|
124
|
+
|
|
125
|
+
if (!fs.existsSync(envPath)) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return fs.readFileSync(envPath, "utf8");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function hasEnvValue(env, key) {
|
|
133
|
+
if (!env) return false;
|
|
134
|
+
|
|
135
|
+
const line =
|
|
136
|
+
env
|
|
137
|
+
.split("\n")
|
|
138
|
+
.find((entry) =>
|
|
139
|
+
entry.startsWith(`${key}=`)
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
if (!line) return false;
|
|
143
|
+
|
|
144
|
+
const value =
|
|
145
|
+
line.slice(key.length + 1).trim();
|
|
146
|
+
|
|
147
|
+
return value.length > 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function runDoctor() {
|
|
151
|
+
const env =
|
|
152
|
+
readEnvFile();
|
|
153
|
+
|
|
154
|
+
const checks = [
|
|
155
|
+
{
|
|
156
|
+
label: ".env.local exists",
|
|
157
|
+
pass: Boolean(env),
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
label: "PADDLE_API_KEY set",
|
|
161
|
+
pass: hasEnvValue(env, "PADDLE_API_KEY"),
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
label: "PADDLE_WEBHOOK_SECRET set",
|
|
165
|
+
pass: hasEnvValue(env, "PADDLE_WEBHOOK_SECRET"),
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
label: "NEXT_PUBLIC_PADDLE_CLIENT_TOKEN set",
|
|
169
|
+
pass: hasEnvValue(env, "NEXT_PUBLIC_PADDLE_CLIENT_TOKEN"),
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
label: "billing config exists",
|
|
173
|
+
pass: exists("src/lib/billing.ts"),
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
label: "webhook route exists",
|
|
177
|
+
pass: exists("src/app/api/paddle/webhook/route.ts"),
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
label: "portal route exists",
|
|
181
|
+
pass: exists("src/app/api/paddle/portal-session/route.ts"),
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
label: "refresh route exists",
|
|
185
|
+
pass: exists("src/app/api/paddle/refresh-subscription/route.ts"),
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
label: "repair route exists",
|
|
189
|
+
pass: exists("src/app/api/paddle/repair-by-email/route.ts"),
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
label: "pricing page exists",
|
|
193
|
+
pass: exists("src/app/billing/pricing/page.tsx"),
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
label: "billing dashboard exists",
|
|
197
|
+
pass: exists("src/app/billing/dashboard/page.tsx"),
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
label: "customer repair page exists",
|
|
201
|
+
pass: exists("src/app/billing/customer-repair/page.tsx"),
|
|
202
|
+
},
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
console.log("");
|
|
206
|
+
console.log("Paddle Checkout Accelerator Doctor");
|
|
207
|
+
console.log("");
|
|
208
|
+
|
|
209
|
+
let failed = 0;
|
|
210
|
+
|
|
211
|
+
for (const check of checks) {
|
|
212
|
+
if (check.pass) {
|
|
213
|
+
console.log(`✅ ${check.label}`);
|
|
214
|
+
} else {
|
|
215
|
+
failed += 1;
|
|
216
|
+
console.log(`❌ ${check.label}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
console.log("");
|
|
221
|
+
|
|
222
|
+
if (failed === 0) {
|
|
223
|
+
console.log("All checks passed.");
|
|
224
|
+
process.exit(0);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
console.log(`${failed} check(s) failed.`);
|
|
228
|
+
console.log("");
|
|
229
|
+
console.log("Fix the missing items above, then run:");
|
|
230
|
+
console.log(" npx paddle-checkout-accelerator doctor");
|
|
231
|
+
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
|
|
118
235
|
async function getConfig() {
|
|
119
236
|
const detectedAdapter =
|
|
120
237
|
detectAdapter();
|
|
@@ -126,6 +243,7 @@ async function getConfig() {
|
|
|
126
243
|
env: true,
|
|
127
244
|
schema:
|
|
128
245
|
detectedAdapter === "prisma",
|
|
246
|
+
ui: !minimal,
|
|
129
247
|
};
|
|
130
248
|
}
|
|
131
249
|
|
|
@@ -180,6 +298,15 @@ async function getConfig() {
|
|
|
180
298
|
active: "yes",
|
|
181
299
|
inactive: "no",
|
|
182
300
|
},
|
|
301
|
+
{
|
|
302
|
+
type: "toggle",
|
|
303
|
+
name: "ui",
|
|
304
|
+
message:
|
|
305
|
+
"Generate billing UI pages?",
|
|
306
|
+
initial: true,
|
|
307
|
+
active: "yes",
|
|
308
|
+
inactive: "no",
|
|
309
|
+
},
|
|
183
310
|
]);
|
|
184
311
|
|
|
185
312
|
if (!answers.adapter) {
|
|
@@ -289,6 +416,198 @@ model BillingEvent {
|
|
|
289
416
|
);
|
|
290
417
|
}
|
|
291
418
|
|
|
419
|
+
function writeUiPages() {
|
|
420
|
+
writeFileSafe(
|
|
421
|
+
"src/app/billing/pricing/page.tsx",
|
|
422
|
+
`import {
|
|
423
|
+
PricingTable,
|
|
424
|
+
} from "paddle-checkout-accelerator";
|
|
425
|
+
|
|
426
|
+
export default function BillingPricingPage() {
|
|
427
|
+
return (
|
|
428
|
+
<main className="mx-auto max-w-5xl p-10">
|
|
429
|
+
<h1 className="text-4xl font-bold">
|
|
430
|
+
Choose your plan
|
|
431
|
+
</h1>
|
|
432
|
+
|
|
433
|
+
<p className="mt-3 text-gray-500">
|
|
434
|
+
Upgrade your account with Paddle billing.
|
|
435
|
+
</p>
|
|
436
|
+
|
|
437
|
+
<div className="mt-8">
|
|
438
|
+
<PricingTable />
|
|
439
|
+
</div>
|
|
440
|
+
</main>
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
`
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
writeFileSafe(
|
|
447
|
+
"src/app/billing/dashboard/page.tsx",
|
|
448
|
+
`import {
|
|
449
|
+
BillingHistory,
|
|
450
|
+
CustomerPortalButton,
|
|
451
|
+
SubscriptionCard,
|
|
452
|
+
TrialBanner,
|
|
453
|
+
UsageMeter,
|
|
454
|
+
} from "paddle-checkout-accelerator";
|
|
455
|
+
|
|
456
|
+
export default function BillingDashboardPage() {
|
|
457
|
+
return (
|
|
458
|
+
<main className="mx-auto max-w-5xl space-y-6 p-10">
|
|
459
|
+
<TrialBanner daysRemaining={5} />
|
|
460
|
+
|
|
461
|
+
<SubscriptionCard
|
|
462
|
+
plan="Pro"
|
|
463
|
+
status="Active"
|
|
464
|
+
/>
|
|
465
|
+
|
|
466
|
+
<UsageMeter
|
|
467
|
+
current={37}
|
|
468
|
+
limit={50}
|
|
469
|
+
/>
|
|
470
|
+
|
|
471
|
+
<CustomerPortalButton />
|
|
472
|
+
|
|
473
|
+
<BillingHistory />
|
|
474
|
+
</main>
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
`
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
writeFileSafe(
|
|
481
|
+
"src/app/billing/customer-repair/page.tsx",
|
|
482
|
+
`"use client";
|
|
483
|
+
|
|
484
|
+
import { useState } from "react";
|
|
485
|
+
|
|
486
|
+
export default function CustomerRepairPage() {
|
|
487
|
+
const [email, setEmail] =
|
|
488
|
+
useState("");
|
|
489
|
+
const [result, setResult] =
|
|
490
|
+
useState("");
|
|
491
|
+
|
|
492
|
+
async function repair() {
|
|
493
|
+
const response =
|
|
494
|
+
await fetch(
|
|
495
|
+
"/api/paddle/repair-by-email",
|
|
496
|
+
{
|
|
497
|
+
method: "POST",
|
|
498
|
+
headers: {
|
|
499
|
+
"Content-Type":
|
|
500
|
+
"application/json",
|
|
501
|
+
},
|
|
502
|
+
body: JSON.stringify({
|
|
503
|
+
email,
|
|
504
|
+
}),
|
|
505
|
+
}
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
const data =
|
|
509
|
+
await response.json();
|
|
510
|
+
|
|
511
|
+
setResult(
|
|
512
|
+
JSON.stringify(data, null, 2)
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
return (
|
|
517
|
+
<main className="mx-auto max-w-3xl space-y-6 p-10">
|
|
518
|
+
<div>
|
|
519
|
+
<h1 className="text-4xl font-bold">
|
|
520
|
+
Repair Customer Access
|
|
521
|
+
</h1>
|
|
522
|
+
|
|
523
|
+
<p className="mt-3 text-gray-500">
|
|
524
|
+
Use this when a customer paid but their account did not unlock.
|
|
525
|
+
</p>
|
|
526
|
+
</div>
|
|
527
|
+
|
|
528
|
+
<input
|
|
529
|
+
value={email}
|
|
530
|
+
onChange={(event) =>
|
|
531
|
+
setEmail(event.target.value)
|
|
532
|
+
}
|
|
533
|
+
placeholder="customer@example.com"
|
|
534
|
+
className="w-full rounded-xl border p-4"
|
|
535
|
+
/>
|
|
536
|
+
|
|
537
|
+
<button
|
|
538
|
+
onClick={repair}
|
|
539
|
+
className="rounded-xl bg-black px-6 py-3 text-white"
|
|
540
|
+
>
|
|
541
|
+
Repair Subscription
|
|
542
|
+
</button>
|
|
543
|
+
|
|
544
|
+
{result && (
|
|
545
|
+
<pre className="overflow-auto rounded-xl border bg-slate-50 p-5 text-sm">
|
|
546
|
+
{result}
|
|
547
|
+
</pre>
|
|
548
|
+
)}
|
|
549
|
+
</main>
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
`
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
writeFileSafe(
|
|
556
|
+
"src/app/billing/protected-api/page.tsx",
|
|
557
|
+
`"use client";
|
|
558
|
+
|
|
559
|
+
import { useState } from "react";
|
|
560
|
+
|
|
561
|
+
export default function ProtectedApiPage() {
|
|
562
|
+
const [result, setResult] =
|
|
563
|
+
useState("");
|
|
564
|
+
|
|
565
|
+
async function run() {
|
|
566
|
+
const response =
|
|
567
|
+
await fetch(
|
|
568
|
+
"/api/demo/protected-generation",
|
|
569
|
+
{
|
|
570
|
+
method: "POST",
|
|
571
|
+
}
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
const data =
|
|
575
|
+
await response.json();
|
|
576
|
+
|
|
577
|
+
setResult(
|
|
578
|
+
JSON.stringify(data, null, 2)
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return (
|
|
583
|
+
<main className="mx-auto max-w-3xl space-y-6 p-10">
|
|
584
|
+
<h1 className="text-4xl font-bold">
|
|
585
|
+
Protected API Demo
|
|
586
|
+
</h1>
|
|
587
|
+
|
|
588
|
+
<p className="text-gray-500">
|
|
589
|
+
Test paid-route protection, feature checks, and usage limits.
|
|
590
|
+
</p>
|
|
591
|
+
|
|
592
|
+
<button
|
|
593
|
+
onClick={run}
|
|
594
|
+
className="rounded-xl bg-black px-6 py-3 text-white"
|
|
595
|
+
>
|
|
596
|
+
Run Protected Action
|
|
597
|
+
</button>
|
|
598
|
+
|
|
599
|
+
{result && (
|
|
600
|
+
<pre className="overflow-auto rounded-xl border bg-slate-50 p-5 text-sm">
|
|
601
|
+
{result}
|
|
602
|
+
</pre>
|
|
603
|
+
)}
|
|
604
|
+
</main>
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
`
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
|
|
292
611
|
function writeRoutes() {
|
|
293
612
|
writeFileSafe(
|
|
294
613
|
"src/app/api/paddle/webhook/route.ts",
|
|
@@ -442,10 +761,16 @@ Usage:
|
|
|
442
761
|
npx paddle-checkout-accelerator init
|
|
443
762
|
npx paddle-checkout-accelerator init --interactive
|
|
444
763
|
npx paddle-checkout-accelerator init --force
|
|
764
|
+
npx paddle-checkout-accelerator doctor
|
|
765
|
+
npx paddle-checkout-accelerator init --minimal
|
|
445
766
|
`);
|
|
446
767
|
process.exit(0);
|
|
447
768
|
}
|
|
448
769
|
|
|
770
|
+
if (command === "doctor") {
|
|
771
|
+
runDoctor();
|
|
772
|
+
}
|
|
773
|
+
|
|
449
774
|
if (command !== "init") {
|
|
450
775
|
console.error(
|
|
451
776
|
`Unknown command: ${command}`
|
|
@@ -472,6 +797,10 @@ Usage:
|
|
|
472
797
|
writeRoutes();
|
|
473
798
|
}
|
|
474
799
|
|
|
800
|
+
if (config.ui) {
|
|
801
|
+
writeUiPages();
|
|
802
|
+
}
|
|
803
|
+
|
|
475
804
|
if (config.env) {
|
|
476
805
|
appendEnvSafe({
|
|
477
806
|
NEXT_PUBLIC_PADDLE_CLIENT_TOKEN: "",
|