"use strict"; function moneyMinor(value) { const text = String(value ?? "").trim().replace(/[ \u00a0]/g, ""); if (!/^\d+(?:[.,]\d{1,2})?$/.test(text)) throw new Error("amount must be non-negative with at most two decimals"); const [whole, fraction = ""] = text.replace(",", ".").split("."); const result = Number(BigInt(whole) * 100n + BigInt(fraction.padEnd(2, "0"))); if (!Number.isSafeInteger(result)) throw new Error("amount exceeds safe range"); return result; } function mapOrder(input) { if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error("order must be an object"); const id = String(input.order_id ?? "").trim(); const email = String(input.customer?.email ?? "").trim().toLowerCase(); const currency = String(input.currency ?? "").toUpperCase(); if (!/^[A-Za-z0-9_-]{1,80}$/.test(id)) throw new Error("order_id is missing or invalid"); if (!/^[^\s@=+]+@[^\s@]+\.[^\s@]+$/.test(email)) throw new Error("customer email is invalid"); if (!["SEK", "EUR", "USD"].includes(currency)) throw new Error("unsupported currency"); return { id, customer_email: email, amount_minor: moneyMinor(input.amount), currency }; } function parseCsv(text) { if (typeof text !== "string" || new TextEncoder().encode(text).length > 1e5) throw new Error("CSV must be at most 100 KB"); const rows = []; let row = []; let field = ""; let quoted = false; let closed = false; for (let i = 0; i < text.length; i++) { const c = text[i]; if (quoted) { if (c === '"' && text[i + 1] === '"') { field += '"'; i++; } else if (c === '"') { quoted = false; closed = true; } else field += c; } else if (c === '"') { if (field || closed) throw new Error("invalid CSV quote"); quoted = true; } else if (c === ",") { row.push(field); field = ""; closed = false; } else if (c === "\n" || c === "\r") { if (c === "\r" && text[i + 1] === "\n") i++; row.push(field); if (row.some((v) => v !== "")) rows.push(row); row = []; field = ""; closed = false; } else { if (closed) throw new Error("characters after closing quote"); field += c; } } if (quoted) throw new Error("unclosed CSV quote"); if (field || row.length || closed) { row.push(field); rows.push(row); } return rows; } function validateCsv(text) { const rows = parseCsv(text); const header = rows.shift(); if (JSON.stringify(header) !== JSON.stringify(["id", "email", "amount"])) throw new Error("expected id,email,amount headers"); const accepted = []; const rejected = []; const seen = /* @__PURE__ */ new Set(); rows.forEach((row, i) => { try { if (row.length !== 3) throw new Error("wrong column count"); const item = mapOrder({ order_id: row[0], customer: { email: row[1] }, amount: row[2], currency: "SEK" }); if (seen.has(item.id)) throw new Error("duplicate id"); seen.add(item.id); accepted.push(item); } catch (error) { rejected.push({ row: i + 2, reason: error.message }); } }); return { accepted, rejected }; } function mapShopifyCsv(text) { const rows = parseCsv(text); if (JSON.stringify(rows.shift()) !== JSON.stringify(["handle", "title", "price"])) throw new Error("expected handle,title,price headers"); const accepted = [], rejected = [], seen = /* @__PURE__ */ new Set(); const quote = /* @__PURE__ */ __name((value) => '"' + String(value).replace(/"/g, '""') + '"', "quote"); rows.forEach((row, i) => { try { if (row.length !== 3) throw new Error("wrong column count"); const [handle, title, price] = row.map((value) => value.trim()); if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(handle)) throw new Error("demo requires a lowercase letter/digit handle separated by hyphens"); if (!title || /^[=+\-@\t\r]/.test(title)) throw new Error("missing title or spreadsheet formula prefix"); if (seen.has(handle)) throw new Error("duplicate handle; variants are outside this demo"); const minor = moneyMinor(price); const formattedPrice = String(Math.floor(minor / 100)) + "." + String(minor % 100).padStart(2, "0"); seen.add(handle); accepted.push([title, handle, formattedPrice, "draft", "false"]); } catch (error) { rejected.push({ row: i + 2, reason: error.message }); } }); return { csv: [["Title", "URL handle", "Price", "Status", "Published on online store"], ...accepted].map((row) => row.map(quote).join(",")).join("\r\n"), accepted_count: accepted.length, rejected, scope: "Synthetic new single-variant products only. Validate in your own store before importing; this does not prove store acceptance." }; } function demoEvidence() { const input = { order_id: " ORD-7 ", customer: { email: " Team@Example.invalid " }, amount: "1 249,50", currency: "sek" }; const csv = 'id,email,amount\nA-1,team@example.invalid,"19,95"\nA-1,team@example.invalid,20\nA-2,not-an-email,10\n=SUM(A1),team@example.invalid,30'; return { provenance: "Self-initiated demonstrations using synthetic data; these are not client projects.", api: { input, before: { id: null, customer_email: null, amount_minor: Math.round(parseFloat(input.amount) * 100), currency: input.currency }, after: mapOrder(input), explanation: "Map the actual field names, normalize case, and convert a Swedish-formatted amount into integer minor units." }, csv: { input: csv, before: { blindly_accepted_rows: 4 }, after: validateCsv(csv), explanation: "Keep valid rows and report duplicates, invalid emails, and unsafe identifiers with row numbers." } }; } module.exports = { moneyMinor, mapOrder, parseCsv, validateCsv, mapShopifyCsv, demoEvidence };