Skip to content

Commit 09c023e

Browse files
authored
Add first and last name fields to stripe membership checkout (#195)
1 parent a5ffe4b commit 09c023e

File tree

8 files changed

+67
-12
lines changed

8 files changed

+67
-12
lines changed

src/api/functions/entraId.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ export async function patchUserProfile(
431431
token: string,
432432
email: string,
433433
userId: string,
434-
data: ProfilePatchRequest,
434+
data: Partial<ProfilePatchRequest>,
435435
): Promise<void> {
436436
try {
437437
const url = `https://graph.microsoft.com/v1.0/users/${userId}`;

src/api/functions/membership.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
addToTenant,
1111
isUserInGroup,
1212
modifyGroup,
13+
patchUserProfile,
1314
resolveEmailToOid,
1415
} from "./entraId.js";
1516
import { EntraGroupError } from "common/errors/index.js";
@@ -112,6 +113,8 @@ export async function setPaidMembershipInTable(
112113

113114
type SetPaidMembershipInput = {
114115
netId: string;
116+
firstName: string;
117+
lastName: string;
115118
dynamoClient: DynamoDBClient;
116119
entraToken: string;
117120
paidMemberGroup: string;
@@ -126,6 +129,8 @@ export async function setPaidMembership({
126129
dynamoClient,
127130
entraToken,
128131
paidMemberGroup,
132+
firstName,
133+
lastName,
129134
}: SetPaidMembershipInput): Promise<SetPaidMembershipOutput> {
130135
const dynamoResult = await setPaidMembershipInTable(
131136
netId,
@@ -151,13 +156,19 @@ export async function setPaidMembership({
151156
30000,
152157
4000,
153158
);
159+
const oid = await resolveEmailToOid(entraToken, email);
154160
await modifyGroup(
155161
entraToken,
156162
email,
157163
paidMemberGroup,
158164
EntraGroupActions.ADD,
159165
dynamoClient,
160166
);
167+
await patchUserProfile(entraToken, email, oid, {
168+
displayName: `${firstName} ${lastName}`,
169+
givenName: firstName,
170+
surname: lastName,
171+
});
161172

162173
return { updated: true };
163174
}

src/api/functions/ses.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export function generateMembershipEmailCommand(
1414
recipientEmail: string,
1515
senderEmail: string,
1616
attachmentBuffer: ArrayBufferLike,
17+
firstName?: string,
1718
): SendRawEmailCommand {
1819
const encodedAttachment = encode(attachmentBuffer as ArrayBuffer);
1920
const boundary = "----BoundaryForEmail";
@@ -81,14 +82,14 @@ export function generateMembershipEmailCommand(
8182
<img src="https://static.acm.illinois.edu/banner-blue.png" style="height: 100px; width: 210px; align-self: center;"/>
8283
<br />
8384
<div class="wrap">
84-
<h2 style="text-align: center;">Welcome</h2>
85+
<h2 style="text-align: center;">Welcome${firstName && `, ${firstName}`}!</h2>
8586
<p>
8687
Thank you for becoming a member of ACM @ UIUC! Attached is your membership pass.
8788
You can add it to your Apple or Google Wallet for easy access.
8889
</p>
8990
<p>
9091
If you have any questions, feel free to contact us at
91-
<a href="mailto:infra@acm.illinois.edu">infra@acm.illinois.edu</a>.
92+
<a href="mailto:officers@acm.illinois.edu">officers@acm.illinois.edu</a>.
9293
</p>
9394
<p>
9495
We also encourage you to check out our resources page, where you can find the benefits associated with your membership.
@@ -101,7 +102,7 @@ export function generateMembershipEmailCommand(
101102
<div class="footer">
102103
<p>
103104
<a href="https://acm.illinois.edu">ACM @ UIUC Homepage</a>
104-
<a href="mailto:admin@acm.illinois.edu">Email ACM @ UIUC</a>
105+
<a href="mailto:officers@acm.illinois.edu">Email ACM @ UIUC</a>
105106
</p>
106107
</div>
107108
</body>

src/api/functions/stripe.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export type StripeCheckoutSessionCreateParams = {
1818
items: { price: string; quantity: number }[];
1919
initiator: string;
2020
allowPromotionCodes: boolean;
21+
customFields?: Stripe.Checkout.SessionCreateParams.CustomField[];
2122
};
2223

2324
/**
@@ -73,6 +74,7 @@ export const createCheckoutSession = async ({
7374
items,
7475
initiator,
7576
allowPromotionCodes,
77+
customFields,
7678
}: StripeCheckoutSessionCreateParams): Promise<string> => {
7779
const stripe = new Stripe(stripeApiKey);
7880
const payload: Stripe.Checkout.SessionCreateParams = {
@@ -89,6 +91,7 @@ export const createCheckoutSession = async ({
8991
initiator,
9092
},
9193
allow_promotion_codes: allowPromotionCodes,
94+
custom_fields: customFields,
9295
};
9396
const session = await stripe.checkout.sessions.create(payload);
9497
if (!session.url) {

src/api/routes/membership.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,24 @@ const membershipPlugin: FastifyPluginAsync = async (fastify, _options) => {
173173
quantity: 1,
174174
},
175175
],
176-
176+
customFields: [
177+
{
178+
key: "firstName",
179+
label: {
180+
type: "custom",
181+
custom: "Student First Name",
182+
},
183+
type: "text",
184+
},
185+
{
186+
key: "lastName",
187+
label: {
188+
type: "custom",
189+
custom: "Student Last Name",
190+
},
191+
type: "text",
192+
},
193+
],
177194
initiator: "purchase-membership",
178195
allowPromotionCodes: true,
179196
}),
@@ -343,12 +360,30 @@ const membershipPlugin: FastifyPluginAsync = async (fastify, _options) => {
343360
event.data.object.metadata.initiator === "purchase-membership"
344361
) {
345362
const customerEmail = event.data.object.customer_email;
363+
const firstName = event.data.object.custom_fields.filter(
364+
(x) => x.key === "firstName",
365+
)[0].text?.value;
366+
const lastName = event.data.object.custom_fields.filter(
367+
(x) => x.key === "lastName",
368+
)[0].text?.value;
346369
if (!customerEmail) {
347370
request.log.info("No customer email found.");
348371
return reply
349372
.code(200)
350373
.send({ handled: false, requestId: request.id });
351374
}
375+
if (!firstName) {
376+
request.log.info("First name not found.");
377+
return reply
378+
.code(200)
379+
.send({ handled: false, requestId: request.id });
380+
}
381+
if (!lastName) {
382+
request.log.info("Last name not found.");
383+
return reply
384+
.code(200)
385+
.send({ handled: false, requestId: request.id });
386+
}
352387
const sqsPayload: SQSPayload<AvailableSQSFunctions.ProvisionNewMember> =
353388
{
354389
function: AvailableSQSFunctions.ProvisionNewMember,
@@ -358,6 +393,8 @@ const membershipPlugin: FastifyPluginAsync = async (fastify, _options) => {
358393
},
359394
payload: {
360395
email: customerEmail,
396+
firstName,
397+
lastName,
361398
},
362399
};
363400
if (!fastify.sqsClient) {

src/api/sqs/handlers/emailMembershipPassHandler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { SESClient } from "@aws-sdk/client-ses";
1414
export const emailMembershipPassHandler: SQSHandlerFunction<
1515
AvailableSQSFunctions.EmailMembershipPass
1616
> = async (payload, metadata, logger) => {
17-
const email = payload.email;
17+
const { email, firstName } = payload;
1818
const commonConfig = { region: genericConfig.AwsRegion };
1919
const clients = await getAuthorizedClients(logger, commonConfig);
2020
const entraIdToken = await getEntraIdToken({
@@ -37,6 +37,7 @@ export const emailMembershipPassHandler: SQSHandlerFunction<
3737
email,
3838
`membership@${environmentConfig[runEnvironment].EmailDomain}`,
3939
pkpass.buffer,
40+
firstName,
4041
);
4142
if (runEnvironment === "dev" && email === "testinguser@illinois.edu") {
4243
return;

src/api/sqs/handlers/provisionNewMember.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { setKey } from "api/functions/redisCache.js";
1717
export const provisionNewMemberHandler: SQSHandlerFunction<
1818
AvailableSQSFunctions.ProvisionNewMember
1919
> = async (payload, metadata, logger) => {
20-
const { email } = payload;
20+
const { email, firstName, lastName } = payload;
2121
const commonConfig = { region: genericConfig.AwsRegion };
2222
const clients = await getAuthorizedClients(logger, commonConfig);
2323
const entraToken = await getEntraIdToken({
@@ -39,6 +39,8 @@ export const provisionNewMemberHandler: SQSHandlerFunction<
3939
dynamoClient: clients.dynamoClient,
4040
entraToken,
4141
paidMemberGroup: currentEnvironmentConfig.PaidMemberGroupId,
42+
firstName,
43+
lastName,
4244
});
4345
if (updated) {
4446
const logPromise = createAuditLogEntry({

src/common/types/sqsMessage.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,16 @@ export const sqsPayloadSchemas = {
3838
),
3939
[AvailableSQSFunctions.EmailMembershipPass]: createSQSSchema(
4040
AvailableSQSFunctions.EmailMembershipPass,
41-
z.object({ email: z.string().email() })
41+
z.object({ email: z.email(), firstName: z.optional(z.string().min(1)) })
4242
),
4343
[AvailableSQSFunctions.ProvisionNewMember]: createSQSSchema(
4444
AvailableSQSFunctions.ProvisionNewMember,
45-
z.object({ email: z.string().email() })
45+
z.object({ email: z.email(), firstName: z.string().min(1), lastName: z.string().min(1) })
4646
),
4747
[AvailableSQSFunctions.SendSaleEmail]: createSQSSchema(
4848
AvailableSQSFunctions.SendSaleEmail,
4949
z.object({
50-
email: z.string().email(),
50+
email: z.email(),
5151
qrCodeContent: z.string().min(1),
5252
itemName: z.string().min(1),
5353
quantity: z.number().min(1),
@@ -58,8 +58,8 @@ export const sqsPayloadSchemas = {
5858
),
5959
[AvailableSQSFunctions.EmailNotifications]: createSQSSchema(
6060
AvailableSQSFunctions.EmailNotifications, z.object({
61-
to: z.array(z.string().email()).min(1),
62-
cc: z.optional(z.array(z.string().email()).min(1)),
61+
to: z.array(z.email()).min(1),
62+
cc: z.optional(z.array(z.email()).min(1)),
6363
bcc: z.optional(z.array(z.string().email()).min(1)),
6464
subject: z.string().min(1),
6565
content: z.string().min(1),

0 commit comments

Comments
 (0)