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 +2 -0
- package/bin/paddle-checkout-accelerator.js +349 -80
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -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 =
|
|
10
|
-
|
|
39
|
+
const absolutePath =
|
|
40
|
+
path.join(cwd, filePath);
|
|
11
41
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
});
|
|
42
|
+
const existed =
|
|
43
|
+
fs.existsSync(absolutePath);
|
|
15
44
|
|
|
16
|
-
|
|
17
|
-
|
|
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(
|
|
22
|
-
|
|
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(
|
|
26
|
-
const envPath =
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
190
|
+
return answers;
|
|
55
191
|
}
|
|
56
192
|
|
|
57
|
-
|
|
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
|
-
|
|
60
|
-
console.log(`
|
|
61
|
-
Paddle Checkout Accelerator
|
|
202
|
+
import { prisma } from "@/lib/prisma";
|
|
62
203
|
|
|
63
|
-
|
|
64
|
-
|
|
204
|
+
const adapter =
|
|
205
|
+
createPrismaBillingAdapter(prisma);
|
|
65
206
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
- .env.local placeholders
|
|
73
|
-
`);
|
|
74
|
-
process.exit(0);
|
|
75
|
-
}
|
|
207
|
+
export const billing =
|
|
208
|
+
configureBilling({
|
|
209
|
+
adapter,
|
|
210
|
+
});
|
|
211
|
+
`
|
|
212
|
+
);
|
|
76
213
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
146
|
-
|
|
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
|
-
|
|
178
|
-
|
|
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
|
-
|
|
208
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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.
|
|
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",
|