Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 19 additions & 5 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
"plugin:prettier/recommended",
"plugin:@typescript-eslint/recommended"
],
"plugins": ["import"],
"plugins": [
"import",
"prettier"
],
"rules": {
"import/no-unresolved": "error",
"import/extensions": [
Expand Down Expand Up @@ -32,27 +35,38 @@
},
"settings": {
"import/parsers": {
"@typescript-eslint/parser": [".ts", ".tsx", ".js", ".jsx"]
"@typescript-eslint/parser": [
".ts",
".tsx",
".js",
".jsx"
]
},
"import/resolver": {
"typescript": {
"alwaysTryTypes": true,
"project": [
"src/api/tsconfig.json", // Path to tsconfig.json in src/api
"src/ui/tsconfig.json" // Path to tsconfig.json in src/ui
"src/ui/tsconfig.json" // Path to tsconfig.json in src/ui
]
}
}
},
"overrides": [
{
"files": ["*.test.ts", "*.testdata.ts"],
"files": [
"*.test.ts",
"*.testdata.ts"
],
"rules": {
"@typescript-eslint/no-explicit-any": "off"
}
},
{
"files": ["src/ui/*", "src/ui/**/*"],
"files": [
"src/ui/*",
"src/ui/**/*"
],
"rules": {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off"
Expand Down
70 changes: 62 additions & 8 deletions src/api/functions/membership.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import {
ConditionalCheckFailedException,
DynamoDBClient,
PutItemCommand,
QueryCommand,
} from "@aws-sdk/client-dynamodb";
import { marshall } from "@aws-sdk/util-dynamodb";
import { genericConfig } from "common/config.js";
import { FastifyBaseLogger } from "fastify";
import { isUserInGroup } from "./entraId.js";
import { isUserInGroup, modifyGroup } from "./entraId.js";
import { EntraGroupError } from "common/errors/index.js";
import { EntraGroupActions } from "common/types/iam.js";

export async function checkPaidMembership(
endpoint: string,
Expand Down Expand Up @@ -76,17 +78,69 @@ export async function checkPaidMembershipFromEntra(
export async function setPaidMembershipInTable(
netId: string,
dynamoClient: DynamoDBClient,
): Promise<void> {
actor: string = "core-api-queried",
): Promise<{ updated: boolean }> {
const obj = {
email: `${netId}@illinois.edu`,
inserted_at: new Date().toISOString(),
inserted_by: "membership-api-queried",
inserted_by: actor,
};

await dynamoClient.send(
new PutItemCommand({
TableName: genericConfig.MembershipTableName,
Item: marshall(obj),
}),
try {
await dynamoClient.send(
new PutItemCommand({
TableName: genericConfig.MembershipTableName,
Item: marshall(obj),
ConditionExpression: "attribute_not_exists(email)",
}),
);
return { updated: true };
} catch (error: unknown) {
if (error instanceof ConditionalCheckFailedException) {
return { updated: false };
}
throw error;
}
}

type SetPaidMembershipInput = {
netId: string;
dynamoClient: DynamoDBClient;
entraToken: string;
paidMemberGroup: string;
};

type SetPaidMembershipOutput = {
updated: boolean;
};

export async function setPaidMembership({
netId,
dynamoClient,
entraToken,
paidMemberGroup,
}: SetPaidMembershipInput): Promise<SetPaidMembershipOutput> {
const dynamoResult = await setPaidMembershipInTable(
netId,
dynamoClient,
"core-api-provisioned",
);
if (!dynamoResult.updated) {
const inEntra = await checkPaidMembershipFromEntra(
netId,
entraToken,
paidMemberGroup,
);
if (inEntra) {
return { updated: false };
}
}
await modifyGroup(
entraToken,
`${netId}@illinois.edu`,
paidMemberGroup,
EntraGroupActions.ADD,
);

return { updated: true };
}
42 changes: 42 additions & 0 deletions src/api/functions/stripe.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { InternalServerError } from "common/errors/index.js";
import Stripe from "stripe";

export type StripeLinkCreateParams = {
Expand All @@ -9,6 +10,15 @@ export type StripeLinkCreateParams = {
stripeApiKey: string;
};

export type StripeCheckoutSessionCreateParams = {
successUrl?: string;
returnUrl?: string;
customerEmail?: string;
stripeApiKey: string;
items: { price: string; quantity: number }[];
initiator: string;
};

/**
* Create a Stripe payment link for an invoice. Note that invoiceAmountUsd MUST IN CENTS!!
* @param {StripeLinkCreateParams} options
Expand Down Expand Up @@ -53,3 +63,35 @@ export const createStripeLink = async ({
priceId: price.id,
};
};

export const createCheckoutSession = async ({
successUrl,
returnUrl,
stripeApiKey,
customerEmail,
items,
initiator,
}: StripeCheckoutSessionCreateParams): Promise<string> => {
const stripe = new Stripe(stripeApiKey);
const payload: Stripe.Checkout.SessionCreateParams = {
success_url: successUrl || "",
cancel_url: returnUrl || "",
payment_method_types: ["card"],
line_items: items.map((item) => ({
price: item.price,
quantity: item.quantity,
})),
mode: "payment",
customer_email: customerEmail,
metadata: {
initiator,
},
};
const session = await stripe.checkout.sessions.create(payload);
if (!session.url) {
throw new InternalServerError({
message: "Could not create Stripe checkout session.",
});
}
return session.url;
};
1 change: 1 addition & 0 deletions src/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"esbuild": "^0.24.2",
"fastify": "^5.1.0",
"fastify-plugin": "^4.5.1",
"fastify-raw-body": "^5.0.0",
"ical-generator": "^7.2.0",
"jsonwebtoken": "^9.0.2",
"jwks-rsa": "^3.1.0",
Expand Down
Loading
Loading