-
-
Save kitze/112fcb1bfcbc60dcfd9500ba3a6a6196 to your computer and use it in GitHub Desktop.
| import { NextApiRequest, NextApiResponse } from "next"; | |
| import { validateLemonSqueezyHook } from "@/pages/api/lemon/validateLemonSqueezyHook"; | |
| import getRawBody from "raw-body"; | |
| import { LemonEventType, ResBody } from "@/pages/api/lemon/types"; | |
| import { onOrderCreated } from "@/pages/api/lemon/hooks/onOrderCreated"; | |
| import { returnError, returnOkay } from "@/pages/api/lemon/utils"; | |
| export const config = { | |
| api: { | |
| bodyParser: false, | |
| }, | |
| }; | |
| const handler = async (req: NextApiRequest, res: NextApiResponse) => { | |
| console.log("🍋: hello"); | |
| console.log("req.method", req.method); | |
| if (req.method !== "POST") { | |
| console.log("🍋: method not allowed"); | |
| return res.status(405).json({ | |
| message: "Method not allowed", | |
| }); | |
| } | |
| console.log("req.method is allowed"); | |
| try { | |
| const rawBody = await getRawBody(req); | |
| const isValidHook = await validateLemonSqueezyHook({ req, rawBody }); | |
| console.log("🍋: isValidHook", isValidHook); | |
| if (!isValidHook) { | |
| return res.status(400).json({ | |
| message: "Invalid signature.", | |
| }); | |
| } | |
| //@ts-ignore | |
| const event: ResBody["body"] = JSON.parse(rawBody); | |
| const eventType = event.meta.event_name; | |
| console.log("🍋: event type", eventType); | |
| const handlers = { | |
| [LemonEventType.OrderCreated]: onOrderCreated, | |
| }; | |
| const foundHandler = handlers[eventType]; | |
| if (foundHandler) { | |
| try { | |
| await foundHandler({ event }); | |
| returnOkay(res); | |
| } catch (err) { | |
| console.log(`🍋: error in handling ${eventType} event`, err); | |
| returnError(res); | |
| } | |
| } else { | |
| console.log(`🍋: no handler found for ${eventType} event`); | |
| } | |
| console.log("eventType", eventType); | |
| } catch (e: unknown) { | |
| if (typeof e === "string") { | |
| return res.status(400).json({ | |
| message: `Webhook error: ${e}`, | |
| }); | |
| } | |
| if (e instanceof Error) { | |
| return res.status(400).json({ | |
| message: `Webhook error: ${e.message}`, | |
| }); | |
| } | |
| throw e; | |
| } | |
| }; | |
| export default handler; |
| import { LemonsqueezySubscriptionPause } from "./client/methods/updateSubscription/types"; | |
| import { NextApiRequest } from "next"; | |
| export enum LemonEventType { | |
| SubCreated = "subscription_created", | |
| SubUpdated = "subscription_updated", | |
| SubPaymentSuccess = "subscription_payment_success", | |
| OrderCreated = "order_created", | |
| } | |
| export type CustomLemonSqueezyCheckoutData = { | |
| user_id: string; | |
| }; | |
| export type LemonMeta = { | |
| test_mode: boolean; | |
| event_name: LemonEventType; | |
| custom_data: CustomLemonSqueezyCheckoutData; | |
| }; | |
| export type SubscriptionCreatedUpdatedCommon = { | |
| type: string; | |
| id: string; | |
| attributes: { | |
| store_id: number; | |
| customer_id: number; | |
| order_id: number; | |
| order_item_id: number; | |
| product_id: number; | |
| variant_id: number; | |
| product_name: string; | |
| variant_name: string; | |
| user_name: string; | |
| user_email: string; | |
| status: string; | |
| status_formatted: string; | |
| card_brand: string; | |
| card_last_four: string; | |
| pause: null | LemonsqueezySubscriptionPause; | |
| cancelled: boolean; | |
| trial_ends_at: null | Date; | |
| billing_anchor: number; | |
| urls: Record<string, string>; | |
| renews_at: string; | |
| ends_at: null | Date; | |
| created_at: string; | |
| updated_at: string; | |
| test_mode: boolean; | |
| }; | |
| relationships?: { | |
| store: Record<string, unknown>; | |
| customer: Record<string, unknown>; | |
| order: Record<string, unknown>; | |
| "order-item": Record<string, unknown>; | |
| product: Record<string, unknown>; | |
| variant: Record<string, unknown>; | |
| "subscription-invoices": Record<string, string>; | |
| }; | |
| links?: { | |
| self: string; | |
| }; | |
| }; | |
| type SubscriptionCreated = Omit< | |
| SubscriptionCreatedUpdatedCommon, | |
| "type" | "relationships" | "links" | |
| > & { | |
| type: string; | |
| relationships: SubscriptionCreatedUpdatedCommon["relationships"]; | |
| links: SubscriptionCreatedUpdatedCommon["links"]; | |
| }; | |
| type SubscriptionUpdated = Omit< | |
| SubscriptionCreatedUpdatedCommon, | |
| "type" | "relationships" | "links" | |
| > & { | |
| type: "subscriptions"; | |
| }; | |
| export type SubscriptionPaymentSuccess = { | |
| type: "subscription-invoices"; | |
| id: string; | |
| attributes: { | |
| store_id: number; | |
| subscription_id: number; | |
| billing_reason: string; | |
| card_brand: string; | |
| card_last_four: string; | |
| currency: string; | |
| currency_rate: string; | |
| subtotal: number; | |
| discount_total: number; | |
| tax: number; | |
| total: number; | |
| subtotal_usd: number; | |
| discount_total_usd: number; | |
| tax_usd: number; | |
| total_usd: number; | |
| status: string; | |
| status_formatted: string; | |
| refunded: boolean; | |
| refunded_at: string | null; | |
| subtotal_formatted: string; | |
| discount_total_formatted: string; | |
| tax_formatted: string; | |
| total_formatted: string; | |
| urls: Record<string, unknown>; | |
| created_at: string; | |
| updated_at: string; | |
| test_mode: boolean; | |
| }; | |
| relationships: { | |
| store: { | |
| data: { | |
| type: "stores"; | |
| id: string; | |
| }; | |
| }; | |
| subscription: { | |
| data: { | |
| type: "subscriptions"; | |
| id: string; | |
| }; | |
| }; | |
| }; | |
| links: { | |
| self: string; | |
| }; | |
| }; | |
| export type SubscriptionCreatedEvent = { | |
| meta: LemonMeta; | |
| data: SubscriptionCreated; | |
| }; | |
| export type SubscriptionUpdatedEvent = { | |
| meta: LemonMeta; | |
| data: SubscriptionUpdated; | |
| }; | |
| export type SubscriptionPaymentSuccessEvent = { | |
| meta: LemonMeta; | |
| data: SubscriptionPaymentSuccess; | |
| }; | |
| export type LemonEvent = | |
| | SubscriptionCreatedEvent | |
| | SubscriptionUpdatedEvent | |
| | SubscriptionPaymentSuccessEvent; | |
| export interface ResBody extends NextApiRequest { | |
| body: LemonEvent; | |
| } |
| import { NextApiRequest } from "next"; | |
| import crypto from "crypto"; | |
| import { env } from "@/env.mjs"; | |
| export const validateLemonSqueezyHook = async ({ | |
| req, | |
| rawBody, | |
| }: { | |
| req: NextApiRequest; | |
| rawBody: any; | |
| }): Promise<boolean> => { | |
| try { | |
| const hmac = crypto.createHmac("sha256", env.LEMONSQUEEZY_WEBHOOK_SECRET); | |
| const digest = Buffer.from(hmac.update(rawBody).digest("hex"), "utf8"); | |
| const signature = Buffer.from(req.headers["x-signature"] as string, "utf8"); | |
| let validated = crypto.timingSafeEqual(digest, signature); | |
| return validated; | |
| } catch (err) { | |
| console.log("err", err); | |
| return false; | |
| } | |
| return false; | |
| }; |
You can find all the lemon squeezy helpers here https://github.com/kitze/lemon-squeezy-helpers
…
-- [image: avatar] Kitze Founder of Sizzy Benji - the ultimate welness & productivity app Zero To Shipped - Master Fullstack development
On December 19, 2023 at 4:25 PM, Cristian Furcila @.***) wrote: @fcristel commented on this gist. you're missing some files here I guess: @/pages/api/lemon/hooks/onOrderCreated @/pages/api/lemon/utils ./client/methods/updateSubscription/types — Reply to this email directly, view it on GitHub https://gist.github.com/kitze/112fcb1bfcbc60dcfd9500ba3a6a6196#gistcomment-4800246 or unsubscribe https://github.com/notifications/unsubscribe-auth/AAI3LEUQDCOME4HML5H2PJLYKGWYDBFKMF2HI4TJMJ2XIZLTSKBKK5TBNR2WLJDHNFZXJJDOMFWWLK3UNBZGKYLEL52HS4DFQKSXMYLMOVS2I5DSOVS2I3TBNVS3W5DIOJSWCZC7OBQXE5DJMNUXAYLOORPWCY3UNF3GS5DZVRZXKYTKMVRXIX3UPFYGLK2HNFZXIQ3PNVWWK3TUUZ2G64DJMNZZDAVEOR4XAZNEM5UXG5FFOZQWY5LFVEYTENZQGA3TENBUU52HE2LHM5SXFJTDOJSWC5DF. You are receiving this email because you authored the thread. Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.
Link is not working
I open sourced all the lemon squeezy helpers here