diff --git a/src/contracts/checkout.ts b/src/contracts/checkout.ts index 5f7262b..c1c2804 100644 --- a/src/contracts/checkout.ts +++ b/src/contracts/checkout.ts @@ -1,16 +1,33 @@ import { oc } from "@orpc/contract"; import { z } from "zod"; -import { CheckoutSchema } from "../schemas/checkout"; +import { + type CheckoutDetail, + CheckoutDetailSchema, + type CheckoutListItem, + CheckoutListItemSchema, + CheckoutSchema, + type CheckoutStatus, + CheckoutStatusSchema, + type CheckoutType, + CheckoutTypeSchema, +} from "../schemas/checkout"; import { CurrencySchema } from "../schemas/currency"; -import { CustomerSchema } from "../schemas/customer"; import { - PaginationInputSchema, + PaginatedInputSchema, PaginationOutputSchema, } from "../schemas/pagination"; +// Re-export entity schemas for backwards compatibility +export { + CheckoutStatusSchema, + CheckoutTypeSchema, + CheckoutListItemSchema, + CheckoutDetailSchema, +}; +export type { CheckoutStatus, CheckoutType, CheckoutListItem, CheckoutDetail }; + /** * Helper to treat empty strings as undefined (not provided). - * This allows clients to pass empty strings without validation errors. */ const emptyStringToUndefined = z .string() @@ -23,22 +40,12 @@ const emailOrEmpty = z.string().email().optional().or(z.literal("")); /** * Valid fields that can be required at checkout time. - * - Standard fields: 'email', 'name' (checked against customer.email/name) - * - Any other string is a custom field (checked against customer[field]) - * - * @example ['email'] - require email - * @example ['email', 'name'] - require both email and name - * @example ['email', 'company'] - require email and company */ export const CustomerFieldSchema = z.string().min(1); export type CustomerField = string; /** - * Customer data object for checkout. - * Flat structure - standard fields (name, email, externalId) plus any custom string fields. - * Empty strings are treated as undefined (not provided). - * - * @example { name: "John", email: "john@example.com", externalId: "user_123", company: "Acme" } + * Customer data object for checkout input. */ export const CustomerInputSchema = z .object({ @@ -50,6 +57,7 @@ export const CustomerInputSchema = z export type CustomerInput = z.infer; +// Input schemas export const CreateCheckoutInputSchema = z.object({ nodeId: z.string(), amount: z.number().optional(), @@ -58,33 +66,13 @@ export const CreateCheckoutInputSchema = z.object({ successUrl: z.string().optional(), allowDiscountCodes: z.boolean().optional(), metadata: z.record(z.string(), z.any()).optional(), - /** - * Customer data for this checkout. - */ customer: CustomerInputSchema.optional(), - /** - * Array of customer fields to require at checkout. - * If a field is listed here and not provided, the checkout UI will prompt for it. - * @example ['email'] - require email - * @example ['email', 'name'] - require both - */ requireCustomerData: z.array(CustomerFieldSchema).optional(), }); export const ConfirmCheckoutInputSchema = z.object({ checkoutId: z.string(), - /** - * Customer data provided at confirm time. - */ customer: CustomerInputSchema.optional(), - /** - * Product selection at confirm time. - * - undefined or [] = keep current selection - * - [{ productId }] = change selection to this product - * - priceAmount required if selected price has amountType: CUSTOM - * - * Currently limited to single selection (max 1 item). - */ products: z .array( z.object({ @@ -120,25 +108,64 @@ export const PaymentReceivedInputSchema = z.object({ ), }); -export const GetCheckoutInputSchema = z.object({ id: z.string() }); +export const GetCheckoutInputSchema = z.object({ + id: z.string().describe("The checkout ID"), +}); +export type GetCheckoutInput = z.infer; export type CreateCheckout = z.infer; export type ConfirmCheckout = z.infer; export type RegisterInvoice = z.infer; export type PaymentReceived = z.infer; +// List output schemas +export const ListCheckoutsOutputSchema = z.object({ + checkouts: z.array(CheckoutSchema), +}); +export type ListCheckoutsOutput = z.infer; + +export const ListCheckoutsPaginatedInputSchema = PaginatedInputSchema.extend({ + status: CheckoutStatusSchema.optional().describe( + "Filter by status: UNCONFIRMED, CONFIRMED, PENDING_PAYMENT, PAYMENT_RECEIVED, or EXPIRED", + ), +}); +export type ListCheckoutsPaginatedInput = z.infer< + typeof ListCheckoutsPaginatedInputSchema +>; + +export const ListCheckoutsPaginatedOutputSchema = PaginationOutputSchema.extend( + { + checkouts: z.array(CheckoutSchema), + }, +); +export type ListCheckoutsPaginatedOutput = z.infer< + typeof ListCheckoutsPaginatedOutputSchema +>; + +export const ListCheckoutsSummaryOutputSchema = PaginationOutputSchema.extend({ + checkouts: z.array(CheckoutListItemSchema), +}); +export type ListCheckoutsSummaryOutput = z.infer< + typeof ListCheckoutsSummaryOutputSchema +>; + +// Contracts export const createCheckoutContract = oc .input(CreateCheckoutInputSchema) .output(CheckoutSchema); + export const applyDiscountCodeContract = oc .input(ApplyDiscountCodeInputSchema) .output(CheckoutSchema); + export const confirmCheckoutContract = oc .input(ConfirmCheckoutInputSchema) .output(CheckoutSchema); + export const registerInvoiceContract = oc .input(RegisterInvoiceInputSchema) .output(CheckoutSchema); + export const getCheckoutContract = oc .input(GetCheckoutInputSchema) .output(CheckoutSchema); @@ -147,67 +174,19 @@ export const paymentReceivedContract = oc .input(PaymentReceivedInputSchema) .output(z.object({ ok: z.boolean() })); -// List checkouts schemas -export const CheckoutStatusSchema = z.enum([ - "UNCONFIRMED", - "CONFIRMED", - "PENDING_PAYMENT", - "PAYMENT_RECEIVED", - "EXPIRED", -]); -export type CheckoutStatus = z.infer; - -export const CheckoutTypeSchema = z.enum(["PRODUCTS", "AMOUNT", "TOP_UP"]); -export type CheckoutType = z.infer; - -const ListCheckoutsInputSchema = PaginationInputSchema.extend({ - status: CheckoutStatusSchema.optional(), -}); - -const ListCheckoutsOutputSchema = PaginationOutputSchema.extend({ - checkouts: z.array(CheckoutSchema), -}); - export const listCheckoutsContract = oc - .input(ListCheckoutsInputSchema) + .input(z.object({})) .output(ListCheckoutsOutputSchema); -const CheckoutCustomerSchema = CustomerSchema.nullable(); - -// MCP-specific summary schema for list (simpler than full CheckoutSchema) -const CheckoutListItemSchema = z.object({ - id: z.string(), - status: CheckoutStatusSchema, - type: CheckoutTypeSchema, - currency: CurrencySchema, - totalAmount: z.number().nullable(), - customerId: z.string().nullable(), - customer: CheckoutCustomerSchema, - productId: z.string().nullable(), - organizationId: z.string(), - expiresAt: z.date(), - createdAt: z.date(), - modifiedAt: z.date().nullable(), -}); - -// MCP-specific detailed schema for get (includes additional fields) -const CheckoutDetailSchema = CheckoutListItemSchema.extend({ - userMetadata: z.record(z.unknown()).nullable(), - successUrl: z.string().nullable(), - discountAmount: z.number().nullable(), - netAmount: z.number().nullable(), - taxAmount: z.number().nullable(), -}); - -const ListCheckoutsSummaryOutputSchema = PaginationOutputSchema.extend({ - checkouts: z.array(CheckoutListItemSchema), -}); +export const listCheckoutsPaginatedContract = oc + .input(ListCheckoutsPaginatedInputSchema) + .output(ListCheckoutsPaginatedOutputSchema); -export const listCheckoutsSummaryContract = oc - .input(ListCheckoutsInputSchema) +export const listCheckoutsSummaryPaginatedContract = oc + .input(ListCheckoutsPaginatedInputSchema) .output(ListCheckoutsSummaryOutputSchema); -export const getCheckoutSummaryContract = oc +export const getCheckoutDetailContract = oc .input(GetCheckoutInputSchema) .output(CheckoutDetailSchema); @@ -218,6 +197,8 @@ export const checkout = { registerInvoice: registerInvoiceContract, paymentReceived: paymentReceivedContract, list: listCheckoutsContract, - listSummary: listCheckoutsSummaryContract, - getSummary: getCheckoutSummaryContract, + listPaginated: listCheckoutsPaginatedContract, + // Original names preserved + listSummary: listCheckoutsSummaryPaginatedContract, + getSummary: getCheckoutDetailContract, }; diff --git a/src/contracts/customer.ts b/src/contracts/customer.ts index 77f21a6..69083d9 100644 --- a/src/contracts/customer.ts +++ b/src/contracts/customer.ts @@ -6,44 +6,107 @@ import { GetCustomerInputSchema as SdkGetCustomerInputSchema, } from "../schemas/customer"; import { - PaginationInputSchema, + PaginatedInputSchema, PaginationOutputSchema, } from "../schemas/pagination"; -// MCP-specific schemas -const ListCustomersInputSchema = PaginationInputSchema; -const ListCustomersOutputSchema = PaginationOutputSchema.extend({ +// Simple list (no pagination) +export const ListCustomersOutputSchema = z.object({ customers: z.array(CustomerSchema), }); +export type ListCustomersOutput = z.infer; -const McpGetCustomerInputSchema = z.object({ id: z.string() }); +// Paginated list (no additional filters for customers) +export const ListCustomersPaginatedInputSchema = PaginatedInputSchema; +export type ListCustomersPaginatedInput = z.infer< + typeof ListCustomersPaginatedInputSchema +>; -const CreateCustomerInputSchema = z.object({ - name: z.string().min(1), - email: z.string().email(), +export const ListCustomersPaginatedOutputSchema = PaginationOutputSchema.extend( + { + customers: z.array(CustomerSchema), + }, +); +export type ListCustomersPaginatedOutput = z.infer< + typeof ListCustomersPaginatedOutputSchema +>; + +// Customer lookup by exactly one identifier (discriminated union for contract validation) +const CustomerLookupByIdSchema = z.object({ + id: z.string().describe("The customer ID"), +}); +const CustomerLookupByEmailSchema = z.object({ + email: z.string().describe("The customer email address"), +}); +const CustomerLookupByExternalIdSchema = z.object({ + externalId: z.string().describe("The external ID from your system"), }); -const UpdateCustomerInputSchema = z.object({ - id: z.string(), - name: z.string().optional(), - email: z.string().email().optional(), - userMetadata: z.record(z.string(), z.string()).optional(), +export const CustomerLookupInputSchema = z.union([ + CustomerLookupByIdSchema, + CustomerLookupByEmailSchema, + CustomerLookupByExternalIdSchema, +]); +export type CustomerLookupInput = z.infer; + +// Flat schema for MCP tools (xmcp needs .shape, unions don't have it) +export const CustomerLookupToolSchema = z.object({ + id: z.string().optional().describe("The customer ID"), + email: z.string().optional().describe("The customer email address"), + externalId: z + .string() + .optional() + .describe("The external ID from your system"), }); -const DeleteCustomerInputSchema = z.object({ id: z.string() }); +export const GetCustomerInputSchema = CustomerLookupToolSchema; +export type GetCustomerInput = z.infer; + +export const DeleteCustomerInputSchema = CustomerLookupToolSchema; +export type DeleteCustomerInput = z.infer; + +export const CreateCustomerInputSchema = z.object({ + name: z.string().min(1).describe("Customer name"), + email: z.string().email().describe("Customer email address"), + externalId: z + .string() + .optional() + .describe("External ID from your system for linking"), +}); + +export const UpdateCustomerInputSchema = z.object({ + id: z.string().describe("The customer ID to update"), + name: z.string().optional().describe("New customer name"), + email: z.string().email().optional().describe("New customer email address"), + externalId: z + .string() + .optional() + .describe("External ID from your system for linking"), + userMetadata: z + .record(z.string(), z.string()) + .optional() + .describe("Custom metadata key-value pairs"), +}); + +export type CreateCustomerInput = z.infer; +export type UpdateCustomerInput = z.infer; // SDK contract - uses flexible lookup (externalId/email/customerId) export const getSdkCustomerContract = oc .input(SdkGetCustomerInputSchema) .output(CustomerWithSubscriptionsSchema); -// MCP contracts +// Contracts export const listCustomersContract = oc - .input(ListCustomersInputSchema) + .input(z.object({})) .output(ListCustomersOutputSchema); +export const listCustomersPaginatedContract = oc + .input(ListCustomersPaginatedInputSchema) + .output(ListCustomersPaginatedOutputSchema); + export const getCustomerContract = oc - .input(McpGetCustomerInputSchema) + .input(GetCustomerInputSchema) .output(CustomerSchema); export const createCustomerContract = oc @@ -56,10 +119,11 @@ export const updateCustomerContract = oc export const deleteCustomerContract = oc .input(DeleteCustomerInputSchema) - .output(z.object({ ok: z.literal(true) })); + .output(z.void()); export const customer = { list: listCustomersContract, + listPaginated: listCustomersPaginatedContract, get: getCustomerContract, getSdk: getSdkCustomerContract, create: createCustomerContract, diff --git a/src/contracts/order.ts b/src/contracts/order.ts index 6ccdf8d..b88730c 100644 --- a/src/contracts/order.ts +++ b/src/contracts/order.ts @@ -1,36 +1,62 @@ import { oc } from "@orpc/contract"; import { z } from "zod"; -import { CustomerSchema } from "../schemas/customer"; -import { OrderItemSchema, OrderSchema } from "../schemas/order"; import { - PaginationInputSchema, + type OrderWithRelations, + OrderWithRelationsSchema, +} from "../schemas/order"; +import { + PaginatedInputSchema, PaginationOutputSchema, } from "../schemas/pagination"; -// Order with related data for list and get views -const OrderWithRelationsSchema = OrderSchema.extend({ - customer: CustomerSchema.nullable(), - orderItems: z.array(OrderItemSchema), +// Re-export entity schema for backwards compatibility +export { OrderWithRelationsSchema }; +export type { OrderWithRelations }; + +// List output schemas +export const ListOrdersOutputSchema = z.object({ + orders: z.array(OrderWithRelationsSchema), }); +export type ListOrdersOutput = z.infer; -const ListOrdersInputSchema = PaginationInputSchema.extend({ - customerId: z.string().optional(), - status: z.string().optional(), // Prisma uses String type for status +export const ListOrdersPaginatedInputSchema = PaginatedInputSchema.extend({ + customerId: z.string().optional().describe("Filter by customer ID"), + status: z + .string() + .optional() + .describe("Filter by status: PENDING, PAID, REFUNDED, or CANCELLED"), }); +export type ListOrdersPaginatedInput = z.infer< + typeof ListOrdersPaginatedInputSchema +>; -const ListOrdersOutputSchema = PaginationOutputSchema.extend({ +export const ListOrdersPaginatedOutputSchema = PaginationOutputSchema.extend({ orders: z.array(OrderWithRelationsSchema), }); +export type ListOrdersPaginatedOutput = z.infer< + typeof ListOrdersPaginatedOutputSchema +>; +export const GetOrderInputSchema = z.object({ + id: z.string().describe("The order ID"), +}); +export type GetOrderInput = z.infer; + +// Contracts export const listOrdersContract = oc - .input(ListOrdersInputSchema) + .input(z.object({})) .output(ListOrdersOutputSchema); +export const listOrdersPaginatedContract = oc + .input(ListOrdersPaginatedInputSchema) + .output(ListOrdersPaginatedOutputSchema); + export const getOrderContract = oc - .input(z.object({ id: z.string() })) + .input(GetOrderInputSchema) .output(OrderWithRelationsSchema); export const order = { list: listOrdersContract, + listPaginated: listOrdersPaginatedContract, get: getOrderContract, }; diff --git a/src/contracts/products.ts b/src/contracts/products.ts index d25359e..6e98d48 100644 --- a/src/contracts/products.ts +++ b/src/contracts/products.ts @@ -1,46 +1,63 @@ import { oc } from "@orpc/contract"; import { z } from "zod"; import { CurrencySchema } from "../schemas/currency"; -import { ProductPriceInputSchema } from "../schemas/product-price-input"; +import { + PaginatedInputSchema, + PaginationOutputSchema, +} from "../schemas/pagination"; +import { + type Product, + type ProductDetail, + ProductDetailSchema, + type ProductPrice, + ProductPriceSchema, + ProductSchema, +} from "../schemas/product"; +import { + PriceAmountTypeSchema, + ProductPriceInputSchema, + RecurringIntervalInputSchema, +} from "../schemas/product-price-input"; -export const ProductPriceSchema = z.object({ - id: z.string(), - amountType: z.enum(["FIXED", "CUSTOM"]), - priceAmount: z.number().nullable(), - currency: CurrencySchema, -}); - -// Products have a prices array to allow future support of metered pricing -// (e.g., base subscription + usage-based charges). Currently only one static price -// (FIXED/CUSTOM) is supported. -export const ProductSchema = z.object({ - id: z.string(), - name: z.string(), - description: z.string().nullable(), - recurringInterval: z.enum(["MONTH", "QUARTER", "YEAR"]).nullable(), - prices: z.array(ProductPriceSchema), -}); +// Re-export entity schemas for backwards compatibility +export { ProductSchema, ProductDetailSchema, ProductPriceSchema }; +export type { Product, ProductDetail, ProductPrice }; +// List output schemas export const ListProductsOutputSchema = z.object({ products: z.array(ProductSchema), }); +export type ListProductsOutput = z.infer; -export type Product = z.infer; -export type ProductPrice = z.infer; +export const ListProductsDetailOutputSchema = PaginationOutputSchema.extend({ + products: z.array(ProductDetailSchema), +}); +export type ListProductsDetailOutput = z.infer< + typeof ListProductsDetailOutputSchema +>; +// Simple list without pagination export const listProductsContract = oc - .input(z.object({}).optional()) + .input(z.object({})) .output(ListProductsOutputSchema); +// Paginated list with full product details +export const ListProductsInputSchema = PaginatedInputSchema; +export type ListProductsInput = z.infer; + +export const listProductsPaginatedContract = oc + .input(ListProductsInputSchema) + .output(ListProductsDetailOutputSchema); + // CRUD input schemas -const CreateProductInputSchema = z.object({ +export const CreateProductInputSchema = z.object({ name: z.string().min(1), description: z.string().optional(), price: ProductPriceInputSchema, userMetadata: z.record(z.string(), z.string()).optional(), }); -const UpdateProductInputSchema = z.object({ +export const UpdateProductInputSchema = z.object({ id: z.string(), name: z.string().min(1).optional(), description: z.string().optional(), @@ -48,24 +65,84 @@ const UpdateProductInputSchema = z.object({ userMetadata: z.record(z.string(), z.string()).optional(), }); +export type CreateProductInput = z.infer; +export type UpdateProductInput = z.infer; + +// Flattened tool input schemas (flat params are easier for AI tools) +export const CreateProductToolInputSchema = z.object({ + name: z.string().min(1).describe("Product name"), + description: z.string().optional().describe("Product description"), + priceAmount: z + .number() + .optional() + .describe( + "Price amount (in cents for USD, whole sats for SAT). Required for fixed pricing.", + ), + currency: CurrencySchema.optional().describe( + "Currency: USD or SAT (default: USD)", + ), + amountType: PriceAmountTypeSchema.optional().describe( + "Amount type: FIXED or CUSTOM (default: FIXED)", + ), + recurringInterval: RecurringIntervalInputSchema.optional().describe( + "Recurring interval: NEVER (one-time), MONTH, QUARTER, or YEAR (default: NEVER)", + ), +}); + +export const UpdateProductToolInputSchema = z.object({ + id: z.string().describe("The product ID to update"), + name: z.string().optional().describe("New product name"), + description: z.string().optional().describe("New product description"), + priceAmount: z + .number() + .optional() + .describe("New price amount (in cents for USD, whole sats for SAT)"), + currency: CurrencySchema.optional().describe("Currency: USD or SAT"), + amountType: PriceAmountTypeSchema.optional().describe( + "Amount type: FIXED or CUSTOM", + ), + recurringInterval: RecurringIntervalInputSchema.optional().describe( + "Recurring interval: NEVER, MONTH, QUARTER, or YEAR", + ), +}); + +export type CreateProductToolInput = z.infer< + typeof CreateProductToolInputSchema +>; +export type UpdateProductToolInput = z.infer< + typeof UpdateProductToolInputSchema +>; + +export const GetProductInputSchema = z.object({ + id: z.string().describe("The product ID"), +}); +export type GetProductInput = z.infer; + +export const DeleteProductInputSchema = z.object({ + id: z.string().describe("The product ID to delete"), +}); +export type DeleteProductInput = z.infer; + +// Contracts export const getProductContract = oc - .input(z.object({ id: z.string() })) - .output(ProductSchema); + .input(GetProductInputSchema) + .output(ProductDetailSchema); export const createProductContract = oc .input(CreateProductInputSchema) - .output(ProductSchema); + .output(ProductDetailSchema); export const updateProductContract = oc .input(UpdateProductInputSchema) - .output(ProductSchema); + .output(ProductDetailSchema); export const deleteProductContract = oc - .input(z.object({ id: z.string() })) - .output(z.object({ ok: z.literal(true) })); + .input(DeleteProductInputSchema) + .output(z.void()); export const products = { list: listProductsContract, + listPaginated: listProductsPaginatedContract, get: getProductContract, create: createProductContract, update: updateProductContract, diff --git a/src/index.ts b/src/index.ts index 179e042..800563e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,13 @@ import { subscription } from "./contracts/subscription"; export type { CheckoutStatus, CheckoutType, + CheckoutListItem, + CheckoutDetail, + ListCheckoutsOutput, + ListCheckoutsPaginatedInput, + ListCheckoutsPaginatedOutput, + ListCheckoutsSummaryOutput, + GetCheckoutInput, ConfirmCheckout, CreateCheckout, PaymentReceived, @@ -16,6 +23,13 @@ export type { export { CheckoutStatusSchema, CheckoutTypeSchema, + CheckoutListItemSchema, + CheckoutDetailSchema, + ListCheckoutsOutputSchema, + ListCheckoutsPaginatedInputSchema, + ListCheckoutsPaginatedOutputSchema, + ListCheckoutsSummaryOutputSchema, + GetCheckoutInputSchema, } from "./contracts/checkout"; export type { BootstrapOnboarding, @@ -31,16 +45,59 @@ export type { CreateRenewalCheckout, GetSubscriptionInput, } from "./contracts/subscription"; -export type { GetCustomerInput } from "./schemas/customer"; +export type { GetCustomerInput as SdkGetCustomerInput } from "./schemas/customer"; +export type { + CreateCustomerInput, + UpdateCustomerInput, + ListCustomersOutput, + ListCustomersPaginatedInput, + ListCustomersPaginatedOutput, + GetCustomerInput, + DeleteCustomerInput, + CustomerLookupInput, +} from "./contracts/customer"; +export { + CreateCustomerInputSchema, + UpdateCustomerInputSchema, + ListCustomersOutputSchema, + ListCustomersPaginatedInputSchema, + ListCustomersPaginatedOutputSchema, + GetCustomerInputSchema, + DeleteCustomerInputSchema, + CustomerLookupInputSchema, + CustomerLookupToolSchema, +} from "./contracts/customer"; export type { Checkout } from "./schemas/checkout"; export { CheckoutSchema } from "./schemas/checkout"; export type { Currency } from "./schemas/currency"; export { CurrencySchema } from "./schemas/currency"; -export type { Product, ProductPrice } from "./contracts/products"; +export type { + Product, + ProductDetail, + ProductPrice, + ListProductsOutput, + ListProductsDetailOutput, + ListProductsInput, + GetProductInput, + DeleteProductInput, + CreateProductInput, + UpdateProductInput, + CreateProductToolInput, + UpdateProductToolInput, +} from "./contracts/products"; export { ProductSchema, + ProductDetailSchema, ProductPriceSchema, ListProductsOutputSchema, + ListProductsDetailOutputSchema, + ListProductsInputSchema, + GetProductInputSchema, + DeleteProductInputSchema, + CreateProductInputSchema, + UpdateProductInputSchema, + CreateProductToolInputSchema, + UpdateProductToolInputSchema, } from "./contracts/products"; export type { RecurringInterval, @@ -60,19 +117,40 @@ export type { Customer, CustomerWithSubscriptions } from "./schemas/customer"; export { CustomerSchema, CustomerWithSubscriptionsSchema, - GetCustomerInputSchema, + GetCustomerInputSchema as SdkGetCustomerInputSchema, } from "./schemas/customer"; // New MCP schemas export type { Order, OrderItem, OrderStatus } from "./schemas/order"; +export type { + OrderWithRelations, + ListOrdersOutput, + ListOrdersPaginatedInput, + ListOrdersPaginatedOutput, + GetOrderInput, +} from "./contracts/order"; +export { + OrderWithRelationsSchema, + ListOrdersOutputSchema, + ListOrdersPaginatedInputSchema, + ListOrdersPaginatedOutputSchema, + GetOrderInputSchema, +} from "./contracts/order"; export { OrderSchema, OrderItemSchema, OrderStatusSchema, } from "./schemas/order"; -export type { PaginationInput, PaginationOutput } from "./schemas/pagination"; +export type { + IdInput, + PaginationInput, + PaginatedInput, + PaginationOutput, +} from "./schemas/pagination"; export { + IdInputSchema, PaginationInputSchema, + PaginatedInputSchema, PaginationOutputSchema, } from "./schemas/pagination"; export type { @@ -114,18 +192,27 @@ export const sdkContract = { // MCP contract - only the methods the MCP router implements export const mcpContract = { customer: { - list: customer.list, + list: customer.listPaginated, get: customer.get, create: customer.create, update: customer.update, delete: customer.delete, }, - order, + order: { + list: order.listPaginated, + get: order.get, + }, checkout: { list: checkout.listSummary, get: checkout.getSummary, }, - products, + products: { + list: products.listPaginated, + get: products.get, + create: products.create, + update: products.update, + delete: products.delete, + }, }; export type { MetadataValidationError } from "./validation/metadata-validation"; @@ -135,3 +222,6 @@ export { MAX_METADATA_SIZE_BYTES, validateMetadata, } from "./validation/metadata-validation"; + +export type { Result } from "./lib/utils"; +export { ok, err } from "./lib/utils"; diff --git a/src/schemas/checkout.ts b/src/schemas/checkout.ts index 1454e85..474805e 100644 --- a/src/schemas/checkout.ts +++ b/src/schemas/checkout.ts @@ -1,5 +1,6 @@ import { z } from "zod"; import { CurrencySchema } from "./currency"; +import { CustomerSchema } from "./customer"; import { BaseInvoiceSchema, DynamicAmountPendingInvoiceSchema, @@ -189,3 +190,43 @@ export const CheckoutSchema = z.union([ ]); export type Checkout = z.infer; + +// Simple enum schemas for filtering/display +export const CheckoutStatusSchema = z.enum([ + "UNCONFIRMED", + "CONFIRMED", + "PENDING_PAYMENT", + "PAYMENT_RECEIVED", + "EXPIRED", +]); +export type CheckoutStatus = z.infer; + +export const CheckoutTypeSchema = z.enum(["PRODUCTS", "AMOUNT", "TOP_UP"]); +export type CheckoutType = z.infer; + +// Summary schema for list views (lighter than full CheckoutSchema) +export const CheckoutListItemSchema = z.object({ + id: z.string(), + status: CheckoutStatusSchema, + type: CheckoutTypeSchema, + currency: CurrencySchema, + totalAmount: z.number().nullable(), + customerId: z.string().nullable(), + customer: CustomerSchema.nullable(), + productId: z.string().nullable(), + organizationId: z.string(), + expiresAt: z.date(), + createdAt: z.date(), + modifiedAt: z.date().nullable(), +}); +export type CheckoutListItem = z.infer; + +// Detail schema (includes additional fields beyond list item) +export const CheckoutDetailSchema = CheckoutListItemSchema.extend({ + userMetadata: z.record(z.unknown()).nullable(), + successUrl: z.string().nullable(), + discountAmount: z.number().nullable(), + netAmount: z.number().nullable(), + taxAmount: z.number().nullable(), +}); +export type CheckoutDetail = z.infer; diff --git a/src/schemas/order.ts b/src/schemas/order.ts index cf4cd2b..88bcf56 100644 --- a/src/schemas/order.ts +++ b/src/schemas/order.ts @@ -49,3 +49,16 @@ export const OrderSchema = z.object({ }); export type Order = z.infer; + +// Import CustomerSchema for relations (lazy to avoid circular deps) +import { CustomerSchema } from "./customer"; + +/** + * Order with related customer and items for detailed views. + */ +export const OrderWithRelationsSchema = OrderSchema.extend({ + customer: CustomerSchema.nullable(), + orderItems: z.array(OrderItemSchema), +}); + +export type OrderWithRelations = z.infer; diff --git a/src/schemas/pagination.ts b/src/schemas/pagination.ts index f9b9a8d..5ef724f 100644 --- a/src/schemas/pagination.ts +++ b/src/schemas/pagination.ts @@ -1,5 +1,14 @@ import { z } from "zod"; +/** + * Common ID input for get/delete operations. + */ +export const IdInputSchema = z.object({ + id: z.string(), +}); + +export type IdInput = z.infer; + /** * Pagination input schema for list operations. * Uses cursor-based pagination for efficient large dataset traversal. @@ -11,6 +20,26 @@ export const PaginationInputSchema = z.object({ export type PaginationInput = z.infer; +/** + * Pagination input with descriptions (for AI tools). + * Use .extend() to add entity-specific filters. + */ +export const PaginatedInputSchema = z.object({ + limit: z + .number() + .int() + .min(1) + .max(100) + .default(50) + .describe("Maximum number of items to return (1-100, default 50)"), + cursor: z + .string() + .optional() + .describe("Cursor for pagination (from previous response)"), +}); + +export type PaginatedInput = z.infer; + /** * Pagination output schema for list operations. * Returns a cursor for the next page, or null if no more results. diff --git a/src/schemas/product.ts b/src/schemas/product.ts index c64c201..0a3b27a 100644 --- a/src/schemas/product.ts +++ b/src/schemas/product.ts @@ -1,20 +1,35 @@ import { z } from "zod"; import { CurrencySchema } from "./currency"; +import { RecurringIntervalSchema } from "./subscription"; -export const CheckoutProductPriceSchema = z.object({ +// Price schema - used in product responses +export const ProductPriceSchema = z.object({ id: z.string(), amountType: z.enum(["FIXED", "CUSTOM"]), priceAmount: z.number().nullable(), currency: CurrencySchema, }); +export type ProductPrice = z.infer; -// Checkout products have a prices array to allow future support of metered pricing -// (e.g., base subscription + usage-based charges). Currently only one static price -// (FIXED/CUSTOM) is supported. -export const CheckoutProductSchema = z.object({ +// Core product fields +export const ProductSchema = z.object({ id: z.string(), name: z.string(), description: z.string().nullable(), - recurringInterval: z.enum(["MONTH", "QUARTER", "YEAR"]).nullable(), - prices: z.array(CheckoutProductPriceSchema), + recurringInterval: RecurringIntervalSchema.nullable(), + prices: z.array(ProductPriceSchema), }); +export type Product = z.infer; + +// Extended with administrative metadata +export const ProductDetailSchema = ProductSchema.extend({ + userMetadata: z.record(z.string(), z.unknown()).nullable(), + organizationId: z.string(), + createdAt: z.date(), + modifiedAt: z.date().nullable(), +}); +export type ProductDetail = z.infer; + +// Aliases for checkout context (backwards compat) +export const CheckoutProductPriceSchema = ProductPriceSchema; +export const CheckoutProductSchema = ProductSchema;