Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
14 changes: 9 additions & 5 deletions apps/web/test/utils/bookingScenario/MockPaymentService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import prismaMock from "../../../../../tests/libs/__mocks__/prisma";

import type { Payment, Prisma, PaymentOption, Booking } from "@prisma/client";
import { v4 as uuidv4 } from "uuid";
import "vitest-fetch-mock";

import { sendAwaitingPaymentEmailAndSMS } from "@calcom/emails";
Expand All @@ -13,8 +12,8 @@ export function getMockPaymentService() {
function createPaymentLink(/*{ paymentUid, name, email, date }*/) {
return "http://mock-payment.example.com/";
}
const paymentUid = uuidv4();
const externalId = uuidv4();
const paymentUid = "MOCK_PAYMENT_UID";
const externalId = "mock_payment_external_id";

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
Expand All @@ -37,7 +36,7 @@ export function getMockPaymentService() {
bookingId,
// booking Booking? @relation(fields: [bookingId], references: [id], onDelete: Cascade)
fee: 10,
success: true,
success: false,
refunded: false,
data: {},
externalId,
Expand All @@ -46,11 +45,16 @@ export function getMockPaymentService() {
currency: payment.currency,
};

const paymentData = prismaMock.payment.create({
const paymentData = await prismaMock.payment.create({
data: paymentCreateData,
});
logger.silly("Created mock payment", JSON.stringify({ paymentData }));

const verifyPayment = await prismaMock.payment.findFirst({
where: { externalId: paymentCreateData.externalId },
});
logger.silly("Verified payment exists", JSON.stringify({ verifyPayment }));

return paymentData;
}
async afterPayment(
Expand Down
13 changes: 1 addition & 12 deletions apps/web/test/utils/bookingScenario/bookingScenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2105,18 +2105,7 @@ export function mockPaymentApp({
appStoreLookupKey?: string;
}) {
appStoreLookupKey = appStoreLookupKey || metadataLookupKey;
const { paymentUid, externalId, MockPaymentService } = getMockPaymentService();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
appStoreMock.default[appStoreLookupKey as keyof typeof appStoreMock.default].mockImplementation(() => {
return new Promise((resolve) => {
resolve({
lib: {
PaymentService: MockPaymentService,
},
});
});
});
const { paymentUid, externalId } = getMockPaymentService();

return {
paymentUid,
Expand Down
19 changes: 19 additions & 0 deletions packages/app-store-cli/src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,24 @@ function generateFiles() {
analyticsOutput.push(...analyticsServices);
}

const paymentOutput = [];
const paymentServices = getExportedObject(
"PaymentServiceMap",
{
importConfig: {
fileToBeImported: "lib/PaymentService.ts",
importName: "PaymentService",
},
lazyImport: true,
},
(app: App) => {
const hasPaymentService = fs.existsSync(path.join(APP_STORE_PATH, app.path, "lib/PaymentService.ts"));
return hasPaymentService;
}
);

paymentOutput.push(...paymentServices);

const banner = `/**
This file is autogenerated using the command \`yarn app-store:build --watch\`.
Don't modify this file manually.
Expand All @@ -430,6 +448,7 @@ function generateFiles() {
["bookerApps.metadata.generated.ts", bookerMetadataOutput],
["crm.apps.generated.ts", crmOutput],
["calendar.services.generated.ts", calendarOutput],
["payment.services.generated.ts", paymentOutput],
];
filesToGenerate.forEach(([fileName, output]) => {
fs.writeFileSync(`${APP_STORE_PATH}/${fileName}`, formatOutput(`${banner}${output.join("\n")}`));
Expand Down
12 changes: 12 additions & 0 deletions packages/app-store/payment.services.generated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
This file is autogenerated using the command `yarn app-store:build --watch`.
Don't modify this file manually.
**/
export const PaymentServiceMap = {
alby: import("./alby/lib/PaymentService"),
btcpayserver: import("./btcpayserver/lib/PaymentService"),
hitpay: import("./hitpay/lib/PaymentService"),
"mock-payment-app": import("./mock-payment-app/lib/PaymentService"),
paypal: import("./paypal/lib/PaymentService"),
stripepayment: import("./stripepayment/lib/PaymentService"),
};
16 changes: 9 additions & 7 deletions packages/lib/getConnectedApps.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Prisma } from "@prisma/client";

import appStore from "@calcom/app-store";
import type { TDependencyData } from "@calcom/app-store/_appRegistry";
import { PaymentServiceMap } from "@calcom/app-store/payment.services.generated";
import type { CredentialOwner } from "@calcom/app-store/types";
import { getAppFromSlug } from "@calcom/app-store/utils";
import { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
Expand All @@ -12,7 +12,6 @@ import type { PrismaClient } from "@calcom/prisma";
import type { User } from "@calcom/prisma/client";
import type { AppCategories } from "@calcom/prisma/enums";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import type { PaymentApp } from "@calcom/types/PaymentService";

import { buildNonDelegationCredentials } from "./delegationCredential/clientAndServer";

Expand Down Expand Up @@ -184,11 +183,14 @@ export async function getConnectedApps({
// undefined it means that app don't require app/setup/page
let isSetupAlready = undefined;
if (credential && app.categories.includes("payment")) {
const paymentApp = (await appStore[app.dirName as keyof typeof appStore]?.()) as PaymentApp | null;
if (paymentApp && "lib" in paymentApp && paymentApp?.lib && "PaymentService" in paymentApp?.lib) {
const PaymentService = paymentApp.lib.PaymentService;
const paymentInstance = new PaymentService(credential);
isSetupAlready = paymentInstance.isSetupAlready();
const paymentAppImportFn = PaymentServiceMap[app.dirName as keyof typeof PaymentServiceMap];
if (paymentAppImportFn) {
const paymentApp = await paymentAppImportFn;
if (paymentApp && "PaymentService" in paymentApp && paymentApp?.PaymentService) {
const PaymentService = paymentApp.PaymentService;
const paymentInstance = new PaymentService(credential);
isSetupAlready = paymentInstance.isSetupAlready();
}
}
}

Expand Down
22 changes: 13 additions & 9 deletions packages/lib/payment/deletePayment.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { Payment, Prisma } from "@prisma/client";

import appStore from "@calcom/app-store";
import { PaymentServiceMap } from "@calcom/app-store/payment.services.generated";
import type { AppCategories } from "@calcom/prisma/enums";
import type { IAbstractPaymentService, PaymentApp } from "@calcom/types/PaymentService";
import type { IAbstractPaymentService } from "@calcom/types/PaymentService";

const deletePayment = async (
paymentId: Payment["id"],
Expand All @@ -15,15 +15,19 @@ const deletePayment = async (
} | null;
}
): Promise<boolean> => {
const paymentApp = (await appStore[
paymentAppCredentials?.app?.dirName as keyof typeof appStore
]?.()) as PaymentApp;
if (!paymentApp?.lib?.PaymentService) {
console.warn(`payment App service of type ${paymentApp} is not implemented`);
const key = paymentAppCredentials?.app?.dirName;
const paymentAppImportFn = PaymentServiceMap[key as keyof typeof PaymentServiceMap];
if (!paymentAppImportFn) {
console.warn(`payment app not implemented for key: ${key}`);
return false;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const PaymentService = paymentApp.lib.PaymentService as any;

const paymentAppModule = await paymentAppImportFn;
if (!paymentAppModule?.PaymentService) {
console.warn(`payment App service not found for key: ${key}`);
return false;
}
const PaymentService = paymentAppModule.PaymentService;
const paymentInstance = new PaymentService(paymentAppCredentials) as IAbstractPaymentService;
const deleted = await paymentInstance.deletePayment(paymentId);
return deleted;
Expand Down
27 changes: 12 additions & 15 deletions packages/lib/payment/handlePayment.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import type { AppCategories, Prisma } from "@prisma/client";

import { PaymentServiceMap } from "@calcom/app-store/payment.services.generated";
import type { EventTypeAppsList } from "@calcom/app-store/utils";
import type { CompleteEventType } from "@calcom/prisma/zod";
import { eventTypeAppMetadataOptionalSchema } from "@calcom/prisma/zod-utils";
import type { CalendarEvent } from "@calcom/types/Calendar";
import type { IAbstractPaymentService, PaymentApp } from "@calcom/types/PaymentService";
import type { IAbstractPaymentService } from "@calcom/types/PaymentService";

const isPaymentApp = (x: unknown): x is PaymentApp =>
!!x &&
typeof x === "object" &&
"lib" in x &&
typeof x.lib === "object" &&
!!x.lib &&
"PaymentService" in x.lib;
const isPaymentService = (x: unknown): x is { PaymentService: any } =>
!!x && typeof x === "object" && "PaymentService" in x && typeof x.PaymentService === "function";

const isKeyOf = <T extends object>(obj: T, key: unknown): key is keyof T =>
typeof key === "string" && key in obj;
Expand Down Expand Up @@ -52,17 +48,18 @@ const handlePayment = async ({
if (isDryRun) return null;
const key = paymentAppCredentials?.app?.dirName;

const appStore = await import("@calcom/app-store").then((m) => m.default);
if (!isKeyOf(appStore, key)) {
console.warn(`key: ${key} is not a valid key in appStore`);
const paymentAppImportFn = PaymentServiceMap[key as keyof typeof PaymentServiceMap];
if (!paymentAppImportFn) {
console.warn(`payment app not implemented for key: ${key}`);
return null;
}
const paymentApp = await appStore[key]?.();
if (!isPaymentApp(paymentApp)) {
console.warn(`payment App service of type ${paymentApp} is not implemented`);

const paymentAppModule = await paymentAppImportFn;
if (!isPaymentService(paymentAppModule)) {
console.warn(`payment App service not found for key: ${key}`);
return null;
}
const PaymentService = paymentApp.lib.PaymentService;
const PaymentService = paymentAppModule.PaymentService;
const paymentInstance = new PaymentService(paymentAppCredentials) as IAbstractPaymentService;

const apps = eventTypeAppMetadataOptionalSchema.parse(selectedEventType?.metadata?.apps);
Expand Down
22 changes: 13 additions & 9 deletions packages/lib/payment/handlePaymentRefund.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { Payment, Prisma } from "@prisma/client";

import appStore from "@calcom/app-store";
import { PaymentServiceMap } from "@calcom/app-store/payment.services.generated";
import type { AppCategories } from "@calcom/prisma/enums";
import type { IAbstractPaymentService, PaymentApp } from "@calcom/types/PaymentService";
import type { IAbstractPaymentService } from "@calcom/types/PaymentService";

const handlePaymentRefund = async (
paymentId: Payment["id"],
Expand All @@ -15,15 +15,19 @@ const handlePaymentRefund = async (
} | null;
}
) => {
const paymentApp = (await appStore[
paymentAppCredentials?.app?.dirName as keyof typeof appStore
]?.()) as PaymentApp;
if (!paymentApp?.lib?.PaymentService) {
console.warn(`payment App service of type ${paymentApp} is not implemented`);
const key = paymentAppCredentials?.app?.dirName;
const paymentAppImportFn = PaymentServiceMap[key as keyof typeof PaymentServiceMap];
if (!paymentAppImportFn) {
console.warn(`payment app not implemented for key: ${key}`);
return false;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const PaymentService = paymentApp.lib.PaymentService as any;

const paymentAppModule = await paymentAppImportFn;
if (!paymentAppModule?.PaymentService) {
console.warn(`payment App service not found for key: ${key}`);
return false;
}
const PaymentService = paymentAppModule.PaymentService;
const paymentInstance = new PaymentService(paymentAppCredentials) as IAbstractPaymentService;
const refund = await paymentInstance.refund(paymentId);
return refund;
Expand Down
18 changes: 10 additions & 8 deletions packages/trpc/server/routers/viewer/payments.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from "zod";

import appStore from "@calcom/app-store";
import { PaymentServiceMap } from "@calcom/app-store/payment.services.generated";
import dayjs from "@calcom/dayjs";
import { sendNoShowFeeChargedEmail } from "@calcom/emails";
import { WebhookService } from "@calcom/features/webhooks/lib/WebhookService";
Expand All @@ -9,7 +9,6 @@ import { getTranslation } from "@calcom/lib/server/i18n";
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
import type { EventTypeMetadata } from "@calcom/prisma/zod-utils";
import type { CalendarEvent } from "@calcom/types/Calendar";
import type { PaymentApp } from "@calcom/types/PaymentService";

import { TRPCError } from "@trpc/server";

Expand Down Expand Up @@ -108,19 +107,22 @@ export const paymentsRouter = router({
throw new TRPCError({ code: "BAD_REQUEST", message: "Invalid payment credential" });
}

const paymentApp = (await appStore[
paymentCredential?.app?.dirName as keyof typeof appStore
]?.()) as PaymentApp | null;
const key = paymentCredential?.app?.dirName;
const paymentAppImportFn = PaymentServiceMap[key as keyof typeof PaymentServiceMap];
if (!paymentAppImportFn) {
throw new TRPCError({ code: "BAD_REQUEST", message: "Payment app not implemented" });
}

if (!(paymentApp && paymentApp.lib && "lib" in paymentApp && "PaymentService" in paymentApp.lib)) {
const paymentApp = await paymentAppImportFn;
if (!(paymentApp && "PaymentService" in paymentApp && paymentApp?.PaymentService)) {
throw new TRPCError({ code: "BAD_REQUEST", message: "Payment service not found" });
}

const PaymentService = paymentApp.lib.PaymentService;
const PaymentService = paymentApp.PaymentService;
const paymentInstance = new PaymentService(paymentCredential);

try {
const paymentData = await paymentInstance.chargeCard(payment);
const paymentData = await paymentInstance.chargeCard(payment, booking.id);

if (!paymentData) {
throw new TRPCError({ code: "NOT_FOUND", message: `Could not generate payment data` });
Expand Down
20 changes: 11 additions & 9 deletions packages/trpc/server/routers/viewer/payments/chargeCard.handler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import appStore from "@calcom/app-store";
import { PaymentServiceMap } from "@calcom/app-store/payment.services.generated";
import dayjs from "@calcom/dayjs";
import { sendNoShowFeeChargedEmail } from "@calcom/emails";
import { ErrorCode } from "@calcom/lib/errorCodes";
Expand All @@ -10,7 +10,7 @@ import { TeamRepository } from "@calcom/lib/server/repository/team";
import type { PrismaClient } from "@calcom/prisma";
import type { EventTypeMetadata } from "@calcom/prisma/zod-utils";
import type { CalendarEvent } from "@calcom/types/Calendar";
import type { IAbstractPaymentService, PaymentApp } from "@calcom/types/PaymentService";
import type { IAbstractPaymentService } from "@calcom/types/PaymentService";

import { TRPCError } from "@trpc/server";

Expand Down Expand Up @@ -125,19 +125,21 @@ export const chargeCardHandler = async ({ ctx, input }: ChargeCardHandlerOptions
throw new TRPCError({ code: "BAD_REQUEST", message: "Invalid payment credential" });
}

const paymentApp = (await appStore[
paymentCredential?.app?.dirName as keyof typeof appStore
]?.()) as PaymentApp;
const key = paymentCredential?.app?.dirName;
const paymentAppImportFn = PaymentServiceMap[key as keyof typeof PaymentServiceMap];
if (!paymentAppImportFn) {
throw new TRPCError({ code: "BAD_REQUEST", message: "Payment app not implemented" });
}

if (!paymentApp?.lib?.PaymentService) {
const paymentApp = await paymentAppImportFn;
if (!paymentApp?.PaymentService) {
throw new TRPCError({ code: "BAD_REQUEST", message: "Payment service not found" });
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const PaymentService = paymentApp.lib.PaymentService as any;
const PaymentService = paymentApp.PaymentService;
const paymentInstance = new PaymentService(paymentCredential) as IAbstractPaymentService;

try {
const paymentData = await paymentInstance.chargeCard(booking.payment[0]);
const paymentData = await paymentInstance.chargeCard(booking.payment[0], booking.id);

if (!paymentData) {
throw new TRPCError({ code: "NOT_FOUND", message: `Could not generate payment data` });
Expand Down
Loading
Loading