223 lines
5.0 KiB
TypeScript
223 lines
5.0 KiB
TypeScript
import { ErrCode } from "@autumn/shared";
|
|
import chalk from "chalk";
|
|
import { StatusCodes } from "http-status-codes";
|
|
import Stripe from "stripe";
|
|
import { ZodError } from "zod";
|
|
|
|
export const isPaymentDeclined = (error: any) => {
|
|
return (
|
|
error instanceof RecaseError && error.code === ErrCode.StripeCardDeclined
|
|
);
|
|
};
|
|
|
|
export default class RecaseError extends Error {
|
|
code: string;
|
|
data: any;
|
|
statusCode: number;
|
|
|
|
constructor({
|
|
message,
|
|
code,
|
|
data,
|
|
statusCode = 400,
|
|
}: {
|
|
message: string;
|
|
code: string;
|
|
data?: any;
|
|
statusCode?: number;
|
|
}) {
|
|
super(message);
|
|
this.name = "RecaseError";
|
|
this.code = code;
|
|
this.data = data;
|
|
this.statusCode = statusCode;
|
|
}
|
|
|
|
print(logger: any) {
|
|
logger.warn(`Code: ${chalk.yellow(this.code)}`);
|
|
logger.warn(`Message: ${chalk.yellow(this.message)}`);
|
|
|
|
if (this.data) {
|
|
logger.warn(`Data:`);
|
|
logger.warn(this.data);
|
|
} else {
|
|
logger.warn("No data");
|
|
}
|
|
}
|
|
}
|
|
|
|
export function formatZodError(error: ZodError): string {
|
|
return error.errors
|
|
.map((err) =>
|
|
err.path.length ? `${err.path.join(".")}: ${err.message}` : err.message
|
|
)
|
|
.join(", ");
|
|
}
|
|
|
|
const getJsonBody = (body: any) => {
|
|
if (Buffer.isBuffer(body)) {
|
|
try {
|
|
return JSON.parse(body.toString());
|
|
} catch (e) {
|
|
return `[Invalid JSON] Raw body: ${body.toString()}`;
|
|
}
|
|
}
|
|
return body;
|
|
};
|
|
|
|
const logRequestBody = (logger: any, req: any, level: "warn" | "error") => {
|
|
if (
|
|
req.body &&
|
|
typeof req.body === "object" &&
|
|
Object.keys(req.body).length > 0
|
|
) {
|
|
logger[level]("Request body:");
|
|
logger[level](getJsonBody(req.body));
|
|
}
|
|
};
|
|
|
|
const logReqUrl = (logger: any, req: any, level: "warn" | "error") => {
|
|
if (req.originalUrl.includes("/webhooks/stripe")) {
|
|
logger[level](`Stripe webhook: ${req.originalUrl}`);
|
|
let body = req.body;
|
|
try {
|
|
body = Buffer.isBuffer(req.body)
|
|
? JSON.parse(req.body.toString())
|
|
: req.body;
|
|
|
|
logger[level](`Event type: ${body.type}, ID: ${body.id}`);
|
|
} catch (error) {
|
|
logger[level](`Invalid JSON body`);
|
|
}
|
|
} else {
|
|
logger[level](`${req.method} ${req.originalUrl}`);
|
|
}
|
|
};
|
|
|
|
export const handleRequestError = ({
|
|
error,
|
|
req,
|
|
res,
|
|
action,
|
|
}: {
|
|
error: any;
|
|
req: any;
|
|
res: any;
|
|
action: string;
|
|
}) => {
|
|
try {
|
|
const logger = req.logtail;
|
|
if (error instanceof RecaseError) {
|
|
logger.warn(
|
|
`RECASE WARNING (${req.org?.slug || "unknown"}): ${error.message} [${error.code}]`,
|
|
{
|
|
error: error.data ?? error,
|
|
}
|
|
);
|
|
|
|
res.status(error.statusCode).json({
|
|
message: error.message,
|
|
code: error.code,
|
|
env: req.env,
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (error instanceof Stripe.errors.StripeError) {
|
|
let curStack;
|
|
try {
|
|
throw new Error("test");
|
|
} catch (e: any) {
|
|
curStack = e.stack;
|
|
}
|
|
|
|
const { raw, headers, ...rest } = error;
|
|
logger.error(
|
|
`STRIPE ERROR (${req.org?.slug || "unknown"}): ${error.message}`,
|
|
{
|
|
error: {
|
|
...rest,
|
|
stack: curStack,
|
|
},
|
|
}
|
|
);
|
|
|
|
res.status(400).json({
|
|
message: `(Stripe Error) ${error.message}`,
|
|
code: `stripe_error`,
|
|
});
|
|
} else if (error instanceof ZodError) {
|
|
logger.error(
|
|
`ZOD ERROR (${req.org?.slug || "unknown"}): ${formatZodError(error)}`
|
|
);
|
|
|
|
res.status(400).json({
|
|
message: formatZodError(error),
|
|
code: ErrCode.InvalidInputs,
|
|
});
|
|
} else {
|
|
logger.error(
|
|
`UNKNOWN ERROR (${req.org?.slug || "unknown"}): ${error.message}, ${error.stack}`,
|
|
{
|
|
error: {
|
|
stack: error.stack,
|
|
message: error.message,
|
|
},
|
|
}
|
|
);
|
|
|
|
res.status(500).json({
|
|
message: error.message || "Unknown error",
|
|
code: error.code || "unknown_error",
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.log("Failed to log error / warning");
|
|
console.log(`Request: ${req.originalUrl}`);
|
|
console.log(`Body: ${req.body}`);
|
|
console.log(`Log Error: ${error}`);
|
|
}
|
|
};
|
|
|
|
export const handleFrontendReqError = ({
|
|
error,
|
|
req,
|
|
res,
|
|
action,
|
|
}: {
|
|
error: any;
|
|
req: any;
|
|
res: any;
|
|
action: string;
|
|
}) => {
|
|
try {
|
|
const logger = req.logger;
|
|
if (
|
|
error instanceof RecaseError &&
|
|
error.statusCode == StatusCodes.NOT_FOUND
|
|
) {
|
|
// Temporarily disable logger to prevent thread-stream crashes
|
|
console.log(`(frontend) ${req.method} ${req.originalUrl}: not found`);
|
|
res.status(404).json({
|
|
message: error.message,
|
|
code: error.code,
|
|
});
|
|
return;
|
|
}
|
|
|
|
logger.error(
|
|
`(frontend) ${req.method} ${req.originalUrl}: ${error.message}`,
|
|
{
|
|
error,
|
|
}
|
|
);
|
|
|
|
res.status(400).json({
|
|
message: error.message || "Unknown error",
|
|
code: error.code || "unknown_error",
|
|
});
|
|
} catch (error) {
|
|
console.log("Failed to log error / warning");
|
|
}
|
|
};
|