@coursebuilder/core
Version:
Core package for Course Builder
337 lines (335 loc) • 12.8 kB
JavaScript
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