Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zomars/cal 748 paid bookings are failing #1335

Merged
merged 30 commits into from
Dec 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
94e0a9d
E2E video adjustments
zomars Dec 15, 2021
89765ba
Adds test to add Stripe integration
zomars Dec 15, 2021
8bc072a
Type fix
zomars Dec 15, 2021
62f8832
WIP: Payment troubleshooting
zomars Dec 15, 2021
30026be
Paid bookings shouldn't be confirmed by default
zomars Dec 15, 2021
25aaa5b
Runs stripe test only if installed
zomars Dec 16, 2021
1819534
BookingListItem Adjustments
zomars Dec 16, 2021
49621a0
Pending paid bookings should be unconfirmed
zomars Dec 16, 2021
e7d4140
Attempt to fix paid bookings
zomars Dec 16, 2021
def87e5
Type fixes
zomars Dec 16, 2021
23af4e4
Type fixes
zomars Dec 16, 2021
888c555
Merge branch 'main' into zomars/cal-748-paid-bookings-are-failing
zomars Dec 16, 2021
2cc7f0a
Tests fixes
zomars Dec 16, 2021
354b6e8
Adds paid booking to seeder
zomars Dec 16, 2021
385de7e
Moves stripe tests to own file
zomars Dec 16, 2021
b432c4f
Matches app locale to Stripe's
zomars Dec 16, 2021
b1fc6c0
Fixes minimun price for testing
zomars Dec 16, 2021
b8cebe1
Stripe test fixes
zomars Dec 16, 2021
747c6d1
Fixes stripe frame test
zomars Dec 16, 2021
9bb04fc
Added some Stripe TODOs
zomars Dec 16, 2021
799d042
Adds Stripe CI env vars
zomars Dec 16, 2021
a74871f
Merge branch 'main' into zomars/cal-748-paid-bookings-are-failing
kodiakhq[bot] Dec 17, 2021
2431d81
Merge branch 'main' into zomars/cal-748-paid-bookings-are-failing
kodiakhq[bot] Dec 17, 2021
720bb6b
Merge branch 'main' into zomars/cal-748-paid-bookings-are-failing
kodiakhq[bot] Dec 17, 2021
1a2e77b
Merge branch 'main' into zomars/cal-748-paid-bookings-are-failing
kodiakhq[bot] Dec 17, 2021
73a2cf5
Ignores all .env files
zomars Dec 17, 2021
a28b73c
Uses playwright server
zomars Dec 17, 2021
951d2e1
Revert "removed empty language files, triyng to debug crowdin (#1341)"
zomars Dec 17, 2021
db5a61b
Merge branch 'main' into zomars/cal-748-paid-bookings-are-failing
kodiakhq[bot] Dec 17, 2021
23e3db4
Sets e2e reporter as list
zomars Dec 17, 2021
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
10 changes: 7 additions & 3 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ jobs:
GOOGLE_API_CREDENTIALS: "{}"
# GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
# CRON_API_KEY: xxx
# CALENDSO_ENCRYPTION_KEY: xxx
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${{ secrets.CI_NEXT_PUBLIC_STRIPE_PUBLIC_KEY }}
STRIPE_PRIVATE_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
STRIPE_CLIENT_ID: ${{ secrets.CI_STRIPE_CLIENT_ID }}
STRIPE_WEBHOOK_SECRET: ${{ secrets.CI_STRIPE_WEBHOOK_SECRET }}
PAYMENT_FEE_PERCENTAGE: 0.005
PAYMENT_FEE_FIXED: 10
Comment on lines +15 to +21
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already added to GitHub

# NEXTAUTH_URL: xxx
# EMAIL_FROM: xxx
# EMAIL_SERVER_HOST: xxx
Expand Down Expand Up @@ -61,8 +67,6 @@ jobs:
- run: yarn db-seed
- run: yarn test
- run: yarn build
- run: yarn start &
- run: npx wait-port 3000 --timeout 10000

- name: Cache playwright binaries
uses: actions/cache@v2
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ yarn-error.log*
.env.development.local
.env.test.local
.env.production.local
.env.*

# vercel
.vercel
Expand Down
31 changes: 19 additions & 12 deletions components/booking/BookingListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { inferQueryOutput, trpc } from "@lib/trpc";

import TableActions, { ActionType } from "@components/ui/TableActions";

type BookingItem = inferQueryOutput<"viewer.bookings">[number];
type BookingItem = inferQueryOutput<"viewer.bookings">["bookings"][number];

function BookingListItem(booking: BookingItem) {
const { t, i18n } = useLocale();
Expand Down Expand Up @@ -73,34 +73,32 @@ function BookingListItem(booking: BookingItem) {
const startTime = dayjs(booking.startTime).format(isUpcoming ? "ddd, D MMM" : "D MMMM YYYY");

return (
<tr>
<tr className="flex">
<td className="hidden px-6 py-4 align-top sm:table-cell whitespace-nowrap">
<div className="text-sm leading-6 text-gray-900">{startTime}</div>
<div className="text-sm text-gray-500">
{dayjs(booking.startTime).format("HH:mm")} - {dayjs(booking.endTime).format("HH:mm")}
</div>
</td>
<td className={"px-6 py-4" + (booking.rejected ? " line-through" : "")}>
<td className={"px-6 py-4 flex-1" + (booking.rejected ? " line-through" : "")}>
<div className="sm:hidden">
{!booking.confirmed && !booking.rejected && (
<span className="mb-2 inline-flex items-center px-1.5 py-0.5 rounded-sm text-xs font-medium bg-yellow-100 text-yellow-800">
{t("unconfirmed")}
</span>
)}
{!booking.confirmed && !booking.rejected && <Tag className="mb-2 mr-2">{t("unconfirmed")}</Tag>}
{!!booking?.eventType?.price && !booking.paid && <Tag className="mb-2 mr-2">Pending payment</Tag>}
<div className="text-sm font-medium text-gray-900">
{startTime}:{" "}
<small className="text-sm text-gray-500">
{dayjs(booking.startTime).format("HH:mm")} - {dayjs(booking.endTime).format("HH:mm")}
</small>
</div>
</div>
<div className="text-sm font-medium leading-6 truncate text-neutral-900 max-w-52 md:max-w-96">
<div className="text-sm font-medium leading-6 truncate text-neutral-900 max-w-52 md:max-w-max">
{booking.eventType?.team && <strong>{booking.eventType.team.name}: </strong>}
{booking.title}
{!!booking?.eventType?.price && !booking.paid && (
<Tag className="hidden ml-2 sm:inline-flex">Pending payment</Tag>
)}
{!booking.confirmed && !booking.rejected && (
<span className="ml-2 hidden sm:inline-flex items-center px-1.5 py-0.5 rounded-sm text-xs font-medium bg-yellow-100 text-yellow-800">
{t("unconfirmed")}
</span>
<Tag className="hidden ml-2 sm:inline-flex">{t("unconfirmed")}</Tag>
)}
</div>
{booking.description && (
Expand Down Expand Up @@ -130,4 +128,13 @@ function BookingListItem(booking: BookingItem) {
);
}

const Tag = ({ children, className = "" }: React.PropsWithChildren<{ className?: string }>) => {
return (
<span
className={`inline-flex items-center px-1.5 py-0.5 rounded-sm text-xs font-medium bg-yellow-100 text-yellow-800 ${className}`}>
{children}
</span>
);
};

export default BookingListItem;
6 changes: 3 additions & 3 deletions components/integrations/CalendarListContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ function ConnectedCalendarsList(props: Props) {
<DisconnectIntegration
id={item.credentialId}
render={(btnProps) => (
<Button {...btnProps} color="warn">
<Button {...btnProps} color="warn" data-testid="integration-connection-button">
{t("disconnect")}
</Button>
)}
Expand Down Expand Up @@ -143,7 +143,7 @@ function ConnectedCalendarsList(props: Props) {
<DisconnectIntegration
id={item.credentialId}
render={(btnProps) => (
<Button {...btnProps} color="warn">
<Button {...btnProps} color="warn" data-testid="integration-connection-button">
Disconnect
</Button>
)}
Expand Down Expand Up @@ -248,7 +248,7 @@ function CalendarList(props: Props) {
<ConnectIntegration
type={item.type}
render={(btnProps) => (
<Button color="secondary" {...btnProps}>
<Button color="secondary" {...btnProps} data-testid="integration-connection-button">
{t("connect")}
</Button>
)}
Expand Down
24 changes: 14 additions & 10 deletions ee/components/stripe/Payment.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { CardElement, useStripe, useElements } from "@stripe/react-stripe-js";
import { StripeCardElementChangeEvent } from "@stripe/stripe-js";
import { CardElement, useElements, useStripe } from "@stripe/react-stripe-js";
import stripejs, { StripeCardElementChangeEvent, StripeElementLocale } from "@stripe/stripe-js";
import { useRouter } from "next/router";
import { stringify } from "querystring";
import React, { useState } from "react";
import { SyntheticEvent } from "react";
import React, { SyntheticEvent, useEffect, useState } from "react";

import { PaymentData } from "@ee/lib/stripe/server";

Expand All @@ -12,7 +11,7 @@ import { useLocale } from "@lib/hooks/useLocale";

import Button from "@components/ui/Button";

const CARD_OPTIONS = {
const CARD_OPTIONS: stripejs.StripeCardElementOptions = {
iconStyle: "solid" as const,
classes: {
base: "block p-2 w-full border-solid border-2 border-gray-300 rounded-md shadow-sm dark:bg-black dark:text-white dark:border-black focus-within:ring-black focus-within:border-black sm:text-sm",
Expand All @@ -29,7 +28,7 @@ const CARD_OPTIONS = {
},
},
},
};
} as const;

type Props = {
payment: {
Expand All @@ -47,18 +46,23 @@ type States =
| { status: "ok" };

export default function PaymentComponent(props: Props) {
const { t } = useLocale();
const { t, i18n } = useLocale();
const router = useRouter();
const { name, date } = router.query;
const [state, setState] = useState<States>({ status: "idle" });
const stripe = useStripe();
const elements = useElements();

const { isDarkMode } = useDarkMode();

useEffect(() => {
elements?.update({ locale: i18n.language as StripeElementLocale });
}, [elements, i18n.language]);

if (isDarkMode) {
CARD_OPTIONS.style.base.color = "#fff";
CARD_OPTIONS.style.base.iconColor = "#fff";
CARD_OPTIONS.style.base["::placeholder"].color = "#fff";
CARD_OPTIONS.style!.base!.color = "#fff";
CARD_OPTIONS.style!.base!.iconColor = "#fff";
CARD_OPTIONS.style!.base!["::placeholder"]!.color = "#fff";
}

const handleChange = async (event: StripeCardElementChangeEvent) => {
Expand Down
6 changes: 5 additions & 1 deletion ee/lib/stripe/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@ export async function handlePayment(
data: {
type: PaymentType.STRIPE,
uid: uuidv4(),
bookingId: booking.id,
booking: {
connect: {
id: booking.id,
},
},
amount: selectedEventType.price,
fee: paymentFee,
currency: selectedEventType.currency,
Expand Down
18 changes: 12 additions & 6 deletions ee/pages/api/integrations/stripepayment/webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import stripe from "@ee/lib/stripe/server";

import { CalendarEvent } from "@lib/calendarClient";
import { IS_PRODUCTION } from "@lib/config/constants";
import { HttpError } from "@lib/core/http/error";
import { HttpError as HttpCode } from "@lib/core/http/error";
import { getErrorFromUnknown } from "@lib/errors";
import EventManager from "@lib/events/EventManager";
import prisma from "@lib/prisma";
Expand All @@ -31,6 +31,7 @@ async function handlePaymentSuccess(event: Stripe.Event) {
booking: {
update: {
paid: true,
confirmed: true,
},
},
},
Expand Down Expand Up @@ -97,7 +98,7 @@ async function handlePaymentSuccess(event: Stripe.Event) {

await prisma.booking.update({
where: {
id: payment.bookingId,
id: booking.id,
},
data: {
references: {
Expand All @@ -106,6 +107,11 @@ async function handlePaymentSuccess(event: Stripe.Event) {
},
});
}

throw new HttpCode({
statusCode: 200,
message: `Booking with id '${booking.id}' was paid and confirmed.`,
});
}

type WebhookHandler = (event: Stripe.Event) => Promise<void>;
Expand All @@ -117,15 +123,15 @@ const webhookHandlers: Record<string, WebhookHandler | undefined> = {
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
if (req.method !== "POST") {
throw new HttpError({ statusCode: 405, message: "Method Not Allowed" });
throw new HttpCode({ statusCode: 405, message: "Method Not Allowed" });
}
const sig = req.headers["stripe-signature"];
if (!sig) {
throw new HttpError({ statusCode: 400, message: "Missing stripe-signature" });
throw new HttpCode({ statusCode: 400, message: "Missing stripe-signature" });
}

if (!process.env.STRIPE_WEBHOOK_SECRET) {
throw new HttpError({ statusCode: 500, message: "Missing process.env.STRIPE_WEBHOOK_SECRET" });
throw new HttpCode({ statusCode: 500, message: "Missing process.env.STRIPE_WEBHOOK_SECRET" });
}
const requestBuffer = await buffer(req);
const payload = requestBuffer.toString();
Expand All @@ -137,7 +143,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await handler(event);
} else {
/** Not really an error, just letting Stripe know that the webhook was received but unhandled */
throw new HttpError({
throw new HttpCode({
statusCode: 202,
message: `Unhandled Stripe Webhook event type ${event.type}`,
});
Expand Down
9 changes: 8 additions & 1 deletion lib/integrations/getIntegrations.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Prisma } from "@prisma/client";
import _ from "lodash";

import { validJson } from "@lib/jsonUtils";
/**
* We can't use aliases in playwright tests (yet)
* https://github.com/microsoft/playwright/issues/7121
*/
import { validJson } from "../../lib/jsonUtils";

const credentialData = Prisma.validator<Prisma.CredentialArgs>()({
select: { id: true, type: true },
Expand Down Expand Up @@ -115,5 +119,8 @@ export function hasIntegration(integrations: IntegrationMeta, type: string): boo
(i) => i.type === type && !!i.installed && (type === "daily_video" || i.credentials.length > 0)
);
}
export function hasIntegrationInstalled(type: Integration["type"]): boolean {
return ALL_INTEGRATIONS.some((i) => i.type === type && !!i.installed);
}

export default getIntegrations;
19 changes: 13 additions & 6 deletions pages/api/availability/eventtype.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,15 @@ import { getSession } from "@lib/auth";
import prisma from "@lib/prisma";
import { WorkingHours } from "@lib/types/schedule";

function handlePeriodType(periodType: string): PeriodType {
return PeriodType[periodType.toUpperCase()];
function isPeriodType(keyInput: string): keyInput is PeriodType {
return Object.keys(PeriodType).includes(keyInput);
}

function handlePeriodType(periodType: string): PeriodType | undefined {
if (typeof periodType !== "string") return undefined;
const passedPeriodType = periodType.toUpperCase();
if (!isPeriodType(passedPeriodType)) return undefined;
return PeriodType[passedPeriodType];
}

function handleCustomInputs(customInputs: EventTypeCustomInput[], eventTypeId: number) {
Expand Down Expand Up @@ -104,7 +111,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
}

if (req.method == "PATCH" || req.method == "POST") {
if (req.method === "PATCH" || req.method === "POST") {
const data: Prisma.EventTypeCreateInput | Prisma.EventTypeUpdateInput = {
title: req.body.title,
slug: req.body.slug.trim(),
Expand All @@ -116,7 +123,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
locations: req.body.locations,
eventName: req.body.eventName,
customInputs: handleCustomInputs(req.body.customInputs as EventTypeCustomInput[], req.body.id),
periodType: req.body.periodType ? handlePeriodType(req.body.periodType) : undefined,
periodType: handlePeriodType(req.body.periodType),
periodDays: req.body.periodDays,
periodStartDate: req.body.periodStartDate,
periodEndDate: req.body.periodEndDate,
Expand Down Expand Up @@ -179,8 +186,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}

const availabilityToCreate = openingHours.map((openingHour) => ({
startTime: openingHour.startTime,
endTime: openingHour.endTime,
startTime: new Date(openingHour.startTime),
endTime: new Date(openingHour.endTime),
days: openingHour.days,
}));

Expand Down
3 changes: 2 additions & 1 deletion pages/api/book/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}

const eventType = await prisma.eventType.findUnique({
rejectOnNotFound: true,
where: {
id: eventTypeId,
},
Expand Down Expand Up @@ -331,7 +332,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
startTime: dayjs(evt.startTime).toDate(),
endTime: dayjs(evt.endTime).toDate(),
description: evt.description,
confirmed: !eventType?.requiresConfirmation || !!rescheduleUid,
confirmed: (!eventType.requiresConfirmation && !eventType.price) || !!rescheduleUid,
location: evt.location,
eventType: {
connect: {
Expand Down
4 changes: 3 additions & 1 deletion pages/bookings/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { NextPageContext } from "next";

import { getSession } from "@lib/auth";

function RedirectPage() {
return null;
}

export async function getServerSideProps(context) {
export async function getServerSideProps(context: NextPageContext) {
const session = await getSession(context);
if (!session?.user?.id) {
return { redirect: { permanent: false, destination: "/auth/login" } };
Expand Down
4 changes: 2 additions & 2 deletions pages/integrations/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ function ConnectOrDisconnectIntegrationButton(props: {
<DisconnectIntegration
id={credentialId}
render={(btnProps) => (
<Button {...btnProps} color="warn">
<Button {...btnProps} color="warn" data-testid="integration-connection-button">
{t("disconnect")}
</Button>
)}
Expand All @@ -488,7 +488,7 @@ function ConnectOrDisconnectIntegrationButton(props: {
<ConnectIntegration
type={props.type}
render={(btnProps) => (
<Button color="secondary" {...btnProps}>
<Button color="secondary" {...btnProps} data-testid="integration-connection-button">
{t("connect")}
</Button>
)}
Expand Down
Loading