import { _ as isPaymentFailedError, a as FulfillmentIncompleteError, c as OrderNotChargeableError, d as isCheckoutValidationError, f as isFulfillmentIncompleteError, g as isOrderNotFoundError, h as isOrderNotChargeableError, i as CheckoutValidationError, l as OrderNotFoundError, m as isOrderIdempotencyConflictError, n as buildChargeKey, o as OrderChargeConflictError, p as isOrderChargeConflictError, r as parseOrderId, s as OrderIdempotencyConflictError, t as settleCheckout, u as PaymentFailedError } from "./settle-DWnnK2UQ.js";
import { reconcileStuckCharges } from "./reconcile/index.js";
import { OrderIntegrityError, OrderNotFoundError as OrderNotFoundError$1, OrderValidationError, claimForCharge, createOrder, getOrderById, isOrderNotChargeableError as isOrderNotChargeableError$1, ordersSchema } from "@platform-modules/commerce-orders";
import { settleCharge } from "@platform-modules/billing";
import { EU_VAT_SCHEDULES, IL_VAT_SCHEDULE, applyVat, extractVat, resolveVatRate } from "@platform-modules/tax/rates-table";
import { and, eq, sql } from "drizzle-orm";
import { product, variant, variantPrice } from "@platform-modules/commerce-catalog";
import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
//#region src/validate.ts
function priceSnapshotsEqual(a, b) {
	return a.amount === b.amount && a.currency === b.currency && a.priceMode === b.priceMode;
}
function validateCheckout(input) {
	const { cart, catalog, currency, priceMode, vatRate } = input;
	if (cart.lines.length === 0) return {
		ok: false,
		error: "EMPTY_CART"
	};
	if (currency !== cart.currency) return {
		ok: false,
		error: "CURRENCY_MISMATCH"
	};
	for (const line of cart.lines) if (line.price.currency !== currency) return {
		ok: false,
		error: "CURRENCY_MISMATCH"
	};
	const stale = {
		removed: [],
		updated: []
	};
	for (const line of cart.lines) {
		const entry = catalog.get(line.variantId);
		if (entry === void 0 || entry.available === false) {
			stale.removed.push(line.variantId);
			continue;
		}
		if (!priceSnapshotsEqual(line.price, entry.price)) stale.updated.push({
			variantId: line.variantId,
			was: line.price.amount,
			now: entry.price.amount
		});
	}
	if (stale.removed.length > 0 || stale.updated.length > 0) return {
		ok: false,
		error: "STALE_ITEMS",
		stale
	};
	for (const line of cart.lines) if (!Number.isInteger(line.qty) || line.qty <= 0) return {
		ok: false,
		error: "INVALID_QTY"
	};
	return {
		ok: true,
		lines: cart.lines.map((line) => {
			const qty = BigInt(line.qty);
			if (priceMode === "exclusive") {
				const { vat, gross } = applyVat(line.price.amount, vatRate);
				return {
					variantId: line.variantId,
					qty: line.qty,
					unitNet: line.price.amount,
					vat,
					lineTotal: gross * qty
				};
			}
			const { net, vat } = extractVat(line.price.amount, vatRate);
			return {
				variantId: line.variantId,
				qty: line.qty,
				unitNet: net,
				vat,
				lineTotal: line.price.amount * qty
			};
		})
	};
}
//#endregion
//#region src/schema.ts
const checkoutSession = pgTable("checkout_session", {
	orderId: uuid("order_id").primaryKey(),
	clientSecret: text("client_secret").notNull(),
	createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
	updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
});
const checkoutSchema = { checkoutSession };
const checkoutDbSchema = {
	...checkoutSchema,
	...ordersSchema
};
//#endregion
//#region src/start.ts
function isChargeIntentPendingError(e) {
	return typeof e === "object" && e !== null && e.code === "CHARGE_INTENT_PENDING";
}
function resolveCart(input) {
	if (input.cart) return input.cart;
	throw new CheckoutValidationError("EMPTY_CART");
}
function resolveVatSchedule(country) {
	if (country === "IL") return IL_VAT_SCHEDULE;
	const eu = EU_VAT_SCHEDULES[country];
	if (eu) return eu;
	throw new CheckoutValidationError("UNKNOWN_COUNTRY");
}
function hostTodayIso() {
	return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
}
function isProductAvailable(status, availableFrom, availableUntil, now) {
	if (status !== "active") return false;
	if (availableFrom !== null && availableFrom > now) return false;
	if (availableUntil !== null && availableUntil <= now) return false;
	return true;
}
async function loadCatalogEntry(db, variantId, currency, now) {
	const [variantRow] = await db.select().from(variant).where(eq(variant.id, variantId));
	if (!variantRow) return;
	const [productRow] = await db.select().from(product).where(eq(product.id, variantRow.productId));
	if (!productRow) return;
	const [priceRow] = await db.select().from(variantPrice).where(and(eq(variantPrice.variantId, variantId), eq(variantPrice.currency, currency)));
	if (!priceRow) return;
	return {
		available: isProductAvailable(productRow.status, productRow.availableFrom, productRow.availableUntil, now),
		price: {
			amount: priceRow.amount,
			currency: priceRow.currency,
			priceMode: priceRow.priceMode
		},
		kind: productRow.kind,
		title: productRow.title,
		vendorId: productRow.vendorId
	};
}
async function buildCatalogSnapshot(db, cart, currency) {
	const now = /* @__PURE__ */ new Date();
	const snapshot = /* @__PURE__ */ new Map();
	const variantIds = [...new Set(cart.lines.map((line) => line.variantId))];
	for (const variantId of variantIds) {
		const entry = await loadCatalogEntry(db, variantId, currency, now);
		if (entry) snapshot.set(variantId, entry);
	}
	return snapshot;
}
function mapValidationError(result) {
	throw new CheckoutValidationError(result.error, result.stale);
}
function assertChargeableTotal(total) {
	if (total > BigInt(Number.MAX_SAFE_INTEGER)) throw new OrderValidationError("total");
}
function aggregateOrderAmounts(lines) {
	let subtotal = 0n;
	let tax = 0n;
	let total = 0n;
	for (const line of lines) {
		const qty = BigInt(line.qty);
		subtotal += line.unitNet * qty;
		tax += line.vat * qty;
		total += line.lineTotal;
	}
	if (subtotal + tax !== total) throw new OrderIntegrityError("total");
	assertChargeableTotal(total);
	return {
		subtotal,
		tax,
		total
	};
}
function buildOrderLines(cart, pricedLines, catalog, currency) {
	const pricedByVariant = new Map(pricedLines.map((line) => [line.variantId, line]));
	return cart.lines.map((line) => {
		const priced = pricedByVariant.get(line.variantId);
		const entry = catalog.get(line.variantId);
		if (!priced || !entry) throw new CheckoutValidationError("STALE_ITEMS");
		return {
			variantId: line.variantId,
			kind: entry.kind,
			qty: line.qty,
			unitPrice: priced.unitNet,
			lineTotal: priced.unitNet * BigInt(line.qty),
			currency,
			vendorId: entry.vendorId ?? null
		};
	});
}
function buildVendorSplits(cart, pricedLines, catalog) {
	const pricedByVariant = new Map(pricedLines.map((line) => [line.variantId, line]));
	const totals = /* @__PURE__ */ new Map();
	for (const line of cart.lines) {
		const priced = pricedByVariant.get(line.variantId);
		const entry = catalog.get(line.variantId);
		if (!priced || !entry) throw new CheckoutValidationError("STALE_ITEMS");
		const vendorId = entry.vendorId ?? null;
		totals.set(vendorId, (totals.get(vendorId) ?? 0n) + priced.lineTotal);
	}
	return [...totals.entries()].map(([vendorId, amount]) => ({
		vendorId,
		amount,
		funder: vendorId === null ? "platform" : "vendor"
	}));
}
function assertBuyerOwnsOrder(order, buyerUserId) {
	if (!("userId" in order.buyerRef) || order.buyerRef.userId !== buyerUserId) throw new OrderNotChargeableError(order.id);
}
async function resolvePhase1Action(tx, order, buyerUserId) {
	let current = order;
	while (true) {
		assertBuyerOwnsOrder(current, buyerUserId);
		switch (current.status) {
			case "pending": try {
				return {
					action: "charge",
					order: await claimForCharge(tx, current.id)
				};
			} catch (e) {
				if (!isOrderNotChargeableError$1(e)) throw e;
				const reloaded = await getOrderById(tx, current.id, { isAdmin: true });
				if (!reloaded) throw new OrderNotChargeableError(current.id);
				current = reloaded;
				continue;
			}
			case "charging": return {
				action: "resume",
				order: current
			};
			case "paid": return {
				action: "paid",
				order: current
			};
			default: throw new OrderNotChargeableError(current.id);
		}
	}
}
async function upsertCheckoutSession(db, orderId, clientSecret) {
	const now = /* @__PURE__ */ new Date();
	await db.insert(checkoutSession).values({
		orderId,
		clientSecret,
		createdAt: now,
		updatedAt: now
	}).onConflictDoUpdate({
		target: checkoutSession.orderId,
		set: {
			clientSecret,
			updatedAt: now
		}
	});
}
async function readCheckoutSession(db, orderId) {
	const [row] = await db.select().from(checkoutSession).where(eq(checkoutSession.orderId, orderId));
	return row?.clientSecret;
}
async function startCheckout(deps, input) {
	const cart = resolveCart(input);
	const vatRate = resolveVatRate(resolveVatSchedule(input.buyerCountry), hostTodayIso());
	const catalog = await buildCatalogSnapshot(deps.db, cart, input.currency);
	const validation = validateCheckout({
		cart,
		catalog,
		currency: input.currency,
		priceMode: input.priceMode,
		vatRate
	});
	if (!validation.ok) mapValidationError(validation);
	const { subtotal, tax, total } = aggregateOrderAmounts(validation.lines);
	const newOrder = {
		idempotencyKey: input.idempotencyKey,
		buyerRef: input.buyerRef,
		currency: input.currency,
		priceMode: input.priceMode,
		subtotal,
		tax,
		discount: 0n,
		total,
		lines: buildOrderLines(cart, validation.lines, catalog, input.currency),
		splits: buildVendorSplits(cart, validation.lines, catalog)
	};
	const { action, order } = await deps.db.transaction(async (tx) => {
		const ordersTx = tx;
		return resolvePhase1Action(ordersTx, await createOrder(ordersTx, newOrder), input.buyerRef.userId);
	});
	if (action === "paid") return { orderId: order.id };
	if (action === "resume") {
		const clientSecret = await readCheckoutSession(deps.db, order.id);
		return clientSecret ? {
			orderId: order.id,
			clientSecret
		} : { orderId: order.id };
	}
	assertChargeableTotal(order.total);
	let result;
	try {
		result = await settleCharge({
			chargeKey: buildChargeKey(order.id),
			amount: Number(order.total),
			currency: order.currency
		}, deps);
	} catch (e) {
		if (isChargeIntentPendingError(e)) return { orderId: order.id };
		throw new PaymentFailedError(e);
	}
	if (result.kind === "requires_client_action") {
		await upsertCheckoutSession(deps.db, order.id, result.clientSecret);
		return {
			orderId: order.id,
			clientSecret: result.clientSecret
		};
	}
	await settleCheckout(deps, order.id, result.providerRef, result.amount);
	return { orderId: order.id };
}
//#endregion
//#region src/status.ts
async function getCheckoutStatus(deps, orderId, buyerRef) {
	const order = await getOrderById(deps.db, orderId, { userId: buyerRef.userId });
	if (!order) throw new OrderNotFoundError$1(orderId);
	const result = { status: order.status };
	if (order.status === "charging") {
		const [session] = await deps.db.select().from(checkoutSession).where(eq(checkoutSession.orderId, orderId));
		if (session) result.clientSecret = session.clientSecret;
	}
	return result;
}
//#endregion
//#region src/migrate.ts
var CheckoutMigrateError = class extends Error {
	detail;
	name = "CheckoutMigrateError";
	code = "CHECKOUT_MIGRATE";
	constructor(detail) {
		super(`checkout migrate: ${detail}`);
		this.detail = detail;
	}
};
function isCheckoutMigrateError(e) {
	return typeof e === "object" && e !== null && e.name === "CheckoutMigrateError" && e.code === "CHECKOUT_MIGRATE";
}
const MIGRATION_STATEMENTS = [sql`
    CREATE TABLE IF NOT EXISTS checkout_session (
      order_id uuid PRIMARY KEY,
      client_secret text NOT NULL,
      created_at timestamptz NOT NULL DEFAULT NOW(),
      updated_at timestamptz NOT NULL DEFAULT NOW()
    )
  `];
async function pushSchema(db) {
	try {
		for (const statement of MIGRATION_STATEMENTS) await db.execute(statement);
	} catch (e) {
		throw new CheckoutMigrateError(e instanceof Error ? e.message : String(e));
	}
}
//#endregion
export { CheckoutMigrateError, CheckoutValidationError, FulfillmentIncompleteError, OrderChargeConflictError, OrderIdempotencyConflictError, OrderNotChargeableError, OrderNotFoundError, PaymentFailedError, buildChargeKey, checkoutDbSchema, checkoutSchema, checkoutSession, getCheckoutStatus, isCheckoutMigrateError, isCheckoutValidationError, isFulfillmentIncompleteError, isOrderChargeConflictError, isOrderIdempotencyConflictError, isOrderNotChargeableError, isOrderNotFoundError, isPaymentFailedError, parseOrderId, pushSchema, reconcileStuckCharges, settleCheckout, startCheckout, validateCheckout };
