cal.com icon indicating copy to clipboard operation
cal.com copied to clipboard

[CAL-3977] Zod Errors in any /payments and /payments/[id] API Endpoints

Open fabriziospadaro opened this issue 1 year ago • 3 comments

Issue Summary

The /payments API and /payments/[id] endpoints are returning zod errors, making it impossible to retrieve the payment UID necessary for constructing abandoned cart recovery links.

Steps to Reproduce

  1. Make a request to the /payments API.
  2. Observe the response, which returns zod errors.
  3. Make a request to the /payments/[id] endpoint.
  4. Observe the response, which also returns zod errors.

This issue is considered a bug because the API should return valid data without errors, enabling the retrieval of the payment UID.

Actual Results

  • The /payments API and /payments/[id] endpoints return zod errors, preventing the retrieval of payment UIDs.

Expected Results

  • The /payments API and /payments/[id] endpoints should return valid responses, including the payment UID, without any zod errors.

Technical details

Evidence

  • This bug was tested by making API requests to /payments and /payments/[id]. Both endpoints returned zod errors.

By following the steps above, you can replicate the issue and observe the unexpected zod errors. The expected outcome is that the API should return the required payment UID without any errors

CAL-3977

fabriziospadaro avatar Jun 20 '24 14:06 fabriziospadaro

To resolve the Zod errors encountered when making requests to the /payments and /payments/[id] API endpoints, ensure that the query parameters and the response data conform to the expected schema definitions. Here is a step-by-step approach to troubleshoot and resolve the Zod errors:

  1. Validate Query Parameters: Ensure that the query parameters passed to the API endpoints match the expected schema. The schemaQueryIdParseInt schema is used to validate and parse the id parameter.

  2. Validate Response Data: Ensure that the response data from the database matches the schemaPaymentPublic schema. This schema is used to validate the payment data before sending it in the response. Note that the schemaPaymentPublic schema omits the externalId field from the payment data.

  3. Error Handling: Properly handle and log errors to understand the cause of the Zod validation failures. Implement a Zod exception filter to catch and log detailed error information.

Here is the relevant code snippet with comments to help you understand the validation process:

import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "@calcom/prisma";
import { withMiddleware } from "~/lib/helpers/withMiddleware";
import type { PaymentResponse } from "~/lib/types";
import { schemaPaymentPublic } from "~/lib/validations/payment";
import {
  schemaQueryIdParseInt,
  withValidQueryIdTransformParseInt,
} from "~/lib/validations/shared/queryIdTransformParseInt";

/**
 * @swagger
 * /payments/{id}:
 *   get:
 *     summary: Find a payment
 *     parameters:
 *       - in: query
 *         name: apiKey
 *         required: true
 *         schema:
 *           type: string
 *         description: Your API key
 *       - in: path
 *         name: id
 *         schema:
 *           type: integer
 *         required: true
 *         description: ID of the payment to get
 *     tags:
 *     - payments
 *     responses:
 *       200:
 *         description: OK
 *       401:
 *        description: Authorization information is missing or invalid.
 *       404:
 *         description: Payment was not found
 */
export async function paymentById(
  { method, query, userId }: NextApiRequest,
  res: NextApiResponse<PaymentResponse>
) {
  // Validate and parse the query parameters
  const safeQuery = schemaQueryIdParseInt.safeParse(query);
  if (safeQuery.success && method === "GET") {
    const userWithBookings = await prisma.user.findUnique({
      where: { id: userId },
      include: { bookings: true },
    });

    // Fetch and validate the payment data
    await prisma.payment
      .findUnique({ where: { id: safeQuery.data.id } })
      .then((data) => schemaPaymentPublic.parse(data))
      .then((payment) => {
        // Check if the user is authorized to view the payment
        if (!userWithBookings?.bookings.map((b) => b.id).includes(payment.bookingId)) {
          res.status(401).json({ message: "Unauthorized" });
        } else {
          res.status(200).json({ payment });
        }
      })
      .catch((error: Error) =>
        res.status(404).json({
          message: `Payment with id: ${safeQuery.data.id} not found`,
          error,
        })
      );
  } else {
    // Handle invalid query parameters
    res.status(400).json({ message: "Invalid query parameters" });
  }
}

export default withMiddleware("HTTP_GET")(withValidQueryIdTransformParseInt(paymentById));

Additionally, implement a Zod exception filter to catch and log detailed error information:

import type { ArgumentsHost, ExceptionFilter } from "@nestjs/common";
import { Catch, HttpStatus, Logger } from "@nestjs/common";
import { Request } from "express";
import { ZodError } from "zod";

import { BAD_REQUEST, ERROR_STATUS } from "@calcom/platform-constants";
import { Response } from "@calcom/platform-types";

@Catch(ZodError)
export class ZodExceptionFilter implements ExceptionFilter {
  private readonly logger = new Logger("ZodExceptionFilter");

  catch(error: ZodError, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const requestId = request.headers["X-Request-Id"];

    this.logger.error(`ZodError: ${error.message}`, {
      error,
      body: request.body,
      headers: request.headers,
      url: request.url,
      method: request.method,
      requestId,
    });

    response.status(HttpStatus.BAD_REQUEST).json({
      status: ERROR_STATUS,
      timestamp: new Date().toISOString(),
      path: request.url,
      error: {
        code: BAD_REQUEST,
        message: error.issues.reduce(
          (acc, issue) => `${issue.path.join(".")} - ${issue.message}, ${acc}`,
          ""
        ),
      },
    });
  }
}

Ensure that the id parameter in the query is an integer and that the payment data returned from the database matches the schemaPaymentPublic schema, which omits the externalId field. If there are any discrepancies, adjust the input data or the schema definitions accordingly [1][2][3].

To continue talking to Dosu, mention @dosu.

dosubot[bot] avatar Jun 20 '24 14:06 dosubot[bot]

Second this! Happens on both endpoints for me as well. It seems like an issue/error occurs when running the parse function of _paymentModel in @calcom/prisma/zod. because of data type differences. Critical issue for us since we can't retrieve payment UID for abandoned carts payment links and other payment details for internal reference.

stefanmuljadi avatar Jun 25 '24 14:06 stefanmuljadi

@PeerRich Duplicate of https://github.com/calcom/cal.com/issues/10665 which has been closed

cc @keithwillcode

asadath1395 avatar Dec 18 '24 12:12 asadath1395

cc @JDP-Neo

CarinaWolli avatar Oct 02 '25 08:10 CarinaWolli