mirror of
https://github.com/hanzoai/calendar.git
synced 2026-10-30 14:46:43 +00:00
* chore: Upgrade prisma to 6.7.0 * Build fixes * type fixes Signed-off-by: Omar López <[email protected]> * Update schema.prisma * Patching * Revert "Update schema.prisma" This reverts commit 47d8618bf89ef4d007b30084df766f17281e21a1. * Revert "Patching" This reverts commit a1d2e3040e71690a44d4324db95d73b4d68c6adb. * Revert schema changes Signed-off-by: Omar López <[email protected]> * WIP Signed-off-by: Omar López <[email protected]> * Update getPublicEvent.ts * Update imports Signed-off-by: Omar López <[email protected]> * Update gitignore Signed-off-by: Omar López <[email protected]> * update remaining imports Signed-off-by: Omar López <[email protected]> * Delete .cursor/config.json * Discard changes to packages/features/eventtypes/lib/getPublicEvent.ts * Update _get.ts * Update user.ts * Update .gitignore * update * Update WorkflowStepContainer.tsx * Update next-auth-custom-adapter.ts * Update getPublicEvent.ts * Update workflow.ts * Update next-auth-custom-adapter.ts * Update next-auth-options.ts * Update bookingScenario.ts * fix missing imports * upgrades prismock Signed-off-by: Omar López <[email protected]> * patches prismock Signed-off-by: Omar López <[email protected]> * Update reschedule.test.ts * Update prisma.ts * patch prismock Signed-off-by: Omar López <[email protected]> * fix enums imports Signed-off-by: Omar López <[email protected]> * Revert "Update prisma.ts" This reverts commit 64edcf8db54171ff4456c209d563b5d431d99619. * Revert "patch prismock" This reverts commit e95819113dc9d88e7130947aa120cd42710977c8. * fix patch * Fix test that overrun the boundary, it shouldn't test too much * Move prisma import to changeSMSLockState * Bring back broken test without illegal imports * Merge with main and fix filter hosts by same round robin host * Fixed buildDryRunBooking fn tests * Fix and move ooo create or update handler test * Fix packages/features/eventtypes/lib/isCurrentlyAvailable.test.ts * Fix packages/trpc/server/routers/viewer/organizations/listMembers.handler.test.ts * Mock @calcom/prisma * Fix: verify-email.test.ts * fix: Moved WebhookService test and fixed default import mock * Fix: Added missing prisma mock, handleNewBooking uses that of course * We're not testing createContext here * fix: Prisma mock fix for listMembers.test.ts * More fixes to broken testcases * Forgot to remove borked test * Prevent the need to mock a lot of dependencies by moving out buildBaseWhereCondition to its own file * Temporarily skip getCalendarEvents, needs a rewrite * Fix: turns out you can access protected in testcases * fix further mocks * Added packages/features/insights/server/buildBaseWhereCondition.ts, types * Always great to have a mock and then not use it * And one less again. * fix: confirm.handler.test, didn't mock prisma * fix: Address minor nit by @eunjae & fix ImpersonationProvider test * Updated isPrismaAvailableCheck that doesn't crash on import * fix: Get Prisma directly from the client, it usually involves the Validator and does not need 'local' inclusion * Add zod-prisma-types without the generator enabled (commented out) * Uncomment and see what happens * Change method of import as imports did not work in Input Schemas * Remove custom 'zod' booking model, it does not belong with Prisma * Fix all other global Model imports * Rewrite most schema includes AND remove barrel file * Add bookingCreateBodySchema to features/bookings * Flurry of type fixes for compatibility with new zod gen * Refactor out the custom prisma type createEventTypeInput * Work around nullable eventTypeLocations * HandlePayment type fix * More fixes, final fix remaining is CompleteEventType * Should fix a bunch more booking related type errors * Missed one * Some props missing from BookingCreateBodySchema * Fix location type in handleChildrenEventTypes * Little bit hacky imo but it works * Final type error \o/ * Forgot to include Prisma * Do not include zod-utils in booker/types * Oops, was already including Booker/types * Fix membership type, also disallow updating createdAt/updatedAt, make part of patch/post * Fix api v1 type errors * Fix EventTypeDescription typings * Remove getParserWithGeneric, use userBodySchema with UserSchema * use centralized timeZoneSchema * Implement feedback by @zomars * Couple of WIP pushes * Fix tests * Type fixes in `handleChildrenEventTypes` test * Try and parse metadata before use * Change zod-prisma-types configuration for optimal performance * Fix prisma validator error in `prisma/selects/credential` * Disable seperate relations model, hits a bug * Import absolute - this makes rollup work in @platform/libraries * Attempt at removing resolutions override * Refactor using `Prisma.validator` to `satisfies` * Build atoms using @calcom/prisma/client * Build atoms using @calcom/prisma/client * fixes * Update eventTypeSelect.ts * Adjust `eventTypeMetaDataSchemaWithUntypedApps` from `unknown` to `record(any)` * `EventTypeDescription` rely on `descriptionAsSafeHTML` instead of `description` * Add `seatsPerTimeSlot` to event type public select * Fix typing in `users-public-view` getServerSide props * Add missing `schedulingType` to prop * chore: bump platform libraries * Function return type is illegal, not sure how this passed eslint (#21567) * Merged with main * Update updateTokenObject.ts * Update handleResponse.ts * Update index.ts * Update handleChildrenEventTypes.ts * Update booking-idempotency-key.ts * Update WebhookService.test.ts * Update events.test.ts * Update queued-response.test.ts * Update events.test.ts * Update getRoutedUrl.test.ts * fix: type checks Signed-off-by: Omar López <[email protected]> * fixes Signed-off-by: Omar López <[email protected]> * chore: bump platform libraries * Update yarn.lock * more fixes Signed-off-by: Omar López <[email protected]> * fixes Signed-off-by: Omar López <[email protected]> * biuld fixes * chore: bump platform libraries * Update conferencing.repository.ts * Update conferencing.repository.ts * Update getCalendarsEvents.test.ts * Update vite.config.js * chore: bump platform libraries * Update users.ts * Discard changes to docs/api-reference/v2/openapi.json * Update vite.config.ts * updated platform libraries * Update get.handler.test.ts * Update get.handler.test.ts * Update schema.prisma * Discard changes to docs/api-reference/v2/openapi.json * Update next-auth-custom-adapter.ts * Update team.ts * Flurry of type fixes * Fix majority of insight related type errors * Type fixes for unlink of account * Make user nullable again * Fixed a bunch of unit tests and one type error * Attempted mock fix * Attempted fix for Attribute type * Ensure default import becomes prisma, but not direct usage * Import default as prisma in prisma.module * Add attributeOption to attribute type * Fix calcom/prisma mock * Refactor Prisma client imports to @calcom/prisma/client Updated all imports from '@prisma/client' to '@calcom/prisma/client' across tests and repository files for consistency and to use the correct Prisma client package. This change improves maintainability and ensures the correct client is referenced throughout the codebase. * Undo removal of max-warnings=0 to get main to merge * Remove unit tests for e2e fixtures, provide new prisma mock * Mock @calcom/prisma in event manager * Mock @calcom/prisma in event manager * Add correct format even with --no-verify * Mock prisma in CalendarManager * Add mock for permission-check.service * Better injection in PrismaApiKeyRepository imports * More mock fixes :) * Fix listMembers.handler.test * Fix User import * Appropriately adjust all types to be imported as types, there were a lot of types imported as normal deps * Why was this a thing? * Strictly speaking; Not using prismock anymore * Ditched patch file for prismock * Fix output.service.ts platform type imports, need concrete for plainToClass * Better typing and tests for unlinkConnectedAccount.handler * Small type fix * Disable calendar cache tests as they are dependent on prismock * chore: bump platform lib * getRoutedUrl test remove of unused import * Extract select to external const on getEventTypesFromDB * Direct select of userSelect from selects/user * fix type error from merging 23653 * Fixed integration tests by removing hardcoded values that were possible due to mocking, but as its now directly hitting the db no longer * fix: vite config atoms prisma client type location * revert: example app prisma client * revert: example app prisma client * bump platform libs * fix: use class instead of type for DI of PlatformBookingsService * update platform libs * remove unused variable * chore: generate prisma client for api v2 * fix: api v2 e2e * fix: atoms e2e * fix: atoms e2e * fix: atoms e2e * fix: api v2 e2e * fix: tsconfig apiv2 enums * publish libraries * Simplify check for existence teamId --------- Signed-off-by: Omar López <[email protected]> Co-authored-by: Alex van Andel <[email protected]> Co-authored-by: Joe Au-Yeung <[email protected]> Co-authored-by: supalarry <[email protected]> Co-authored-by: cal.com <[email protected]> Co-authored-by: Morgan <[email protected]> Co-authored-by: Benny Joo <[email protected]>
364 lines
8.9 KiB
TypeScript
364 lines
8.9 KiB
TypeScript
import { v4 as uuidv4 } from "uuid";
|
|
import z from "zod";
|
|
|
|
import { IS_PRODUCTION, WEBAPP_URL } from "@calcom/lib/constants";
|
|
import logger from "@calcom/lib/logger";
|
|
import prisma from "@calcom/prisma";
|
|
import type { Prisma } from "@calcom/prisma/client";
|
|
|
|
class Paypal {
|
|
url: string;
|
|
clientId: string;
|
|
secretKey: string;
|
|
accessToken: string | null = null;
|
|
expiresAt: number | null = null;
|
|
|
|
constructor(opts: { clientId: string; secretKey: string }) {
|
|
this.url = IS_PRODUCTION ? "https://api-m.paypal.com" : "https://api-m.sandbox.paypal.com";
|
|
this.clientId = opts.clientId;
|
|
this.secretKey = opts.secretKey;
|
|
}
|
|
|
|
private fetcher = async (endpoint: string, init?: RequestInit | undefined) => {
|
|
await this.getAccessToken();
|
|
return fetch(`${this.url}${endpoint}`, {
|
|
method: "get",
|
|
...init,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${this.accessToken}`,
|
|
...init?.headers,
|
|
},
|
|
});
|
|
};
|
|
|
|
async getAccessToken(): Promise<void> {
|
|
if (this.accessToken && this.expiresAt && this.expiresAt > Date.now()) {
|
|
return;
|
|
}
|
|
const headers = {
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
Authorization: `Basic ${Buffer.from(`${this.clientId}:${this.secretKey}`).toString("base64")}`,
|
|
};
|
|
|
|
const body = new URLSearchParams();
|
|
body.append("grant_type", "client_credentials");
|
|
|
|
try {
|
|
const response = await fetch(`${this.url}/v1/oauth2/token`, {
|
|
method: "POST",
|
|
headers,
|
|
body,
|
|
});
|
|
if (response.ok) {
|
|
const { access_token, expires_in } = await response.json();
|
|
this.accessToken = access_token;
|
|
this.expiresAt = Date.now() + expires_in;
|
|
} else if (response?.status) {
|
|
console.error(`Request failed with status ${response.status}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
|
|
// Orders
|
|
async createOrder({
|
|
referenceId,
|
|
amount,
|
|
currency,
|
|
returnUrl,
|
|
cancelUrl,
|
|
intent = "CAPTURE",
|
|
}: {
|
|
referenceId: string;
|
|
amount: number;
|
|
currency: string;
|
|
returnUrl: string;
|
|
cancelUrl: string;
|
|
intent?: "CAPTURE" | "AUTHORIZE";
|
|
}): Promise<CreateOrderResponse> {
|
|
const createOrderRequestBody: CreateOrderRequestBody = {
|
|
intent,
|
|
purchase_units: [
|
|
{
|
|
reference_id: referenceId,
|
|
amount: {
|
|
currency_code: currency,
|
|
value: (amount / 100).toString(),
|
|
},
|
|
},
|
|
],
|
|
payment_source: {
|
|
paypal: {
|
|
experience_context: {
|
|
user_action: "PAY_NOW",
|
|
return_url: returnUrl,
|
|
cancel_url: cancelUrl,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
try {
|
|
const response = await this.fetcher("/v2/checkout/orders", {
|
|
method: "POST",
|
|
headers: {
|
|
"PayPal-Request-Id": uuidv4(),
|
|
},
|
|
body: JSON.stringify(createOrderRequestBody),
|
|
});
|
|
|
|
if (response.ok) {
|
|
const createOrderResponse: CreateOrderResponse = await response.json();
|
|
return createOrderResponse;
|
|
} else {
|
|
console.error(`Request failed with status ${response.status}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
return {} as CreateOrderResponse;
|
|
}
|
|
|
|
async captureOrder(orderId: string): Promise<boolean> {
|
|
try {
|
|
const captureResult = await this.fetcher(`/v2/checkout/orders/${orderId}/capture`, {
|
|
method: "POST",
|
|
});
|
|
if (captureResult.ok) {
|
|
const result = await captureResult.json();
|
|
if (result?.status === "COMPLETED") {
|
|
// Get payment reference id
|
|
|
|
const payment = await prisma.payment.findFirst({
|
|
where: {
|
|
externalId: orderId,
|
|
},
|
|
select: {
|
|
id: true,
|
|
bookingId: true,
|
|
data: true,
|
|
},
|
|
});
|
|
|
|
if (!payment) {
|
|
throw new Error("Payment not found");
|
|
}
|
|
|
|
await prisma.payment.update({
|
|
where: {
|
|
id: payment?.id,
|
|
},
|
|
data: {
|
|
success: true,
|
|
data: Object.assign(
|
|
{},
|
|
{ ...(payment?.data as Record<string, string | number>), capture: result.id }
|
|
) as unknown as Prisma.InputJsonValue,
|
|
},
|
|
});
|
|
|
|
// Update booking as paid
|
|
await prisma.booking.update({
|
|
where: {
|
|
id: payment.bookingId,
|
|
},
|
|
data: {
|
|
status: "ACCEPTED",
|
|
},
|
|
});
|
|
|
|
return true;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
throw error;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
async createWebhook(): Promise<boolean | string> {
|
|
const body = {
|
|
url: `${WEBAPP_URL}/api/integrations/paypal/webhook`,
|
|
event_types: [
|
|
{
|
|
name: "CHECKOUT.ORDER.APPROVED",
|
|
},
|
|
{
|
|
name: "CHECKOUT.ORDER.COMPLETED",
|
|
},
|
|
],
|
|
};
|
|
|
|
try {
|
|
const response = await this.fetcher(`/v1/notifications/webhooks`, {
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const message = `${response.statusText}: ${JSON.stringify(await response.json())}`;
|
|
throw new Error(message);
|
|
}
|
|
|
|
const result = await response.json();
|
|
return result.id as string;
|
|
} catch (e) {
|
|
logger.error("Error creating webhook", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
async listWebhooks(): Promise<string[]> {
|
|
try {
|
|
const response = await this.fetcher(`/v1/notifications/webhooks`);
|
|
|
|
if (response.ok) {
|
|
const { webhooks } = await response.json();
|
|
|
|
return webhooks
|
|
.filter((webhook: { id: string; url: string }) => {
|
|
return webhook.url.includes("api/integrations/paypal/webhook");
|
|
})
|
|
.map((webhook: { id: string }) => webhook.id);
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
return [];
|
|
}
|
|
return [];
|
|
}
|
|
|
|
async deleteWebhook(webhookId: string): Promise<boolean> {
|
|
try {
|
|
const response = await this.fetcher(`/v1/notifications/webhooks/${webhookId}`, {
|
|
method: "DELETE",
|
|
});
|
|
|
|
if (response.ok) {
|
|
return true;
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
async test(): Promise<boolean> {
|
|
// Always get a new access token
|
|
try {
|
|
await this.getAccessToken();
|
|
} catch (error) {
|
|
console.error(error);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
async verifyWebhook(options: WebhookEventVerifyRequest): Promise<void> {
|
|
const parseRequest = webhookEventVerifyRequestSchema.safeParse(options);
|
|
|
|
// Webhook event should be parsable
|
|
if (!parseRequest.success) {
|
|
console.error(parseRequest.error);
|
|
throw new Error("Request is malformed");
|
|
}
|
|
|
|
const stringy = JSON.stringify({
|
|
auth_algo: options.body.auth_algo,
|
|
cert_url: options.body.cert_url,
|
|
transmission_id: options.body.transmission_id,
|
|
transmission_sig: options.body.transmission_sig,
|
|
transmission_time: options.body.transmission_time,
|
|
webhook_id: options.body.webhook_id,
|
|
});
|
|
|
|
const bodyToString = `${stringy.slice(0, -1)},"webhook_event":${options.body.webhook_event}}`;
|
|
|
|
try {
|
|
const response = await this.fetcher(`/v1/notifications/verify-webhook-signature`, {
|
|
method: "POST",
|
|
body: bodyToString,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw response;
|
|
}
|
|
const data = await response.json();
|
|
|
|
if (data.verification_status !== "SUCCESS") {
|
|
throw data;
|
|
}
|
|
} catch (err) {
|
|
console.error(err);
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
|
|
export default Paypal;
|
|
|
|
interface PurchaseUnit {
|
|
amount: {
|
|
currency_code: string;
|
|
value: string;
|
|
};
|
|
reference_id: string;
|
|
}
|
|
|
|
interface ExperienceContext {
|
|
payment_method_preference?: string;
|
|
payment_method_selected?: string;
|
|
brand_name?: string;
|
|
locale?: string;
|
|
landing_page?: string;
|
|
shipping_preference?: string;
|
|
user_action: string;
|
|
return_url: string;
|
|
cancel_url: string;
|
|
}
|
|
|
|
interface PaymentSource {
|
|
paypal: {
|
|
experience_context: ExperienceContext;
|
|
};
|
|
}
|
|
|
|
interface CreateOrderRequestBody {
|
|
purchase_units: PurchaseUnit[];
|
|
intent: string;
|
|
payment_source?: PaymentSource;
|
|
}
|
|
|
|
interface Link {
|
|
href: string;
|
|
rel: string;
|
|
method: string;
|
|
}
|
|
|
|
interface CreateOrderResponse {
|
|
id: string;
|
|
status: string;
|
|
payment_source: PaymentSource;
|
|
links: Link[];
|
|
}
|
|
|
|
const webhookEventVerifyRequestSchema = z.object({
|
|
body: z
|
|
.object({
|
|
auth_algo: z.string(),
|
|
cert_url: z.string(),
|
|
transmission_id: z.string(),
|
|
transmission_sig: z.string(),
|
|
transmission_time: z.string(),
|
|
webhook_event: z.string(),
|
|
webhook_id: z.string(),
|
|
})
|
|
.required(),
|
|
});
|
|
|
|
export type WebhookEventVerifyRequest = z.infer<typeof webhookEventVerifyRequestSchema>;
|