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 +2 -0
- package/bin/paddle-checkout-accelerator.js +340 -0
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -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.
|
|
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
|
}
|