stripe-no-webhooks 0.0.1
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 +152 -0
- package/bin/cli.js +238 -0
- package/dist/index.d.mts +37 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +118 -0
- package/dist/index.mjs +81 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# stripe-no-webhooks
|
|
2
|
+
|
|
3
|
+
Stripe integration without dealing with webhooks. Automatically syncs Stripe data to your PostgreSQL database and provides simple callbacks for subscription events.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install stripe-no-webhooks stripe
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
### 1. Create Stripe schema and tables
|
|
14
|
+
|
|
15
|
+
**Option 1:** Run the migration command
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx stripe-no-webhooks migrate postgresql://postgres.[USER]:[PASSWORD]@[DB_URL]/postgres
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Option 2:** Copy `stripe_schema.sql` and run the query manually
|
|
22
|
+
|
|
23
|
+
### 2. Set up the webhook handler
|
|
24
|
+
|
|
25
|
+
Create a webhook endpoint in your Next.js app:
|
|
26
|
+
|
|
27
|
+
#### App Router (recommended)
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
// app/api/stripe/webhook/route.ts
|
|
31
|
+
import { createStripeWebhookHandler } from "stripe-no-webhooks";
|
|
32
|
+
|
|
33
|
+
const handler = createStripeWebhookHandler({
|
|
34
|
+
databaseUrl: process.env.DATABASE_URL!,
|
|
35
|
+
stripeSecretKey: process.env.STRIPE_SECRET_KEY!,
|
|
36
|
+
stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
|
|
37
|
+
callbacks: {
|
|
38
|
+
onSubscriptionCreated: async (subscription) => {
|
|
39
|
+
// Called when a new subscription is created
|
|
40
|
+
console.log("New subscription:", subscription.id);
|
|
41
|
+
// e.g., send welcome email, provision resources, etc.
|
|
42
|
+
},
|
|
43
|
+
onSubscriptionCancelled: async (subscription) => {
|
|
44
|
+
// Called when a subscription is cancelled
|
|
45
|
+
console.log("Subscription cancelled:", subscription.id);
|
|
46
|
+
// e.g., send cancellation email, revoke access, etc.
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export const POST = handler;
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### Pages Router
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
// pages/api/stripe/webhook.ts
|
|
58
|
+
import { createStripeWebhookHandler } from "stripe-no-webhooks";
|
|
59
|
+
import type { NextApiRequest, NextApiResponse } from "next";
|
|
60
|
+
|
|
61
|
+
const handler = createStripeWebhookHandler({
|
|
62
|
+
databaseUrl: process.env.DATABASE_URL!,
|
|
63
|
+
stripeSecretKey: process.env.STRIPE_SECRET_KEY!,
|
|
64
|
+
stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
|
|
65
|
+
callbacks: {
|
|
66
|
+
onSubscriptionCreated: async (subscription) => {
|
|
67
|
+
console.log("New subscription:", subscription.id);
|
|
68
|
+
},
|
|
69
|
+
onSubscriptionCancelled: async (subscription) => {
|
|
70
|
+
console.log("Subscription cancelled:", subscription.id);
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Disable body parsing, we need the raw body for webhook verification
|
|
76
|
+
export const config = {
|
|
77
|
+
api: {
|
|
78
|
+
bodyParser: false,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export default async function webhookHandler(
|
|
83
|
+
req: NextApiRequest,
|
|
84
|
+
res: NextApiResponse
|
|
85
|
+
) {
|
|
86
|
+
if (req.method !== "POST") {
|
|
87
|
+
return res.status(405).json({ error: "Method not allowed" });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Convert NextApiRequest to Request for the handler
|
|
91
|
+
const body = await new Promise<string>((resolve) => {
|
|
92
|
+
let data = "";
|
|
93
|
+
req.on("data", (chunk) => (data += chunk));
|
|
94
|
+
req.on("end", () => resolve(data));
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const request = new Request(`https://${req.headers.host}${req.url}`, {
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers: new Headers(req.headers as Record<string, string>),
|
|
100
|
+
body,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const response = await handler(request);
|
|
104
|
+
res.status(response.status).send(await response.text());
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 3. Configure Stripe webhook
|
|
109
|
+
|
|
110
|
+
Run the config command to automatically create a webhook in your Stripe account:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npx stripe-no-webhooks config
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
This will:
|
|
117
|
+
|
|
118
|
+
1. Ask for your Stripe Secret Key
|
|
119
|
+
2. Ask for your site URL (defaults to `NEXT_PUBLIC_SITE_URL` if set)
|
|
120
|
+
3. Create a webhook endpoint at `https://yoursite.com/api/stripe/webhook` listening to all events
|
|
121
|
+
4. Automatically add `STRIPE_WEBHOOK_SECRET` to your `.env` files (if they exist)
|
|
122
|
+
|
|
123
|
+
## Environment Variables
|
|
124
|
+
|
|
125
|
+
```env
|
|
126
|
+
DATABASE_URL=postgresql://user:pass@host:port/db
|
|
127
|
+
STRIPE_SECRET_KEY=sk_test_...
|
|
128
|
+
STRIPE_WEBHOOK_SECRET=whsec_... # Output from `npx stripe-no-webhooks config`
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## What gets synced?
|
|
132
|
+
|
|
133
|
+
All Stripe webhook events are automatically synced to your PostgreSQL database in the `stripe` schema. This includes:
|
|
134
|
+
|
|
135
|
+
- Customers
|
|
136
|
+
- Subscriptions
|
|
137
|
+
- Products
|
|
138
|
+
- Prices
|
|
139
|
+
- Invoices
|
|
140
|
+
- Payment methods
|
|
141
|
+
- And more...
|
|
142
|
+
|
|
143
|
+
You can query this data directly from your database without making API calls to Stripe.
|
|
144
|
+
|
|
145
|
+
## Callbacks
|
|
146
|
+
|
|
147
|
+
| Callback | Event | Description |
|
|
148
|
+
| ------------------------- | --------------------------------------------------------------- | ----------------------------------------- |
|
|
149
|
+
| `onSubscriptionCreated` | `customer.subscription.created` | Called when a new subscription is created |
|
|
150
|
+
| `onSubscriptionCancelled` | `customer.subscription.deleted` or status changes to `canceled` | Called when a subscription is cancelled |
|
|
151
|
+
|
|
152
|
+
Both callbacks receive the full Stripe `Subscription` object.
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { runMigrations } = require("@supabase/stripe-sync-engine");
|
|
4
|
+
const readline = require("readline");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
const command = args[0];
|
|
10
|
+
const databaseUrl = args[1];
|
|
11
|
+
|
|
12
|
+
function createPrompt() {
|
|
13
|
+
return readline.createInterface({
|
|
14
|
+
input: process.stdin,
|
|
15
|
+
output: process.stdout,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function question(rl, query, defaultValue = "") {
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
const prompt = defaultValue ? `${query} (${defaultValue}): ` : `${query}: `;
|
|
22
|
+
rl.question(prompt, (answer) => {
|
|
23
|
+
resolve(answer || defaultValue);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function questionHidden(rl, query) {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
const stdin = process.stdin;
|
|
31
|
+
const stdout = process.stdout;
|
|
32
|
+
|
|
33
|
+
stdout.write(`${query}: `);
|
|
34
|
+
|
|
35
|
+
stdin.setRawMode(true);
|
|
36
|
+
stdin.resume();
|
|
37
|
+
stdin.setEncoding("utf8");
|
|
38
|
+
|
|
39
|
+
let input = "";
|
|
40
|
+
const onData = (char) => {
|
|
41
|
+
if (char === "\n" || char === "\r" || char === "\u0004") {
|
|
42
|
+
stdin.setRawMode(false);
|
|
43
|
+
stdin.pause();
|
|
44
|
+
stdin.removeListener("data", onData);
|
|
45
|
+
stdout.write("\n");
|
|
46
|
+
resolve(input);
|
|
47
|
+
} else if (char === "\u0003") {
|
|
48
|
+
// Ctrl+C
|
|
49
|
+
process.exit();
|
|
50
|
+
} else if (char === "\u007F" || char === "\b") {
|
|
51
|
+
// Backspace
|
|
52
|
+
if (input.length > 0) {
|
|
53
|
+
input = input.slice(0, -1);
|
|
54
|
+
stdout.write("\b \b"); // Erase character from display
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
input += char;
|
|
58
|
+
stdout.write("**************"); // Show asterisk for each character
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
stdin.on("data", onData);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function migrate(databaseUrl) {
|
|
67
|
+
if (!databaseUrl) {
|
|
68
|
+
console.error("❌ Missing database URL.\n");
|
|
69
|
+
console.log(
|
|
70
|
+
"Usage:\n npx stripe-no-webhooks migrate <postgres_connection_string>"
|
|
71
|
+
);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log("🚀 Running Stripe migrations...");
|
|
76
|
+
try {
|
|
77
|
+
await runMigrations({
|
|
78
|
+
databaseUrl,
|
|
79
|
+
schema: "stripe",
|
|
80
|
+
logger: console,
|
|
81
|
+
});
|
|
82
|
+
console.log("✅ Migrations completed successfully!");
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error("❌ Migration failed:");
|
|
85
|
+
console.error(error);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function config() {
|
|
91
|
+
let Stripe;
|
|
92
|
+
try {
|
|
93
|
+
Stripe = require("stripe").default || require("stripe");
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.error("❌ Stripe package not found.");
|
|
96
|
+
console.log("Please install it first: npm install stripe");
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const rl = createPrompt();
|
|
101
|
+
|
|
102
|
+
console.log("\n🔧 Stripe Webhook Configuration\n");
|
|
103
|
+
|
|
104
|
+
// Get Stripe API key (hidden input)
|
|
105
|
+
rl.close(); // Close readline for hidden input
|
|
106
|
+
const stripeSecretKey = await questionHidden(
|
|
107
|
+
null,
|
|
108
|
+
"Enter your Stripe Secret Key (sk_...)"
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
if (!stripeSecretKey || !stripeSecretKey.startsWith("sk_")) {
|
|
112
|
+
console.error("❌ Invalid Stripe Secret Key. It should start with 'sk_'");
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Reopen readline for remaining questions
|
|
117
|
+
const rl2 = createPrompt();
|
|
118
|
+
|
|
119
|
+
// Get site URL with default from env
|
|
120
|
+
const defaultSiteUrl = process.env.NEXT_PUBLIC_SITE_URL || "";
|
|
121
|
+
const siteUrl = await question(rl2, "Enter your site URL", defaultSiteUrl);
|
|
122
|
+
|
|
123
|
+
if (!siteUrl) {
|
|
124
|
+
console.error("❌ Site URL is required");
|
|
125
|
+
rl2.close();
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Validate URL
|
|
130
|
+
let webhookUrl;
|
|
131
|
+
try {
|
|
132
|
+
const url = new URL(siteUrl);
|
|
133
|
+
webhookUrl = `${url.origin}/api/stripe/webhook`;
|
|
134
|
+
} catch (e) {
|
|
135
|
+
console.error("❌ Invalid URL format");
|
|
136
|
+
rl2.close();
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
rl2.close();
|
|
141
|
+
|
|
142
|
+
console.log(`\n📡 Creating webhook endpoint: ${webhookUrl}\n`);
|
|
143
|
+
|
|
144
|
+
const stripe = new Stripe(stripeSecretKey);
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
// Get all available event types
|
|
148
|
+
const webhook = await stripe.webhookEndpoints.create({
|
|
149
|
+
url: webhookUrl,
|
|
150
|
+
enabled_events: ["*"], // Listen to all events
|
|
151
|
+
description: "Created by stripe-no-webhooks CLI",
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
console.log("✅ Webhook created successfully!\n");
|
|
155
|
+
|
|
156
|
+
// Try to add webhook secret to env files
|
|
157
|
+
const envFiles = [
|
|
158
|
+
".env",
|
|
159
|
+
".env.local",
|
|
160
|
+
".env.development",
|
|
161
|
+
".env.production",
|
|
162
|
+
];
|
|
163
|
+
const cwd = process.cwd();
|
|
164
|
+
const updatedFiles = [];
|
|
165
|
+
|
|
166
|
+
for (const envFile of envFiles) {
|
|
167
|
+
const envPath = path.join(cwd, envFile);
|
|
168
|
+
if (fs.existsSync(envPath)) {
|
|
169
|
+
let content = fs.readFileSync(envPath, "utf8");
|
|
170
|
+
const secretLine = `STRIPE_WEBHOOK_SECRET=${webhook.secret}`;
|
|
171
|
+
|
|
172
|
+
if (content.includes("STRIPE_WEBHOOK_SECRET=")) {
|
|
173
|
+
// Replace existing value
|
|
174
|
+
content = content.replace(/STRIPE_WEBHOOK_SECRET=.*/, secretLine);
|
|
175
|
+
} else {
|
|
176
|
+
// Append to file
|
|
177
|
+
const newline = content.endsWith("\n") ? "" : "\n";
|
|
178
|
+
content = content + newline + secretLine + "\n";
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
fs.writeFileSync(envPath, content);
|
|
182
|
+
updatedFiles.push(envFile);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (updatedFiles.length > 0) {
|
|
187
|
+
console.log(
|
|
188
|
+
`📝 Updated ${updatedFiles.join(
|
|
189
|
+
", "
|
|
190
|
+
)} with STRIPE_WEBHOOK_SECRET\nREMEMBER: Update the enviroment variable in Vercel too\nSTRIPE_WEBHOOK_SECRET=${
|
|
191
|
+
webhook.secret
|
|
192
|
+
}`
|
|
193
|
+
);
|
|
194
|
+
} else {
|
|
195
|
+
console.log("Add this to your environment variables:\n");
|
|
196
|
+
console.log(`STRIPE_WEBHOOK_SECRET=${webhook.secret}\n`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.log("─".repeat(50));
|
|
200
|
+
console.log("Webhook ID:", webhook.id);
|
|
201
|
+
console.log("Webhook URL:", webhook.url);
|
|
202
|
+
console.log("Events: All events (*)");
|
|
203
|
+
if (updatedFiles.length === 0) {
|
|
204
|
+
console.log("Secret:", webhook.secret);
|
|
205
|
+
}
|
|
206
|
+
console.log("─".repeat(50));
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (error.type === "StripeAuthenticationError") {
|
|
209
|
+
console.error("❌ Authentication failed. Check your Stripe Secret Key.");
|
|
210
|
+
} else if (error.type === "StripeInvalidRequestError") {
|
|
211
|
+
console.error("❌ Invalid request:", error.message);
|
|
212
|
+
} else {
|
|
213
|
+
console.error("❌ Failed to create webhook:");
|
|
214
|
+
console.error(error.message);
|
|
215
|
+
}
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function main() {
|
|
221
|
+
switch (command) {
|
|
222
|
+
case "migrate":
|
|
223
|
+
await migrate(databaseUrl);
|
|
224
|
+
break;
|
|
225
|
+
|
|
226
|
+
case "config":
|
|
227
|
+
await config();
|
|
228
|
+
break;
|
|
229
|
+
|
|
230
|
+
default:
|
|
231
|
+
console.log("Usage:");
|
|
232
|
+
console.log(" npx stripe-no-webhooks migrate <connection_string>");
|
|
233
|
+
console.log(" npx stripe-no-webhooks config");
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
main();
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import Stripe from 'stripe';
|
|
2
|
+
|
|
3
|
+
interface StripeWebhookCallbacks {
|
|
4
|
+
/**
|
|
5
|
+
* Called when a new subscription is created
|
|
6
|
+
*/
|
|
7
|
+
onSubscriptionCreated?: (subscription: Stripe.Subscription) => void | Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Called when a subscription is cancelled
|
|
10
|
+
*/
|
|
11
|
+
onSubscriptionCancelled?: (subscription: Stripe.Subscription) => void | Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
interface StripeWebhookConfig {
|
|
14
|
+
/**
|
|
15
|
+
* PostgreSQL connection string for the Stripe sync database
|
|
16
|
+
*/
|
|
17
|
+
databaseUrl: string;
|
|
18
|
+
/**
|
|
19
|
+
* Stripe secret key (sk_test_... or sk_live_...)
|
|
20
|
+
*/
|
|
21
|
+
stripeSecretKey: string;
|
|
22
|
+
/**
|
|
23
|
+
* Stripe webhook signing secret (whsec_...)
|
|
24
|
+
*/
|
|
25
|
+
stripeWebhookSecret: string;
|
|
26
|
+
/**
|
|
27
|
+
* Database schema name (defaults to 'stripe')
|
|
28
|
+
*/
|
|
29
|
+
schema?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Callbacks for subscription events
|
|
32
|
+
*/
|
|
33
|
+
callbacks?: StripeWebhookCallbacks;
|
|
34
|
+
}
|
|
35
|
+
declare function createStripeWebhookHandler(config: StripeWebhookConfig): (request: Request) => Promise<Response>;
|
|
36
|
+
|
|
37
|
+
export { type StripeWebhookCallbacks, type StripeWebhookConfig, createStripeWebhookHandler };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import Stripe from 'stripe';
|
|
2
|
+
|
|
3
|
+
interface StripeWebhookCallbacks {
|
|
4
|
+
/**
|
|
5
|
+
* Called when a new subscription is created
|
|
6
|
+
*/
|
|
7
|
+
onSubscriptionCreated?: (subscription: Stripe.Subscription) => void | Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Called when a subscription is cancelled
|
|
10
|
+
*/
|
|
11
|
+
onSubscriptionCancelled?: (subscription: Stripe.Subscription) => void | Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
interface StripeWebhookConfig {
|
|
14
|
+
/**
|
|
15
|
+
* PostgreSQL connection string for the Stripe sync database
|
|
16
|
+
*/
|
|
17
|
+
databaseUrl: string;
|
|
18
|
+
/**
|
|
19
|
+
* Stripe secret key (sk_test_... or sk_live_...)
|
|
20
|
+
*/
|
|
21
|
+
stripeSecretKey: string;
|
|
22
|
+
/**
|
|
23
|
+
* Stripe webhook signing secret (whsec_...)
|
|
24
|
+
*/
|
|
25
|
+
stripeWebhookSecret: string;
|
|
26
|
+
/**
|
|
27
|
+
* Database schema name (defaults to 'stripe')
|
|
28
|
+
*/
|
|
29
|
+
schema?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Callbacks for subscription events
|
|
32
|
+
*/
|
|
33
|
+
callbacks?: StripeWebhookCallbacks;
|
|
34
|
+
}
|
|
35
|
+
declare function createStripeWebhookHandler(config: StripeWebhookConfig): (request: Request) => Promise<Response>;
|
|
36
|
+
|
|
37
|
+
export { type StripeWebhookCallbacks, type StripeWebhookConfig, createStripeWebhookHandler };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
createStripeWebhookHandler: () => createStripeWebhookHandler
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// src/handler.ts
|
|
38
|
+
var import_stripe_sync_engine = require("@supabase/stripe-sync-engine");
|
|
39
|
+
var import_stripe = __toESM(require("stripe"));
|
|
40
|
+
function createStripeWebhookHandler(config) {
|
|
41
|
+
const {
|
|
42
|
+
databaseUrl,
|
|
43
|
+
stripeSecretKey,
|
|
44
|
+
stripeWebhookSecret,
|
|
45
|
+
schema = "stripe",
|
|
46
|
+
callbacks
|
|
47
|
+
} = config;
|
|
48
|
+
const sync = new import_stripe_sync_engine.StripeSync({
|
|
49
|
+
poolConfig: {
|
|
50
|
+
connectionString: databaseUrl
|
|
51
|
+
},
|
|
52
|
+
schema,
|
|
53
|
+
stripeSecretKey,
|
|
54
|
+
stripeWebhookSecret
|
|
55
|
+
});
|
|
56
|
+
const stripe = new import_stripe.default(stripeSecretKey);
|
|
57
|
+
return async function handler(request) {
|
|
58
|
+
try {
|
|
59
|
+
const body = await request.text();
|
|
60
|
+
const signature = request.headers.get("stripe-signature");
|
|
61
|
+
if (!signature) {
|
|
62
|
+
return new Response("Missing stripe-signature header", { status: 400 });
|
|
63
|
+
}
|
|
64
|
+
let event;
|
|
65
|
+
try {
|
|
66
|
+
event = stripe.webhooks.constructEvent(
|
|
67
|
+
body,
|
|
68
|
+
signature,
|
|
69
|
+
stripeWebhookSecret
|
|
70
|
+
);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
73
|
+
return new Response(
|
|
74
|
+
`Webhook signature verification failed: ${message}`,
|
|
75
|
+
{ status: 400 }
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
await sync.processWebhook(body, signature);
|
|
79
|
+
if (callbacks) {
|
|
80
|
+
await handleCallbacks(event, callbacks);
|
|
81
|
+
}
|
|
82
|
+
return new Response(JSON.stringify({ received: true }), {
|
|
83
|
+
status: 200,
|
|
84
|
+
headers: { "Content-Type": "application/json" }
|
|
85
|
+
});
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error("Stripe webhook error:", error);
|
|
88
|
+
const message = error instanceof Error ? error.message : "Internal server error";
|
|
89
|
+
return new Response(message, { status: 500 });
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async function handleCallbacks(event, callbacks) {
|
|
94
|
+
const { onSubscriptionCreated, onSubscriptionCancelled } = callbacks;
|
|
95
|
+
switch (event.type) {
|
|
96
|
+
case "customer.subscription.created":
|
|
97
|
+
if (onSubscriptionCreated) {
|
|
98
|
+
await onSubscriptionCreated(event.data.object);
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
case "customer.subscription.deleted":
|
|
102
|
+
if (onSubscriptionCancelled) {
|
|
103
|
+
await onSubscriptionCancelled(event.data.object);
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
case "customer.subscription.updated":
|
|
107
|
+
const subscription = event.data.object;
|
|
108
|
+
const previousAttributes = event.data.previous_attributes;
|
|
109
|
+
if (onSubscriptionCancelled && subscription.status === "canceled" && previousAttributes?.status && previousAttributes.status !== "canceled") {
|
|
110
|
+
await onSubscriptionCancelled(subscription);
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
116
|
+
0 && (module.exports = {
|
|
117
|
+
createStripeWebhookHandler
|
|
118
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// src/handler.ts
|
|
2
|
+
import { StripeSync } from "@supabase/stripe-sync-engine";
|
|
3
|
+
import Stripe from "stripe";
|
|
4
|
+
function createStripeWebhookHandler(config) {
|
|
5
|
+
const {
|
|
6
|
+
databaseUrl,
|
|
7
|
+
stripeSecretKey,
|
|
8
|
+
stripeWebhookSecret,
|
|
9
|
+
schema = "stripe",
|
|
10
|
+
callbacks
|
|
11
|
+
} = config;
|
|
12
|
+
const sync = new StripeSync({
|
|
13
|
+
poolConfig: {
|
|
14
|
+
connectionString: databaseUrl
|
|
15
|
+
},
|
|
16
|
+
schema,
|
|
17
|
+
stripeSecretKey,
|
|
18
|
+
stripeWebhookSecret
|
|
19
|
+
});
|
|
20
|
+
const stripe = new Stripe(stripeSecretKey);
|
|
21
|
+
return async function handler(request) {
|
|
22
|
+
try {
|
|
23
|
+
const body = await request.text();
|
|
24
|
+
const signature = request.headers.get("stripe-signature");
|
|
25
|
+
if (!signature) {
|
|
26
|
+
return new Response("Missing stripe-signature header", { status: 400 });
|
|
27
|
+
}
|
|
28
|
+
let event;
|
|
29
|
+
try {
|
|
30
|
+
event = stripe.webhooks.constructEvent(
|
|
31
|
+
body,
|
|
32
|
+
signature,
|
|
33
|
+
stripeWebhookSecret
|
|
34
|
+
);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
37
|
+
return new Response(
|
|
38
|
+
`Webhook signature verification failed: ${message}`,
|
|
39
|
+
{ status: 400 }
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
await sync.processWebhook(body, signature);
|
|
43
|
+
if (callbacks) {
|
|
44
|
+
await handleCallbacks(event, callbacks);
|
|
45
|
+
}
|
|
46
|
+
return new Response(JSON.stringify({ received: true }), {
|
|
47
|
+
status: 200,
|
|
48
|
+
headers: { "Content-Type": "application/json" }
|
|
49
|
+
});
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error("Stripe webhook error:", error);
|
|
52
|
+
const message = error instanceof Error ? error.message : "Internal server error";
|
|
53
|
+
return new Response(message, { status: 500 });
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async function handleCallbacks(event, callbacks) {
|
|
58
|
+
const { onSubscriptionCreated, onSubscriptionCancelled } = callbacks;
|
|
59
|
+
switch (event.type) {
|
|
60
|
+
case "customer.subscription.created":
|
|
61
|
+
if (onSubscriptionCreated) {
|
|
62
|
+
await onSubscriptionCreated(event.data.object);
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
case "customer.subscription.deleted":
|
|
66
|
+
if (onSubscriptionCancelled) {
|
|
67
|
+
await onSubscriptionCancelled(event.data.object);
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
case "customer.subscription.updated":
|
|
71
|
+
const subscription = event.data.object;
|
|
72
|
+
const previousAttributes = event.data.previous_attributes;
|
|
73
|
+
if (onSubscriptionCancelled && subscription.status === "canceled" && previousAttributes?.status && previousAttributes.status !== "canceled") {
|
|
74
|
+
await onSubscriptionCancelled(subscription);
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export {
|
|
80
|
+
createStripeWebhookHandler
|
|
81
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "stripe-no-webhooks",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"author": "Ramon Garate",
|
|
5
|
+
"description": "Stripe integration without dealing with webhooks",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"stripe-no-webhooks": "./bin/cli.js"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"bin"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
25
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
26
|
+
"prepublishOnly": "npm run build",
|
|
27
|
+
"publish": "npm version patch && npm publish"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@supabase/stripe-sync-engine": "^0.47.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"stripe": ">=14.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^22.10.2",
|
|
37
|
+
"stripe": "^17.4.0",
|
|
38
|
+
"tsup": "^8.3.5",
|
|
39
|
+
"typescript": "^5.7.2"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"stripe",
|
|
43
|
+
"webhooks",
|
|
44
|
+
"nextjs",
|
|
45
|
+
"supabase",
|
|
46
|
+
"sync"
|
|
47
|
+
],
|
|
48
|
+
"license": "MIT"
|
|
49
|
+
}
|