diff --git a/src/action/quickbooks.action.ts b/src/action/quickbooks.action.ts index f074cfe..ef22527 100644 --- a/src/action/quickbooks.action.ts +++ b/src/action/quickbooks.action.ts @@ -45,7 +45,7 @@ export async function checkForNonUsCompany(tokenInfo: IntuitAPITokensType) { message: 'checkForNonUsCompany | Company Info', }) - return companyInfo?.Country !== 'US' + return companyInfo.Country !== 'US' } export async function reconnectIfCta(type?: string) { diff --git a/src/app/api/core/utils/withErrorHandler.ts b/src/app/api/core/utils/withErrorHandler.ts index 64bbc65..838b8bf 100644 --- a/src/app/api/core/utils/withErrorHandler.ts +++ b/src/app/api/core/utils/withErrorHandler.ts @@ -9,7 +9,7 @@ import { NextRequest, NextResponse } from 'next/server' import { ZodError, ZodFormattedError } from 'zod' import { isAxiosError } from '@/app/api/core/exceptions/custom' import * as Sentry from '@sentry/nextjs' -import { IntuitAPIErrorMessage } from '@/utils/intuitAPI' +import { RetryableError } from '@/utils/error' type RequestHandler = (req: NextRequest, params: any) => Promise @@ -64,6 +64,9 @@ export const withErrorHandler = (handler: RequestHandler): RequestHandler => { status = error.status message = error.message || message errors = error.errors + } else if (error instanceof RetryableError) { + status = error.status + message = error.message || message } else if (error instanceof Error && error.message) { message = error.message } else if (isAxiosError(error)) { diff --git a/src/app/api/core/utils/withRetry.ts b/src/app/api/core/utils/withRetry.ts index 56210b5..3a71b07 100644 --- a/src/app/api/core/utils/withRetry.ts +++ b/src/app/api/core/utils/withRetry.ts @@ -1,6 +1,7 @@ import { StatusableError } from '@/type/CopilotApiError' import pRetry, { FailedAttemptError } from 'p-retry' import * as Sentry from '@sentry/nextjs' +import { RetryableError } from '@/utils/error' export const withRetry = async ( fn: (...args: any[]) => Promise, @@ -46,6 +47,10 @@ export const withRetry = async ( ) }, shouldRetry: (error: any) => { + if (error instanceof RetryableError) { + return error.retry + } + // Typecasting because Copilot doesn't export an error class const err = error as StatusableError // Retry only if statusCode === 429 diff --git a/src/type/dto/intuitAPI.dto.ts b/src/type/dto/intuitAPI.dto.ts index 3fd9924..91d5474 100644 --- a/src/type/dto/intuitAPI.dto.ts +++ b/src/type/dto/intuitAPI.dto.ts @@ -200,3 +200,12 @@ export const QBDeletePayloadSchema = z.object({ }) export type QBDeletePayloadType = z.infer + +export const CompanyInfoSchema = z.object({ + CompanyInfo: z.array( + z.object({ + Country: z.string(), + }), + ), +}) +export type CompanyInfoType = z.infer diff --git a/src/utils/error.ts b/src/utils/error.ts index 5eaa6bb..f404368 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -40,3 +40,14 @@ export const getMessageAndCodeFromError = ( } return { message, code } } + +export class RetryableError extends Error { + readonly retry: boolean + readonly status: number + + constructor(status: number, message: string, retry: boolean) { + super(message) + this.retry = retry + this.status = status + } +} diff --git a/src/utils/intuitAPI.ts b/src/utils/intuitAPI.ts index 65e86b7..6dd117f 100644 --- a/src/utils/intuitAPI.ts +++ b/src/utils/intuitAPI.ts @@ -21,7 +21,10 @@ import { QBAccountUpdatePayloadType, QBAccountResponseType, QBAccountResponseSchema, + CompanyInfoType, + CompanyInfoSchema, } from '@/type/dto/intuitAPI.dto' +import { RetryableError } from '@/utils/error' import CustomLogger from '@/utils/logger' import httpStatus from 'http-status' @@ -768,16 +771,21 @@ export default class IntuitAPI { return purchase } - async _getCompanyInfo() { + async _getCompanyInfo(): Promise { CustomLogger.info({ message: `IntuitAPI#getCompanyInfo | Company Info query start for realmId: ${this.tokens.intuitRealmId}.`, }) const query = `SELECT * FROM CompanyInfo maxresults 1` const companyInfo = await this.customQuery(query) - if (!companyInfo) return null + if (!companyInfo) + throw new RetryableError( + httpStatus.NOT_FOUND, + 'No company info found', + true, + ) - if (companyInfo?.Fault) { + if (companyInfo.Fault) { CustomLogger.error({ obj: companyInfo.Fault?.Error, message: 'Error: ' }) throw new APIError( companyInfo.Fault?.Error?.code || httpStatus.BAD_REQUEST, @@ -786,7 +794,8 @@ export default class IntuitAPI { ) } - return companyInfo.CompanyInfo?.[0] + const parsedCompanyInfo = CompanyInfoSchema.parse(companyInfo) + return parsedCompanyInfo.CompanyInfo[0] } private wrapWithRetry(