@wallfar/ocd-studio-core-sdk
Version:
Helper SDK for our OneClick Studio modules
196 lines (191 loc) • 7.71 kB
JavaScript
import { v as validateAddress } from './shared/ocd-studio-core-sdk.ChPIrm-j.mjs';
import Stripe from 'stripe';
async function validatePromoCode(config, code) {
try {
const { db, promoCodes: promo_codes_collection } = config;
let result = await db.collection(promo_codes_collection).where("code", "==", code).get();
if (result.empty || !result?.docs?.[0]) {
throw new Error();
}
let promo = result.docs[0].data();
if (promo.disabled) throw new Error();
if (promo.usageLimit !== 0 && promo.usageLimit <= promo.usageCount) throw new Error("Code is already used");
if (promo.startsAt?.seconds && new Date(promo.startsAt.seconds * 1e3) > /* @__PURE__ */ new Date()) throw new Error();
if (promo.expiresAt?.seconds && new Date(promo.expiresAt.seconds * 1e3) < /* @__PURE__ */ new Date()) throw new Error();
return {
valid: true,
promo: {
id: result.docs[0].id,
code: promo.code,
type: promo.type,
value: promo.value,
minOrderAmount: promo.minOrderAmount
}
};
} catch (error) {
return {
valid: false,
error: error.message || "Code is invalid"
};
}
}
async function createPaymentLinkStripe(config, stripeConfig, checkoutData) {
if (!stripeConfig.key) throw new Error("No stripe key provided");
const stripe = new Stripe(stripeConfig.key);
const { cart, shippingOption, activePromoCode, deliveryAddress } = checkoutData;
if (!cart || cart.length === 0) throw new Error("No products in cart");
if (!shippingOption) throw new Error("No shipping option selected");
if (!deliveryAddress) throw new Error("No delivery address");
let subtotal = 0;
let discountObject = null;
const products = await Promise.all(cart.map(async (item) => {
const product = await config.db.collection(config.products).doc(item.productId).get();
return { id: product.id, ...product.data() };
}));
cart.forEach((cart_item) => {
let product = products.find((i) => i.id == cart_item.productId);
let stock = product.stock, price = product.price;
if (Array.isArray(product.options) && product.options.length > 0) {
let variant = getVariant(product, cart_item.variant);
stock = variant.stock;
price = variant.price;
}
let new_item = {
id: cart_item.productId,
slug: product.slug,
title: product.title,
featuredMedia: product.featuredMedia,
variant: cart_item.variant,
quantity: stock < cart_item.quantity ? stock : cart_item.quantity,
price
};
subtotal += new_item.quantity * new_item.price;
});
if (activePromoCode) {
let { valid, promo, error } = await validatePromoCode(config, activePromoCode);
if (!valid) throw new Error(error);
discountObject = promo;
}
const { valid: isValidShippingOption, price: shippingFee } = await validateShippingOption(config, shippingOption, deliveryAddress, subtotal);
if (!isValidShippingOption) throw new Error("Invalid shipping option");
const isValidAddress = validateAddress(deliveryAddress);
if (isValidAddress.length > 0) throw new Error(isValidAddress.join(", "));
const discount = discountObject ? calculateDiscount(discountObject, subtotal) : 0;
let total = subtotal - discount + (shippingFee || 0);
if (total <= 0) total = 0;
const session = await stripe.checkout.sessions.create({
customer_email: "maxim@wallfar.com",
payment_method_types: ["card", "bancontact", "ideal", "sofort"],
line_items: [{
price_data: {
currency: "eur",
unit_amount: parseInt((total * 100).toFixed(0)),
product_data: {
name: "Order",
images: []
}
},
quantity: 1
}],
mode: "payment",
success_url: "https://soakhandmade.be/checkout/success",
cancel_url: "https://soakhandmade.be/checkout/cancel",
metadata: {
fbase_order_id: "fbase_order_id"
},
payment_intent_data: {
metadata: {
fbase_order_id: "fbase_order_id"
}
}
});
if (!session.url) throw new Error("Could not create payment link");
return session.url;
}
async function validateShippingOption(config, optionId, deliveryAddress, subtotal) {
const shippingOptions = await getAllShippingOptions(config);
const shippingOption = shippingOptions?.find((opt) => opt.id === optionId);
if (!shippingOption) return { valid: false };
if (shippingOption.status !== "published") return { valid: false };
const validCountry = !shippingOption.countries || shippingOption.countries.length === 0 || shippingOption.countries.includes(deliveryAddress.country);
if (!validCountry) return { valid: false };
let validCondition = shippingOption.condition === "always";
if (!validCondition) {
if (shippingOption.condition === "price_based") {
const MIN = shippingOption.condition_min || 0;
const MAX = shippingOption.condition_max || Infinity;
validCondition = subtotal >= MIN && subtotal <= MAX;
} else if (shippingOption.condition === "weight_based") ;
}
return {
valid: validCountry && validCondition,
price: shippingOption.price || 0
};
}
async function getAllShippingOptions(config) {
const { shippingOptions: shipping_options_collection } = config;
if (!shipping_options_collection) return null;
if (shipping_options_collection.includes(":")) {
const collectionParts = shipping_options_collection.split(":");
const pathParts = collectionParts[0].split("/");
const path = pathParts.slice(0, pathParts.length - 1);
const docId = pathParts[pathParts.length - 1];
const docSnap = await config.db.collection(path.join("/")).doc(docId).get();
const options = docSnap.data()?.[collectionParts[1]];
return options?.filter((opt) => opt.status === "published") || null;
} else {
const snapshot = await config.db.collection(shipping_options_collection).get();
return !snapshot.empty ? snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() })).filter((opt) => opt.status === "published") : null;
}
}
function getVariant(product, variant) {
if (!product.variants || product.variants?.length === 0) {
return {
id: product.id,
options: [],
combinedOptions: "",
stock: product.stock,
price: product.price,
featuredMedia: product.featuredMedia,
disabled: false,
title: product.title,
slug: product.slug
};
}
return product.variants?.find(
(v) => v.options.every((o) => o.value === variant[o.option])
);
}
function calculateDiscount(promo, subtotal) {
if (!promo) return 0;
if (promo.type === "percentage") {
return subtotal * ((promo.value || 0) / 100) || 0;
}
if (promo.type === "fixed") {
return promo.value || 0;
}
return 0;
}
class WebshopServer {
config;
constructor(config) {
this.config = config;
if (!this.config.defaultBrand) this.config.defaultBrand = "";
if (!this.config.defaultCurrency) this.config.defaultCurrency = "USD";
if (!this.config.defaultLocale) this.config.defaultLocale = "en-US";
}
async validatePromoCode(code) {
if (this.config.provider === "firebase") {
return await validatePromoCode(this.config.firebase, code);
}
throw new Error(`Provider ${this.config.provider} not supported.`);
}
async createPaymentLink(checkoutData) {
if (this.config.provider === "firebase" && this.config.paymentProvider === "stripe") {
return await createPaymentLinkStripe(this.config.firebase, this.config.stripe, checkoutData);
}
throw new Error(`Provider ${this.config.provider} not supported.`);
}
}
const studioComponentsKey = Symbol("studioComponents");
export { WebshopServer, studioComponentsKey };