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.
- package/bin/paddle-checkout-accelerator.js +342 -166
- package/package.json +1 -1
|
@@ -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
|
-
|
|
10
|
-
|
|
11
|
-
fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
|
|
10
|
+
const command =
|
|
11
|
+
process.argv[2];
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
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(
|
|
19
|
-
|
|
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 =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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(
|
|
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(
|
|
112
|
+
console.log(
|
|
113
|
+
"Skipped .env.local because Paddle keys already exist"
|
|
114
|
+
);
|
|
47
115
|
}
|
|
48
116
|
}
|
|
49
117
|
|
|
50
|
-
async function
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
}
|
|
288
|
+
`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
218
291
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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(
|
|
306
|
+
return NextResponse.json(
|
|
307
|
+
{ error: "Missing Paddle signature" },
|
|
308
|
+
{ status: 400 }
|
|
309
|
+
);
|
|
234
310
|
}
|
|
235
311
|
|
|
236
312
|
try {
|
|
237
|
-
const event =
|
|
313
|
+
const event =
|
|
314
|
+
await verifyPaddleWebhook(
|
|
315
|
+
rawBody,
|
|
316
|
+
signature
|
|
317
|
+
);
|
|
318
|
+
|
|
238
319
|
await syncPaddleEvent(event);
|
|
239
|
-
|
|
320
|
+
|
|
321
|
+
return NextResponse.json({
|
|
322
|
+
success: true,
|
|
323
|
+
});
|
|
240
324
|
} catch (error) {
|
|
241
325
|
return NextResponse.json(
|
|
242
|
-
{
|
|
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
|
-
|
|
249
|
-
);
|
|
336
|
+
`
|
|
337
|
+
);
|
|
250
338
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
import {
|
|
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 =
|
|
258
|
-
|
|
259
|
-
|
|
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(
|
|
354
|
+
return NextResponse.json(
|
|
355
|
+
{ error: "Missing customerId" },
|
|
356
|
+
{ status: 400 }
|
|
357
|
+
);
|
|
264
358
|
}
|
|
265
359
|
|
|
266
|
-
const session =
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
275
|
-
);
|
|
368
|
+
`
|
|
369
|
+
);
|
|
276
370
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
import {
|
|
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 =
|
|
284
|
-
|
|
285
|
-
|
|
379
|
+
const body =
|
|
380
|
+
(await request.json()) as {
|
|
381
|
+
subscriptionId?: string;
|
|
382
|
+
};
|
|
286
383
|
|
|
287
384
|
if (!body.subscriptionId) {
|
|
288
|
-
return NextResponse.json(
|
|
385
|
+
return NextResponse.json(
|
|
386
|
+
{ error: "Missing subscriptionId" },
|
|
387
|
+
{ status: 400 }
|
|
388
|
+
);
|
|
289
389
|
}
|
|
290
390
|
|
|
291
|
-
const subscription =
|
|
391
|
+
const subscription =
|
|
392
|
+
await refreshSubscriptionFromPaddle(
|
|
393
|
+
body.subscriptionId
|
|
394
|
+
);
|
|
395
|
+
|
|
292
396
|
return NextResponse.json(subscription);
|
|
293
397
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
);
|
|
398
|
+
`
|
|
399
|
+
);
|
|
297
400
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
import {
|
|
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 =
|
|
305
|
-
|
|
306
|
-
|
|
409
|
+
const body =
|
|
410
|
+
(await request.json()) as {
|
|
411
|
+
email?: string;
|
|
412
|
+
};
|
|
307
413
|
|
|
308
414
|
if (!body.email) {
|
|
309
|
-
return NextResponse.json(
|
|
415
|
+
return NextResponse.json(
|
|
416
|
+
{ error: "Missing email" },
|
|
417
|
+
{ status: 400 }
|
|
418
|
+
);
|
|
310
419
|
}
|
|
311
420
|
|
|
312
|
-
const result =
|
|
421
|
+
const result =
|
|
422
|
+
await repairSubscriptionByEmail(
|
|
423
|
+
body.email
|
|
424
|
+
);
|
|
425
|
+
|
|
313
426
|
return NextResponse.json(result);
|
|
314
427
|
}
|
|
315
|
-
|
|
316
|
-
|
|
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 (
|
|
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(
|
|
484
|
+
console.log(
|
|
485
|
+
"✅ Paddle Checkout Accelerator installed."
|
|
486
|
+
);
|
|
330
487
|
console.log("");
|
|
331
488
|
console.log("Next:");
|
|
332
|
-
console.log(
|
|
333
|
-
|
|
334
|
-
|
|
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) => {
|