paddle-checkout-accelerator 2.2.0 → 2.3.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,123 @@
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();
7
8
 
8
- function writeFileSafe(filePath, content) {
9
+ function writeFileSafe(filePath, content, overwrite = false) {
9
10
  const absolutePath = path.join(cwd, filePath);
10
- const dir = path.dirname(absolutePath);
11
+ fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
11
12
 
12
- fs.mkdirSync(dir, {
13
- recursive: true,
14
- });
15
-
16
- if (fs.existsSync(absolutePath)) {
13
+ if (fs.existsSync(absolutePath) && !overwrite) {
17
14
  console.log(`Skipped ${filePath} because it already exists`);
18
15
  return;
19
16
  }
20
17
 
21
18
  fs.writeFileSync(absolutePath, content);
22
- console.log(`Created ${filePath}`);
19
+ console.log(`${fs.existsSync(absolutePath) ? "Updated" : "Created"} ${filePath}`);
23
20
  }
24
21
 
25
- function appendEnvSafe(content) {
22
+ function appendEnvSafe(values) {
26
23
  const envPath = path.join(cwd, ".env.local");
24
+ const content = Object.entries(values)
25
+ .map(([key, value]) => `${key}=${value ?? ""}`)
26
+ .join("\n");
27
27
 
28
28
  if (!fs.existsSync(envPath)) {
29
- fs.writeFileSync(envPath, content);
29
+ fs.writeFileSync(envPath, content + "\n");
30
30
  console.log("Created .env.local");
31
31
  return;
32
32
  }
33
33
 
34
34
  const current = fs.readFileSync(envPath, "utf8");
35
-
36
- const nextLines = content
35
+ const lines = content
37
36
  .split("\n")
38
- .filter(Boolean)
39
37
  .filter((line) => {
40
38
  const key = line.split("=")[0];
41
39
  return !current.includes(`${key}=`);
42
40
  });
43
41
 
44
- if (nextLines.length === 0) {
42
+ if (lines.length) {
43
+ fs.appendFileSync(envPath, "\n" + lines.join("\n") + "\n");
44
+ console.log("Updated .env.local");
45
+ } else {
45
46
  console.log("Skipped .env.local because Paddle keys already exist");
46
- return;
47
47
  }
48
-
49
- fs.appendFileSync(
50
- envPath,
51
- `\n${nextLines.join("\n")}\n`
52
- );
53
-
54
- console.log("Updated .env.local");
55
48
  }
56
49
 
57
- const command = process.argv[2];
50
+ async function main() {
51
+ const command = process.argv[2];
58
52
 
59
- if (!command || command === "--help" || command === "-h") {
60
- console.log(`
53
+ if (!command || command === "--help" || command === "-h") {
54
+ console.log(`
61
55
  Paddle Checkout Accelerator
62
56
 
63
57
  Usage:
64
58
  npx paddle-checkout-accelerator init
65
-
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
59
  `);
74
- process.exit(0);
75
- }
60
+ process.exit(0);
61
+ }
76
62
 
77
- if (command !== "init") {
78
- console.error(`Unknown command: ${command}`);
79
- process.exit(1);
80
- }
63
+ if (command !== "init") {
64
+ console.error(`Unknown command: ${command}`);
65
+ process.exit(1);
66
+ }
81
67
 
82
- writeFileSafe(
83
- "src/lib/billing.ts",
84
- `import {
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
+ ]);
112
+
113
+ if (!answers.adapter) {
114
+ console.log("Install cancelled.");
115
+ process.exit(0);
116
+ }
117
+
118
+ if (answers.adapter === "memory") {
119
+ writeFileSafe(
120
+ "src/lib/billing.ts",
121
+ `import {
85
122
  configureBilling,
86
123
  memoryBillingAdapter,
87
124
  } from "paddle-checkout-accelerator";
@@ -90,12 +127,99 @@ export const billing =
90
127
  configureBilling({
91
128
  adapter: memoryBillingAdapter,
92
129
  });
93
- `
94
- );
130
+ `,
131
+ answers.force
132
+ );
133
+ }
134
+
135
+ if (answers.adapter === "prisma") {
136
+ writeFileSafe(
137
+ "src/lib/billing.ts",
138
+ `import {
139
+ configureBilling,
140
+ createPrismaBillingAdapter,
141
+ } from "paddle-checkout-accelerator";
142
+
143
+ import { prisma } from "@/lib/prisma";
144
+
145
+ const adapter =
146
+ createPrismaBillingAdapter(prisma);
147
+
148
+ export const billing =
149
+ configureBilling({
150
+ adapter,
151
+ });
152
+ `,
153
+ answers.force
154
+ );
155
+
156
+ writeFileSafe(
157
+ "src/lib/prisma.ts",
158
+ `import { PrismaClient } from "@prisma/client";
159
+
160
+ const globalForPrisma =
161
+ globalThis as unknown as {
162
+ prisma?: PrismaClient;
163
+ };
164
+
165
+ export const prisma =
166
+ globalForPrisma.prisma ??
167
+ new PrismaClient();
168
+
169
+ if (process.env.NODE_ENV !== "production") {
170
+ globalForPrisma.prisma = prisma;
171
+ }
172
+ `,
173
+ answers.force
174
+ );
175
+
176
+ if (answers.schema) {
177
+ writeFileSafe(
178
+ "prisma/paddle-accelerator.schema.prisma",
179
+ `model Subscription {
180
+ userId String @id
181
+ plan String
182
+ status String
183
+ paddleCustomerId String?
184
+ paddleSubscriptionId String?
185
+ currentPeriodEnd String?
186
+ }
187
+
188
+ model UsageEvent {
189
+ id String @id @default(cuid())
190
+ userId String
191
+ key String
192
+ period String
193
+ count Int
95
194
 
96
- writeFileSafe(
97
- "src/app/api/paddle/webhook/route.ts",
98
- `import { NextRequest, NextResponse } from "next/server";
195
+ @@unique([userId, key, period], name: "userId_key_period")
196
+ }
197
+
198
+ model Team {
199
+ teamId String @id
200
+ name String
201
+ plan String
202
+ ownerId String
203
+ members Json
204
+ }
205
+
206
+ model BillingEvent {
207
+ id String @id
208
+ userId String
209
+ type String
210
+ createdAt DateTime
211
+ metadata Json?
212
+ }
213
+ `,
214
+ answers.force
215
+ );
216
+ }
217
+ }
218
+
219
+ if (answers.routes) {
220
+ writeFileSafe(
221
+ "src/app/api/paddle/webhook/route.ts",
222
+ `import { NextRequest, NextResponse } from "next/server";
99
223
  import {
100
224
  syncPaddleEvent,
101
225
  verifyPaddleWebhook,
@@ -103,145 +227,114 @@ import {
103
227
 
104
228
  export async function POST(request: NextRequest) {
105
229
  const rawBody = await request.text();
106
-
107
- const signature =
108
- request.headers.get("paddle-signature");
230
+ const signature = request.headers.get("paddle-signature");
109
231
 
110
232
  if (!signature) {
111
- return NextResponse.json(
112
- { error: "Missing Paddle signature" },
113
- { status: 400 }
114
- );
233
+ return NextResponse.json({ error: "Missing Paddle signature" }, { status: 400 });
115
234
  }
116
235
 
117
236
  try {
118
- const event =
119
- await verifyPaddleWebhook(
120
- rawBody,
121
- signature
122
- );
123
-
237
+ const event = await verifyPaddleWebhook(rawBody, signature);
124
238
  await syncPaddleEvent(event);
125
-
126
- return NextResponse.json({
127
- success: true,
128
- });
239
+ return NextResponse.json({ success: true });
129
240
  } catch (error) {
130
241
  return NextResponse.json(
131
- {
132
- error:
133
- error instanceof Error
134
- ? error.message
135
- : "Webhook verification failed",
136
- },
242
+ { error: error instanceof Error ? error.message : "Webhook verification failed" },
137
243
  { status: 401 }
138
244
  );
139
245
  }
140
246
  }
141
- `
142
- );
247
+ `,
248
+ answers.force
249
+ );
143
250
 
144
- writeFileSafe(
145
- "src/app/api/paddle/portal-session/route.ts",
146
- `import { NextRequest, NextResponse } from "next/server";
147
- import {
148
- createCustomerPortalSession,
149
- } from "paddle-checkout-accelerator";
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";
150
255
 
151
256
  export async function POST(request: NextRequest) {
152
- const body =
153
- (await request.json()) as {
154
- customerId?: string;
155
- returnUrl?: string;
156
- };
257
+ const body = (await request.json()) as {
258
+ customerId?: string;
259
+ returnUrl?: string;
260
+ };
157
261
 
158
262
  if (!body.customerId) {
159
- return NextResponse.json(
160
- { error: "Missing customerId" },
161
- { status: 400 }
162
- );
263
+ return NextResponse.json({ error: "Missing customerId" }, { status: 400 });
163
264
  }
164
265
 
165
- const session =
166
- await createCustomerPortalSession({
167
- customerId: body.customerId,
168
- returnUrl: body.returnUrl,
169
- });
266
+ const session = await createCustomerPortalSession({
267
+ customerId: body.customerId,
268
+ returnUrl: body.returnUrl,
269
+ });
170
270
 
171
271
  return NextResponse.json(session);
172
272
  }
173
- `
174
- );
273
+ `,
274
+ answers.force
275
+ );
175
276
 
176
- writeFileSafe(
177
- "src/app/api/paddle/refresh-subscription/route.ts",
178
- `import { NextRequest, NextResponse } from "next/server";
179
- import {
180
- refreshSubscriptionFromPaddle,
181
- } from "paddle-checkout-accelerator";
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";
182
281
 
183
282
  export async function POST(request: NextRequest) {
184
- const body =
185
- (await request.json()) as {
186
- subscriptionId?: string;
187
- };
283
+ const body = (await request.json()) as {
284
+ subscriptionId?: string;
285
+ };
188
286
 
189
287
  if (!body.subscriptionId) {
190
- return NextResponse.json(
191
- { error: "Missing subscriptionId" },
192
- { status: 400 }
193
- );
288
+ return NextResponse.json({ error: "Missing subscriptionId" }, { status: 400 });
194
289
  }
195
290
 
196
- const subscription =
197
- await refreshSubscriptionFromPaddle(
198
- body.subscriptionId
199
- );
200
-
291
+ const subscription = await refreshSubscriptionFromPaddle(body.subscriptionId);
201
292
  return NextResponse.json(subscription);
202
293
  }
203
- `
204
- );
294
+ `,
295
+ answers.force
296
+ );
205
297
 
206
- writeFileSafe(
207
- "src/app/api/paddle/repair-by-email/route.ts",
208
- `import { NextRequest, NextResponse } from "next/server";
209
- import {
210
- repairSubscriptionByEmail,
211
- } from "paddle-checkout-accelerator";
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";
212
302
 
213
303
  export async function POST(request: NextRequest) {
214
- const body =
215
- (await request.json()) as {
216
- email?: string;
217
- };
304
+ const body = (await request.json()) as {
305
+ email?: string;
306
+ };
218
307
 
219
308
  if (!body.email) {
220
- return NextResponse.json(
221
- { error: "Missing email" },
222
- { status: 400 }
223
- );
309
+ return NextResponse.json({ error: "Missing email" }, { status: 400 });
224
310
  }
225
311
 
226
- const result =
227
- await repairSubscriptionByEmail(
228
- body.email
229
- );
230
-
312
+ const result = await repairSubscriptionByEmail(body.email);
231
313
  return NextResponse.json(result);
232
314
  }
233
- `
234
- );
315
+ `,
316
+ answers.force
317
+ );
318
+ }
235
319
 
236
- appendEnvSafe(`NEXT_PUBLIC_PADDLE_CLIENT_TOKEN=
237
- PADDLE_API_KEY=
238
- PADDLE_WEBHOOK_SECRET=
239
- `);
320
+ if (answers.env) {
321
+ appendEnvSafe({
322
+ NEXT_PUBLIC_PADDLE_CLIENT_TOKEN: "",
323
+ PADDLE_API_KEY: "",
324
+ PADDLE_WEBHOOK_SECRET: "",
325
+ });
326
+ }
327
+
328
+ console.log("");
329
+ console.log("✅ Paddle Checkout Accelerator installed.");
330
+ console.log("");
331
+ 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");
335
+ }
240
336
 
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");
337
+ main().catch((error) => {
338
+ console.error(error);
339
+ process.exit(1);
340
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paddle-checkout-accelerator",
3
- "version": "2.2.0",
3
+ "version": "2.3.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",