UNPKG

@coursebuilder/core

Version:

Core package for Course Builder

337 lines (335 loc) 12.8 kB
import { getFixedDiscountForIndividualUpgrade } from "./chunk-NTGLTY3X.js"; import { getCalculatedPrice } from "./chunk-PQKCCDCP.js"; import { CheckoutSessionMetadataSchema } from "./chunk-35XRVA3B.js"; import { z } from "./chunk-JLNB6NRA.js"; import { __name, __publicField } from "./chunk-VLQXSCFN.js"; // src/lib/pricing/stripe-checkout.ts import { add } from "date-fns"; import { first, isEmpty } from "@coursebuilder/nodash"; var CheckoutParamsSchema = z.object({ ip_address: z.string().optional(), productId: z.string(), quantity: z.coerce.number().optional().transform((val) => Number(val) || 0), country: z.string().optional(), couponId: z.string().optional(), userId: z.string().optional(), upgradeFromPurchaseId: z.string().optional(), bulk: z.preprocess((val) => { return val === "false" ? false : Boolean(val); }, z.coerce.boolean()), cancelUrl: z.string(), usedCouponId: z.string().optional(), organizationId: z.string().optional() }); var buildSearchParams = /* @__PURE__ */ __name((params) => { if (isEmpty(params)) { return ""; } else { return Object.entries(params).map(([key, value]) => { return `${key}=${value}`; }).join("&"); } }, "buildSearchParams"); async function findOrCreateStripeCustomerId(userId, adapter, paymentsAdapter) { const user = await adapter.getUser?.(userId); if (user) { const merchantCustomer = await adapter.getMerchantCustomerForUserId(user.id); const customerId = user && merchantCustomer ? merchantCustomer.identifier : false; if (customerId) { return customerId; } else { const merchantAccount = await adapter.getMerchantAccount({ provider: "stripe" }); if (merchantAccount) { const customerId2 = await paymentsAdapter.createCustomer({ email: user.email, metadata: { userId: user.id } }); await adapter.createMerchantCustomer({ identifier: customerId2, merchantAccountId: merchantAccount.id, userId }); return customerId2; } } } return false; } __name(findOrCreateStripeCustomerId, "findOrCreateStripeCustomerId"); var _CheckoutError = class _CheckoutError extends Error { constructor(message, productId, couponId) { super(message); __publicField(this, "couponId"); __publicField(this, "productId"); this.name = "CheckoutError"; this.couponId = couponId; this.productId = productId; } }; __name(_CheckoutError, "CheckoutError"); var CheckoutError = _CheckoutError; var buildCouponNameWithProductName = /* @__PURE__ */ __name((pre, productName, post) => { const totalLength = pre.length + productName.length + post.length; if (totalLength > 40) { const excess = totalLength - 40 + 3; productName = productName.slice(0, -excess) + "..."; } return pre + productName + post; }, "buildCouponNameWithProductName"); var buildCouponName = /* @__PURE__ */ __name((upgradeFromPurchase, productId, availableUpgrade, purchaseWillBeRestricted, stripeCouponPercentOff) => { let couponName = null; if (upgradeFromPurchase?.status === "Restricted" && !purchaseWillBeRestricted && upgradeFromPurchase.productId === productId) { couponName = "Unrestricted"; } else if (availableUpgrade && upgradeFromPurchase?.status === "Valid") { couponName = buildCouponNameWithProductName("Upgrade from ", upgradeFromPurchase.product?.name || "", ""); } else if (availableUpgrade && upgradeFromPurchase?.status === "Restricted" && purchaseWillBeRestricted) { couponName = buildCouponNameWithProductName("Upgrade from ", upgradeFromPurchase.product?.name || "", ` + PPP ${Math.floor(stripeCouponPercentOff * 100)}% off`); } else if (availableUpgrade && upgradeFromPurchase?.status === "Restricted" && !purchaseWillBeRestricted) { couponName = buildCouponNameWithProductName("Unrestricted Upgrade from ", upgradeFromPurchase.product?.name || "", ""); } else { couponName = "Discount"; } return couponName; }, "buildCouponName"); var LoadedProductSchema = z.object({ id: z.string() }); async function stripeCheckout({ params, config, adapter }) { try { if (!adapter) { throw new Error("Adapter is required"); } const ip_address = params.ip_address; let errorRedirectUrl = void 0; try { const { productId, quantity: queryQuantity = 1, couponId, userId, upgradeFromPurchaseId, bulk = false, usedCouponId } = params; errorRedirectUrl = config.errorRedirectUrl; const cancelUrl = params.cancelUrl || config.cancelUrl; const quantity = Number(queryQuantity); const user = userId ? await adapter.getUser?.(userId) : false; console.log("user", user); const upgradeFromPurchase = upgradeFromPurchaseId ? await adapter.getPurchase(upgradeFromPurchaseId) : null; const availableUpgrade = quantity === 1 && upgradeFromPurchase ? await adapter.getUpgradableProducts({ upgradableFromId: upgradeFromPurchase.productId, upgradableToId: productId }) : null; const customerId = user ? await findOrCreateStripeCustomerId(user.id, adapter, config.paymentsAdapter) : false; console.log("customerId", customerId); const loadedProduct = await adapter.getProduct(productId); const result = LoadedProductSchema.safeParse(loadedProduct); if (!result.success) { const errorMessages = result.error.errors.map((err) => err.message).join(", "); console.error(`No product (${productId}) was found (${errorMessages})`); throw new CheckoutError(`No product was found`, String(loadedProduct?.id), couponId); } const loadedProductData = result.data; const merchantProduct = await adapter.getMerchantProductForProductId(loadedProductData.id); const merchantProductIdentifier = merchantProduct?.identifier; if (!merchantProduct) { throw new Error("No merchant product found"); } const merchantPrice = await adapter.getMerchantPriceForProductId(merchantProduct.id); const merchantPriceIdentifier = merchantPrice?.identifier; if (!merchantPriceIdentifier || !merchantProductIdentifier) { throw new Error("No merchant price or product found"); } const stripePrice = await config.paymentsAdapter.getPrice(merchantPriceIdentifier); const isRecurring = stripePrice?.recurring; const merchantCoupon = couponId ? await adapter.getMerchantCoupon(couponId) : null; const stripeCouponPercentOff = merchantCoupon && merchantCoupon.identifier ? await config.paymentsAdapter.getCouponPercentOff(merchantCoupon.identifier) : 0; let discounts = []; let appliedPPPStripeCouponId = void 0; let upgradedFromPurchaseId = void 0; const isUpgrade = Boolean((availableUpgrade || upgradeFromPurchase?.status === "Restricted") && upgradeFromPurchase); const TWELVE_FOUR_HOURS_FROM_NOW = Math.floor(add(/* @__PURE__ */ new Date(), { hours: 12 }).getTime() / 1e3); if (isUpgrade && upgradeFromPurchase && loadedProduct && customerId) { const purchaseWillBeRestricted = merchantCoupon?.type === "ppp"; appliedPPPStripeCouponId = merchantCoupon?.identifier; upgradedFromPurchaseId = upgradeFromPurchase.id; const fixedDiscountForIndividualUpgrade = await getFixedDiscountForIndividualUpgrade({ purchaseToBeUpgraded: upgradeFromPurchase, productToBePurchased: loadedProduct, purchaseWillBeRestricted, userId, ctx: adapter }); const productPrice = await adapter.getPriceForProduct(loadedProduct.id); const fullPrice = productPrice?.unitAmount || 0; const calculatedPrice = getCalculatedPrice({ unitPrice: fullPrice, percentOfDiscount: stripeCouponPercentOff, quantity: 1, fixedDiscount: fixedDiscountForIndividualUpgrade }); const upgradeFromProduct = await adapter.getProduct(upgradeFromPurchase.productId); if (fixedDiscountForIndividualUpgrade > 0) { const couponName = buildCouponName({ ...upgradeFromPurchase, product: upgradeFromProduct }, productId, first(availableUpgrade), purchaseWillBeRestricted, stripeCouponPercentOff); const amount_off_in_cents = (fullPrice - calculatedPrice) * 100; const couponId2 = await config.paymentsAdapter.createCoupon({ amount_off: amount_off_in_cents, name: couponName, max_redemptions: 1, redeem_by: TWELVE_FOUR_HOURS_FROM_NOW, currency: "USD", applies_to: { products: [ merchantProductIdentifier ] } }); discounts.push({ coupon: couponId2 }); } } else if (merchantCoupon && merchantCoupon.identifier) { const isNotPPP = merchantCoupon.type !== "ppp"; if (isNotPPP || quantity === 1) { appliedPPPStripeCouponId = merchantCoupon.type === "ppp" ? merchantCoupon?.identifier : void 0; const promotionCodeId = await config.paymentsAdapter.createPromotionCode({ coupon: merchantCoupon.identifier, max_redemptions: 1, expires_at: TWELVE_FOUR_HOURS_FROM_NOW }); discounts.push({ promotion_code: promotionCodeId }); } } if (!loadedProduct) { throw new Error("No product was found"); } let successUrl = (() => { const baseQueryParams = { session_id: "{CHECKOUT_SESSION_ID}", provider: "stripe" }; if (isRecurring) { const queryParamString = buildSearchParams(baseQueryParams); return `${config.baseSuccessUrl}/thanks/subscription?${queryParamString}`; } if (isUpgrade) { const queryParamString = buildSearchParams({ ...baseQueryParams, upgrade: "true" }); return `${config.baseSuccessUrl}/welcome?${queryParamString}`; } else { const queryParamString = buildSearchParams(baseQueryParams); return `${config.baseSuccessUrl}/thanks/purchase?${queryParamString}`; } })(); const metadata = CheckoutSessionMetadataSchema.parse({ ...Boolean(availableUpgrade && upgradeFromPurchase) && { upgradeFromPurchaseId }, bulk: Boolean(bulk) ? "true" : quantity > 1 ? "true" : "false", ...appliedPPPStripeCouponId && { appliedPPPStripeCouponId }, ...upgradedFromPurchaseId && { upgradedFromPurchaseId }, country: params.country || process.env.DEFAULT_COUNTRY || "US", ip_address: ip_address || "", ...usedCouponId && { usedCouponId }, productId: loadedProduct.id, product: loadedProduct.name, ...user && { userId: user.id }, siteName: process.env.NEXT_PUBLIC_APP_NAME, ...params.organizationId && { organizationId: params.organizationId } }); const sessionUrl = await config.paymentsAdapter.createCheckoutSession({ discounts, line_items: [ { price: merchantPriceIdentifier, quantity: Number(quantity) } ], expires_at: TWELVE_FOUR_HOURS_FROM_NOW, mode: isRecurring ? "subscription" : "payment", success_url: successUrl, cancel_url: cancelUrl, ...isRecurring ? customerId ? { customer: customerId } : user && { customer_email: user.email } : customerId ? { customer: customerId } : { customer_creation: "always" }, metadata, ...!isRecurring && { payment_intent_data: { metadata } } }); if (sessionUrl) { return { redirect: sessionUrl, status: 303 }; } else { throw new CheckoutError("no-stripe-session", loadedProduct.id, couponId); } } catch (err) { console.error("err", err); if (errorRedirectUrl) { return { redirect: errorRedirectUrl, status: 303 }; } return { status: 500, body: { error: true, message: err.message } }; } } catch (error) { return { status: 500, body: { error: true, message: error.message } }; } } __name(stripeCheckout, "stripeCheckout"); export { CheckoutParamsSchema, CheckoutError, stripeCheckout }; //# sourceMappingURL=chunk-RLQCDCZC.js.map