WIP mercado pago
This commit is contained in:
parent
f26368f405
commit
d331d8c5e4
|
@ -28,6 +28,7 @@ export const EventTypeAddonMap = {
|
||||||
qr_code: dynamic(() => import("./qr_code/components/EventTypeAppCardInterface")),
|
qr_code: dynamic(() => import("./qr_code/components/EventTypeAppCardInterface")),
|
||||||
rainbow: dynamic(() => import("./rainbow/components/EventTypeAppCardInterface")),
|
rainbow: dynamic(() => import("./rainbow/components/EventTypeAppCardInterface")),
|
||||||
stripepayment: dynamic(() => import("./stripepayment/components/EventTypeAppCardInterface")),
|
stripepayment: dynamic(() => import("./stripepayment/components/EventTypeAppCardInterface")),
|
||||||
|
"mercado_pago": dynamic(() => import("./mercado_pago/components/EventTypeAppCardInterface")),
|
||||||
"booking-pages-tag": dynamic(() =>
|
"booking-pages-tag": dynamic(() =>
|
||||||
import("./templates/booking-pages-tag/components/EventTypeAppCardInterface")
|
import("./templates/booking-pages-tag/components/EventTypeAppCardInterface")
|
||||||
),
|
),
|
||||||
|
|
|
@ -10,6 +10,7 @@ const appStore = {
|
||||||
huddle01video: import("./huddle01video"),
|
huddle01video: import("./huddle01video"),
|
||||||
jitsivideo: import("./jitsivideo"),
|
jitsivideo: import("./jitsivideo"),
|
||||||
larkcalendar: import("./larkcalendar"),
|
larkcalendar: import("./larkcalendar"),
|
||||||
|
mercado_pago: import("./mercado_pago"),
|
||||||
office365calendar: import("./office365calendar"),
|
office365calendar: import("./office365calendar"),
|
||||||
office365video: import("./office365video"),
|
office365video: import("./office365video"),
|
||||||
plausible: import("./plausible"),
|
plausible: import("./plausible"),
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
export { default as add } from "./add";
|
export { default as add } from "./add";
|
||||||
|
export { default as webhook } from "./webhook";
|
||||||
|
|
|
@ -0,0 +1,321 @@
|
||||||
|
import { BookingStatus } from "@prisma/client";
|
||||||
|
import type { Prisma } from "@prisma/client";
|
||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import * as z from "zod";
|
||||||
|
|
||||||
|
import EventManager from "@calcom/core/EventManager";
|
||||||
|
import { sendScheduledEmails } from "@calcom/emails";
|
||||||
|
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||||
|
import { handleConfirmation } from "@calcom/features/bookings/lib/handleConfirmation";
|
||||||
|
import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
|
||||||
|
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||||
|
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||||
|
import { HttpError as HttpCode } from "@calcom/lib/http-error";
|
||||||
|
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||||
|
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function getEventType(id: number) {
|
||||||
|
return prisma.eventType.findUnique({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
recurringEvent: true,
|
||||||
|
requiresConfirmation: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getBooking(bookingId: number) {
|
||||||
|
const booking = await prisma.booking.findUnique({
|
||||||
|
where: {
|
||||||
|
id: bookingId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
...bookingMinimalSelect,
|
||||||
|
eventType: true,
|
||||||
|
smsReminderNumber: true,
|
||||||
|
location: true,
|
||||||
|
eventTypeId: true,
|
||||||
|
userId: true,
|
||||||
|
uid: true,
|
||||||
|
paid: true,
|
||||||
|
destinationCalendar: true,
|
||||||
|
status: true,
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
credentials: true,
|
||||||
|
timeZone: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
locale: true,
|
||||||
|
destinationCalendar: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!booking) throw new HttpCode({ statusCode: 204, message: "No booking found" });
|
||||||
|
|
||||||
|
type EventTypeRaw = Awaited<ReturnType<typeof getEventType>>;
|
||||||
|
let eventTypeRaw: EventTypeRaw | null = null;
|
||||||
|
if (booking.eventTypeId) {
|
||||||
|
eventTypeRaw = await getEventType(booking.eventTypeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { user } = booking;
|
||||||
|
|
||||||
|
if (!user) throw new HttpCode({ statusCode: 204, message: "No user found" });
|
||||||
|
|
||||||
|
const t = await getTranslation(user.locale ?? "en", "common");
|
||||||
|
const attendeesListPromises = booking.attendees.map(async (attendee) => {
|
||||||
|
return {
|
||||||
|
name: attendee.name,
|
||||||
|
email: attendee.email,
|
||||||
|
timeZone: attendee.timeZone,
|
||||||
|
language: {
|
||||||
|
translate: await getTranslation(attendee.locale ?? "en", "common"),
|
||||||
|
locale: attendee.locale ?? "en",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const attendeesList = await Promise.all(attendeesListPromises);
|
||||||
|
|
||||||
|
const evt: CalendarEvent = {
|
||||||
|
type: booking.title,
|
||||||
|
title: booking.title,
|
||||||
|
description: booking.description || undefined,
|
||||||
|
startTime: booking.startTime.toISOString(),
|
||||||
|
endTime: booking.endTime.toISOString(),
|
||||||
|
customInputs: isPrismaObjOrUndefined(booking.customInputs),
|
||||||
|
organizer: {
|
||||||
|
email: user.email,
|
||||||
|
name: user.name!,
|
||||||
|
timeZone: user.timeZone,
|
||||||
|
language: { translate: t, locale: user.locale ?? "en" },
|
||||||
|
id: user.id,
|
||||||
|
},
|
||||||
|
attendees: attendeesList,
|
||||||
|
uid: booking.uid,
|
||||||
|
destinationCalendar: booking.destinationCalendar || user.destinationCalendar,
|
||||||
|
recurringEvent: parseRecurringEvent(eventTypeRaw?.recurringEvent),
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
booking,
|
||||||
|
user,
|
||||||
|
evt,
|
||||||
|
eventTypeRaw,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handlePaymentSuccess(payload: z.infer<typeof MercadoPagoPayloadSchema>) {
|
||||||
|
return true;
|
||||||
|
const payment = await prisma.payment.findFirst({
|
||||||
|
where: {
|
||||||
|
externalId: paymentIntent.id,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
bookingId: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!payment?.bookingId) {
|
||||||
|
console.log(JSON.stringify(paymentIntent), JSON.stringify(payment));
|
||||||
|
}
|
||||||
|
if (!payment?.bookingId) throw new HttpCode({ statusCode: 204, message: "Payment not found" });
|
||||||
|
|
||||||
|
const booking = await prisma.booking.findUnique({
|
||||||
|
where: {
|
||||||
|
id: payment.bookingId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
...bookingMinimalSelect,
|
||||||
|
eventType: true,
|
||||||
|
smsReminderNumber: true,
|
||||||
|
location: true,
|
||||||
|
eventTypeId: true,
|
||||||
|
userId: true,
|
||||||
|
uid: true,
|
||||||
|
paid: true,
|
||||||
|
destinationCalendar: true,
|
||||||
|
status: true,
|
||||||
|
responses: true,
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
credentials: true,
|
||||||
|
timeZone: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
locale: true,
|
||||||
|
destinationCalendar: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!booking) throw new HttpCode({ statusCode: 204, message: "No booking found" });
|
||||||
|
|
||||||
|
type EventTypeRaw = Awaited<ReturnType<typeof getEventType>>;
|
||||||
|
let eventTypeRaw: EventTypeRaw | null = null;
|
||||||
|
if (booking.eventTypeId) {
|
||||||
|
eventTypeRaw = await getEventType(booking.eventTypeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { user } = booking;
|
||||||
|
|
||||||
|
if (!user) throw new HttpCode({ statusCode: 204, message: "No user found" });
|
||||||
|
|
||||||
|
const t = await getTranslation(user.locale ?? "en", "common");
|
||||||
|
const attendeesListPromises = booking.attendees.map(async (attendee) => {
|
||||||
|
return {
|
||||||
|
name: attendee.name,
|
||||||
|
email: attendee.email,
|
||||||
|
timeZone: attendee.timeZone,
|
||||||
|
language: {
|
||||||
|
translate: await getTranslation(attendee.locale ?? "en", "common"),
|
||||||
|
locale: attendee.locale ?? "en",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const attendeesList = await Promise.all(attendeesListPromises);
|
||||||
|
|
||||||
|
const evt: CalendarEvent = {
|
||||||
|
type: booking.title,
|
||||||
|
title: booking.title,
|
||||||
|
description: booking.description || undefined,
|
||||||
|
startTime: booking.startTime.toISOString(),
|
||||||
|
endTime: booking.endTime.toISOString(),
|
||||||
|
customInputs: isPrismaObjOrUndefined(booking.customInputs),
|
||||||
|
...getCalEventResponses({
|
||||||
|
booking: booking,
|
||||||
|
bookingFields: booking.eventType?.bookingFields || null,
|
||||||
|
}),
|
||||||
|
organizer: {
|
||||||
|
email: user.email,
|
||||||
|
name: user.name!,
|
||||||
|
timeZone: user.timeZone,
|
||||||
|
language: { translate: t, locale: user.locale ?? "en" },
|
||||||
|
},
|
||||||
|
attendees: attendeesList,
|
||||||
|
uid: booking.uid,
|
||||||
|
destinationCalendar: booking.destinationCalendar || user.destinationCalendar,
|
||||||
|
recurringEvent: parseRecurringEvent(eventTypeRaw?.recurringEvent),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (booking.location) evt.location = booking.location;
|
||||||
|
|
||||||
|
const bookingData: Prisma.BookingUpdateInput = {
|
||||||
|
paid: true,
|
||||||
|
status: BookingStatus.ACCEPTED,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isConfirmed = booking.status === BookingStatus.ACCEPTED;
|
||||||
|
if (isConfirmed) {
|
||||||
|
const eventManager = new EventManager(user);
|
||||||
|
const scheduleResult = await eventManager.create(evt);
|
||||||
|
bookingData.references = { create: scheduleResult.referencesToCreate };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventTypeRaw?.requiresConfirmation) {
|
||||||
|
delete bookingData.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentUpdate = prisma.payment.update({
|
||||||
|
where: {
|
||||||
|
id: payment.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const bookingUpdate = prisma.booking.update({
|
||||||
|
where: {
|
||||||
|
id: booking.id,
|
||||||
|
},
|
||||||
|
data: bookingData,
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.$transaction([paymentUpdate, bookingUpdate]);
|
||||||
|
|
||||||
|
if (!isConfirmed && !eventTypeRaw?.requiresConfirmation) {
|
||||||
|
await handleConfirmation({ user, evt, prisma, bookingId: booking.id, booking, paid: true });
|
||||||
|
} else {
|
||||||
|
await sendScheduledEmails({ ...evt });
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new HttpCode({
|
||||||
|
statusCode: 200,
|
||||||
|
message: `Booking with id '${booking.id}' was paid and confirmed.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebhookHandler = (action: "test.created" | "created") => Promise<void>;
|
||||||
|
|
||||||
|
const webhookHandlers: Record<string, WebhookHandler | undefined> = {
|
||||||
|
"test.created": handlePaymentSuccess,
|
||||||
|
created: handlePaymentSuccess,
|
||||||
|
};
|
||||||
|
|
||||||
|
const MercadoPagoPayloadSchema = z.object({
|
||||||
|
action: z.string(),
|
||||||
|
api_version: z.string(),
|
||||||
|
application_id: z.string(),
|
||||||
|
date_created: z.string(),
|
||||||
|
id: z.string(),
|
||||||
|
live_mode: z.enum(["false", "true"]),
|
||||||
|
type: z.literal("test"),
|
||||||
|
user_id: z.string(),
|
||||||
|
data: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
try {
|
||||||
|
if (req.method !== "POST") {
|
||||||
|
throw new HttpCode({ statusCode: 405, message: "Method Not Allowed" });
|
||||||
|
}
|
||||||
|
const { body } = req;
|
||||||
|
const parse = MercadoPagoPayloadSchema.safeParse(body);
|
||||||
|
if (!parse.success) {
|
||||||
|
throw new HttpCode({ statusCode: 400, message: "Bad Request" });
|
||||||
|
}
|
||||||
|
const { data: parsedPayload } = parse;
|
||||||
|
const handler = webhookHandlers[parsedPayload.action];
|
||||||
|
if (handler) {
|
||||||
|
await handler(parsedPayload);
|
||||||
|
} else {
|
||||||
|
/** Not really an error, just letting Stripe know that the webhook was received but unhandled */
|
||||||
|
throw new HttpCode({
|
||||||
|
statusCode: 202,
|
||||||
|
message: `Unhandled Stripe Webhook event type ${event.type}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (_err) {
|
||||||
|
const err = getErrorFromUnknown(_err);
|
||||||
|
console.error(`Webhook Error: ${err.message}`);
|
||||||
|
res.status(err.statusCode ?? 500).send({
|
||||||
|
message: err.message,
|
||||||
|
stack: IS_PRODUCTION ? undefined : err.stack,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a response to acknowledge receipt of the event
|
||||||
|
res.json({ received: true });
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import type { IMercadoPagoPaymentComponentProps } from "@calcom/app-store/mercado_pago/types";
|
||||||
|
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||||
|
|
||||||
|
export const MercadoPagoPaymentComponent = (props: IMercadoPagoPaymentComponentProps) => {
|
||||||
|
const { payment, eventType, user, location, bookingId, bookingUid } = props;
|
||||||
|
const paymentInitLink = IS_PRODUCTION ? payment.data.init_point : payment.data.sandbox_init_point;
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full flex-col items-center justify-center">
|
||||||
|
MercadoPago
|
||||||
|
<Link href={paymentInitLink}>Pay</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,24 +1,22 @@
|
||||||
import type { Booking, Payment, Prisma } from "@prisma/client";
|
import type { Booking, Payment, PaymentOption, Prisma } from "@prisma/client";
|
||||||
import { PaymentType } from "@prisma/client";
|
|
||||||
import * as MercadoPago from "mercadopago";
|
import * as MercadoPago from "mercadopago";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
import { AbstractPaymentService } from "@calcom/lib/PaymentService";
|
import type { IAbstractPaymentService } from "@calcom/lib/PaymentService";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
const mercadoPagoCredentialKeysSchema = z.object({
|
const mercadoPagoCredentialKeysSchema = z.object({
|
||||||
access_token: z.string(),
|
access_token: z.string(),
|
||||||
public_key: z.string(),
|
public_key: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export class PaymentService extends AbstractPaymentService {
|
export class PaymentService implements IAbstractPaymentService {
|
||||||
private mercadoPago: typeof MercadoPago;
|
private mercadoPago: typeof MercadoPago;
|
||||||
private credentials: z.infer<typeof mercadoPagoCredentialKeysSchema>;
|
private credentials: z.infer<typeof mercadoPagoCredentialKeysSchema>;
|
||||||
|
|
||||||
constructor(credentials: { key: Prisma.JsonValue }) {
|
constructor(credentials: { key: Prisma.JsonValue }) {
|
||||||
super();
|
|
||||||
|
|
||||||
this.credentials = mercadoPagoCredentialKeysSchema.parse(credentials.key);
|
this.credentials = mercadoPagoCredentialKeysSchema.parse(credentials.key);
|
||||||
|
|
||||||
this.mercadoPago = MercadoPago;
|
this.mercadoPago = MercadoPago;
|
||||||
|
@ -66,7 +64,11 @@ export class PaymentService extends AbstractPaymentService {
|
||||||
const paymentData = await prisma?.payment.create({
|
const paymentData = await prisma?.payment.create({
|
||||||
data: {
|
data: {
|
||||||
uid: uuidv4(),
|
uid: uuidv4(),
|
||||||
type: PaymentType.MERCADO_PAGO,
|
app: {
|
||||||
|
connect: {
|
||||||
|
slug: "mercado_pago",
|
||||||
|
},
|
||||||
|
},
|
||||||
booking: {
|
booking: {
|
||||||
connect: {
|
connect: {
|
||||||
id: bookingId,
|
id: bookingId,
|
||||||
|
@ -99,10 +101,40 @@ export class PaymentService extends AbstractPaymentService {
|
||||||
async refund(): Promise<Payment> {
|
async refund(): Promise<Payment> {
|
||||||
throw new Error("Method not implemented.");
|
throw new Error("Method not implemented.");
|
||||||
}
|
}
|
||||||
getPaymentPaidStatus(): string {
|
|
||||||
|
collectCard(
|
||||||
|
payment: Pick<Prisma.PaymentUncheckedCreateInput, "amount" | "currency">,
|
||||||
|
bookingId: number,
|
||||||
|
bookerEmail: string,
|
||||||
|
paymentOption: PaymentOption
|
||||||
|
): Promise<Payment> {
|
||||||
throw new Error("Method not implemented.");
|
throw new Error("Method not implemented.");
|
||||||
}
|
}
|
||||||
getPaymentDetails(): Payment {
|
chargeCard(
|
||||||
|
payment: Pick<Prisma.PaymentUncheckedCreateInput, "amount" | "currency">,
|
||||||
|
bookingId: number
|
||||||
|
): Promise<Payment> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
getPaymentPaidStatus(): Promise<string> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
getPaymentDetails(): Promise<Payment> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
afterPayment(
|
||||||
|
event: CalendarEvent,
|
||||||
|
booking: {
|
||||||
|
user: { email: string | null; name: string | null; timeZone: string } | null;
|
||||||
|
id: number;
|
||||||
|
startTime: { toISOString: () => string };
|
||||||
|
uid: string;
|
||||||
|
},
|
||||||
|
paymentData: Payment
|
||||||
|
): Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
deletePayment(paymentId: number): Promise<boolean> {
|
||||||
throw new Error("Method not implemented.");
|
throw new Error("Method not implemented.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
export interface IMercadoPagoPaymentComponentProps {
|
||||||
|
payment: {
|
||||||
|
amount: number;
|
||||||
|
appId: string;
|
||||||
|
bookingId: number;
|
||||||
|
currency: string;
|
||||||
|
data: MercadoPagoPaymentResponse;
|
||||||
|
paymentOption: string;
|
||||||
|
refunded: boolean;
|
||||||
|
success: boolean;
|
||||||
|
uid: string;
|
||||||
|
};
|
||||||
|
eventType: EventType;
|
||||||
|
user: User;
|
||||||
|
location?: string | null;
|
||||||
|
bookingId: number;
|
||||||
|
bookingUid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MercadoPagoPaymentResponse {
|
||||||
|
additional_info: string;
|
||||||
|
auto_return: string;
|
||||||
|
back_urls: {
|
||||||
|
failure: string;
|
||||||
|
pending: string;
|
||||||
|
success: string;
|
||||||
|
};
|
||||||
|
binary_mode: boolean;
|
||||||
|
client_id: string;
|
||||||
|
collector_id: number;
|
||||||
|
coupon_code: null;
|
||||||
|
coupon_labels: null;
|
||||||
|
date_created: string;
|
||||||
|
date_of_expiration: null;
|
||||||
|
expiration_date_from: null;
|
||||||
|
expiration_date_to: null;
|
||||||
|
expires: boolean;
|
||||||
|
external_reference: string;
|
||||||
|
id: string;
|
||||||
|
init_point: string;
|
||||||
|
internal_metadata: null;
|
||||||
|
items: object[];
|
||||||
|
last_updated: null;
|
||||||
|
marketplace: string;
|
||||||
|
marketplace_fee: number;
|
||||||
|
metadata: object;
|
||||||
|
notification_url: null;
|
||||||
|
operation_type: string;
|
||||||
|
payer: {
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
phone: object;
|
||||||
|
address: object;
|
||||||
|
surname: string;
|
||||||
|
};
|
||||||
|
payment_methods: {
|
||||||
|
installments: null;
|
||||||
|
default_card_id: null;
|
||||||
|
default_installments: null;
|
||||||
|
excluded_payment_types: string[];
|
||||||
|
excluded_payment_methods: string[];
|
||||||
|
};
|
||||||
|
processing_modes: null;
|
||||||
|
product_id: null;
|
||||||
|
redirect_urls: {
|
||||||
|
failure: string;
|
||||||
|
pending: string;
|
||||||
|
success: string;
|
||||||
|
};
|
||||||
|
sandbox_init_point: string;
|
||||||
|
shipments: {
|
||||||
|
receiver_address: object;
|
||||||
|
default_shipping_method: null;
|
||||||
|
};
|
||||||
|
site_id: string;
|
||||||
|
stripeAccount: string;
|
||||||
|
stripe_publishable_key: string;
|
||||||
|
total_amount: null;
|
||||||
|
}
|
|
@ -80,7 +80,7 @@ export default function MercadoPagoSetup(props: IMercadoPagoSetupProps) {
|
||||||
<div className="m-auto max-w-[43em] overflow-auto rounded bg-white pb-10 md:p-10">
|
<div className="m-auto max-w-[43em] overflow-auto rounded bg-white pb-10 md:p-10">
|
||||||
<div className="md:flex md:flex-row">
|
<div className="md:flex md:flex-row">
|
||||||
<div className="invisible md:visible">
|
<div className="invisible md:visible">
|
||||||
<img className="h-11" src="/api/app-store/mercado_pago/icon.svg" alt="Zapier Logo" />
|
<img className="h-11" src="/api/app-store/mercado_pago/icon.svg" alt="Mercado Pago Logo" />
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-2 ltr:mr-2 rtl:ml-2 md:ml-5">
|
<div className="ml-2 ltr:mr-2 rtl:ml-2 md:ml-5">
|
||||||
<p className="text-lg">Mercado Pago</p>
|
<p className="text-lg">Mercado Pago</p>
|
||||||
|
@ -94,7 +94,7 @@ export default function MercadoPagoSetup(props: IMercadoPagoSetupProps) {
|
||||||
type="text"
|
type="text"
|
||||||
name="public_key"
|
name="public_key"
|
||||||
id="public_key"
|
id="public_key"
|
||||||
className="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
className="block w-full rounded-md border-gray-300 text-black shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||||
value={newPublicKey}
|
value={newPublicKey}
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
onChange={(e) => setNewPublicKey(e.target.value)}
|
onChange={(e) => setNewPublicKey(e.target.value)}
|
||||||
|
@ -110,7 +110,7 @@ export default function MercadoPagoSetup(props: IMercadoPagoSetupProps) {
|
||||||
type="password"
|
type="password"
|
||||||
name="access_token"
|
name="access_token"
|
||||||
id="access_token"
|
id="access_token"
|
||||||
className="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
className="block w-full rounded-md border-gray-300 text-black shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||||
value={newAccessToken}
|
value={newAccessToken}
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
onChange={(e) => setNewAccessToken(e.target.value)}
|
onChange={(e) => setNewAccessToken(e.target.value)}
|
||||||
|
@ -124,6 +124,7 @@ export default function MercadoPagoSetup(props: IMercadoPagoSetupProps) {
|
||||||
<Select
|
<Select
|
||||||
options={currencyOptions}
|
options={currencyOptions}
|
||||||
value={selectedCurrency}
|
value={selectedCurrency}
|
||||||
|
className="text-black"
|
||||||
defaultValue={selectedCurrency?.value}
|
defaultValue={selectedCurrency?.value}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (e) {
|
if (e) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { useEffect, useState } from "react";
|
||||||
import { FormattedNumber, IntlProvider } from "react-intl";
|
import { FormattedNumber, IntlProvider } from "react-intl";
|
||||||
|
|
||||||
import { getSuccessPageLocationMessage } from "@calcom/app-store/locations";
|
import { getSuccessPageLocationMessage } from "@calcom/app-store/locations";
|
||||||
|
import { MercadoPagoPaymentComponent } from "@calcom/app-store/mercado_pago/components/MercadoPagoPaymentComponent";
|
||||||
import getStripe from "@calcom/app-store/stripepayment/lib/client";
|
import getStripe from "@calcom/app-store/stripepayment/lib/client";
|
||||||
import type { StripePaymentData } from "@calcom/app-store/stripepayment/lib/server";
|
import type { StripePaymentData } from "@calcom/app-store/stripepayment/lib/server";
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
|
@ -138,6 +139,16 @@ const PaymentPage: FC<PaymentPageProps> = (props) => {
|
||||||
/>
|
/>
|
||||||
</Elements>
|
</Elements>
|
||||||
)}
|
)}
|
||||||
|
{props.payment.appId === "mercado_pago" && !props.payment.success && (
|
||||||
|
<MercadoPagoPaymentComponent
|
||||||
|
payment={props.payment}
|
||||||
|
eventType={props.eventType}
|
||||||
|
user={props.user}
|
||||||
|
location={props.booking.location}
|
||||||
|
bookingId={props.booking.id}
|
||||||
|
bookingUid={props.booking.uid}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{props.payment.refunded && (
|
{props.payment.refunded && (
|
||||||
<div className="text-default mt-4 text-center dark:text-gray-300">{t("refunded")}</div>
|
<div className="text-default mt-4 text-center dark:text-gray-300">{t("refunded")}</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { AppCategories, Prisma } from "@prisma/client";
|
import type { AppCategories, Prisma, PaymentOption } from "@prisma/client";
|
||||||
|
|
||||||
import appStore from "@calcom/app-store";
|
import appStore from "@calcom/app-store";
|
||||||
import type { EventTypeAppsList } from "@calcom/app-store/utils";
|
import type { EventTypeAppsList } from "@calcom/app-store/utils";
|
||||||
|
@ -25,6 +25,7 @@ const handlePayment = async (
|
||||||
bookerEmail: string
|
bookerEmail: string
|
||||||
) => {
|
) => {
|
||||||
const paymentApp = await appStore[paymentAppCredentials?.app?.dirName as keyof typeof appStore];
|
const paymentApp = await appStore[paymentAppCredentials?.app?.dirName as keyof typeof appStore];
|
||||||
|
|
||||||
if (!(paymentApp && "lib" in paymentApp && "PaymentService" in paymentApp.lib)) {
|
if (!(paymentApp && "lib" in paymentApp && "PaymentService" in paymentApp.lib)) {
|
||||||
console.warn(`payment App service of type ${paymentApp} is not implemented`);
|
console.warn(`payment App service of type ${paymentApp} is not implemented`);
|
||||||
return null;
|
return null;
|
||||||
|
@ -32,7 +33,7 @@ const handlePayment = async (
|
||||||
const PaymentService = paymentApp.lib.PaymentService;
|
const PaymentService = paymentApp.lib.PaymentService;
|
||||||
const paymentInstance = new PaymentService(paymentAppCredentials);
|
const paymentInstance = new PaymentService(paymentAppCredentials);
|
||||||
|
|
||||||
const paymentOption =
|
const paymentOption: PaymentOption =
|
||||||
selectedEventType?.metadata?.apps?.[paymentAppCredentials.appId].paymentOption || "ON_BOOKING";
|
selectedEventType?.metadata?.apps?.[paymentAppCredentials.appId].paymentOption || "ON_BOOKING";
|
||||||
|
|
||||||
let paymentData;
|
let paymentData;
|
||||||
|
|
18
yarn.lock
18
yarn.lock
|
@ -3956,6 +3956,7 @@ __metadata:
|
||||||
"@calcom/types": "*"
|
"@calcom/types": "*"
|
||||||
"@calcom/ui": "*"
|
"@calcom/ui": "*"
|
||||||
"@calcom/zoomvideo": "*"
|
"@calcom/zoomvideo": "*"
|
||||||
|
"@types/mercadopago": ^1.5.8
|
||||||
lodash: ^4.17.21
|
lodash: ^4.17.21
|
||||||
qs-stringify: ^1.2.1
|
qs-stringify: ^1.2.1
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
|
@ -4465,6 +4466,15 @@ __metadata:
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
|
"@calcom/mercado_pago@workspace:packages/app-store/mercado_pago":
|
||||||
|
version: 0.0.0-use.local
|
||||||
|
resolution: "@calcom/mercado_pago@workspace:packages/app-store/mercado_pago"
|
||||||
|
dependencies:
|
||||||
|
"@calcom/lib": "*"
|
||||||
|
"@calcom/types": "*"
|
||||||
|
languageName: unknown
|
||||||
|
linkType: soft
|
||||||
|
|
||||||
"@calcom/n8n@workspace:packages/app-store/n8n":
|
"@calcom/n8n@workspace:packages/app-store/n8n":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@calcom/n8n@workspace:packages/app-store/n8n"
|
resolution: "@calcom/n8n@workspace:packages/app-store/n8n"
|
||||||
|
@ -4939,6 +4949,7 @@ __metadata:
|
||||||
markdown-it: ^13.0.1
|
markdown-it: ^13.0.1
|
||||||
md5: ^2.3.0
|
md5: ^2.3.0
|
||||||
memory-cache: ^0.2.0
|
memory-cache: ^0.2.0
|
||||||
|
mercadopago: ^1.5.14
|
||||||
micro: ^10.0.1
|
micro: ^10.0.1
|
||||||
mime-types: ^2.1.35
|
mime-types: ^2.1.35
|
||||||
mockdate: ^3.0.5
|
mockdate: ^3.0.5
|
||||||
|
@ -12788,6 +12799,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@types/mercadopago@npm:^1.5.8":
|
||||||
|
version: 1.5.8
|
||||||
|
resolution: "@types/mercadopago@npm:1.5.8"
|
||||||
|
checksum: 0ad1e1bd98bbfe84ecf31e535aff2eb3551c7ef61d494de0b7b933182ad11d1d4300e3df12f7b179baac0c4cb0d44479f9e4467c5a168e7fd6b5f7b0575597a1
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@types/micro@npm:7.3.7":
|
"@types/micro@npm:7.3.7":
|
||||||
version: 7.3.7
|
version: 7.3.7
|
||||||
resolution: "@types/micro@npm:7.3.7"
|
resolution: "@types/micro@npm:7.3.7"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user