import { initContract } from '@ts-rest/core'; import { z } from 'zod'; import { shopOrderSchema, paymentConnectionSchema, createOrderSchema, collectPaymentSchema, upsertPaymentConnectionSchema, supplierSchema, createSupplierSchema, supplierInvoiceSchema, createSupplierInvoiceSchema, supplierPaymentSchema, createSupplierPaymentSchema, approveSupplierPaymentSchema, approveOrderSchema, rejectOrderSchema, reconciliationSummarySchema, reconciliationMismatchesResponseSchema, supplierInvoiceDetailSchema, orderStatusEnum, } from '../schemas/shopkeeper-payments.schemas'; import { errorResponseSchema } from '../schemas/common.schemas'; const c = initContract(); export const shopkeeperPaymentsContract = c.router({ listPaymentConnections: { method: 'GET', path: '/shopkeeper/payment-connections', responses: { 200: z.object({ connections: z.array(paymentConnectionSchema) }), 401: errorResponseSchema, }, summary: 'List merchant payment provider connections', }, upsertPaymentConnection: { method: 'POST', path: '/shopkeeper/payment-connections', body: upsertPaymentConnectionSchema, responses: { 200: z.object({ connection: paymentConnectionSchema }), 400: errorResponseSchema, 401: errorResponseSchema, }, summary: 'Connect or update a payment provider for this shop', }, deletePaymentConnection: { method: 'DELETE', path: '/shopkeeper/payment-connections/:provider', pathParams: z.object({ provider: z.enum(['paystack', 'opay']) }), responses: { 200: z.object({ deleted: z.boolean() }), 401: errorResponseSchema, 404: errorResponseSchema, }, summary: 'Remove a payment provider connection', }, createOrder: { method: 'POST', path: '/shopkeeper/orders', body: createOrderSchema, responses: { 201: z.object({ order: shopOrderSchema }), 400: errorResponseSchema, 401: errorResponseSchema, 409: errorResponseSchema, }, summary: 'Create a commerce order from cart items', }, listOrders: { method: 'GET', path: '/shopkeeper/orders', query: z.object({ status: orderStatusEnum.optional(), limit: z.coerce.number().int().min(1).max(100).optional(), }), responses: { 200: z.object({ orders: z.array(shopOrderSchema) }), 401: errorResponseSchema, }, summary: 'List shop orders', }, getOrder: { method: 'GET', path: '/shopkeeper/orders/:id', pathParams: z.object({ id: z.string().uuid() }), responses: { 200: z.object({ order: shopOrderSchema }), 401: errorResponseSchema, 404: errorResponseSchema, }, summary: 'Get order with payment attempts', }, collectPayment: { method: 'POST', path: '/shopkeeper/orders/:id/collect-payment', pathParams: z.object({ id: z.string().uuid() }), body: collectPaymentSchema, responses: { 200: z.object({ order: shopOrderSchema, paymentUrl: z.string().optional(), qrPayload: z.string().optional(), reference: z.string(), expiresAt: z.coerce.date().optional(), }), 400: errorResponseSchema, 401: errorResponseSchema, 404: errorResponseSchema, }, summary: 'Generate payment link or QR for an order', }, verifyOrderPayment: { method: 'POST', path: '/shopkeeper/orders/:id/verify-payment', pathParams: z.object({ id: z.string().uuid() }), body: z.object({ reference: z.string().optional() }), responses: { 200: z.object({ verified: z.boolean(), order: shopOrderSchema.optional() }), 401: errorResponseSchema, 404: errorResponseSchema, }, summary: 'Poll-verify order payment status', }, cancelOrder: { method: 'POST', path: '/shopkeeper/orders/:id/cancel', pathParams: z.object({ id: z.string().uuid() }), body: z.object({}).optional(), responses: { 200: z.object({ order: shopOrderSchema }), 400: errorResponseSchema, 401: errorResponseSchema, 404: errorResponseSchema, }, summary: 'Cancel a pending order', }, approveOrder: { method: 'POST', path: '/shopkeeper/orders/:id/approve', pathParams: z.object({ id: z.string().uuid() }), body: approveOrderSchema, responses: { 200: z.object({ order: shopOrderSchema }), 400: errorResponseSchema, 401: errorResponseSchema, 404: errorResponseSchema, }, summary: 'Approve a customer-initiated order awaiting owner review', }, rejectOrder: { method: 'POST', path: '/shopkeeper/orders/:id/reject', pathParams: z.object({ id: z.string().uuid() }), body: rejectOrderSchema, responses: { 200: z.object({ order: shopOrderSchema }), 400: errorResponseSchema, 401: errorResponseSchema, 404: errorResponseSchema, }, summary: 'Reject a pending-approval order with reason', }, paymentWebhook: { method: 'POST', path: '/shopkeeper/payments/webhook/:provider', pathParams: z.object({ provider: z.enum(['paystack', 'opay']) }), body: z.record(z.unknown()), responses: { 200: z.object({ received: z.boolean() }), 400: errorResponseSchema, }, summary: 'Unified payment provider webhook ingress', }, listSuppliers: { method: 'GET', path: '/shopkeeper/suppliers', responses: { 200: z.object({ suppliers: z.array(supplierSchema) }), 401: errorResponseSchema, }, summary: 'List registered suppliers', }, createSupplier: { method: 'POST', path: '/shopkeeper/suppliers', body: createSupplierSchema, responses: { 201: z.object({ supplier: supplierSchema }), 400: errorResponseSchema, 401: errorResponseSchema, }, summary: 'Register a supplier', }, listSupplierInvoices: { method: 'GET', path: '/shopkeeper/supplier-invoices', query: z.object({ status: z.enum(['open', 'partial', 'paid', 'cancelled']).optional(), supplierId: z.string().uuid().optional(), }), responses: { 200: z.object({ invoices: z.array(supplierInvoiceSchema) }), 401: errorResponseSchema, }, summary: 'List supplier bills', }, createSupplierInvoice: { method: 'POST', path: '/shopkeeper/supplier-invoices', body: createSupplierInvoiceSchema, responses: { 201: z.object({ invoice: supplierInvoiceSchema }), 400: errorResponseSchema, 401: errorResponseSchema, }, summary: 'Create a supplier bill', }, getSupplierInvoice: { method: 'GET', path: '/shopkeeper/supplier-invoices/:id', pathParams: z.object({ id: z.string().uuid() }), responses: { 200: z.object({ invoice: supplierInvoiceDetailSchema }), 401: errorResponseSchema, 404: errorResponseSchema, }, summary: 'Get supplier invoice with payment history', }, listSupplierPayments: { method: 'GET', path: '/shopkeeper/supplier-payments', query: z.object({ status: z .enum([ 'pending_confirmation', 'approved', 'processing', 'completed', 'failed', 'cancelled', ]) .optional(), }), responses: { 200: z.object({ payments: z.array(supplierPaymentSchema) }), 401: errorResponseSchema, }, summary: 'List supplier payment requests', }, createSupplierPayment: { method: 'POST', path: '/shopkeeper/supplier-payments', body: createSupplierPaymentSchema, responses: { 201: z.object({ payment: supplierPaymentSchema }), 400: errorResponseSchema, 401: errorResponseSchema, }, summary: 'Draft a supplier payment (requires owner approval)', }, confirmSupplierPayment: { method: 'POST', path: '/shopkeeper/supplier-payments/:id/confirm', pathParams: z.object({ id: z.string().uuid() }), body: z.object({ transferReference: z.string().optional(), notes: z.string().optional(), }), responses: { 200: z.object({ payment: supplierPaymentSchema }), 400: errorResponseSchema, 401: errorResponseSchema, 404: errorResponseSchema, }, summary: 'Mark supplier payment as paid manually (offline transfer)', }, approveSupplierPayment: { method: 'POST', path: '/shopkeeper/supplier-payments/:id/approve', pathParams: z.object({ id: z.string().uuid() }), body: approveSupplierPaymentSchema, responses: { 200: z.object({ payment: supplierPaymentSchema }), 400: errorResponseSchema, 401: errorResponseSchema, 404: errorResponseSchema, }, summary: 'Approve and initiate orchestrated supplier transfer', }, getReconciliationSummary: { method: 'GET', path: '/shopkeeper/payments/reconciliation', query: z.object({ date: z.string().optional(), }), responses: { 200: reconciliationSummarySchema, 401: errorResponseSchema, }, summary: 'Daily payment reconciliation summary', }, getReconciliationMismatches: { method: 'GET', path: '/shopkeeper/payments/reconciliation/mismatches', query: z.object({ date: z.string().optional(), page: z.coerce.number().int().min(1).optional(), limit: z.coerce.number().int().min(1).max(100).optional(), }), responses: { 200: reconciliationMismatchesResponseSchema, 401: errorResponseSchema, }, summary: 'Paginated payment reconciliation mismatches', }, });