Skip to content

Commit 6c918f2

Browse files
committed
Setup Stripe webhooks
1 parent 728ec84 commit 6c918f2

File tree

6 files changed

+76
-58
lines changed

6 files changed

+76
-58
lines changed

backend/typescript/rest/camperRoutes.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ const camperRouter: Router = Router();
2323

2424
const camperService: ICamperService = new CamperService();
2525

26+
// TODO: secure stripe keys
27+
const STRIPE_ENDPOINT_KEY = process.env.STRIPE_ENDPOINT_SECRET || "";
28+
2629
// ROLES: Leaving unprotected as the registration flow probs needs this endpoint to register @dhruv
2730
/* Create a camper */
2831
camperRouter.post("/register", createCampersDtoValidator, async (req, res) => {
@@ -115,16 +118,25 @@ camperRouter.get("/:chargeId/:sessionId", async (req, res) => {
115118
});
116119

117120
// ROLES: unprotected
118-
/* On successful payment, mark camper as paid */
119-
camperRouter.post("/confirm-payment/:chargeId", async (req, res) => {
120-
const { chargeId } = req.params;
121+
/* Initiated by Stripe webhook. On successful payment, mark camper as paid. */
122+
camperRouter.post("/confirm-payment", async (req, res) => {
121123
try {
122-
const camper = await camperService.confirmCamperPayment(
123-
(chargeId as unknown) as string,
124-
);
125-
res.status(200).json(camper);
124+
const { body } = req;
125+
126+
if (body.type === "checkout.session.completed") {
127+
const chargeId = body.data.object.id;
128+
129+
if (body.data.object.payment_status === "paid") {
130+
await camperService.confirmCamperPayment(
131+
(chargeId as unknown) as string,
132+
);
133+
}
134+
}
135+
136+
res.status(200).json();
126137
} catch (error: unknown) {
127-
res.status(500).json({ error: getErrorMessage(error) });
138+
// Stripe requires that 200 response sent
139+
res.status(200).json({ error: getErrorMessage(error) });
128140
}
129141
});
130142

backend/typescript/services/implementations/camperService.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import EmailService from "./emailService";
2424
import {
2525
createStripeCheckoutSession,
2626
createStripeLineItems,
27-
retrieveStripeCheckoutSession,
2827
} from "../../utilities/stripeUtils";
2928
import { getEDUnits, getLPUnits } from "../../utilities/CampUtils";
3029

@@ -246,13 +245,6 @@ class CamperService implements ICamperService {
246245
}),
247246
);
248247

249-
// Email the parent about all the campers and sessions they have signed up for
250-
await emailService.sendParentConfirmationEmail(
251-
camp,
252-
registeredCampers,
253-
sessionsToRegister,
254-
);
255-
256248
// Send admin an email for all the sessions that are now full
257249
// Note: At this point, the sessionsToRegister's campers field has been updated with the registered campers
258250
const fullSessions = sessionsToRegister.filter(
@@ -495,17 +487,6 @@ class CamperService implements ICamperService {
495487
session.startTransaction();
496488

497489
try {
498-
const checkoutSession = await retrieveStripeCheckoutSession(chargeId);
499-
if (!checkoutSession) {
500-
throw new Error(`Could not find checkout session with id ${chargeId}`);
501-
}
502-
503-
if (checkoutSession.payment_status !== "paid") {
504-
throw new Error(
505-
`Checkout session status is ${checkoutSession.payment_status}, expected status to be "paid"`,
506-
);
507-
}
508-
509490
const campers = await MgCamper.find({ chargeId });
510491
if (!campers || campers.length === 0) {
511492
throw new Error(
@@ -518,6 +499,36 @@ class CamperService implements ICamperService {
518499
{ $set: { hasPaid: true } },
519500
{ session, runValidators: true },
520501
);
502+
503+
const campSessionIds = Array.from(
504+
new Set(campers.map((camper: Camper) => camper.campSession)),
505+
);
506+
507+
const campSessions: Array<CampSession> = await MgCampSession.find({
508+
_id: { $in: campSessionIds },
509+
});
510+
511+
if (!campSessions || campSessions.length === 0) {
512+
throw new Error(
513+
`Could not find camp session(s) associated with objects in checkout session with id ${chargeId}`,
514+
);
515+
}
516+
517+
const camp: Camp | null = await MgCamp.findById(campSessions[0].camp);
518+
519+
if (!camp) {
520+
throw new Error(
521+
`Could not find camp associated with objects in checkout session with id ${chargeId}`,
522+
);
523+
}
524+
525+
// Email the parent about all the campers and sessions they have signed up for
526+
await emailService.sendParentConfirmationEmail(
527+
camp,
528+
campers,
529+
campSessions,
530+
);
531+
521532
await session.commitTransaction();
522533
} catch (error: unknown) {
523534
await session.abortTransaction();

backend/typescript/utilities/stripeUtils.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,3 @@ export async function createStripeCheckoutSession(
146146

147147
return checkoutSession;
148148
}
149-
150-
export async function retrieveStripeCheckoutSession(
151-
chargeId: string,
152-
): Promise<Stripe.Response<Stripe.Checkout.Session>> {
153-
return stripe.checkout.sessions.retrieve(chargeId);
154-
}

frontend/src/components/pages/RegistrantExperience/RegistrationResult/index.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect } from "react";
1+
import React from "react";
22
import { Text, Flex, VStack, HStack } from "@chakra-ui/react";
33
import { CartItem, EdlpChoice } from "../../../../types/RegistrationTypes";
44

@@ -16,7 +16,6 @@ import {
1616
} from "./textStyles";
1717
import { CampResponse, CampSession } from "../../../../types/CampsTypes";
1818
import { RegistrantExperienceCamper } from "../../../../types/CamperTypes";
19-
import CamperAPIClient from "../../../../APIClients/CamperAPIClient";
2019

2120
const NoSessionDataFound = (): React.ReactElement => {
2221
return (
@@ -73,12 +72,6 @@ const RegistrationResult = ({
7372
edlpChoices,
7473
chargeId,
7574
}: RegistrationResultProps): React.ReactElement => {
76-
useEffect(() => {
77-
if (chargeId) {
78-
CamperAPIClient.confirmPayment(chargeId);
79-
}
80-
}, [chargeId]);
81-
8275
return (
8376
<Flex
8477
direction="column"

frontend/src/components/pages/RegistrantExperience/SessionSelection/CampSessionBoard.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import React from "react";
22
import { Box, Text, Grid, GridItem, VStack } from "@chakra-ui/react";
3-
import {
4-
CampResponse,
5-
CampSessionResponse,
6-
} from "../../../../types/CampsTypes";
3+
import { CampResponse } from "../../../../types/CampsTypes";
74
import {
85
SessionCardState,
96
SessionSelectionState,
@@ -53,7 +50,7 @@ const CampSessionBoard = ({
5350
fee={camp.fee}
5451
startTime={camp.startTime}
5552
endTime={camp.endTime}
56-
campSession={campSession as CampSessionResponse}
53+
campSession={campSession}
5754
handleClick={handleSessionClick}
5855
state={getCardState(
5956
campSession.id,
@@ -73,7 +70,7 @@ const CampSessionBoard = ({
7370
fee={camp.fee}
7471
startTime={camp.startTime}
7572
endTime={camp.endTime}
76-
campSession={campSession as CampSessionResponse}
73+
campSession={campSession}
7774
handleClick={handleSessionClick}
7875
state={getCardState(
7976
campSession.id,

frontend/src/components/pages/RegistrantExperience/SessionSelection/SessionCard.tsx

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from "react";
22
import { Box, Text, VStack, Flex, useMediaQuery } from "@chakra-ui/react";
3-
import { CampSessionResponse } from "../../../../types/CampsTypes";
3+
import { CampSession } from "../../../../types/CampsTypes";
44
import { SessionCardState } from "./SessionSelectionTypes";
55
import {
66
sessionCardBoldTextStyles,
@@ -17,7 +17,7 @@ type SessionCardDetailsProps = {
1717
fee: number;
1818
startTime: string;
1919
endTime: string;
20-
campSession: CampSessionResponse;
20+
campSession: CampSession;
2121
};
2222

2323
const SessionCardDetails = ({
@@ -26,14 +26,25 @@ const SessionCardDetails = ({
2626
endTime,
2727
campSession,
2828
}: SessionCardDetailsProps): React.ReactElement => {
29-
const getCampSessionDates = () => {
30-
const startDate = new Date(campSession.dates[0]);
31-
const endDate = new Date(campSession.dates[campSession.dates.length - 1]);
32-
return `${startDate.toLocaleString("default", {
33-
month: "short",
34-
})} ${startDate.getDay()} - ${endDate.toLocaleString("default", {
35-
month: "short",
36-
})} ${endDate.getDay()}, ${endDate.getFullYear()}`;
29+
const getCampSessionDates = (): string => {
30+
if (campSession.dates.length > 1) {
31+
const startDate = new Date(campSession.dates[0]);
32+
const endDate = new Date(campSession.dates[campSession.dates.length - 1]);
33+
return `${startDate.toLocaleDateString("en-us", {
34+
month: "short",
35+
})} ${startDate.getDate()} - ${endDate.toLocaleDateString("en-us", {
36+
month: "short",
37+
})} ${endDate.getDate()}, ${endDate.getFullYear()}`;
38+
}
39+
40+
if (campSession.dates.length === 1) {
41+
const date = new Date(campSession.dates[0]);
42+
return `${date.toLocaleDateString("en-us", {
43+
month: "short",
44+
})} ${date.getDate()}, ${date.getFullYear()}`;
45+
}
46+
47+
return "";
3748
};
3849

3950
const formattedTimeString = (start: string, end: string): string => {
@@ -66,7 +77,7 @@ type SessionCardProps = {
6677
fee: number;
6778
startTime: string;
6879
endTime: string;
69-
campSession: CampSessionResponse;
80+
campSession: CampSession;
7081
handleClick: (sessionID: string) => void;
7182
state: SessionCardState;
7283
sessionIsWaitlisted: boolean;

0 commit comments

Comments
 (0)