From d55af4257a843a266778b9336ddad0a10e81b5b9 Mon Sep 17 00:00:00 2001 From: Andy Baird Date: Mon, 30 Oct 2023 12:16:57 -0600 Subject: [PATCH] adding support for promo codes --- src/backend.ts | 237 +++++++++++++++++++++++++------------------------ 1 file changed, 119 insertions(+), 118 deletions(-) diff --git a/src/backend.ts b/src/backend.ts index d71bf30..27f365c 100644 --- a/src/backend.ts +++ b/src/backend.ts @@ -1,138 +1,139 @@ -import Stripe from "stripe"; +import Stripe from "stripe" export const stripeApiClient = process.env.STRIPE_SECRET_KEY - ? new Stripe(process.env.STRIPE_SECRET_KEY, { - apiVersion: null, - }) - : null; + ? new Stripe(process.env.STRIPE_SECRET_KEY, { + apiVersion: null, + }) + : null export interface CustomerHasFeatureArgs { - customerId: string; - feature: string; + customerId: string + feature: string } export const customerHasFeature = async ({ customerId, feature }) => { - const customer = (await stripeApiClient.customers.retrieve(customerId, { - expand: ["subscriptions"], - })) as Stripe.Customer; - let subscription = customer.subscriptions.data[0] || null; - if (subscription) { - subscription = await stripeApiClient.subscriptions.retrieve( - subscription.id, - { expand: ["items.data.price.product"] } - ); - const features = (subscription.items.data[0].price - .product as Stripe.Product).metadata.features; - return features?.includes(feature); - } - return false; -}; + const customer = (await stripeApiClient.customers.retrieve(customerId, { + expand: ["subscriptions"], + })) as Stripe.Customer + let subscription = customer.subscriptions.data[0] || null + if (subscription) { + subscription = await stripeApiClient.subscriptions.retrieve( + subscription.id, + { expand: ["items.data.price.product"] } + ) + const features = ( + subscription.items.data[0].price.product as Stripe.Product + ).metadata.features + return features?.includes(feature) + } + return false +} export const subscriptionHandler = async ({ customerId, query, body }) => { - if (query.action === "useSubscription") { - return await useSubscription({ customerId }); - } + if (query.action === "useSubscription") { + return await useSubscription({ customerId }) + } - if (query.action === "redirectToCheckout") { - return await redirectToCheckout({ customerId, body }); - } + if (query.action === "redirectToCheckout") { + return await redirectToCheckout({ customerId, body }) + } - if (query.action === "redirectToCustomerPortal") { - return await redirectToCustomerPortal({ customerId, body }); - } + if (query.action === "redirectToCustomerPortal") { + return await redirectToCustomerPortal({ customerId, body }) + } - return { error: "Action not found" }; -}; + return { error: "Action not found" } +} async function useSubscription({ customerId }) { - // Retrieve products based on default billing portal config - - // First, retrieve the configuration - const configurations = await stripeApiClient.billingPortal.configurations.list( - { - is_default: true, - expand: ["data.features.subscription_update.products"], - } - ); - - // Stripe doesn't let us expand as much as we'd like. - // Run this big mess to manually expand - - // We preserve the order stripe returns things in - const products = new Array( - configurations.data[0].features.subscription_update.products.length - ); - const pricePromises = configurations.data[0].features.subscription_update.products - .map((product, i) => - product.prices.map(async (price, j) => { - const priceData = await stripeApiClient.prices.retrieve(price, { - expand: ["product"], - }); - const cleanPriceData = { - ...priceData, - product: (priceData.product as Stripe.Product).id, - }; - if (!products[i]) { - products[i] = { - product: priceData.product, - prices: new Array(product.prices.length), - }; - products[i].prices[j] = cleanPriceData; - } else { - products[i].prices[j] = cleanPriceData; - } - }) - ) - .flat(); - - let subscription; - const subscriptionPromise = stripeApiClient.customers - .retrieve(customerId, { expand: ["subscriptions"] }) - .then((customer) => { - // This package is limited to one subscription at a time - // @ts-ignore - subscription = customer.subscriptions.data[0] || null; - }); - - await Promise.all([...pricePromises, subscriptionPromise]); - - return { products, subscription }; + // Retrieve products based on default billing portal config + + // First, retrieve the configuration + const configurations = + await stripeApiClient.billingPortal.configurations.list({ + is_default: true, + expand: ["data.features.subscription_update.products"], + }) + + // Stripe doesn't let us expand as much as we'd like. + // Run this big mess to manually expand + + // We preserve the order stripe returns things in + const products = new Array( + configurations.data[0].features.subscription_update.products.length + ) + const pricePromises = + configurations.data[0].features.subscription_update.products + .map((product, i) => + product.prices.map(async (price, j) => { + const priceData = await stripeApiClient.prices.retrieve(price, { + expand: ["product"], + }) + const cleanPriceData = { + ...priceData, + product: (priceData.product as Stripe.Product).id, + } + if (!products[i]) { + products[i] = { + product: priceData.product, + prices: new Array(product.prices.length), + } + products[i].prices[j] = cleanPriceData + } else { + products[i].prices[j] = cleanPriceData + } + }) + ) + .flat() + + let subscription + const subscriptionPromise = stripeApiClient.customers + .retrieve(customerId, { expand: ["subscriptions"] }) + .then((customer) => { + // This package is limited to one subscription at a time + // @ts-ignore + subscription = customer.subscriptions.data[0] || null + }) + + await Promise.all([...pricePromises, subscriptionPromise]) + + return { products, subscription } } async function redirectToCustomerPortal({ customerId, body }) { - return await stripeApiClient.billingPortal.sessions.create({ - customer: customerId, - return_url: body.returnUrl, - }); + return await stripeApiClient.billingPortal.sessions.create({ + customer: customerId, + return_url: body.returnUrl, + }) } async function redirectToCheckout({ customerId, body }) { - const configurations = await stripeApiClient.billingPortal.configurations.list( - { - is_default: true, - expand: ["data.features.subscription_update.products"], - } - ); - - // Make sure the price ID is in here somewhere - let go = false; - for (let product of configurations.data[0].features.subscription_update - .products) { - for (let price of product.prices) { - if (price === body.price) { - go = true; - break; - } - } - } - - if (go) { - return await stripeApiClient.checkout.sessions.create({ - customer: customerId, - success_url: body.successUrl, - cancel_url: body.cancelUrl, - line_items: [{ price: body.price, quantity: 1 }], - mode: "subscription", - }); - } - return { error: "Error" }; + const configurations = + await stripeApiClient.billingPortal.configurations.list({ + is_default: true, + expand: ["data.features.subscription_update.products"], + }) + + // Make sure the price ID is in here somewhere + let go = false + for (let product of configurations.data[0].features.subscription_update + .products) { + for (let price of product.prices) { + if (price === body.price) { + go = true + break + } + } + } + + if (go) { + return await stripeApiClient.checkout.sessions.create({ + customer: customerId, + success_url: body.successUrl, + cancel_url: body.cancelUrl, + line_items: [{ price: body.price, quantity: 1 }], + mode: "subscription", + allow_promotion_codes: true, + }) + } + return { error: "Error" } }