paddle-checkout-accelerator 2.2.0 → 2.4.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/README.md CHANGED
@@ -386,3 +386,5 @@ npx paddle-accelerator init
386
386
  Copyright © NamahLogistics.
387
387
 
388
388
  Commercial license. Redistribution, resale, sublicensing, or republication of the source code is prohibited without written permission.
389
+
390
+ - v2.2.0 — CLI Init Installer
@@ -2,86 +2,240 @@
2
2
 
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
+ import prompts from "prompts";
5
6
 
6
7
  const cwd = process.cwd();
8
+ const args = new Set(process.argv.slice(2));
9
+
10
+ const command =
11
+ process.argv[2];
12
+
13
+ const force =
14
+ args.has("--force");
15
+
16
+ const interactive =
17
+ args.has("--interactive") ||
18
+ args.has("-i");
19
+
20
+ function exists(filePath) {
21
+ return fs.existsSync(
22
+ path.join(cwd, filePath)
23
+ );
24
+ }
25
+
26
+ function detectAdapter() {
27
+ if (
28
+ exists("prisma/schema.prisma") ||
29
+ exists("src/lib/prisma.ts") ||
30
+ exists("lib/prisma.ts")
31
+ ) {
32
+ return "prisma";
33
+ }
34
+
35
+ return "memory";
36
+ }
7
37
 
8
38
  function writeFileSafe(filePath, content) {
9
- const absolutePath = path.join(cwd, filePath);
10
- const dir = path.dirname(absolutePath);
39
+ const absolutePath =
40
+ path.join(cwd, filePath);
11
41
 
12
- fs.mkdirSync(dir, {
13
- recursive: true,
14
- });
42
+ const existed =
43
+ fs.existsSync(absolutePath);
15
44
 
16
- if (fs.existsSync(absolutePath)) {
17
- console.log(`Skipped ${filePath} because it already exists`);
45
+ fs.mkdirSync(
46
+ path.dirname(absolutePath),
47
+ { recursive: true }
48
+ );
49
+
50
+ if (existed && !force) {
51
+ console.log(
52
+ `Skipped ${filePath} because it already exists`
53
+ );
18
54
  return;
19
55
  }
20
56
 
21
- fs.writeFileSync(absolutePath, content);
22
- console.log(`Created ${filePath}`);
57
+ fs.writeFileSync(
58
+ absolutePath,
59
+ content
60
+ );
61
+
62
+ console.log(
63
+ `${existed ? "Updated" : "Created"} ${filePath}`
64
+ );
23
65
  }
24
66
 
25
- function appendEnvSafe(content) {
26
- const envPath = path.join(cwd, ".env.local");
67
+ function appendEnvSafe(values) {
68
+ const envPath =
69
+ path.join(cwd, ".env.local");
70
+
71
+ const content =
72
+ Object.entries(values)
73
+ .map(
74
+ ([key, value]) =>
75
+ `${key}=${value ?? ""}`
76
+ )
77
+ .join("\n");
27
78
 
28
79
  if (!fs.existsSync(envPath)) {
29
- fs.writeFileSync(envPath, content);
80
+ fs.writeFileSync(
81
+ envPath,
82
+ content + "\n"
83
+ );
84
+
30
85
  console.log("Created .env.local");
31
86
  return;
32
87
  }
33
88
 
34
- const current = fs.readFileSync(envPath, "utf8");
89
+ const current =
90
+ fs.readFileSync(envPath, "utf8");
91
+
92
+ const lines =
93
+ content
94
+ .split("\n")
95
+ .filter((line) => {
96
+ const key =
97
+ line.split("=")[0];
98
+
99
+ return !current.includes(
100
+ `${key}=`
101
+ );
102
+ });
103
+
104
+ if (lines.length) {
105
+ fs.appendFileSync(
106
+ envPath,
107
+ "\n" + lines.join("\n") + "\n"
108
+ );
35
109
 
36
- const nextLines = content
37
- .split("\n")
38
- .filter(Boolean)
39
- .filter((line) => {
40
- const key = line.split("=")[0];
41
- return !current.includes(`${key}=`);
42
- });
110
+ console.log("Updated .env.local");
111
+ } else {
112
+ console.log(
113
+ "Skipped .env.local because Paddle keys already exist"
114
+ );
115
+ }
116
+ }
43
117
 
44
- if (nextLines.length === 0) {
45
- console.log("Skipped .env.local because Paddle keys already exist");
46
- return;
118
+ async function getConfig() {
119
+ const detectedAdapter =
120
+ detectAdapter();
121
+
122
+ if (!interactive) {
123
+ return {
124
+ adapter: detectedAdapter,
125
+ routes: true,
126
+ env: true,
127
+ schema:
128
+ detectedAdapter === "prisma",
129
+ };
47
130
  }
48
131
 
49
- fs.appendFileSync(
50
- envPath,
51
- `\n${nextLines.join("\n")}\n`
52
- );
132
+ const answers =
133
+ await prompts([
134
+ {
135
+ type: "select",
136
+ name: "adapter",
137
+ message:
138
+ "Which storage adapter do you want?",
139
+ choices: [
140
+ {
141
+ title: "Auto-detect",
142
+ value: detectedAdapter,
143
+ },
144
+ {
145
+ title: "Memory demo adapter",
146
+ value: "memory",
147
+ },
148
+ {
149
+ title: "Prisma production adapter",
150
+ value: "prisma",
151
+ },
152
+ ],
153
+ initial: 0,
154
+ },
155
+ {
156
+ type: "toggle",
157
+ name: "routes",
158
+ message:
159
+ "Generate Paddle API routes?",
160
+ initial: true,
161
+ active: "yes",
162
+ inactive: "no",
163
+ },
164
+ {
165
+ type: "toggle",
166
+ name: "env",
167
+ message:
168
+ "Create/update .env.local placeholders?",
169
+ initial: true,
170
+ active: "yes",
171
+ inactive: "no",
172
+ },
173
+ {
174
+ type: "toggle",
175
+ name: "schema",
176
+ message:
177
+ "Generate Prisma schema example?",
178
+ initial:
179
+ detectedAdapter === "prisma",
180
+ active: "yes",
181
+ inactive: "no",
182
+ },
183
+ ]);
184
+
185
+ if (!answers.adapter) {
186
+ console.log("Install cancelled.");
187
+ process.exit(0);
188
+ }
53
189
 
54
- console.log("Updated .env.local");
190
+ return answers;
55
191
  }
56
192
 
57
- const command = process.argv[2];
193
+ function writeBillingFile(adapter) {
194
+ if (adapter === "prisma") {
195
+ writeFileSafe(
196
+ "src/lib/billing.ts",
197
+ `import {
198
+ configureBilling,
199
+ createPrismaBillingAdapter,
200
+ } from "paddle-checkout-accelerator";
58
201
 
59
- if (!command || command === "--help" || command === "-h") {
60
- console.log(`
61
- Paddle Checkout Accelerator
202
+ import { prisma } from "@/lib/prisma";
62
203
 
63
- Usage:
64
- npx paddle-checkout-accelerator init
204
+ const adapter =
205
+ createPrismaBillingAdapter(prisma);
65
206
 
66
- This creates:
67
- - src/lib/billing.ts
68
- - src/app/api/paddle/webhook/route.ts
69
- - src/app/api/paddle/portal-session/route.ts
70
- - src/app/api/paddle/refresh-subscription/route.ts
71
- - src/app/api/paddle/repair-by-email/route.ts
72
- - .env.local placeholders
73
- `);
74
- process.exit(0);
75
- }
207
+ export const billing =
208
+ configureBilling({
209
+ adapter,
210
+ });
211
+ `
212
+ );
76
213
 
77
- if (command !== "init") {
78
- console.error(`Unknown command: ${command}`);
79
- process.exit(1);
214
+ writeFileSafe(
215
+ "src/lib/prisma.ts",
216
+ `import { PrismaClient } from "@prisma/client";
217
+
218
+ const globalForPrisma =
219
+ globalThis as unknown as {
220
+ prisma?: PrismaClient;
221
+ };
222
+
223
+ export const prisma =
224
+ globalForPrisma.prisma ??
225
+ new PrismaClient();
226
+
227
+ if (process.env.NODE_ENV !== "production") {
228
+ globalForPrisma.prisma = prisma;
80
229
  }
230
+ `
231
+ );
232
+
233
+ return;
234
+ }
81
235
 
82
- writeFileSafe(
83
- "src/lib/billing.ts",
84
- `import {
236
+ writeFileSafe(
237
+ "src/lib/billing.ts",
238
+ `import {
85
239
  configureBilling,
86
240
  memoryBillingAdapter,
87
241
  } from "paddle-checkout-accelerator";
@@ -91,11 +245,54 @@ export const billing =
91
245
  adapter: memoryBillingAdapter,
92
246
  });
93
247
  `
94
- );
248
+ );
249
+ }
250
+
251
+ function writePrismaSchema() {
252
+ writeFileSafe(
253
+ "prisma/paddle-accelerator.schema.prisma",
254
+ `model Subscription {
255
+ userId String @id
256
+ plan String
257
+ status String
258
+ paddleCustomerId String?
259
+ paddleSubscriptionId String?
260
+ currentPeriodEnd String?
261
+ }
262
+
263
+ model UsageEvent {
264
+ id String @id @default(cuid())
265
+ userId String
266
+ key String
267
+ period String
268
+ count Int
269
+
270
+ @@unique([userId, key, period], name: "userId_key_period")
271
+ }
272
+
273
+ model Team {
274
+ teamId String @id
275
+ name String
276
+ plan String
277
+ ownerId String
278
+ members Json
279
+ }
280
+
281
+ model BillingEvent {
282
+ id String @id
283
+ userId String
284
+ type String
285
+ createdAt DateTime
286
+ metadata Json?
287
+ }
288
+ `
289
+ );
290
+ }
95
291
 
96
- writeFileSafe(
97
- "src/app/api/paddle/webhook/route.ts",
98
- `import { NextRequest, NextResponse } from "next/server";
292
+ function writeRoutes() {
293
+ writeFileSafe(
294
+ "src/app/api/paddle/webhook/route.ts",
295
+ `import { NextRequest, NextResponse } from "next/server";
99
296
  import {
100
297
  syncPaddleEvent,
101
298
  verifyPaddleWebhook,
@@ -103,9 +300,7 @@ import {
103
300
 
104
301
  export async function POST(request: NextRequest) {
105
302
  const rawBody = await request.text();
106
-
107
- const signature =
108
- request.headers.get("paddle-signature");
303
+ const signature = request.headers.get("paddle-signature");
109
304
 
110
305
  if (!signature) {
111
306
  return NextResponse.json(
@@ -139,11 +334,11 @@ export async function POST(request: NextRequest) {
139
334
  }
140
335
  }
141
336
  `
142
- );
337
+ );
143
338
 
144
- writeFileSafe(
145
- "src/app/api/paddle/portal-session/route.ts",
146
- `import { NextRequest, NextResponse } from "next/server";
339
+ writeFileSafe(
340
+ "src/app/api/paddle/portal-session/route.ts",
341
+ `import { NextRequest, NextResponse } from "next/server";
147
342
  import {
148
343
  createCustomerPortalSession,
149
344
  } from "paddle-checkout-accelerator";
@@ -171,11 +366,11 @@ export async function POST(request: NextRequest) {
171
366
  return NextResponse.json(session);
172
367
  }
173
368
  `
174
- );
369
+ );
175
370
 
176
- writeFileSafe(
177
- "src/app/api/paddle/refresh-subscription/route.ts",
178
- `import { NextRequest, NextResponse } from "next/server";
371
+ writeFileSafe(
372
+ "src/app/api/paddle/refresh-subscription/route.ts",
373
+ `import { NextRequest, NextResponse } from "next/server";
179
374
  import {
180
375
  refreshSubscriptionFromPaddle,
181
376
  } from "paddle-checkout-accelerator";
@@ -201,11 +396,11 @@ export async function POST(request: NextRequest) {
201
396
  return NextResponse.json(subscription);
202
397
  }
203
398
  `
204
- );
399
+ );
205
400
 
206
- writeFileSafe(
207
- "src/app/api/paddle/repair-by-email/route.ts",
208
- `import { NextRequest, NextResponse } from "next/server";
401
+ writeFileSafe(
402
+ "src/app/api/paddle/repair-by-email/route.ts",
403
+ `import { NextRequest, NextResponse } from "next/server";
209
404
  import {
210
405
  repairSubscriptionByEmail,
211
406
  } from "paddle-checkout-accelerator";
@@ -231,17 +426,91 @@ export async function POST(request: NextRequest) {
231
426
  return NextResponse.json(result);
232
427
  }
233
428
  `
234
- );
429
+ );
430
+ }
235
431
 
236
- appendEnvSafe(`NEXT_PUBLIC_PADDLE_CLIENT_TOKEN=
237
- PADDLE_API_KEY=
238
- PADDLE_WEBHOOK_SECRET=
432
+ async function main() {
433
+ if (
434
+ !command ||
435
+ command === "--help" ||
436
+ command === "-h"
437
+ ) {
438
+ console.log(`
439
+ Paddle Checkout Accelerator
440
+
441
+ Usage:
442
+ npx paddle-checkout-accelerator init
443
+ npx paddle-checkout-accelerator init --interactive
444
+ npx paddle-checkout-accelerator init --force
239
445
  `);
446
+ process.exit(0);
447
+ }
448
+
449
+ if (command !== "init") {
450
+ console.error(
451
+ `Unknown command: ${command}`
452
+ );
453
+ process.exit(1);
454
+ }
455
+
456
+ const config =
457
+ await getConfig();
458
+
459
+ console.log("");
460
+ console.log(
461
+ `Detected adapter: ${config.adapter}`
462
+ );
463
+ console.log("");
240
464
 
241
- console.log("");
242
- console.log("Paddle Checkout Accelerator installed.");
243
- console.log("");
244
- console.log("Next:");
245
- console.log("1. Add your Paddle keys to .env.local");
246
- console.log("2. Replace memoryBillingAdapter with Prisma adapter for production");
247
- console.log("3. Configure Paddle webhook URL: /api/paddle/webhook");
465
+ writeBillingFile(config.adapter);
466
+
467
+ if (config.schema) {
468
+ writePrismaSchema();
469
+ }
470
+
471
+ if (config.routes) {
472
+ writeRoutes();
473
+ }
474
+
475
+ if (config.env) {
476
+ appendEnvSafe({
477
+ NEXT_PUBLIC_PADDLE_CLIENT_TOKEN: "",
478
+ PADDLE_API_KEY: "",
479
+ PADDLE_WEBHOOK_SECRET: "",
480
+ });
481
+ }
482
+
483
+ console.log("");
484
+ console.log(
485
+ "✅ Paddle Checkout Accelerator installed."
486
+ );
487
+ console.log("");
488
+ console.log("Next:");
489
+ console.log(
490
+ "1. Add your Paddle keys to .env.local"
491
+ );
492
+
493
+ if (config.adapter === "prisma") {
494
+ console.log(
495
+ "2. Copy generated Prisma models into your main prisma/schema.prisma"
496
+ );
497
+ console.log(
498
+ "3. Run your Prisma migration"
499
+ );
500
+ console.log(
501
+ "4. Configure Paddle webhook URL: /api/paddle/webhook"
502
+ );
503
+ } else {
504
+ console.log(
505
+ "2. Use Prisma adapter for production"
506
+ );
507
+ console.log(
508
+ "3. Configure Paddle webhook URL: /api/paddle/webhook"
509
+ );
510
+ }
511
+ }
512
+
513
+ main().catch((error) => {
514
+ console.error(error);
515
+ process.exit(1);
516
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paddle-checkout-accelerator",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "scripts": {
5
5
  "dev": "next dev",
6
6
  "build": "next build",
@@ -27,7 +27,8 @@
27
27
  "react": "19.2.4",
28
28
  "react-dom": "19.2.4",
29
29
  "svix": "^1.95.2",
30
- "tailwind-merge": "^3.6.0"
30
+ "tailwind-merge": "^3.6.0",
31
+ "prompts": "^2.4.2"
31
32
  },
32
33
  "devDependencies": {
33
34
  "@tailwindcss/postcss": "^4",
@@ -36,6 +37,7 @@
36
37
  "@types/react-dom": "^19",
37
38
  "eslint": "^9",
38
39
  "eslint-config-next": "16.2.9",
40
+ "prompts": "^2.4.2",
39
41
  "tailwindcss": "^4",
40
42
  "tsup": "^8.5.1",
41
43
  "typescript": "^5",