-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/customer checkout #423
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
base: main
Are you sure you want to change the base?
Changes from all commits
f2aeaf5
7df5f73
fdd9efc
64459fa
9e14803
ec075bb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,6 +2,16 @@ import { isProd } from "api/utils.js"; | |||||||||||||||||||||||||||||||||||||||||
| import { InternalServerError, ValidationError } from "common/errors/index.js"; | ||||||||||||||||||||||||||||||||||||||||||
| import { capitalizeFirstLetter } from "common/types/roomRequest.js"; | ||||||||||||||||||||||||||||||||||||||||||
| import Stripe from "stripe"; | ||||||||||||||||||||||||||||||||||||||||||
| import { createLock, IoredisAdapter, type SimpleLock } from "redlock-universal"; | ||||||||||||||||||||||||||||||||||||||||||
| import { Redis } from "api/types.js"; | ||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||
| TransactWriteItemsCommand, | ||||||||||||||||||||||||||||||||||||||||||
| QueryCommand, | ||||||||||||||||||||||||||||||||||||||||||
| UpdateItemCommand, | ||||||||||||||||||||||||||||||||||||||||||
| DynamoDBClient, | ||||||||||||||||||||||||||||||||||||||||||
| } from "@aws-sdk/client-dynamodb"; | ||||||||||||||||||||||||||||||||||||||||||
| import { genericConfig } from "common/config.js"; | ||||||||||||||||||||||||||||||||||||||||||
| import { marshall } from "@aws-sdk/util-dynamodb"; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export type StripeLinkCreateParams = { | ||||||||||||||||||||||||||||||||||||||||||
| invoiceId: string; | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -325,3 +335,248 @@ export const createStripeCustomer = async ({ | |||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||
| return customer.id; | ||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export type checkCustomerParams = { | ||||||||||||||||||||||||||||||||||||||||||
| acmOrg: string; | ||||||||||||||||||||||||||||||||||||||||||
| emailDomain: string; | ||||||||||||||||||||||||||||||||||||||||||
| redisClient: Redis; | ||||||||||||||||||||||||||||||||||||||||||
| dynamoClient: DynamoDBClient; | ||||||||||||||||||||||||||||||||||||||||||
| customerEmail: string; | ||||||||||||||||||||||||||||||||||||||||||
| customerName: string; | ||||||||||||||||||||||||||||||||||||||||||
| stripeApiKey: string; | ||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export type CheckOrCreateResult = { | ||||||||||||||||||||||||||||||||||||||||||
| customerId: string; | ||||||||||||||||||||||||||||||||||||||||||
| needsConfirmation?: boolean; | ||||||||||||||||||||||||||||||||||||||||||
| current?: { name?: string | null; email?: string | null }; | ||||||||||||||||||||||||||||||||||||||||||
| incoming?: { name: string; email: string }; | ||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export const checkOrCreateCustomer = async ({ | ||||||||||||||||||||||||||||||||||||||||||
| acmOrg, | ||||||||||||||||||||||||||||||||||||||||||
| emailDomain, | ||||||||||||||||||||||||||||||||||||||||||
| redisClient, | ||||||||||||||||||||||||||||||||||||||||||
| dynamoClient, | ||||||||||||||||||||||||||||||||||||||||||
| customerEmail, | ||||||||||||||||||||||||||||||||||||||||||
| customerName, | ||||||||||||||||||||||||||||||||||||||||||
| stripeApiKey, | ||||||||||||||||||||||||||||||||||||||||||
| }: checkCustomerParams): Promise<CheckOrCreateResult> => { | ||||||||||||||||||||||||||||||||||||||||||
| const normalizedEmail = customerEmail.trim().toLowerCase(); | ||||||||||||||||||||||||||||||||||||||||||
| const [, domainPart] = normalizedEmail.split("@"); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (!domainPart) { | ||||||||||||||||||||||||||||||||||||||||||
| throw new Error(`Could not derive email domain for "${customerEmail}".`); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const normalizedDomain = domainPart.toLowerCase(); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const lock = createLock({ | ||||||||||||||||||||||||||||||||||||||||||
| adapter: new IoredisAdapter(redisClient), | ||||||||||||||||||||||||||||||||||||||||||
| key: `stripe:${acmOrg}:${normalizedDomain}`, | ||||||||||||||||||||||||||||||||||||||||||
| retryAttempts: 5, | ||||||||||||||||||||||||||||||||||||||||||
| retryDelay: 300, | ||||||||||||||||||||||||||||||||||||||||||
| }) as SimpleLock; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const pk = `${acmOrg}#${normalizedDomain}`; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return await lock.using(async () => { | ||||||||||||||||||||||||||||||||||||||||||
| const checkCustomer = new QueryCommand({ | ||||||||||||||||||||||||||||||||||||||||||
| TableName: genericConfig.StripePaymentsDynamoTableName, | ||||||||||||||||||||||||||||||||||||||||||
| KeyConditionExpression: "primaryKey = :pk AND sortKey = :sk", | ||||||||||||||||||||||||||||||||||||||||||
| ExpressionAttributeValues: { | ||||||||||||||||||||||||||||||||||||||||||
| ":pk": { S: pk }, | ||||||||||||||||||||||||||||||||||||||||||
| ":sk": { S: "CUSTOMER" }, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| ConsistentRead: true, | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const customerResponse = await dynamoClient.send(checkCustomer); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (customerResponse.Count === 0) { | ||||||||||||||||||||||||||||||||||||||||||
| const customer = await createStripeCustomer({ | ||||||||||||||||||||||||||||||||||||||||||
| email: normalizedEmail, | ||||||||||||||||||||||||||||||||||||||||||
| name: customerName, | ||||||||||||||||||||||||||||||||||||||||||
| stripeApiKey, | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const createCustomer = new TransactWriteItemsCommand({ | ||||||||||||||||||||||||||||||||||||||||||
| TransactItems: [ | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| Put: { | ||||||||||||||||||||||||||||||||||||||||||
| TableName: genericConfig.StripePaymentsDynamoTableName, | ||||||||||||||||||||||||||||||||||||||||||
| Item: marshall( | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| primaryKey: pk, | ||||||||||||||||||||||||||||||||||||||||||
| sortKey: "CUSTOMER", | ||||||||||||||||||||||||||||||||||||||||||
| stripeCustomerId: customer, | ||||||||||||||||||||||||||||||||||||||||||
| totalAmount: 0, | ||||||||||||||||||||||||||||||||||||||||||
| createdAt: new Date().toISOString(), | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { removeUndefinedValues: true }, | ||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||
| ConditionExpression: | ||||||||||||||||||||||||||||||||||||||||||
| "attribute_not_exists(primaryKey) AND attribute_not_exists(sortKey)", | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| Put: { | ||||||||||||||||||||||||||||||||||||||||||
| TableName: genericConfig.StripePaymentsDynamoTableName, | ||||||||||||||||||||||||||||||||||||||||||
| Item: marshall( | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| primaryKey: pk, | ||||||||||||||||||||||||||||||||||||||||||
| sortKey: `EMAIL#${normalizedEmail}`, | ||||||||||||||||||||||||||||||||||||||||||
| stripeCustomerId: customer, | ||||||||||||||||||||||||||||||||||||||||||
| createdAt: new Date().toISOString(), | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { removeUndefinedValues: true }, | ||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||
| ConditionExpression: | ||||||||||||||||||||||||||||||||||||||||||
| "attribute_not_exists(primaryKey) AND attribute_not_exists(sortKey)", | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| await dynamoClient.send(createCustomer); | ||||||||||||||||||||||||||||||||||||||||||
| return { customerId: customer }; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const existingCustomerId = (customerResponse.Items![0] as any) | ||||||||||||||||||||||||||||||||||||||||||
| .stripeCustomerId.S as string; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const stripeClient = new Stripe(stripeApiKey); | ||||||||||||||||||||||||||||||||||||||||||
| const stripeCustomer = | ||||||||||||||||||||||||||||||||||||||||||
| await stripeClient.customers.retrieve(existingCustomerId); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const liveName = | ||||||||||||||||||||||||||||||||||||||||||
| "name" in stripeCustomer ? (stripeCustomer as any).name : null; | ||||||||||||||||||||||||||||||||||||||||||
| const liveEmail = | ||||||||||||||||||||||||||||||||||||||||||
| "email" in stripeCustomer ? (stripeCustomer as any).email : null; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const needsConfirmation = | ||||||||||||||||||||||||||||||||||||||||||
| (!!liveName && liveName !== customerName) || | ||||||||||||||||||||||||||||||||||||||||||
| (!!liveEmail && liveEmail.toLowerCase() !== normalizedEmail); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const ensureEmailMap = new TransactWriteItemsCommand({ | ||||||||||||||||||||||||||||||||||||||||||
| TransactItems: [ | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| Put: { | ||||||||||||||||||||||||||||||||||||||||||
| TableName: genericConfig.StripePaymentsDynamoTableName, | ||||||||||||||||||||||||||||||||||||||||||
| Item: marshall( | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| primaryKey: pk, | ||||||||||||||||||||||||||||||||||||||||||
| sortKey: `EMAIL#${normalizedEmail}`, | ||||||||||||||||||||||||||||||||||||||||||
| stripeCustomerId: existingCustomerId, | ||||||||||||||||||||||||||||||||||||||||||
| createdAt: new Date().toISOString(), | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { removeUndefinedValues: true }, | ||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||
| ConditionExpression: | ||||||||||||||||||||||||||||||||||||||||||
| "attribute_not_exists(primaryKey) AND attribute_not_exists(sortKey)", | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||
| await dynamoClient.send(ensureEmailMap); | ||||||||||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||||||||||
| // ignore | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+480
to
+484
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Silent error suppression may hide legitimate issues. Lines 480-484 attempt to create an At minimum, log the error for debugging and only suppress the specific try {
await dynamoClient.send(ensureEmailMap);
} catch (e) {
- // ignore
+ // Ignore if the EMAIL# entry already exists (ConditionalCheckFailedException)
+ if (
+ !(e instanceof Error) ||
+ !e.name.includes("ConditionalCheckFailedException")
+ ) {
+ // Log unexpected errors for debugging
+ console.warn(
+ `Failed to create EMAIL# mapping for ${normalizedEmail}:`,
+ e
+ );
+ }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (needsConfirmation) { | ||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||
| customerId: existingCustomerId, | ||||||||||||||||||||||||||||||||||||||||||
| needsConfirmation: true, | ||||||||||||||||||||||||||||||||||||||||||
| current: { name: liveName ?? null, email: liveEmail ?? null }, | ||||||||||||||||||||||||||||||||||||||||||
| incoming: { name: customerName, email: normalizedEmail }, | ||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return { customerId: existingCustomerId }; | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export type InvoiceAddParams = { | ||||||||||||||||||||||||||||||||||||||||||
| acmOrg: string; | ||||||||||||||||||||||||||||||||||||||||||
| emailDomain: string; | ||||||||||||||||||||||||||||||||||||||||||
| invoiceId: string; | ||||||||||||||||||||||||||||||||||||||||||
| invoiceAmountUsd: number; | ||||||||||||||||||||||||||||||||||||||||||
| redisClient: Redis; | ||||||||||||||||||||||||||||||||||||||||||
| dynamoClient: DynamoDBClient; | ||||||||||||||||||||||||||||||||||||||||||
| contactEmail: string; | ||||||||||||||||||||||||||||||||||||||||||
| contactName: string; | ||||||||||||||||||||||||||||||||||||||||||
| stripeApiKey: string; | ||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export const addInvoice = async ({ | ||||||||||||||||||||||||||||||||||||||||||
| contactName, | ||||||||||||||||||||||||||||||||||||||||||
| contactEmail, | ||||||||||||||||||||||||||||||||||||||||||
| acmOrg, | ||||||||||||||||||||||||||||||||||||||||||
| invoiceId, | ||||||||||||||||||||||||||||||||||||||||||
| invoiceAmountUsd, | ||||||||||||||||||||||||||||||||||||||||||
| emailDomain, | ||||||||||||||||||||||||||||||||||||||||||
| redisClient, | ||||||||||||||||||||||||||||||||||||||||||
| dynamoClient, | ||||||||||||||||||||||||||||||||||||||||||
| stripeApiKey, | ||||||||||||||||||||||||||||||||||||||||||
| }: InvoiceAddParams): Promise<CheckOrCreateResult> => { | ||||||||||||||||||||||||||||||||||||||||||
| const normalizedEmail = contactEmail.trim().toLowerCase(); | ||||||||||||||||||||||||||||||||||||||||||
| const [, domainPart] = normalizedEmail.split("@"); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (!domainPart) { | ||||||||||||||||||||||||||||||||||||||||||
| throw new Error(`Could not derive email domain for "${contactEmail}".`); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const normalizedDomain = domainPart.toLowerCase(); | ||||||||||||||||||||||||||||||||||||||||||
| const pk = `${acmOrg}#${normalizedDomain}`; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const result = await checkOrCreateCustomer({ | ||||||||||||||||||||||||||||||||||||||||||
| acmOrg, | ||||||||||||||||||||||||||||||||||||||||||
| emailDomain: normalizedDomain, | ||||||||||||||||||||||||||||||||||||||||||
| redisClient, | ||||||||||||||||||||||||||||||||||||||||||
| dynamoClient, | ||||||||||||||||||||||||||||||||||||||||||
| customerEmail: contactEmail, | ||||||||||||||||||||||||||||||||||||||||||
| customerName: contactName, | ||||||||||||||||||||||||||||||||||||||||||
| stripeApiKey, | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (result.needsConfirmation) { | ||||||||||||||||||||||||||||||||||||||||||
| return result; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const dynamoCommand = new TransactWriteItemsCommand({ | ||||||||||||||||||||||||||||||||||||||||||
| TransactItems: [ | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| Put: { | ||||||||||||||||||||||||||||||||||||||||||
| TableName: genericConfig.StripePaymentsDynamoTableName, | ||||||||||||||||||||||||||||||||||||||||||
| Item: marshall( | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| primaryKey: pk, | ||||||||||||||||||||||||||||||||||||||||||
| sortKey: `CHARGE#${invoiceId}`, | ||||||||||||||||||||||||||||||||||||||||||
| invoiceAmtUsd: invoiceAmountUsd, | ||||||||||||||||||||||||||||||||||||||||||
| createdAt: new Date().toISOString(), | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { removeUndefinedValues: true }, | ||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||
| ConditionExpression: | ||||||||||||||||||||||||||||||||||||||||||
| "attribute_not_exists(primaryKey) AND attribute_not_exists(sortKey)", | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| Update: { | ||||||||||||||||||||||||||||||||||||||||||
| TableName: genericConfig.StripePaymentsDynamoTableName, | ||||||||||||||||||||||||||||||||||||||||||
| Key: { | ||||||||||||||||||||||||||||||||||||||||||
| primaryKey: { S: pk }, | ||||||||||||||||||||||||||||||||||||||||||
| sortKey: { S: "CUSTOMER" }, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| UpdateExpression: "SET totalAmount = totalAmount + :inc", | ||||||||||||||||||||||||||||||||||||||||||
| ExpressionAttributeValues: { | ||||||||||||||||||||||||||||||||||||||||||
| ":inc": { N: invoiceAmountUsd.toString() }, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| await dynamoClient.send(dynamoCommand); | ||||||||||||||||||||||||||||||||||||||||||
| return { customerId: result.customerId }; | ||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Address ESLint import/extensions complaints for new imports
ESLint is flagging the
.jsextensions on the new imports from"api/types.js"and"common/config.js".Depending on the project’s convention, either:
import/extensions(e.g.import { Redis } from "api/types";,import { genericConfig } from "common/config";), or.jsextensions for these path-mapped imports, in line with how other modules in this repo are handled.Please align this with whatever pattern the rest of the codebase is using so lint passes.
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 31
🏁 Script executed:
Length of output: 2800
🏁 Script executed:
Length of output: 99
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 613
🏁 Script executed:
Length of output: 4665
🏁 Script executed:
Length of output: 22578
🏁 Script executed:
Length of output: 120
🏁 Script executed:
Length of output: 53
🏁 Script executed:
Length of output: 198
🏁 Script executed:
Length of output: 1529
🏁 Script executed:
Length of output: 306
🏁 Script executed:
Length of output: 764
🏁 Script executed:
Length of output: 529
🏁 Script executed:
Length of output: 2607
🏁 Script executed:
Length of output: 550
🏁 Script executed:
Length of output: 52
Remove
.jsextensions from path-mapped imports to comply with ESLintimport/extensionsruleThe ESLint configuration (eslint.config.mjs) explicitly requires that
.jsand.tsextensions be omitted from imports:"import/extensions": ["error", "ignorePackages", { js: "never", jsx: "never", ts: "never", tsx: "never" }].The imports in stripe.ts at lines 6 and 13 violate this rule:
import { Redis } from "api/types.js";toimport { Redis } from "api/types";import { genericConfig } from "common/config.js";toimport { genericConfig } from "common/config";The pre-commit hook runs
yarn lint --fix, which will catch this during commit. Note: the entire codebase currently uses.jsextensions for these paths (127+ instances forapi/*, 132+ forcommon/*), so a widespread fix may be needed beyond this file.🧰 Tools
🪛 ESLint
[error] 6-6: Unexpected use of file extension "js" for "api/types.js"
(import/extensions)
[error] 13-13: Unexpected use of file extension "js" for "common/config.js"
(import/extensions)
🤖 Prompt for AI Agents