/*! * Nuva Checkout Foundation — Phase 6B * --------------------------------------------------------------------------- * Builds the checkout STRUCTURE only: field validation, an order summary * derived from the cart + catalog (never from a remembered cart price), * a payment-method architecture with two seams (online, cash/pay-on- * delivery) that are both explicitly marked NOT LIVE, and the Part 21 order * data model. No real payment is processed by this file, ever. * * ORDERS is an in-memory array used only so this foundation can be tested * end-to-end. It is not a real persistence layer — Part 43 requires a real * backend to validate price/stock server-side and persist orders before * this can go live. clearOrders() exists so test/dev runs never leave * residual data behind (Part 40). */ (function (root) { 'use strict'; var CHECKOUT_VERSION = '1.0'; var PAYMENT_METHODS = [ { id: 'online-payment', label: 'Online Payment', live: false, note: 'Online payment provider integration is not yet configured. Selecting this option does not process a real payment in this phase.' }, { id: 'cash-on-delivery', label: 'Cash on Delivery / Pay on Delivery', live: false, note: 'COD/POD logistics integration is not yet configured. Selecting this option does not trigger real fulfillment in this phase.' } ]; var ORDERS = []; function str(v) { return typeof v === 'string' ? v.trim() : ''; } /* =========================================================================== * PART 19 — customer field validation * =========================================================================== */ function validatePhone(phone) { var cleaned = str(phone).replace(/[\s-]/g, ''); // Nigeria-friendly: 0XXXXXXXXXX or +234XXXXXXXXXX / 234XXXXXXXXXX, local // subscriber number starting 7/8/9. return /^(\+?234|0)[789]\d{9}$/.test(cleaned); } function validateEmail(email) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str(email)); } function validateCustomerInfo(customer, delivery) { customer = customer || {}; delivery = delivery || {}; var errors = {}; if (!str(customer.name)) errors.name = 'Full name is required.'; if (!str(customer.phone)) errors.phone = 'Phone number is required.'; else if (!validatePhone(customer.phone)) errors.phone = 'Enter a valid Nigerian phone number (e.g. 080XXXXXXXX or +234XXXXXXXXXX).'; if (!str(customer.email)) errors.email = 'Email is required.'; else if (!validateEmail(customer.email)) errors.email = 'Enter a valid email address.'; if (!str(delivery.address)) errors.address = 'Delivery address is required.'; if (!str(delivery.city)) errors.city = 'City is required.'; if (!str(delivery.state)) errors.state = 'State is required.'; // delivery.notes is optional return { valid: Object.keys(errors).length === 0, errors: errors }; } /* =========================================================================== * PART 19 — order summary (built from the CART's own re-resolved items, * never from a value the client remembered on its own) * =========================================================================== */ function buildOrderSummary(deliveryFee) { var cart = root.NuvaCart; if (!cart) throw new Error('NuvaCheckout requires window.NuvaCart (Phase 6B) to be loaded first.'); var items = cart.getItems().filter(function (i) { return i.available; }); var subtotal = items.reduce(function (sum, i) { return sum + i.lineTotal; }, 0); var fee = typeof deliveryFee === 'number' ? deliveryFee : null; return { items: items.map(function (i) { return { productId: i.productId, name: i.name, quantity: i.quantity, unitPrice: i.unitPrice, currency: i.currency, lineTotal: i.lineTotal }; }), subtotal: subtotal, deliveryFee: fee, total: subtotal + (fee || 0) }; } function getPaymentMethods() { return PAYMENT_METHODS.slice(); } function isPaymentMethodValid(id) { return PAYMENT_METHODS.some(function (m) { return m.id === id; }); } function generateOrderId() { // Development-only scheme. A real deployment must generate this // server-side alongside server-side price/stock validation (Part 43). return 'DEV-ORDER-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8).toUpperCase(); } /* =========================================================================== * PART 21/22 — order creation * =========================================================================== */ function createOrder(input) { input = input || {}; var customer = input.customer || {}; var delivery = input.delivery || {}; var paymentMethod = input.paymentMethod; var source = input.source || {}; var fieldCheck = validateCustomerInfo(customer, delivery); if (!fieldCheck.valid) { return { ok: false, order: null, errors: fieldCheck.errors }; } if (!isPaymentMethodValid(paymentMethod)) { return { ok: false, order: null, errors: { paymentMethod: 'Select a valid payment method.' } }; } var summary = buildOrderSummary(input.deliveryFee); if (!summary.items.length) { return { ok: false, order: null, errors: { cart: 'Your cart has no available items to order.' } }; } var order = { orderId: generateOrderId(), customer: { name: str(customer.name), phone: str(customer.phone), email: str(customer.email) }, delivery: { address: str(delivery.address), city: str(delivery.city), state: str(delivery.state), notes: str(delivery.notes) }, items: summary.items, subtotal: summary.subtotal, deliveryFee: summary.deliveryFee, total: summary.total, paymentMethod: paymentMethod, paymentStatus: 'pending', orderStatus: 'pending', source: { // PART 22 — non-sensitive traceability only. No photos, no full // assessment payload, nothing transmitted externally. consultationType: str(source.consultationType), assessmentId: str(source.assessmentId), recommendationId: str(source.recommendationId) }, createdAt: new Date().toISOString() }; ORDERS.push(order); return { ok: true, order: order, errors: {} }; } function getOrders() { return JSON.parse(JSON.stringify(ORDERS)); } function clearOrders() { ORDERS.length = 0; // mutate in place so _internals.ORDERS (a live reference) stays accurate } var api = { version: CHECKOUT_VERSION, getPaymentMethods: getPaymentMethods, isPaymentMethodValid: isPaymentMethodValid, validateCustomerInfo: validateCustomerInfo, validatePhone: validatePhone, validateEmail: validateEmail, buildOrderSummary: buildOrderSummary, createOrder: createOrder, getOrders: getOrders, clearOrders: clearOrders, _internals: { ORDERS: ORDERS, PAYMENT_METHODS: PAYMENT_METHODS } }; root.NuvaCheckout = api; if (typeof module !== 'undefined' && module.exports) { module.exports = api; } })(typeof window !== 'undefined' ? window : this);