Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@moneydevkit/api-contract",
"version": "0.1.14",
"version": "0.1.15",
"description": "API Contract for moneydevkit",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
Expand Down
5 changes: 4 additions & 1 deletion src/contracts/products.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ export const ProductPriceSchema = z.object({
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/FREE) is supported.
export const ProductSchema = z.object({
id: z.string(),
name: z.string(),
description: z.string().nullable(),
recurringInterval: z.enum(["MONTH", "QUARTER", "YEAR"]).nullable(),
price: ProductPriceSchema.nullable(),
prices: z.array(ProductPriceSchema),
});

export const ListProductsOutputSchema = z.object({
Expand Down
5 changes: 4 additions & 1 deletion src/schemas/product.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ export const CheckoutProductPriceSchema = z.object({
currency: CurrencySchema,
});

// 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/FREE) is supported.
export const CheckoutProductSchema = z.object({
id: z.string(),
name: z.string(),
description: z.string().nullable(),
recurringInterval: z.enum(["MONTH", "QUARTER", "YEAR"]).nullable(),
price: CheckoutProductPriceSchema.nullable(),
prices: z.array(CheckoutProductPriceSchema),
});
4 changes: 2 additions & 2 deletions tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ describe('API Contract Index', () => {
name: 'Test Product',
description: null,
recurringInterval: null,
price: {
prices: [{
id: 'price_123',
amountType: 'FIXED' as const,
priceAmount: 1000,
currency: 'USD',
},
}],
}],
providedAmount: null,
totalAmount: null,
Expand Down
4 changes: 2 additions & 2 deletions tests/schemas/checkout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ const mockProduct = {
name: 'Test Product',
description: 'A test product',
recurringInterval: null,
price: {
prices: [{
id: 'price_123',
amountType: 'FIXED' as const,
priceAmount: 1000,
currency: 'USD',
},
}],
};

const mockInvoice = {
Expand Down
86 changes: 48 additions & 38 deletions tests/schemas/product.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const baseProductData = {
name: "Test Product",
description: null,
recurringInterval: null,
price: baseProductPriceData,
prices: [baseProductPriceData],
};

describe("Product Schemas", () => {
Expand Down Expand Up @@ -148,12 +148,14 @@ describe("Product Schemas", () => {
test("should validate product with a custom price", () => {
const productWithCustomPrice = {
...baseProductData,
price: {
...baseProductPriceData,
id: "price_2",
amountType: "CUSTOM" as const,
priceAmount: null,
},
prices: [
{
...baseProductPriceData,
id: "price_2",
amountType: "CUSTOM" as const,
priceAmount: null,
},
],
};

const result = CheckoutProductSchema.safeParse(productWithCustomPrice);
Expand All @@ -180,23 +182,23 @@ describe("Product Schemas", () => {
expect(result.success).toBe(false);
});

test("should reject product without price field", () => {
const productWithoutPrice = {
test("should reject product without prices field", () => {
const productWithoutPrices = {
...baseProductData,
price: undefined,
prices: undefined,
};

const result = CheckoutProductSchema.safeParse(productWithoutPrice);
const result = CheckoutProductSchema.safeParse(productWithoutPrices);
expect(result.success).toBe(false);
});

test("should allow product with null price", () => {
const productWithNullPrice = {
test("should validate product with empty prices array", () => {
const productWithEmptyPrices = {
...baseProductData,
price: null,
prices: [],
};

const result = CheckoutProductSchema.safeParse(productWithNullPrice);
const result = CheckoutProductSchema.safeParse(productWithEmptyPrices);
expect(result.success).toBe(true);
});

Expand All @@ -210,13 +212,15 @@ describe("Product Schemas", () => {
expect(result.success).toBe(false);
});

test("should reject product with invalid price object", () => {
test("should reject product with invalid price in prices array", () => {
const productWithInvalidPrice = {
...baseProductData,
price: {
...baseProductPriceData,
amountType: "INVALID_TYPE" as any,
},
prices: [
{
...baseProductPriceData,
amountType: "INVALID_TYPE" as any,
},
],
};

const result = CheckoutProductSchema.safeParse(productWithInvalidPrice);
Expand Down Expand Up @@ -250,32 +254,38 @@ describe("Product Schemas", () => {
{
...baseProductData,
id: "product_fixed",
price: {
...baseProductPriceData,
id: "price_fixed",
amountType: "FIXED" as const,
priceAmount: 2999,
},
prices: [
{
...baseProductPriceData,
id: "price_fixed",
amountType: "FIXED" as const,
priceAmount: 2999,
},
],
},
{
...baseProductData,
id: "product_custom",
price: {
...baseProductPriceData,
id: "price_custom",
amountType: "CUSTOM" as const,
priceAmount: null,
},
prices: [
{
...baseProductPriceData,
id: "price_custom",
amountType: "CUSTOM" as const,
priceAmount: null,
},
],
},
{
...baseProductData,
id: "product_free",
price: {
...baseProductPriceData,
id: "price_free",
amountType: "FREE" as const,
priceAmount: 0,
},
prices: [
{
...baseProductPriceData,
id: "price_free",
amountType: "FREE" as const,
priceAmount: 0,
},
],
},
];

Expand Down