UNPKG

@wallfar/ocd-studio-core-sdk

Version:

Helper SDK for our OneClick Studio modules

209 lines (208 loc) 8.11 kB
import { validateAddress } from '../../shared/webshop/validateAddress.js'; import Stripe from 'stripe'; export async function validatePromoCode(config, code) { try { const { db, 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 * 1000) > new Date()) throw new Error(); if (promo.expiresAt?.seconds && new Date(promo.expiresAt.seconds * 1000) < 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' }; } } export 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 validatedCart = []; let subtotal = 0; let discountObject = null; // 1. Get correct cart data const products = await Promise.all(cart.map(async (item) => { const product = await config.db.collection(config.products_collection).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 }; validatedCart.push(new_item); subtotal += (new_item.quantity * new_item.price); }); // 2. Validate promo code if (activePromoCode) { let { valid, promo, error } = await validatePromoCode(config, activePromoCode); if (!valid) throw new Error(error); discountObject = promo; } // 3. Validate shipping option const { valid: isValidShippingOption, price: shippingFee } = await validateShippingOption(config, shippingOption, deliveryAddress, subtotal); if (!isValidShippingOption) throw new Error('Invalid shipping option'); // 4. Validate delivery address const isValidAddress = validateAddress(deliveryAddress); if (isValidAddress.length > 0) throw new Error(isValidAddress.join(', ')); // 5. Calculate total const discount = discountObject ? calculateDiscount(discountObject, subtotal) : 0; let total = subtotal - discount + (shippingFee || 0); if (total <= 0) total = 0; console.log("stripe = ", { price_data: { currency: 'eur', unit_amount: (total * 100).toFixed(0), product_data: { name: "Order", images: [] } }, quantity: 1 }); // 6. Create payment link 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') { // TODO } } return { valid: validCountry && validCondition, price: shippingOption.price || 0 }; } async function getAllShippingOptions(config) { const { 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; }