import { z } from 'zod'; import { timestampSchema } from './common.schemas'; export const paymentProviderEnum = z.enum(['paystack', 'opay']); export const orderChannelEnum = z.enum([ 'litepos', 'whatsapp', 'storefront_web', 'voice', 'kiosk', ]); export const orderStatusEnum = z.enum([ 'pending_approval', 'approved', 'awaiting_fulfillment', 'pending_payment', 'awaiting_manual_payment', 'paid', 'cancelled', 'failed', 'expired', ]); export const orderPaymentStatusEnum = z.enum([ 'pending', 'paid', 'failed', 'expired', 'cancelled', ]); export const collectPaymentModeEnum = z.enum(['link', 'qr', 'auto']); export const orderLineItemSchema = z.object({ productId: z.string().uuid(), quantity: z.number().positive(), unitPrice: z.number().nonnegative(), lineTotal: z.number().nonnegative(), productName: z.string(), }); export const shopOrderPaymentSchema = z .object({ id: z.string().uuid(), provider: paymentProviderEnum.or(z.literal('manual')), reference: z.string(), amount: z.number(), status: orderPaymentStatusEnum, paymentUrl: z.string().nullable(), qrPayload: z.string().nullable(), expiresAt: z.coerce.date().nullable(), }) .merge(timestampSchema); export const shopOrderSchema = z .object({ id: z.string().uuid(), status: orderStatusEnum, channel: orderChannelEnum, currency: z.string(), totalAmount: z.number(), buyerEmail: z.string().nullable(), buyerPhone: z.string().nullable(), buyerName: z.string().nullable(), customerId: z.string().uuid().nullable(), lineItems: z.array(orderLineItemSchema), notes: z.string().nullable(), payments: z.array(shopOrderPaymentSchema).optional(), }) .merge(timestampSchema); export const paymentConnectionSchema = z .object({ id: z.string().uuid(), provider: paymentProviderEnum, status: z.enum(['active', 'inactive', 'pending']), capabilities: z.object({ qr: z.boolean().optional(), link: z.boolean().optional(), transfer: z.boolean().optional(), }), merchantId: z.string().nullable(), isDefault: z.boolean(), }) .merge(timestampSchema); export const createOrderSchema = z.object({ items: z .array( z.object({ productId: z.string().uuid(), quantity: z.number().positive(), }), ) .min(1), channel: orderChannelEnum.default('litepos'), buyerEmail: z.string().email().optional(), buyerPhone: z.string().optional(), buyerName: z.string().optional(), customerId: z.string().uuid().optional(), notes: z.string().max(500).optional(), }); export const collectPaymentSchema = z.object({ provider: paymentProviderEnum.optional(), mode: collectPaymentModeEnum.default('auto'), buyerEmail: z.string().email().optional(), expiryMinutes: z.number().int().min(5).max(60).default(10), }); export const approveOrderSchema = z.object({ paymentMode: z.enum(['collect', 'cash_on_pickup']).default('collect'), }); export const rejectOrderSchema = z.object({ reason: z.string().min(1).max(500), }); export const upsertPaymentConnectionSchema = z.object({ provider: paymentProviderEnum, credentials: z.object({ merchantId: z.string().optional(), subaccountCode: z.string().optional(), publicKey: z.string().optional(), secretKey: z.string().optional(), apiKey: z.string().optional(), }), isDefault: z.boolean().optional(), }); export const supplierSchema = z .object({ id: z.string().uuid(), name: z.string(), phone: z.string().nullable(), email: z.string().nullable(), bankDetails: z.record(z.unknown()).nullable(), categories: z.array(z.string()).nullable(), isActive: z.boolean(), }) .merge(timestampSchema); export const createSupplierSchema = z.object({ name: z.string().min(1).max(200), phone: z.string().optional(), email: z.string().email().optional(), bankDetails: z.record(z.unknown()).optional(), categories: z.array(z.string()).optional(), }); export const supplierInvoiceSchema = z .object({ id: z.string().uuid(), supplierId: z.string().uuid(), amount: z.number(), amountPaid: z.number(), remainingBalance: z.number(), currency: z.string(), status: z.enum(['open', 'partial', 'paid', 'cancelled']), dueDate: z.coerce.date().nullable(), lineItems: z.array(z.record(z.unknown())).nullable(), notes: z.string().nullable(), }) .merge(timestampSchema); export const createSupplierInvoiceSchema = z.object({ supplierId: z.string().uuid(), amount: z.number().positive(), currency: z.string().default('NGN'), dueDate: z.coerce.date().optional(), lineItems: z.array(z.record(z.unknown())).optional(), notes: z.string().optional(), }); export const supplierPaymentSchema = z .object({ id: z.string().uuid(), supplierId: z.string().uuid(), invoiceId: z.string().uuid().nullable(), amount: z.number(), currency: z.string(), status: z.enum([ 'pending_confirmation', 'approved', 'processing', 'completed', 'failed', 'cancelled', ]), transferReference: z.string().nullable(), provider: paymentProviderEnum.nullable(), }) .merge(timestampSchema); export const supplierInvoiceDetailSchema = supplierInvoiceSchema.extend({ payments: z.array(supplierPaymentSchema), }); export const createSupplierPaymentSchema = z.object({ supplierId: z.string().uuid(), invoiceId: z.string().uuid().optional(), amount: z.number().positive(), notes: z.string().optional(), }); export const approveSupplierPaymentSchema = z.object({ confirm: z.literal(true), }); const breakdownBucketSchema = z.object({ count: z.number(), total: z.number(), }); export const reconciliationSummarySchema = z.object({ date: z.string(), paidCount: z.number(), pendingCount: z.number(), expiredCount: z.number(), paidTotal: z.number(), pendingTotal: z.number(), mismatchCount: z.number(), byProvider: z.object({ paystack: breakdownBucketSchema, opay: breakdownBucketSchema, manual: breakdownBucketSchema, }), byChannel: z.record(breakdownBucketSchema), byPaymentMethod: z.record(breakdownBucketSchema), cashCreditTotals: z.object({ cashTotal: z.number(), creditTotal: z.number(), transferTotal: z.number(), }), mismatches: z.array( z.object({ orderId: z.string().uuid().optional(), paymentId: z.string().uuid().optional(), issue: z.string(), }), ), }); export const reconciliationMismatchSchema = z.object({ orderId: z.string().uuid().optional(), paymentId: z.string().uuid().optional(), issue: z.string(), amount: z.number().optional(), createdAt: z.coerce.date().optional(), }); export const reconciliationMismatchesResponseSchema = z.object({ date: z.string(), items: z.array(reconciliationMismatchSchema), page: z.number(), limit: z.number(), total: z.number(), totalPages: z.number(), }); export type ShopOrder = z.infer; export type OrderLineItem = z.infer; export type PaymentConnection = z.infer;