+2



![coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>](/assets/img/avatar_default.png)


![coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>](/assets/img/avatar_default.png)


Anwar Sadath
GitHub
coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Omar López
amrit
coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Kartik Saini
Anik Dhabal Babu
Devanshu Sharma
Benny Joo
Peer Richelsen
Volnei Munhoz
55e6682832
* feat: Addons to bookings * Add tests * Add tests * Fix price field not shown immediately after enabling payment for an event * Add support for all currencies in addons and fix type errors * Address review comments * Add tests to cover all input types * Address cubic-dev-ai comments * Remove openapi.json from git tracking * Revert changes * Revert unrelated linter/formatter changes * Address cubic-dev-ai comments * Addres cubic-dev-ai comment * Update to use test id instead of hardcoded label Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Address coderabbits review comments * Fix failing unit test * some fixes... * Fix tests * Fix import * Move handlePayment test file to appropriate location * Fix test --------- Co-authored-by: Omar López <zomars@me.com> Co-authored-by: amrit <iamamrit27@gmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Kartik Saini <41051387+kart1ka@users.noreply.github.com> Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> Co-authored-by: Devanshu Sharma <devanshusharma658@gmail.com> Co-authored-by: Benny Joo <sldisek783@gmail.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Volnei Munhoz <volnei.munhoz@gmail.com>
301 lines
7.4 KiB
TypeScript
301 lines
7.4 KiB
TypeScript
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
|
|
import { handlePayment } from "./handlePayment";
|
|
|
|
vi.mock("@calcom/app-store/zod-utils", () => ({
|
|
eventTypeMetaDataSchemaWithTypedApps: {
|
|
parse: vi.fn((data) => data),
|
|
},
|
|
}));
|
|
|
|
vi.mock("@calcom/app-store/payment.services.generated", () => ({
|
|
PaymentServiceMap: {
|
|
stripepayment: Promise.resolve({
|
|
PaymentService: class MockPaymentService {
|
|
async create(paymentData: { amount: number; currency: string }) {
|
|
return {
|
|
...paymentData,
|
|
id: "mock-payment-id",
|
|
externalId: "mock-external-id",
|
|
success: true,
|
|
};
|
|
}
|
|
|
|
async collectCard(paymentData: { amount: number; currency: string }) {
|
|
return {
|
|
...paymentData,
|
|
id: "mock-payment-id",
|
|
externalId: "mock-external-id",
|
|
success: true,
|
|
};
|
|
}
|
|
|
|
async afterPayment() {
|
|
return true;
|
|
}
|
|
},
|
|
}),
|
|
},
|
|
}));
|
|
|
|
describe("handlePayment", () => {
|
|
const mockBooking = {
|
|
id: 123,
|
|
userId: 456,
|
|
startTime: { toISOString: () => "2025-03-12T10:00:00Z" },
|
|
uid: "test-uid",
|
|
user: {
|
|
email: "user@example.com",
|
|
name: "Test User",
|
|
timeZone: "UTC",
|
|
username: "testuser",
|
|
},
|
|
};
|
|
|
|
const mockEventType = {
|
|
title: "Test Event",
|
|
metadata: {
|
|
apps: {
|
|
stripe: {
|
|
enabled: true,
|
|
price: 1000,
|
|
currency: "USD",
|
|
paymentOption: "ON_BOOKING",
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
const mockPaymentCredentials = {
|
|
key: {},
|
|
appId: "stripe" as const,
|
|
app: {
|
|
dirName: "stripepayment",
|
|
categories: ["payment"],
|
|
},
|
|
};
|
|
|
|
const mockEvent = {
|
|
type: "testEvent",
|
|
title: "Test Event",
|
|
startTime: "2025-03-12T10:00:00Z",
|
|
endTime: "2025-03-12T11:00:00Z",
|
|
organizer: {
|
|
name: "Test Organizer",
|
|
email: "organizer@example.com",
|
|
timeZone: "UTC",
|
|
language: { locale: "en" },
|
|
},
|
|
attendees: [],
|
|
responses: {},
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("should handle payment on dry run", async () => {
|
|
const result = await handlePayment({
|
|
evt: mockEvent,
|
|
selectedEventType: mockEventType,
|
|
paymentAppCredentials: mockPaymentCredentials,
|
|
booking: mockBooking,
|
|
bookerName: "John Doe",
|
|
bookerEmail: "john@example.com",
|
|
bookingFields: [],
|
|
isDryRun: true,
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("should calculate total amount with no addons", async () => {
|
|
const result = await handlePayment({
|
|
evt: mockEvent,
|
|
selectedEventType: mockEventType,
|
|
paymentAppCredentials: mockPaymentCredentials,
|
|
booking: mockBooking,
|
|
bookerName: "John Doe",
|
|
bookerEmail: "john@example.com",
|
|
bookingFields: [],
|
|
});
|
|
|
|
expect(result?.amount).toBe(1000);
|
|
expect(result?.currency).toBe("USD");
|
|
});
|
|
|
|
it("should calculate total amount with number field addon", async () => {
|
|
const mockEventWithResponse = {
|
|
...mockEvent,
|
|
responses: {
|
|
quantity: { value: "2" },
|
|
},
|
|
};
|
|
|
|
const bookingFields = [
|
|
{
|
|
name: "quantity",
|
|
type: "number" as const,
|
|
price: 5,
|
|
required: false,
|
|
},
|
|
];
|
|
|
|
const result = await handlePayment({
|
|
evt: mockEventWithResponse,
|
|
selectedEventType: mockEventType,
|
|
paymentAppCredentials: mockPaymentCredentials,
|
|
booking: mockBooking,
|
|
bookerName: "John Doe",
|
|
bookerEmail: "john@example.com",
|
|
bookingFields,
|
|
});
|
|
|
|
expect(result?.amount).toBe(2000); // Base 1000 cents + (2 * $5 * 100 cents)
|
|
});
|
|
|
|
it("should calculate total amount with boolean field addon", async () => {
|
|
const mockEventWithResponse = {
|
|
...mockEvent,
|
|
responses: {
|
|
extraService: { value: true },
|
|
},
|
|
};
|
|
|
|
const bookingFields = [
|
|
{
|
|
name: "extraService",
|
|
type: "boolean" as const,
|
|
price: 15,
|
|
required: false,
|
|
},
|
|
];
|
|
|
|
const result = await handlePayment({
|
|
evt: mockEventWithResponse,
|
|
selectedEventType: mockEventType,
|
|
paymentAppCredentials: mockPaymentCredentials,
|
|
booking: mockBooking,
|
|
bookerName: "John Doe",
|
|
bookerEmail: "john@example.com",
|
|
bookingFields,
|
|
});
|
|
|
|
expect(result?.amount).toBe(2500); // Base 1000 cents + ($15 * 100 cents)
|
|
});
|
|
|
|
it("should calculate total amount with select field addon", async () => {
|
|
const mockEventWithResponse = {
|
|
...mockEvent,
|
|
responses: {
|
|
package: { value: "premium" },
|
|
},
|
|
};
|
|
|
|
const bookingFields = [
|
|
{
|
|
name: "package",
|
|
type: "select" as const,
|
|
required: false,
|
|
options: [
|
|
{ value: "basic", label: "Basic Package", price: 10 },
|
|
{ value: "premium", label: "Premium Package", price: 20 },
|
|
],
|
|
},
|
|
];
|
|
|
|
const result = await handlePayment({
|
|
evt: mockEventWithResponse,
|
|
selectedEventType: mockEventType,
|
|
paymentAppCredentials: mockPaymentCredentials,
|
|
booking: mockBooking,
|
|
bookerName: "John Doe",
|
|
bookerEmail: "john@example.com",
|
|
bookingFields,
|
|
});
|
|
|
|
expect(result?.amount).toBe(3000); // Base 1000 cents + ($20 * 100 cents)
|
|
});
|
|
|
|
it("should calculate total amount with multiple addons", async () => {
|
|
const mockEventWithResponse = {
|
|
...mockEvent,
|
|
responses: {
|
|
quantity: { value: "2" },
|
|
extraService: { value: true },
|
|
package: { value: "premium" },
|
|
},
|
|
};
|
|
|
|
const bookingFields = [
|
|
{
|
|
name: "quantity",
|
|
type: "number" as const,
|
|
price: 5,
|
|
required: false,
|
|
},
|
|
{
|
|
name: "extraService",
|
|
type: "boolean" as const,
|
|
price: 15,
|
|
required: false,
|
|
},
|
|
{
|
|
name: "package",
|
|
type: "select" as const,
|
|
required: false,
|
|
options: [
|
|
{ value: "basic", label: "Basic Package", price: 10 },
|
|
{ value: "premium", label: "Premium Package", price: 20 },
|
|
],
|
|
},
|
|
];
|
|
|
|
const result = await handlePayment({
|
|
evt: mockEventWithResponse,
|
|
selectedEventType: mockEventType,
|
|
paymentAppCredentials: mockPaymentCredentials,
|
|
booking: mockBooking,
|
|
bookerName: "John Doe",
|
|
bookerEmail: "john@example.com",
|
|
bookingFields,
|
|
});
|
|
|
|
expect(result?.amount).toBe(5500); // Base 1000 cents + (2 * $5 * 100 cents) + ($15 * 100 cents) + ($20 * 100 cents)
|
|
});
|
|
|
|
it("should calculate total amount with radio field addon using locale", async () => {
|
|
const mockEventWithResponse = {
|
|
...mockEvent,
|
|
responses: {
|
|
mealChoice: { value: "premium ($20.00)" }, // Formatted with price
|
|
},
|
|
};
|
|
|
|
const bookingFields = [
|
|
{
|
|
name: "mealChoice",
|
|
type: "radio" as const,
|
|
required: false,
|
|
options: [
|
|
{ value: "basic", label: "Basic Meal", price: 10 },
|
|
{ value: "premium", label: "Premium Meal", price: 20 },
|
|
],
|
|
},
|
|
];
|
|
|
|
const result = await handlePayment({
|
|
evt: mockEventWithResponse,
|
|
selectedEventType: mockEventType,
|
|
paymentAppCredentials: mockPaymentCredentials,
|
|
booking: mockBooking,
|
|
bookerName: "John Doe",
|
|
bookerEmail: "john@example.com",
|
|
bookingFields,
|
|
locale: "en",
|
|
});
|
|
|
|
expect(result?.amount).toBe(3000); // Base 1000 cents + ($20 * 100 cents)
|
|
});
|
|
});
|