calcom/ee/lib/stripe/server.ts
Alex Johansson 850497ea80
add select primary calendar (#1133)
* add primary

* fix

* refactor eventmanager to take `CalendarDestination`

* `DestinationCalendar`

* fix

* wip

* wip

* Minor fixes (#1156)

* Followup for #1242

* Updates schema

* Renames fields to destinationCalendar

* Migration fixes

* Updates user destination calendar

* Abstracts convertDate to BaseCalendarApiAdapter

* Type fixes

* Uses abstracted convertDate method

* Abstracts getDuration and getAttendees

* Fixes circular dependecy issue

* Adds notEmpty util

* Reverts empty location string

* Fixes property name

* Removes deprecated code

* WIP

* AppleCal is basically CalDav

* Fixes missing destinationCalendar

* Type fixes

* Select primary calendar on Office and gCal

* Adds pretty basic instructions for destination calendar

* Cleanup

* Type fix

* Test fixes

* Updates test snapshot

* Local test fixes

* Type fixes

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2021-12-09 15:51:37 +00:00

167 lines
4.3 KiB
TypeScript

import { PaymentType, Prisma } from "@prisma/client";
import Stripe from "stripe";
import { v4 as uuidv4 } from "uuid";
import { CalendarEvent } from "@lib/calendarClient";
import { sendAwaitingPaymentEmail, sendOrganizerPaymentRefundFailedEmail } from "@lib/emails/email-manager";
import { getErrorFromUnknown } from "@lib/errors";
import prisma from "@lib/prisma";
import { createPaymentLink } from "./client";
export type PaymentInfo = {
link?: string | null;
reason?: string | null;
id?: string | null;
};
export type PaymentData = Stripe.Response<Stripe.PaymentIntent> & {
stripe_publishable_key: string;
stripeAccount: string;
};
export type StripeData = Stripe.OAuthToken & {
default_currency: string;
};
const stripePrivateKey = process.env.STRIPE_PRIVATE_KEY!;
const paymentFeePercentage = process.env.PAYMENT_FEE_PERCENTAGE!;
const paymentFeeFixed = process.env.PAYMENT_FEE_FIXED!;
const stripe = new Stripe(stripePrivateKey, {
apiVersion: "2020-08-27",
});
export async function handlePayment(
evt: CalendarEvent,
selectedEventType: {
price: number;
currency: string;
},
stripeCredential: { key: Prisma.JsonValue },
booking: {
user: { email: string | null; name: string | null; timeZone: string } | null;
id: number;
startTime: { toISOString: () => string };
uid: string;
}
) {
const paymentFee = Math.round(
selectedEventType.price * parseFloat(`${paymentFeePercentage}`) + parseInt(`${paymentFeeFixed}`)
);
const { stripe_user_id, stripe_publishable_key } = stripeCredential.key as Stripe.OAuthToken;
const params: Stripe.PaymentIntentCreateParams = {
amount: selectedEventType.price,
currency: selectedEventType.currency,
payment_method_types: ["card"],
application_fee_amount: paymentFee,
};
const paymentIntent = await stripe.paymentIntents.create(params, { stripeAccount: stripe_user_id });
const payment = await prisma.payment.create({
data: {
type: PaymentType.STRIPE,
uid: uuidv4(),
bookingId: booking.id,
amount: selectedEventType.price,
fee: paymentFee,
currency: selectedEventType.currency,
success: false,
refunded: false,
data: Object.assign({}, paymentIntent, {
stripe_publishable_key,
stripeAccount: stripe_user_id,
}) as PaymentData as unknown as Prisma.JsonValue,
externalId: paymentIntent.id,
},
});
await sendAwaitingPaymentEmail({
...evt,
paymentInfo: {
link: createPaymentLink({
paymentUid: payment.uid,
name: booking.user?.name,
date: booking.startTime.toISOString(),
}),
},
});
return payment;
}
export async function refund(
booking: {
id: number;
uid: string;
startTime: Date;
payment: {
id: number;
success: boolean;
refunded: boolean;
externalId: string;
data: Prisma.JsonValue;
type: PaymentType;
}[];
},
calEvent: CalendarEvent
) {
try {
const payment = booking.payment.find((e) => e.success && !e.refunded);
if (!payment) return;
if (payment.type !== PaymentType.STRIPE) {
await handleRefundError({
event: calEvent,
reason: "cannot refund non Stripe payment",
paymentId: "unknown",
});
return;
}
const refund = await stripe.refunds.create(
{
payment_intent: payment.externalId,
},
{ stripeAccount: (payment.data as unknown as PaymentData)["stripeAccount"] }
);
if (!refund || refund.status === "failed") {
await handleRefundError({
event: calEvent,
reason: refund?.failure_reason || "unknown",
paymentId: payment.externalId,
});
return;
}
await prisma.payment.update({
where: {
id: payment.id,
},
data: {
refunded: true,
},
});
} catch (e) {
const err = getErrorFromUnknown(e);
console.error(err, "Refund failed");
await handleRefundError({
event: calEvent,
reason: err.message || "unknown",
paymentId: "unknown",
});
}
}
async function handleRefundError(opts: { event: CalendarEvent; reason: string; paymentId: string }) {
console.error(`refund failed: ${opts.reason} for booking '${opts.event.uid}'`);
await sendOrganizerPaymentRefundFailedEmail({
...opts.event,
paymentInfo: { reason: opts.reason, id: opts.paymentId },
});
}
export default stripe;