Untitled
unknown
plain_text
2 years ago
13 kB
7
Indexable
"use node"; import { api, internal } from "../_generated/api"; import { action } from "../_generated/server"; import Stripe from "stripe"; import { logSlackPayment, sendSlackHelp, withErrorLog, logSlackError, } from "./utils/slackLogger"; const stripe = new Stripe( process.env.STRIPE_SECRET_KEY as string, { timeout: 20 * 1000, maxNetworkRetries: 2 } as any ); export const createCheckoutSession = action( async ({ runQuery, runMutation }, { quantity }: { quantity: number }) => { return await withErrorLog(async () => { const user = await runQuery(api.users.getUser); const userId = user?._id; if (!userId) { throw new Error("No user found"); } let id = null; if (!user.stripeCustomerId || user.stripeCustomerId === "") { const customer: Stripe.Response<Stripe.Customer> = await stripe.customers.create({ email: user.email, name: user.name, metadata: { userId: user._id.toString(), }, }); if (!customer) { throw new Error("Failed to create customer"); } await runMutation(api.users.updateUserStripeCustomerId, { stripeCustomerId: customer.id, }); id = customer.id; } if (!id) { id = user.stripeCustomerId; } const prices = await stripe.prices.list({ active: true, limit: 3, }); const session: Stripe.Response<Stripe.Checkout.Session> = await stripe.checkout.sessions.create({ line_items: [ { price: prices.data[0].id, quantity, }, ], mode: "subscription", success_url: `${process.env.DOMAIN}/plans/success`, cancel_url: `${process.env.DOMAIN}/plans`, customer: id, allow_promotion_codes: true, }); return session.id; }, "stripe:ts: createCheckoutSession"); } ); export const createCheckoutSessionOnImport = action( async ({ runQuery, runMutation }, { quantity }: { quantity: number }) => { return await withErrorLog(async () => { const user = await runQuery(api.users.getUser); const userId = user?._id; if (!userId) { throw new Error("No user found"); } let id = null; if (!user.stripeCustomerId || user.stripeCustomerId === "") { const customer: Stripe.Response<Stripe.Customer> = await stripe.customers.create({ email: user.email, name: user.name, metadata: { userId: user._id.toString(), }, }); if (!customer) { throw new Error("Failed to create customer"); } await runMutation(api.users.updateUserStripeCustomerId, { stripeCustomerId: customer.id, }); id = customer.id; } if (!id) { id = user.stripeCustomerId; } const prices = await stripe.prices.list({ active: true, limit: 3, }); const session: Stripe.Response<Stripe.Checkout.Session> = await stripe.checkout.sessions.create({ line_items: [ { price: prices.data[0].id, quantity, }, ], mode: "subscription", success_url: `${process.env.DOMAIN}/listings/add`, cancel_url: `${process.env.DOMAIN}/listings/add`, customer: id, allow_promotion_codes: true, }); return session.id; }, "stripe:ts: createCheckoutSessionOnImport"); } ); export const createPortalSession = action(async ({ runQuery }) => { return await withErrorLog(async () => { const subscription = await runQuery(api.subscriptions.get); if (!subscription) { throw new Error("No subscription found"); } const session: Stripe.Response<Stripe.BillingPortal.Session> = await stripe.billingPortal.sessions.create({ customer: subscription.stripeCustomerId, return_url: `${process.env.DOMAIN}/plans`, }); return session.url; }, "stripe:ts: createPortalSession"); }); export const updateSubscriptionQuantity = action( async ({ runQuery }, { quantity }: { quantity: number }) => { return await withErrorLog(async () => { const subscription = await runQuery(api.subscriptions.get); if (!subscription) { throw new Error("No subscription found"); } const customerSubscriptionIds = await stripe.subscriptionItems.list( { subscription: subscription.subscriptionId, } ); const updatedSubscription: Stripe.Response<Stripe.Subscription> = await stripe.subscriptions.update(subscription.subscriptionId, { items: [ { id: customerSubscriptionIds.data[0].id, price: subscription.priceId, quantity, }, ], }); return updatedSubscription; }, "stripe:ts: updateSubscriptionQuantity"); } ); export const receiveWebhook = action( async ( { runQuery, runAction, runMutation }, { body, sig }: { body: string; sig: string } ): Promise<{ status: number; message: string }> => { const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!; let event: Stripe.Event; try { event = stripe.webhooks.constructEvent(body, sig, webhookSecret); } catch (err) { const errorMessage = err instanceof Error ? err.message : "Unknown error"; // On error, log and return the error message. if (err! instanceof Error) console.log(err); console.log(`❌ Error message: ${errorMessage}`); return { status: 400, message: `Webhook Error: ${errorMessage}` }; } // Successfully constructed event. console.log("✅ Success:", event.id); let message = ""; // Cast event data to Stripe object. try { switch (event.type) { case "payment_intent.succeeded": { const paymentIntent = event.data .object as Stripe.PaymentIntent; message = `💰 PaymentIntent status: ${paymentIntent.status}`; break; } case "payment_intent.payment_failed": { const paymentIntent = event.data .object as Stripe.PaymentIntent; message = `❌ Payment failed: ${paymentIntent.last_payment_error?.message}`; break; } case "customer.subscription.created": { const subscription = event.data .object as Stripe.Subscription; await runMutation(api.subscriptions.create, { subscriptionObj: subscription, }); message = `💰 Subscription created: ${subscription.id}`; const subcriptionItem = await runQuery( internal.subscriptions.getByCustomerId, { customerId: subscription.customer as string, } ); const userId = subcriptionItem?.userId; if (userId && process.env.ENVIROMENT === "production") { await runAction( internal.actions.utils.airtable .updateAirtablePaidAndQuantityByUser, { userId, paid: true, quantity: (subscription as any).quantity, } ); } if (process.env.ENVIRONMENT == "production") await logSlackPayment(message); break; } case "customer.subscription.updated": { const subscription = event.data .object as Stripe.Subscription; await runMutation(api.subscriptions.update, { subscriptionObj: subscription, }); message = `🔔 Subscription updated: ${subscription.id}`; const subscriptionItem = await runQuery( internal.subscriptions.getByCustomerId, { customerId: subscription.customer as string, } ); const userId = subscriptionItem?.userId; if (userId && process.env.ENVIROMENT === "production") { await runAction( internal.actions.utils.airtable .updateAirtablePaidAndQuantityByUser, { userId, paid: !subscription.cancel_at_period_end, quantity: (subscription as any).quantity, } ); } break; } case "customer.subscription.deleted": { const subscription = event.data .object as Stripe.Subscription; await runMutation(api.subscriptions.deleteSub, { customerId: subscription.customer as string, }); message = `🔔 Subscription deleted: ${subscription.id}`; const subscriptionItem = await runQuery( internal.subscriptions.getByCustomerId, { customerId: subscription.customer as string, } ); const userId = subscriptionItem?.userId; if (userId && process.env.ENVIROMENT === "production") { await runAction( internal.actions.utils.airtable .updateAirtablePaidAndQuantityByUser, { userId, paid: false, quantity: (subscription as any).quantity, } ); } break; } case "invoice.created": { const invoice = event.data.object as Stripe.Invoice; message = `🔔 Invoice created: ${invoice.id}`; break; } case "invoice.paid": { const invoice = event.data.object as Stripe.Invoice; message = `💰 Invoice paid: ${invoice.id}`; break; } case "invoice.payment_failed": { const invoice = event.data.object as Stripe.Invoice; message = `❌ Invoice payment failed: ${invoice.id}`; break; } default: { message = `🤷♀️ Unhandled event type: ${event.type}`; console.warn(message); break; } } console.log(message); } catch (e: any) { await logSlackError(e.message, "stripe.ts: receiveWebhook"); return { status: 400, message: `Webhook Error: ${e}` }; } // Return a response to acknowledge receipt of the event. return { status: 200, message: "success" }; } );
Editor is loading...
Leave a Comment