Skip to content

Commit 7d7a16c

Browse files
Workflow - Vercel AI SDK example (#45)
* add: vercel ai sdk example * fix export import * finalize * fix: replace tab chars with spaces --------- Co-authored-by: CahidArda <[email protected]>
1 parent 53c3fc4 commit 7d7a16c

File tree

4 files changed

+328
-211
lines changed

4 files changed

+328
-211
lines changed

examples/nextjs-webhook-stripe/app/api/workflow/onboarding/route.ts

Lines changed: 168 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -8,180 +8,180 @@ import { Webhook } from 'svix';
88
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY ?? "");
99
const resend = new Resend(process.env.RESEND_API_KEY ?? "")
1010
export type OnboardingPayload = {
11-
event: string;
12-
clerkUserId: string;
13-
email: string;
14-
firstName: string;
15-
lastName: string;
11+
event: string;
12+
clerkUserId: string;
13+
email: string;
14+
firstName: string;
15+
lastName: string;
1616
}
1717

1818
async function validateRequest(payloadString: string, headerPayload: Headers) {
1919

20-
const svixHeaders = {
21-
"svix-id": headerPayload.get("svix-id") as string,
22-
"svix-timestamp": headerPayload.get("svix-timestamp") as string,
23-
"svix-signature": headerPayload.get("svix-signature") as string,
24-
}
25-
const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET ?? "");
26-
return wh.verify(payloadString, svixHeaders) as WebhookEvent;
20+
const svixHeaders = {
21+
"svix-id": headerPayload.get("svix-id") as string,
22+
"svix-timestamp": headerPayload.get("svix-timestamp") as string,
23+
"svix-signature": headerPayload.get("svix-signature") as string,
24+
}
25+
const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET ?? "");
26+
return wh.verify(payloadString, svixHeaders) as WebhookEvent;
2727
}
2828

2929
export const { POST } = serve<string>(async (context) => {
30-
const payloadString = context.requestPayload;
31-
const headerPayload = context.headers;
32-
33-
let event: WebhookEvent;
34-
try {
35-
event = await validateRequest(payloadString, headerPayload);
36-
} catch {
37-
return
38-
}
39-
40-
const user = await context.run<false | OnboardingPayload>("handle-clerk-webhook-event", async () => {
41-
if (event.type === "user.created") {
42-
const { id: clerkUserId, email_addresses, first_name, last_name } = event.data;
43-
const primaryEmail = email_addresses.find(email => email.id === event.data.primary_email_address_id)
44-
45-
if (!primaryEmail) {
46-
return false
47-
}
48-
49-
return {
50-
event: event.type,
51-
clerkUserId: clerkUserId,
52-
email: primaryEmail.email_address,
53-
firstName: first_name,
54-
lastName: last_name,
55-
} as OnboardingPayload
56-
}
57-
return false
58-
})
59-
60-
if (!user) {
61-
return
62-
}
63-
64-
const customer = await context.run("create-stripe-customer", async () => {
65-
return await stripe.customers.create({
66-
email: user.email,
67-
name: `${user.firstName} ${user.lastName}`,
68-
metadata: {
69-
clerkUserId: user.clerkUserId
70-
}
71-
})
72-
})
73-
74-
await context.run("send-welcome-email", async () => {
75-
console.log("Sending welcome email to:", user.email)
76-
77-
await resend.emails.send({
78-
79-
to: user.email,
80-
subject: 'Welcome to Your Trial!',
81-
html: `
82-
<h1>Welcome ${user.firstName || 'there'}!</h1>
83-
<p>Thanks for signing up! Your trial starts now.</p>
84-
<p>You have 7 days to explore all our premium features.</p>
85-
<p>What you get with your trial:</p>
86-
<ul>
87-
<li>Feature 1</li>
88-
<li>Feature 2</li>
89-
<li>Feature 3</li>
90-
</ul>
91-
<p>Get started now: <a href="${process.env.NEXT_PUBLIC_URL}/dashboard">Visit Dashboard</a></p>
92-
`
93-
});
94-
95-
})
96-
97-
const subscription = await context.run("create-trial", async () => {
98-
return await stripe.subscriptions.create({
99-
customer: customer.id,
100-
items: [{ price: "price_1QQQWaCKnqweyLP9MPbARyG" }],
101-
trial_period_days: 7,
102-
metadata: {
103-
clerkUserId: user.clerkUserId,
104-
workflowRunId: context.workflowRunId
105-
}
106-
})
107-
})
108-
109-
await context.run("store-subscription", async () => {
110-
console.log(subscription)
111-
})
112-
113-
114-
/**
115-
* This is where we start waiting for the payment method to be added to the subscription.
116-
* If the payment method is added within 7 days, workflow on the `api/stripe/route` will notify this workflow with `payment_method_<SUBSCRIPTION_ID>`.
117-
* If the payment method is not added within 7 days, we will handle the trial end.
118-
*/
119-
const { timeout } = await context.waitForEvent("await-payment-method", `payment_method_${subscription.id}`, {
120-
timeout: "7d"
121-
})
122-
123-
124-
if (!timeout) {
125-
await context.run("send-subscription-start-welcome-mail", async () => {
126-
console.log("Sending subscription started email to:", user.email)
127-
128-
await resend.emails.send({
129-
130-
to: user.email,
131-
subject: 'Payment Method Added Successfully!',
132-
html: `
133-
<h1>Thank you for adding your payment method!</h1>
134-
<p>Your subscription will continue automatically after the trial period.</p>
135-
<p>Your trial benefits:</p>
136-
<ul>
137-
<li>Unlimited access to all features</li>
138-
<li>Priority support</li>
139-
<li>No interruption in service</li>
140-
</ul>
141-
<p>Need help? Reply to this email or visit our support center.</p>
142-
`
143-
});
144-
})
145-
146-
} else {
147-
await context.run("handle-trial-end", async () => {
148-
await stripe.subscriptions.update(subscription.id, {
149-
cancel_at_period_end: true
150-
})
151-
152-
return { status: 'trial_ended' }
153-
})
154-
155-
156-
await context.run("send-trial-ending-mail", async () => {
157-
console.log("Sending trial ending email to:", user.email)
158-
159-
await resend.emails.send({
160-
161-
to: user.email,
162-
subject: 'Your Trial is Ending Soon',
163-
html: `
164-
<h1>Don't Lose Access!</h1>
165-
<p>Your trial is coming to an end. Add a payment method to keep your access:</p>
166-
<ul>
167-
<li>Keep all your data and settings</li>
168-
<li>Continue using premium features</li>
169-
<li>No interruption in service</li>
170-
</ul>
171-
<a href="${process.env.NEXT_PUBLIC_URL}/billing" style="
172-
display: inline-block;
173-
padding: 12px 24px;
174-
background-color: #0070f3;
175-
color: white;
176-
text-decoration: none;
177-
border-radius: 5px;
178-
margin: 20px 0;
179-
">Add Payment Method</a>
180-
<p>Questions? Contact our support team!</p>
181-
`
182-
});
183-
184-
})
185-
}
30+
const payloadString = context.requestPayload;
31+
const headerPayload = context.headers;
32+
33+
let event: WebhookEvent;
34+
try {
35+
event = await validateRequest(payloadString, headerPayload);
36+
} catch {
37+
return
38+
}
39+
40+
const user = await context.run<false | OnboardingPayload>("handle-clerk-webhook-event", async () => {
41+
if (event.type === "user.created") {
42+
const { id: clerkUserId, email_addresses, first_name, last_name } = event.data;
43+
const primaryEmail = email_addresses.find(email => email.id === event.data.primary_email_address_id)
44+
45+
if (!primaryEmail) {
46+
return false
47+
}
48+
49+
return {
50+
event: event.type,
51+
clerkUserId: clerkUserId,
52+
email: primaryEmail.email_address,
53+
firstName: first_name,
54+
lastName: last_name,
55+
} as OnboardingPayload
56+
}
57+
return false
58+
})
59+
60+
if (!user) {
61+
return
62+
}
63+
64+
const customer = await context.run("create-stripe-customer", async () => {
65+
return await stripe.customers.create({
66+
email: user.email,
67+
name: `${user.firstName} ${user.lastName}`,
68+
metadata: {
69+
clerkUserId: user.clerkUserId
70+
}
71+
})
72+
})
73+
74+
await context.run("send-welcome-email", async () => {
75+
console.log("Sending welcome email to:", user.email)
76+
77+
await resend.emails.send({
78+
79+
to: user.email,
80+
subject: 'Welcome to Your Trial!',
81+
html: `
82+
<h1>Welcome ${user.firstName || 'there'}!</h1>
83+
<p>Thanks for signing up! Your trial starts now.</p>
84+
<p>You have 7 days to explore all our premium features.</p>
85+
<p>What you get with your trial:</p>
86+
<ul>
87+
<li>Feature 1</li>
88+
<li>Feature 2</li>
89+
<li>Feature 3</li>
90+
</ul>
91+
<p>Get started now: <a href="${process.env.NEXT_PUBLIC_URL}/dashboard">Visit Dashboard</a></p>
92+
`
93+
});
94+
95+
})
96+
97+
const subscription = await context.run("create-trial", async () => {
98+
return await stripe.subscriptions.create({
99+
customer: customer.id,
100+
items: [{ price: "price_1QQQWaCKnqweyLP9MPbARyG" }],
101+
trial_period_days: 7,
102+
metadata: {
103+
clerkUserId: user.clerkUserId,
104+
workflowRunId: context.workflowRunId
105+
}
106+
})
107+
})
108+
109+
await context.run("store-subscription", async () => {
110+
console.log(subscription)
111+
})
112+
113+
114+
/**
115+
* This is where we start waiting for the payment method to be added to the subscription.
116+
* If the payment method is added within 7 days, workflow on the `api/stripe/route` will notify this workflow with `payment_method_<SUBSCRIPTION_ID>`.
117+
* If the payment method is not added within 7 days, we will handle the trial end.
118+
*/
119+
const { timeout } = await context.waitForEvent("await-payment-method", `payment_method_${subscription.id}`, {
120+
timeout: "7d"
121+
})
122+
123+
124+
if (!timeout) {
125+
await context.run("send-subscription-start-welcome-mail", async () => {
126+
console.log("Sending subscription started email to:", user.email)
127+
128+
await resend.emails.send({
129+
130+
to: user.email,
131+
subject: 'Payment Method Added Successfully!',
132+
html: `
133+
<h1>Thank you for adding your payment method!</h1>
134+
<p>Your subscription will continue automatically after the trial period.</p>
135+
<p>Your trial benefits:</p>
136+
<ul>
137+
<li>Unlimited access to all features</li>
138+
<li>Priority support</li>
139+
<li>No interruption in service</li>
140+
</ul>
141+
<p>Need help? Reply to this email or visit our support center.</p>
142+
`
143+
});
144+
})
145+
146+
} else {
147+
await context.run("handle-trial-end", async () => {
148+
await stripe.subscriptions.update(subscription.id, {
149+
cancel_at_period_end: true
150+
})
151+
152+
return { status: 'trial_ended' }
153+
})
154+
155+
156+
await context.run("send-trial-ending-mail", async () => {
157+
console.log("Sending trial ending email to:", user.email)
158+
159+
await resend.emails.send({
160+
161+
to: user.email,
162+
subject: 'Your Trial is Ending Soon',
163+
html: `
164+
<h1>Don't Lose Access!</h1>
165+
<p>Your trial is coming to an end. Add a payment method to keep your access:</p>
166+
<ul>
167+
<li>Keep all your data and settings</li>
168+
<li>Continue using premium features</li>
169+
<li>No interruption in service</li>
170+
</ul>
171+
<a href="${process.env.NEXT_PUBLIC_URL}/billing" style="
172+
display: inline-block;
173+
padding: 12px 24px;
174+
background-color: #0070f3;
175+
color: white;
176+
text-decoration: none;
177+
border-radius: 5px;
178+
margin: 20px 0;
179+
">Add Payment Method</a>
180+
<p>Questions? Contact our support team!</p>
181+
`
182+
});
183+
184+
})
185+
}
186186

187187
}, { baseUrl: "<BASE_URL>", initialPayloadParser: (payload) => { return payload } })

0 commit comments

Comments
 (0)