paddle-checkout-accelerator 2.3.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.
@@ -5,134 +5,193 @@ 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
- function writeFileSafe(filePath, content, overwrite = false) {
10
- const absolutePath = path.join(cwd, filePath);
11
- fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
10
+ const command =
11
+ process.argv[2];
12
12
 
13
- if (fs.existsSync(absolutePath) && !overwrite) {
14
- console.log(`Skipped ${filePath} because it already exists`);
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
+ }
37
+
38
+ function writeFileSafe(filePath, content) {
39
+ const absolutePath =
40
+ path.join(cwd, filePath);
41
+
42
+ const existed =
43
+ fs.existsSync(absolutePath);
44
+
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
+ );
15
54
  return;
16
55
  }
17
56
 
18
- fs.writeFileSync(absolutePath, content);
19
- console.log(`${fs.existsSync(absolutePath) ? "Updated" : "Created"} ${filePath}`);
57
+ fs.writeFileSync(
58
+ absolutePath,
59
+ content
60
+ );
61
+
62
+ console.log(
63
+ `${existed ? "Updated" : "Created"} ${filePath}`
64
+ );
20
65
  }
21
66
 
22
67
  function appendEnvSafe(values) {
23
- const envPath = path.join(cwd, ".env.local");
24
- const content = Object.entries(values)
25
- .map(([key, value]) => `${key}=${value ?? ""}`)
26
- .join("\n");
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 + "\n");
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");
35
- const lines = content
36
- .split("\n")
37
- .filter((line) => {
38
- const key = line.split("=")[0];
39
- return !current.includes(`${key}=`);
40
- });
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
+ });
41
103
 
42
104
  if (lines.length) {
43
- fs.appendFileSync(envPath, "\n" + lines.join("\n") + "\n");
105
+ fs.appendFileSync(
106
+ envPath,
107
+ "\n" + lines.join("\n") + "\n"
108
+ );
109
+
44
110
  console.log("Updated .env.local");
45
111
  } else {
46
- console.log("Skipped .env.local because Paddle keys already exist");
112
+ console.log(
113
+ "Skipped .env.local because Paddle keys already exist"
114
+ );
47
115
  }
48
116
  }
49
117
 
50
- async function main() {
51
- const command = process.argv[2];
52
-
53
- if (!command || command === "--help" || command === "-h") {
54
- console.log(`
55
- Paddle Checkout Accelerator
56
-
57
- Usage:
58
- npx paddle-checkout-accelerator init
59
- `);
60
- process.exit(0);
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
+ };
61
130
  }
62
131
 
63
- if (command !== "init") {
64
- console.error(`Unknown command: ${command}`);
65
- process.exit(1);
66
- }
67
-
68
- const answers = await prompts([
69
- {
70
- type: "select",
71
- name: "adapter",
72
- message: "Which storage adapter do you want?",
73
- choices: [
74
- { title: "Memory demo adapter", value: "memory" },
75
- { title: "Prisma production adapter", value: "prisma" },
76
- ],
77
- initial: 0,
78
- },
79
- {
80
- type: "toggle",
81
- name: "routes",
82
- message: "Generate Paddle API routes?",
83
- initial: true,
84
- active: "yes",
85
- inactive: "no",
86
- },
87
- {
88
- type: "toggle",
89
- name: "env",
90
- message: "Create/update .env.local placeholders?",
91
- initial: true,
92
- active: "yes",
93
- inactive: "no",
94
- },
95
- {
96
- type: "toggle",
97
- name: "schema",
98
- message: "Generate Prisma schema example?",
99
- initial: true,
100
- active: "yes",
101
- inactive: "no",
102
- },
103
- {
104
- type: "toggle",
105
- name: "force",
106
- message: "Overwrite existing generated files?",
107
- initial: false,
108
- active: "yes",
109
- inactive: "no",
110
- },
111
- ]);
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
+ ]);
112
184
 
113
185
  if (!answers.adapter) {
114
186
  console.log("Install cancelled.");
115
187
  process.exit(0);
116
188
  }
117
189
 
118
- if (answers.adapter === "memory") {
119
- writeFileSafe(
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
- }
190
+ return answers;
191
+ }
134
192
 
135
- if (answers.adapter === "prisma") {
193
+ function writeBillingFile(adapter) {
194
+ if (adapter === "prisma") {
136
195
  writeFileSafe(
137
196
  "src/lib/billing.ts",
138
197
  `import {
@@ -149,8 +208,7 @@ export const billing =
149
208
  configureBilling({
150
209
  adapter,
151
210
  });
152
- `,
153
- answers.force
211
+ `
154
212
  );
155
213
 
156
214
  writeFileSafe(
@@ -169,14 +227,31 @@ export const prisma =
169
227
  if (process.env.NODE_ENV !== "production") {
170
228
  globalForPrisma.prisma = prisma;
171
229
  }
172
- `,
173
- answers.force
230
+ `
174
231
  );
175
232
 
176
- if (answers.schema) {
177
- writeFileSafe(
178
- "prisma/paddle-accelerator.schema.prisma",
179
- `model Subscription {
233
+ return;
234
+ }
235
+
236
+ writeFileSafe(
237
+ "src/lib/billing.ts",
238
+ `import {
239
+ configureBilling,
240
+ memoryBillingAdapter,
241
+ } from "paddle-checkout-accelerator";
242
+
243
+ export const billing =
244
+ configureBilling({
245
+ adapter: memoryBillingAdapter,
246
+ });
247
+ `
248
+ );
249
+ }
250
+
251
+ function writePrismaSchema() {
252
+ writeFileSafe(
253
+ "prisma/paddle-accelerator.schema.prisma",
254
+ `model Subscription {
180
255
  userId String @id
181
256
  plan String
182
257
  status String
@@ -210,16 +285,14 @@ model BillingEvent {
210
285
  createdAt DateTime
211
286
  metadata Json?
212
287
  }
213
- `,
214
- answers.force
215
- );
216
- }
217
- }
288
+ `
289
+ );
290
+ }
218
291
 
219
- if (answers.routes) {
220
- writeFileSafe(
221
- "src/app/api/paddle/webhook/route.ts",
222
- `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";
223
296
  import {
224
297
  syncPaddleEvent,
225
298
  verifyPaddleWebhook,
@@ -230,94 +303,176 @@ export async function POST(request: NextRequest) {
230
303
  const signature = request.headers.get("paddle-signature");
231
304
 
232
305
  if (!signature) {
233
- return NextResponse.json({ error: "Missing Paddle signature" }, { status: 400 });
306
+ return NextResponse.json(
307
+ { error: "Missing Paddle signature" },
308
+ { status: 400 }
309
+ );
234
310
  }
235
311
 
236
312
  try {
237
- const event = await verifyPaddleWebhook(rawBody, signature);
313
+ const event =
314
+ await verifyPaddleWebhook(
315
+ rawBody,
316
+ signature
317
+ );
318
+
238
319
  await syncPaddleEvent(event);
239
- return NextResponse.json({ success: true });
320
+
321
+ return NextResponse.json({
322
+ success: true,
323
+ });
240
324
  } catch (error) {
241
325
  return NextResponse.json(
242
- { error: error instanceof Error ? error.message : "Webhook verification failed" },
326
+ {
327
+ error:
328
+ error instanceof Error
329
+ ? error.message
330
+ : "Webhook verification failed",
331
+ },
243
332
  { status: 401 }
244
333
  );
245
334
  }
246
335
  }
247
- `,
248
- answers.force
249
- );
336
+ `
337
+ );
250
338
 
251
- writeFileSafe(
252
- "src/app/api/paddle/portal-session/route.ts",
253
- `import { NextRequest, NextResponse } from "next/server";
254
- import { createCustomerPortalSession } from "paddle-checkout-accelerator";
339
+ writeFileSafe(
340
+ "src/app/api/paddle/portal-session/route.ts",
341
+ `import { NextRequest, NextResponse } from "next/server";
342
+ import {
343
+ createCustomerPortalSession,
344
+ } from "paddle-checkout-accelerator";
255
345
 
256
346
  export async function POST(request: NextRequest) {
257
- const body = (await request.json()) as {
258
- customerId?: string;
259
- returnUrl?: string;
260
- };
347
+ const body =
348
+ (await request.json()) as {
349
+ customerId?: string;
350
+ returnUrl?: string;
351
+ };
261
352
 
262
353
  if (!body.customerId) {
263
- return NextResponse.json({ error: "Missing customerId" }, { status: 400 });
354
+ return NextResponse.json(
355
+ { error: "Missing customerId" },
356
+ { status: 400 }
357
+ );
264
358
  }
265
359
 
266
- const session = await createCustomerPortalSession({
267
- customerId: body.customerId,
268
- returnUrl: body.returnUrl,
269
- });
360
+ const session =
361
+ await createCustomerPortalSession({
362
+ customerId: body.customerId,
363
+ returnUrl: body.returnUrl,
364
+ });
270
365
 
271
366
  return NextResponse.json(session);
272
367
  }
273
- `,
274
- answers.force
275
- );
368
+ `
369
+ );
276
370
 
277
- writeFileSafe(
278
- "src/app/api/paddle/refresh-subscription/route.ts",
279
- `import { NextRequest, NextResponse } from "next/server";
280
- import { refreshSubscriptionFromPaddle } from "paddle-checkout-accelerator";
371
+ writeFileSafe(
372
+ "src/app/api/paddle/refresh-subscription/route.ts",
373
+ `import { NextRequest, NextResponse } from "next/server";
374
+ import {
375
+ refreshSubscriptionFromPaddle,
376
+ } from "paddle-checkout-accelerator";
281
377
 
282
378
  export async function POST(request: NextRequest) {
283
- const body = (await request.json()) as {
284
- subscriptionId?: string;
285
- };
379
+ const body =
380
+ (await request.json()) as {
381
+ subscriptionId?: string;
382
+ };
286
383
 
287
384
  if (!body.subscriptionId) {
288
- return NextResponse.json({ error: "Missing subscriptionId" }, { status: 400 });
385
+ return NextResponse.json(
386
+ { error: "Missing subscriptionId" },
387
+ { status: 400 }
388
+ );
289
389
  }
290
390
 
291
- const subscription = await refreshSubscriptionFromPaddle(body.subscriptionId);
391
+ const subscription =
392
+ await refreshSubscriptionFromPaddle(
393
+ body.subscriptionId
394
+ );
395
+
292
396
  return NextResponse.json(subscription);
293
397
  }
294
- `,
295
- answers.force
296
- );
398
+ `
399
+ );
297
400
 
298
- writeFileSafe(
299
- "src/app/api/paddle/repair-by-email/route.ts",
300
- `import { NextRequest, NextResponse } from "next/server";
301
- import { repairSubscriptionByEmail } from "paddle-checkout-accelerator";
401
+ writeFileSafe(
402
+ "src/app/api/paddle/repair-by-email/route.ts",
403
+ `import { NextRequest, NextResponse } from "next/server";
404
+ import {
405
+ repairSubscriptionByEmail,
406
+ } from "paddle-checkout-accelerator";
302
407
 
303
408
  export async function POST(request: NextRequest) {
304
- const body = (await request.json()) as {
305
- email?: string;
306
- };
409
+ const body =
410
+ (await request.json()) as {
411
+ email?: string;
412
+ };
307
413
 
308
414
  if (!body.email) {
309
- return NextResponse.json({ error: "Missing email" }, { status: 400 });
415
+ return NextResponse.json(
416
+ { error: "Missing email" },
417
+ { status: 400 }
418
+ );
310
419
  }
311
420
 
312
- const result = await repairSubscriptionByEmail(body.email);
421
+ const result =
422
+ await repairSubscriptionByEmail(
423
+ body.email
424
+ );
425
+
313
426
  return NextResponse.json(result);
314
427
  }
315
- `,
316
- answers.force
428
+ `
429
+ );
430
+ }
431
+
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
445
+ `);
446
+ process.exit(0);
447
+ }
448
+
449
+ if (command !== "init") {
450
+ console.error(
451
+ `Unknown command: ${command}`
317
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("");
464
+
465
+ writeBillingFile(config.adapter);
466
+
467
+ if (config.schema) {
468
+ writePrismaSchema();
469
+ }
470
+
471
+ if (config.routes) {
472
+ writeRoutes();
318
473
  }
319
474
 
320
- if (answers.env) {
475
+ if (config.env) {
321
476
  appendEnvSafe({
322
477
  NEXT_PUBLIC_PADDLE_CLIENT_TOKEN: "",
323
478
  PADDLE_API_KEY: "",
@@ -326,12 +481,33 @@ export async function POST(request: NextRequest) {
326
481
  }
327
482
 
328
483
  console.log("");
329
- console.log("✅ Paddle Checkout Accelerator installed.");
484
+ console.log(
485
+ "✅ Paddle Checkout Accelerator installed."
486
+ );
330
487
  console.log("");
331
488
  console.log("Next:");
332
- console.log("1. Add your Paddle keys to .env.local");
333
- console.log("2. Add schema models to your main prisma/schema.prisma if using Prisma");
334
- console.log("3. Configure Paddle webhook URL: /api/paddle/webhook");
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
+ }
335
511
  }
336
512
 
337
513
  main().catch((error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paddle-checkout-accelerator",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "scripts": {
5
5
  "dev": "next dev",
6
6
  "build": "next build",