requested changes: move code from core to file, fix for trpc usecontext
parent
a562842a8d
commit
6e80abc82f
|
@ -125,11 +125,4 @@ SALESFORCE_CONSUMER_SECRET=""
|
|||
ZOHOCRM_CLIENT_ID=""
|
||||
ZOHOCRM_CLIENT_SECRET=""
|
||||
|
||||
# ALBY
|
||||
# Used for the Alby payment app / receiving Alby payments
|
||||
# Get it from: https://getalby.com/developer/oauth_clients
|
||||
# Set callbackUrl to YOUR_APP_DOMAIN/api/integrations/alby/alby-webhook
|
||||
NEXT_PUBLIC_ALBY_CLIENT_ID=""
|
||||
NEXT_PUBLIC_ALBY_CLIENT_SECRET=""
|
||||
|
||||
# *********************************************************************************************************
|
||||
|
|
|
@ -1 +1,125 @@
|
|||
export { default, config } from "@calcom/features/ee/payments/api/alby-webhook";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import getRawBody from "raw-body";
|
||||
import * as z from "zod";
|
||||
|
||||
import { albyCredentialKeysSchema } from "@calcom/app-store/alby/lib";
|
||||
import parseInvoice from "@calcom/app-store/alby/lib/parseInvoice";
|
||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import { HttpError as HttpCode } from "@calcom/lib/http-error";
|
||||
import { handlePaymentSuccess } from "@calcom/lib/payment/handlePaymentSuccess";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
},
|
||||
};
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
if (req.method !== "POST") {
|
||||
throw new HttpCode({ statusCode: 405, message: "Method Not Allowed" });
|
||||
}
|
||||
|
||||
const bodyRaw = await getRawBody(req);
|
||||
const headers = req.headers;
|
||||
const bodyAsString = bodyRaw.toString();
|
||||
|
||||
const parseHeaders = webhookHeadersSchema.safeParse(headers);
|
||||
if (!parseHeaders.success) {
|
||||
console.error(parseHeaders.error);
|
||||
throw new HttpCode({ statusCode: 400, message: "Bad Request" });
|
||||
}
|
||||
|
||||
const { data: parsedHeaders } = parseHeaders;
|
||||
|
||||
const parse = eventSchema.safeParse(JSON.parse(bodyAsString));
|
||||
if (!parse.success) {
|
||||
console.error(parse.error);
|
||||
throw new HttpCode({ statusCode: 400, message: "Bad Request" });
|
||||
}
|
||||
|
||||
const { data: parsedPayload } = parse;
|
||||
|
||||
if (parsedPayload.metadata?.payer_data?.appId !== "cal.com") {
|
||||
throw new HttpCode({ statusCode: 204, message: "Payment not for cal.com" });
|
||||
}
|
||||
|
||||
const payment = await prisma.payment.findFirst({
|
||||
where: {
|
||||
uid: parsedPayload.metadata.payer_data.referenceId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
amount: true,
|
||||
bookingId: true,
|
||||
booking: {
|
||||
select: {
|
||||
user: {
|
||||
select: {
|
||||
credentials: {
|
||||
where: {
|
||||
type: "alby_payment",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!payment) throw new HttpCode({ statusCode: 204, message: "Payment not found" });
|
||||
const key = payment.booking?.user?.credentials?.[0].key;
|
||||
if (!key) throw new HttpCode({ statusCode: 204, message: "Credentials not found" });
|
||||
|
||||
const parseCredentials = albyCredentialKeysSchema.safeParse(key);
|
||||
if (!parseCredentials.success) {
|
||||
console.error(parseCredentials.error);
|
||||
throw new HttpCode({ statusCode: 500, message: "Credentials not valid" });
|
||||
}
|
||||
|
||||
const credentials = parseCredentials.data;
|
||||
|
||||
const albyInvoice = await parseInvoice(bodyAsString, parsedHeaders, credentials.webhook_endpoint_secret);
|
||||
if (!albyInvoice) throw new HttpCode({ statusCode: 204, message: "Invoice not found" });
|
||||
if (albyInvoice.amount !== payment.amount) {
|
||||
throw new HttpCode({ statusCode: 400, message: "invoice amount does not match payment amount" });
|
||||
}
|
||||
|
||||
return await handlePaymentSuccess(payment.id, payment.bookingId);
|
||||
} catch (_err) {
|
||||
const err = getErrorFromUnknown(_err);
|
||||
console.error(`Webhook Error: ${err.message}`);
|
||||
return res.status(err.statusCode || 500).send({
|
||||
message: err.message,
|
||||
stack: IS_PRODUCTION ? undefined : err.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const payerDataSchema = z
|
||||
.object({
|
||||
appId: z.string().optional(),
|
||||
referenceId: z.string().optional(),
|
||||
})
|
||||
.optional();
|
||||
|
||||
const metadataSchema = z
|
||||
.object({
|
||||
payer_data: payerDataSchema,
|
||||
})
|
||||
.optional();
|
||||
|
||||
const eventSchema = z.object({
|
||||
metadata: metadataSchema,
|
||||
});
|
||||
|
||||
const webhookHeadersSchema = z
|
||||
.object({
|
||||
"svix-id": z.string(),
|
||||
"svix-timestamp": z.string(),
|
||||
"svix-signature": z.string(),
|
||||
})
|
||||
.passthrough();
|
||||
|
|
|
@ -1 +1,200 @@
|
|||
export { default, config } from "@calcom/features/ee/payments/api/paypal-webhook";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import getRawBody from "raw-body";
|
||||
import * as z from "zod";
|
||||
|
||||
import { paypalCredentialKeysSchema } from "@calcom/app-store/paypal/lib";
|
||||
import Paypal from "@calcom/app-store/paypal/lib/Paypal";
|
||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import { HttpError as HttpCode } from "@calcom/lib/http-error";
|
||||
import { handlePaymentSuccess } from "@calcom/lib/payment/handlePaymentSuccess";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
},
|
||||
};
|
||||
|
||||
export async function handlePaypalPaymentSuccess(
|
||||
payload: z.infer<typeof eventSchema>,
|
||||
rawPayload: string,
|
||||
webhookHeaders: WebHookHeadersType
|
||||
) {
|
||||
const payment = await prisma.payment.findFirst({
|
||||
where: {
|
||||
externalId: payload?.resource?.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
bookingId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!payment?.bookingId) throw new HttpCode({ statusCode: 204, message: "Payment not found" });
|
||||
|
||||
const booking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
id: payment.bookingId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!booking) throw new HttpCode({ statusCode: 204, message: "No booking found" });
|
||||
// Probably booking it's already paid from /capture but we need to send confirmation email
|
||||
const foundCredentials = await findPaymentCredentials(booking.id);
|
||||
if (!foundCredentials) throw new HttpCode({ statusCode: 204, message: "No credentials found" });
|
||||
const { webhookId, ...credentials } = foundCredentials;
|
||||
|
||||
const paypalClient = new Paypal(credentials);
|
||||
await paypalClient.getAccessToken();
|
||||
await paypalClient.verifyWebhook({
|
||||
body: {
|
||||
auth_algo: webhookHeaders["paypal-auth-algo"],
|
||||
cert_url: webhookHeaders["paypal-cert-url"],
|
||||
transmission_id: webhookHeaders["paypal-transmission-id"],
|
||||
transmission_sig: webhookHeaders["paypal-transmission-sig"],
|
||||
transmission_time: webhookHeaders["paypal-transmission-time"],
|
||||
webhook_id: webhookId,
|
||||
webhook_event: rawPayload,
|
||||
},
|
||||
});
|
||||
|
||||
return await handlePaymentSuccess(payment.id, payment.bookingId);
|
||||
}
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
if (req.method !== "POST") {
|
||||
throw new HttpCode({ statusCode: 405, message: "Method Not Allowed" });
|
||||
}
|
||||
|
||||
const bodyRaw = await getRawBody(req);
|
||||
const headers = req.headers;
|
||||
const bodyAsString = bodyRaw.toString();
|
||||
|
||||
const parseHeaders = webhookHeadersSchema.safeParse(headers);
|
||||
if (!parseHeaders.success) {
|
||||
console.error(parseHeaders.error);
|
||||
throw new HttpCode({ statusCode: 400, message: "Bad Request" });
|
||||
}
|
||||
const parse = eventSchema.safeParse(JSON.parse(bodyAsString));
|
||||
if (!parse.success) {
|
||||
console.error(parse.error);
|
||||
throw new HttpCode({ statusCode: 400, message: "Bad Request" });
|
||||
}
|
||||
|
||||
const { data: parsedPayload } = parse;
|
||||
|
||||
if (parsedPayload.event_type === "CHECKOUT.ORDER.APPROVED") {
|
||||
return await handlePaypalPaymentSuccess(parsedPayload, bodyAsString, parseHeaders.data);
|
||||
}
|
||||
} catch (_err) {
|
||||
const err = getErrorFromUnknown(_err);
|
||||
console.error(`Webhook Error: ${err.message}`);
|
||||
res.status(200).send({
|
||||
message: err.message,
|
||||
stack: IS_PRODUCTION ? undefined : err.stack,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Return a response to acknowledge receipt of the event
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
const resourceSchema = z
|
||||
.object({
|
||||
create_time: z.string(),
|
||||
id: z.string(),
|
||||
payment_source: z.object({
|
||||
paypal: z.object({}).optional(),
|
||||
}),
|
||||
intent: z.string(),
|
||||
payer: z.object({
|
||||
email_address: z.string(),
|
||||
payer_id: z.string(),
|
||||
address: z.object({
|
||||
country_code: z.string(),
|
||||
}),
|
||||
}),
|
||||
status: z.string().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
const eventSchema = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
create_time: z.string(),
|
||||
resource_type: z.string(),
|
||||
event_type: z.string(),
|
||||
summary: z.string(),
|
||||
resource: resourceSchema,
|
||||
status: z.string().optional(),
|
||||
event_version: z.string(),
|
||||
resource_version: z.string(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
const webhookHeadersSchema = z
|
||||
.object({
|
||||
"paypal-auth-algo": z.string(),
|
||||
"paypal-cert-url": z.string(),
|
||||
"paypal-transmission-id": z.string(),
|
||||
"paypal-transmission-sig": z.string(),
|
||||
"paypal-transmission-time": z.string(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
type WebHookHeadersType = z.infer<typeof webhookHeadersSchema>;
|
||||
|
||||
export const findPaymentCredentials = async (
|
||||
bookingId: number
|
||||
): Promise<{ clientId: string; secretKey: string; webhookId: string }> => {
|
||||
try {
|
||||
// @TODO: what about team bookings with paypal?
|
||||
const userFromBooking = await prisma.booking.findFirst({
|
||||
where: {
|
||||
id: bookingId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
userId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!userFromBooking) throw new Error("No user found");
|
||||
|
||||
const credentials = await prisma.credential.findFirst({
|
||||
where: {
|
||||
appId: "paypal",
|
||||
userId: userFromBooking?.userId,
|
||||
},
|
||||
select: {
|
||||
key: true,
|
||||
},
|
||||
});
|
||||
if (!credentials) {
|
||||
throw new Error("No credentials found");
|
||||
}
|
||||
const parsedCredentials = paypalCredentialKeysSchema.safeParse(credentials?.key);
|
||||
if (!parsedCredentials.success) {
|
||||
throw new Error("Credentials malformed");
|
||||
}
|
||||
|
||||
return {
|
||||
clientId: parsedCredentials.data.client_id,
|
||||
secretKey: parsedCredentials.data.secret_key,
|
||||
webhookId: parsedCredentials.data.webhook_id,
|
||||
};
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return {
|
||||
clientId: "",
|
||||
secretKey: "",
|
||||
webhookId: "",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ import type { PaymentPageProps } from "@calcom/features/ee/payments/pages/paymen
|
|||
import { useBookingSuccessRedirect } from "@calcom/lib/bookingSuccessRedirect";
|
||||
import { useCopy } from "@calcom/lib/hooks/useCopy";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { trpc } from "@calcom/trpc";
|
||||
import { Button } from "@calcom/ui";
|
||||
import { showToast } from "@calcom/ui";
|
||||
import { ClipboardCheck, Clipboard } from "@calcom/ui/components/icon";
|
||||
|
@ -135,11 +135,11 @@ function PaymentChecker(props: PaymentCheckerProps) {
|
|||
if (props.booking.status === "ACCEPTED") {
|
||||
return;
|
||||
}
|
||||
const bookingsResult = await utils.viewer.bookings.find.fetch({
|
||||
const { booking: bookingResult } = await utils.viewer.bookings.find.fetch({
|
||||
bookingUid: props.booking.uid,
|
||||
});
|
||||
|
||||
if (bookingsResult.booking.paid) {
|
||||
if (bookingResult.paid) {
|
||||
showToast("Payment successful", "success");
|
||||
|
||||
const params: {
|
||||
|
|
|
@ -30,4 +30,7 @@ export const appDataSchema = eventTypeAppCardZod.merge(
|
|||
paymentOption: z.string().optional(),
|
||||
})
|
||||
);
|
||||
export const appKeysSchema = z.object({});
|
||||
export const appKeysSchema = z.object({
|
||||
client_id: z.string(),
|
||||
client_secret: z.string(),
|
||||
});
|
||||
|
|
|
@ -1,125 +0,0 @@
|
|||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import getRawBody from "raw-body";
|
||||
import * as z from "zod";
|
||||
|
||||
import { albyCredentialKeysSchema } from "@calcom/app-store/alby/lib";
|
||||
import parseInvoice from "@calcom/app-store/alby/lib/parseInvoice";
|
||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import { HttpError as HttpCode } from "@calcom/lib/http-error";
|
||||
import { handlePaymentSuccess } from "@calcom/lib/payment/handlePaymentSuccess";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
},
|
||||
};
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
if (req.method !== "POST") {
|
||||
throw new HttpCode({ statusCode: 405, message: "Method Not Allowed" });
|
||||
}
|
||||
|
||||
const bodyRaw = await getRawBody(req);
|
||||
const headers = req.headers;
|
||||
const bodyAsString = bodyRaw.toString();
|
||||
|
||||
const parseHeaders = webhookHeadersSchema.safeParse(headers);
|
||||
if (!parseHeaders.success) {
|
||||
console.error(parseHeaders.error);
|
||||
throw new HttpCode({ statusCode: 400, message: "Bad Request" });
|
||||
}
|
||||
|
||||
const { data: parsedHeaders } = parseHeaders;
|
||||
|
||||
const parse = eventSchema.safeParse(JSON.parse(bodyAsString));
|
||||
if (!parse.success) {
|
||||
console.error(parse.error);
|
||||
throw new HttpCode({ statusCode: 400, message: "Bad Request" });
|
||||
}
|
||||
|
||||
const { data: parsedPayload } = parse;
|
||||
|
||||
if (parsedPayload.metadata?.payer_data?.appId !== "cal.com") {
|
||||
throw new HttpCode({ statusCode: 204, message: "Payment not for cal.com" });
|
||||
}
|
||||
|
||||
const payment = await prisma.payment.findFirst({
|
||||
where: {
|
||||
uid: parsedPayload.metadata.payer_data.referenceId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
amount: true,
|
||||
bookingId: true,
|
||||
booking: {
|
||||
select: {
|
||||
user: {
|
||||
select: {
|
||||
credentials: {
|
||||
where: {
|
||||
type: "alby_payment",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!payment) throw new HttpCode({ statusCode: 204, message: "Payment not found" });
|
||||
const key = payment.booking?.user?.credentials?.[0].key;
|
||||
if (!key) throw new HttpCode({ statusCode: 204, message: "Credentials not found" });
|
||||
|
||||
const parseCredentials = albyCredentialKeysSchema.safeParse(key);
|
||||
if (!parseCredentials.success) {
|
||||
console.error(parseCredentials.error);
|
||||
throw new HttpCode({ statusCode: 500, message: "Credentials not valid" });
|
||||
}
|
||||
|
||||
const credentials = parseCredentials.data;
|
||||
|
||||
const albyInvoice = await parseInvoice(bodyAsString, parsedHeaders, credentials.webhook_endpoint_secret);
|
||||
if (!albyInvoice) throw new HttpCode({ statusCode: 204, message: "Invoice not found" });
|
||||
if (albyInvoice.amount !== payment.amount) {
|
||||
throw new HttpCode({ statusCode: 400, message: "invoice amount does not match payment amount" });
|
||||
}
|
||||
|
||||
return await handlePaymentSuccess(payment.id, payment.bookingId);
|
||||
} catch (_err) {
|
||||
const err = getErrorFromUnknown(_err);
|
||||
console.error(`Webhook Error: ${err.message}`);
|
||||
return res.status(err.statusCode || 500).send({
|
||||
message: err.message,
|
||||
stack: IS_PRODUCTION ? undefined : err.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const payerDataSchema = z
|
||||
.object({
|
||||
appId: z.string().optional(),
|
||||
referenceId: z.string().optional(),
|
||||
})
|
||||
.optional();
|
||||
|
||||
const metadataSchema = z
|
||||
.object({
|
||||
payer_data: payerDataSchema,
|
||||
})
|
||||
.optional();
|
||||
|
||||
const eventSchema = z.object({
|
||||
metadata: metadataSchema,
|
||||
});
|
||||
|
||||
const webhookHeadersSchema = z
|
||||
.object({
|
||||
"svix-id": z.string(),
|
||||
"svix-timestamp": z.string(),
|
||||
"svix-signature": z.string(),
|
||||
})
|
||||
.passthrough();
|
|
@ -1,200 +0,0 @@
|
|||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import getRawBody from "raw-body";
|
||||
import * as z from "zod";
|
||||
|
||||
import { paypalCredentialKeysSchema } from "@calcom/app-store/paypal/lib";
|
||||
import Paypal from "@calcom/app-store/paypal/lib/Paypal";
|
||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import { HttpError as HttpCode } from "@calcom/lib/http-error";
|
||||
import { handlePaymentSuccess } from "@calcom/lib/payment/handlePaymentSuccess";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
},
|
||||
};
|
||||
|
||||
export async function handlePaypalPaymentSuccess(
|
||||
payload: z.infer<typeof eventSchema>,
|
||||
rawPayload: string,
|
||||
webhookHeaders: WebHookHeadersType
|
||||
) {
|
||||
const payment = await prisma.payment.findFirst({
|
||||
where: {
|
||||
externalId: payload?.resource?.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
bookingId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!payment?.bookingId) throw new HttpCode({ statusCode: 204, message: "Payment not found" });
|
||||
|
||||
const booking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
id: payment.bookingId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!booking) throw new HttpCode({ statusCode: 204, message: "No booking found" });
|
||||
// Probably booking it's already paid from /capture but we need to send confirmation email
|
||||
const foundCredentials = await findPaymentCredentials(booking.id);
|
||||
if (!foundCredentials) throw new HttpCode({ statusCode: 204, message: "No credentials found" });
|
||||
const { webhookId, ...credentials } = foundCredentials;
|
||||
|
||||
const paypalClient = new Paypal(credentials);
|
||||
await paypalClient.getAccessToken();
|
||||
await paypalClient.verifyWebhook({
|
||||
body: {
|
||||
auth_algo: webhookHeaders["paypal-auth-algo"],
|
||||
cert_url: webhookHeaders["paypal-cert-url"],
|
||||
transmission_id: webhookHeaders["paypal-transmission-id"],
|
||||
transmission_sig: webhookHeaders["paypal-transmission-sig"],
|
||||
transmission_time: webhookHeaders["paypal-transmission-time"],
|
||||
webhook_id: webhookId,
|
||||
webhook_event: rawPayload,
|
||||
},
|
||||
});
|
||||
|
||||
return await handlePaymentSuccess(payment.id, payment.bookingId);
|
||||
}
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
if (req.method !== "POST") {
|
||||
throw new HttpCode({ statusCode: 405, message: "Method Not Allowed" });
|
||||
}
|
||||
|
||||
const bodyRaw = await getRawBody(req);
|
||||
const headers = req.headers;
|
||||
const bodyAsString = bodyRaw.toString();
|
||||
|
||||
const parseHeaders = webhookHeadersSchema.safeParse(headers);
|
||||
if (!parseHeaders.success) {
|
||||
console.error(parseHeaders.error);
|
||||
throw new HttpCode({ statusCode: 400, message: "Bad Request" });
|
||||
}
|
||||
const parse = eventSchema.safeParse(JSON.parse(bodyAsString));
|
||||
if (!parse.success) {
|
||||
console.error(parse.error);
|
||||
throw new HttpCode({ statusCode: 400, message: "Bad Request" });
|
||||
}
|
||||
|
||||
const { data: parsedPayload } = parse;
|
||||
|
||||
if (parsedPayload.event_type === "CHECKOUT.ORDER.APPROVED") {
|
||||
return await handlePaypalPaymentSuccess(parsedPayload, bodyAsString, parseHeaders.data);
|
||||
}
|
||||
} catch (_err) {
|
||||
const err = getErrorFromUnknown(_err);
|
||||
console.error(`Webhook Error: ${err.message}`);
|
||||
res.status(200).send({
|
||||
message: err.message,
|
||||
stack: IS_PRODUCTION ? undefined : err.stack,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Return a response to acknowledge receipt of the event
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
const resourceSchema = z
|
||||
.object({
|
||||
create_time: z.string(),
|
||||
id: z.string(),
|
||||
payment_source: z.object({
|
||||
paypal: z.object({}).optional(),
|
||||
}),
|
||||
intent: z.string(),
|
||||
payer: z.object({
|
||||
email_address: z.string(),
|
||||
payer_id: z.string(),
|
||||
address: z.object({
|
||||
country_code: z.string(),
|
||||
}),
|
||||
}),
|
||||
status: z.string().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
const eventSchema = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
create_time: z.string(),
|
||||
resource_type: z.string(),
|
||||
event_type: z.string(),
|
||||
summary: z.string(),
|
||||
resource: resourceSchema,
|
||||
status: z.string().optional(),
|
||||
event_version: z.string(),
|
||||
resource_version: z.string(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
const webhookHeadersSchema = z
|
||||
.object({
|
||||
"paypal-auth-algo": z.string(),
|
||||
"paypal-cert-url": z.string(),
|
||||
"paypal-transmission-id": z.string(),
|
||||
"paypal-transmission-sig": z.string(),
|
||||
"paypal-transmission-time": z.string(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
type WebHookHeadersType = z.infer<typeof webhookHeadersSchema>;
|
||||
|
||||
export const findPaymentCredentials = async (
|
||||
bookingId: number
|
||||
): Promise<{ clientId: string; secretKey: string; webhookId: string }> => {
|
||||
try {
|
||||
// @TODO: what about team bookings with paypal?
|
||||
const userFromBooking = await prisma.booking.findFirst({
|
||||
where: {
|
||||
id: bookingId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
userId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!userFromBooking) throw new Error("No user found");
|
||||
|
||||
const credentials = await prisma.credential.findFirst({
|
||||
where: {
|
||||
appId: "paypal",
|
||||
userId: userFromBooking?.userId,
|
||||
},
|
||||
select: {
|
||||
key: true,
|
||||
},
|
||||
});
|
||||
if (!credentials) {
|
||||
throw new Error("No credentials found");
|
||||
}
|
||||
const parsedCredentials = paypalCredentialKeysSchema.safeParse(credentials?.key);
|
||||
if (!parsedCredentials.success) {
|
||||
throw new Error("Credentials malformed");
|
||||
}
|
||||
|
||||
return {
|
||||
clientId: parsedCredentials.data.client_id,
|
||||
secretKey: parsedCredentials.data.secret_key,
|
||||
webhookId: parsedCredentials.data.webhook_id,
|
||||
};
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return {
|
||||
clientId: "",
|
||||
secretKey: "",
|
||||
webhookId: "",
|
||||
};
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue