paddle-checkout-accelerator 2.1.1 → 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
@@ -0,0 +1,340 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import prompts from "prompts";
6
+
7
+ const cwd = process.cwd();
8
+
9
+ function writeFileSafe(filePath, content, overwrite = false) {
10
+ const absolutePath = path.join(cwd, filePath);
11
+ fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
12
+
13
+ if (fs.existsSync(absolutePath) && !overwrite) {
14
+ console.log(`Skipped ${filePath} because it already exists`);
15
+ return;
16
+ }
17
+
18
+ fs.writeFileSync(absolutePath, content);
19
+ console.log(`${fs.existsSync(absolutePath) ? "Updated" : "Created"} ${filePath}`);
20
+ }
21
+
22
+ 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");
27
+
28
+ if (!fs.existsSync(envPath)) {
29
+ fs.writeFileSync(envPath, content + "\n");
30
+ console.log("Created .env.local");
31
+ return;
32
+ }
33
+
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
+ });
41
+
42
+ if (lines.length) {
43
+ fs.appendFileSync(envPath, "\n" + lines.join("\n") + "\n");
44
+ console.log("Updated .env.local");
45
+ } else {
46
+ console.log("Skipped .env.local because Paddle keys already exist");
47
+ }
48
+ }
49
+
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);
61
+ }
62
+
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
+ ]);
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 {
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
+ }
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
194
+
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";
223
+ import {
224
+ syncPaddleEvent,
225
+ verifyPaddleWebhook,
226
+ } from "paddle-checkout-accelerator";
227
+
228
+ export async function POST(request: NextRequest) {
229
+ const rawBody = await request.text();
230
+ const signature = request.headers.get("paddle-signature");
231
+
232
+ if (!signature) {
233
+ return NextResponse.json({ error: "Missing Paddle signature" }, { status: 400 });
234
+ }
235
+
236
+ try {
237
+ const event = await verifyPaddleWebhook(rawBody, signature);
238
+ await syncPaddleEvent(event);
239
+ return NextResponse.json({ success: true });
240
+ } catch (error) {
241
+ return NextResponse.json(
242
+ { error: error instanceof Error ? error.message : "Webhook verification failed" },
243
+ { status: 401 }
244
+ );
245
+ }
246
+ }
247
+ `,
248
+ answers.force
249
+ );
250
+
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";
255
+
256
+ export async function POST(request: NextRequest) {
257
+ const body = (await request.json()) as {
258
+ customerId?: string;
259
+ returnUrl?: string;
260
+ };
261
+
262
+ if (!body.customerId) {
263
+ return NextResponse.json({ error: "Missing customerId" }, { status: 400 });
264
+ }
265
+
266
+ const session = await createCustomerPortalSession({
267
+ customerId: body.customerId,
268
+ returnUrl: body.returnUrl,
269
+ });
270
+
271
+ return NextResponse.json(session);
272
+ }
273
+ `,
274
+ answers.force
275
+ );
276
+
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";
281
+
282
+ export async function POST(request: NextRequest) {
283
+ const body = (await request.json()) as {
284
+ subscriptionId?: string;
285
+ };
286
+
287
+ if (!body.subscriptionId) {
288
+ return NextResponse.json({ error: "Missing subscriptionId" }, { status: 400 });
289
+ }
290
+
291
+ const subscription = await refreshSubscriptionFromPaddle(body.subscriptionId);
292
+ return NextResponse.json(subscription);
293
+ }
294
+ `,
295
+ answers.force
296
+ );
297
+
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";
302
+
303
+ export async function POST(request: NextRequest) {
304
+ const body = (await request.json()) as {
305
+ email?: string;
306
+ };
307
+
308
+ if (!body.email) {
309
+ return NextResponse.json({ error: "Missing email" }, { status: 400 });
310
+ }
311
+
312
+ const result = await repairSubscriptionByEmail(body.email);
313
+ return NextResponse.json(result);
314
+ }
315
+ `,
316
+ answers.force
317
+ );
318
+ }
319
+
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
+ }
336
+
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.1.1",
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",
@@ -53,10 +55,14 @@
53
55
  },
54
56
  "files": [
55
57
  "dist",
58
+ "bin",
56
59
  "README.md",
57
60
  "LICENSE",
58
61
  "docs",
59
62
  "recipes"
60
63
  ],
61
- "module": "./dist/index.js"
64
+ "module": "./dist/index.js",
65
+ "bin": {
66
+ "paddle-checkout-accelerator": "./bin/paddle-checkout-accelerator.js"
67
+ }
62
68
  }