Skip to content

Conversation

@aimensahnoun
Copy link
Member

@aimensahnoun aimensahnoun commented Aug 20, 2025

Problem:

TRPC throwing wrong errors

Solution:

Update the code to throw correct TRPC errors, in all of routers.

image

Summary by CodeRabbit

  • New Features

    • New endpoint to fetch a user’s active subscriptions.
    • New "Get all plans" endpoint (currently returns an empty list).
  • Bug Fixes

    • API now returns more accurate HTTP status codes for errors (e.g., 400, 401, 404, 422, 429) instead of generic 500s.
    • More consistent error responses across payments, invoices, compliance, recurring payments, and subscription plans.
  • Refactor

    • Centralized error handling and translation for standardized, predictable API error behavior.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 20, 2025

Walkthrough

Centralizes error handling with a new toTRPCError/ResourceNotFoundError and maps tRPC error codes to HTTP statuses via responseMeta in the tRPC route. Multiple routers now wrap logic in try/catch and delegate error translation. subscription-plan adds getUserActiveSubscriptions and a getAllPlans stub.

Changes

Cohort / File(s) Summary of changes
Error handling infrastructure & HTTP status mapping
src/lib/errors.ts, src/app/api/trpc/[trpc]/route.ts
Added toTRPCError and ResourceNotFoundError; added TRPC→HTTP status map and getHttpStatusFromTrpcError; integrated responseMeta to set HTTP status from first tRPC error.
Routers adopting centralized error handling
src/server/routers/compliance.ts, src/server/routers/invoice-me.ts, src/server/routers/invoice.ts, src/server/routers/payment.ts, src/server/routers/recurring-payment.ts, src/server/routers/subscription-plan.ts
Replaced scattered TRPCError constructions with toTRPCError(error) in catch blocks; wrapped DB/API logic in try/catch. subscription-plan adds getUserActiveSubscriptions and a stub getAllPlans.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant TRPCRoute as "tRPC Route"
  participant RouterProc as "Router Procedure"
  participant ErrorMapper as "toTRPCError"
  participant RespMeta as "responseMeta"

  Client->>TRPCRoute: HTTP tRPC request (GET/POST)
  TRPCRoute->>RouterProc: invoke procedure
  alt success
    RouterProc-->>TRPCRoute: result
    TRPCRoute->>RespMeta: responseMeta({ result, errors: [] })
    RespMeta-->>TRPCRoute: set status 200
    TRPCRoute-->>Client: HTTP 200 + data
  else error
    RouterProc->>ErrorMapper: toTRPCError(error)
    ErrorMapper-->>RouterProc: TRPCError(code, message)
    RouterProc-->>TRPCRoute: returns TRPC Error
    TRPCRoute->>RespMeta: responseMeta({ errors: [TRPCError] })
    RespMeta-->>TRPCRoute: set status (map TRPC→HTTP)
    TRPCRoute-->>Client: HTTP <mapped status> + error
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • rodrigopavezi
  • bassgeta
  • MantisClone

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 54d1750 and 418ee77.

📒 Files selected for processing (1)
  • src/lib/errors.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/errors.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 111-easyinvoice---improve-trpc-error-propogation-we-_do_-propogate-but-with-generic-error-messages

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@aimensahnoun aimensahnoun marked this pull request as ready for review August 20, 2025 09:59
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (23)
src/lib/errors.ts (3)

12-35: Map more HTTP statuses to precise tRPC codes (405, 408, 412, 413, 504).

This improves fidelity and client UX by returning the most accurate tRPC code for common scenarios.

Apply this diff to extend mappings, including timeout handling for 408/504:

 function mapStatusCodeToTRPCCode(statusCode: number): TRPC_ERROR_CODE_KEY {
-  if (statusCode >= 400 && statusCode < 500) {
+  // Explicit timeout handling
+  if (statusCode === 408 || statusCode === 504) {
+    return "TIMEOUT";
+  }
+
+  if (statusCode >= 400 && statusCode < 500) {
     switch (statusCode) {
       case 400:
         return "BAD_REQUEST";
+      case 405:
+        return "METHOD_NOT_SUPPORTED";
       case 401:
         return "UNAUTHORIZED";
       case 403:
         return "FORBIDDEN";
       case 404:
         return "NOT_FOUND";
+      case 412:
+        return "PRECONDITION_FAILED";
       case 409:
         return "CONFLICT";
+      case 413:
+        return "PAYLOAD_TOO_LARGE";
       case 422:
         return "UNPROCESSABLE_CONTENT";
       case 429:
         return "TOO_MANY_REQUESTS";
       default:
         return "BAD_REQUEST";
     }
   }
 
   return "INTERNAL_SERVER_ERROR";
 }

71-78: Consider reducing message leakage for unknown throwables (optional).

If non-Error values get thrown, returning their message could leak internal details. In production, you may want a generic message while keeping the original as cause.

-  const message =
-    error instanceof Error ? error.message : "Unknown error occurred";
+  const message =
+    process.env.NODE_ENV === "production"
+      ? "Internal server error"
+      : error instanceof Error
+        ? error.message
+        : "Unknown error occurred";

37-78: Add unit tests to lock down toTRPCError behavior across inputs.

This function is now a critical path. Suggest tests for:

  • TRPCError passthrough
  • AxiosError: 400/401/403/404/409/412/413/422/429, 408/504 (timeout), network error (no response)
  • ResourceNotFoundError mapping
  • Generic Error with status/statusCode and without

I can scaffold a jest/vitest test suite covering these cases if you want.

src/server/routers/invoice.ts (1)

135-143: Consistent error propagation: wrap all API/DB interactions in try/catch using toTRPCError.

This block is now correct. For consistency across the router, consider updating sendPaymentIntent to also catch and translate Axios errors; otherwise, network/API failures will surface as generic INTERNAL_SERVER_ERROR without proper HTTP mapping.

Proposed change for sendPaymentIntent (outside this hunk):

// sendPaymentIntent
.mutation(async ({ input }) => {
  const { paymentIntent, payload } = input;

  try {
    const response = await apiClient.post(
      `/v2/request/payment-intents/${paymentIntent}`,
      payload,
    );
    return response.data;
  } catch (error) {
    throw toTRPCError(error);
  }
}),
src/server/routers/compliance.ts (2)

4-4: Centralized error translation imported—consider applying to remaining handlers for consistency.

Most procedures now use toTRPCError, but submitComplianceInfo and the outer catch in getComplianceStatus still manually map and default to INTERNAL_SERVER_ERROR. For uniformity and future maintainability, consider switching those to toTRPCError (retaining the 404-special-case return in getComplianceStatus).

If helpful, I can provide a patch to refactor those blocks as well.


385-401: Avoid logging full upstream response bodies; redact or summarize to minimize PII leakage.

The JSON-stringified Axios response body can contain PII. Keep context (status, code, message) but redact payloads.

Apply this diff to minimize sensitive logging while preserving diagnostics:

-        console.error(
-          "Error allowing payment details:",
-          error instanceof AxiosError
-            ? JSON.stringify(error.response?.data)
-            : error instanceof Error
-              ? error.message
-              : error,
-        );
+        console.error("Error allowing payment details:", {
+          status: error instanceof AxiosError ? error.response?.status : undefined,
+          code: error instanceof AxiosError ? error.code : undefined,
+          message: error instanceof AxiosError
+            ? (typeof error.response?.data === "string"
+                ? error.response?.data
+                : (error.response?.data as any)?.message) ?? error.message
+            : error instanceof Error
+              ? error.message
+              : String(error),
+        });
src/server/routers/recurring-payment.ts (3)

122-127: Redundant status check: axios throws on non-2xx by default.

The manual response.status check won’t execute for non-2xx unless validateStatus is customized. Rely on axios throwing and let toTRPCError map the failure.

-        if (response.status !== 200) {
-          throw new TRPCError({
-            code: "INTERNAL_SERVER_ERROR",
-            message: "Failed to update recurring payment",
-          });
-        }
+        // No-op: axios throws for non-2xx; if we get here, it's successful.

188-194: Apply the same URL path fix and typing here.

Mirror the leading slash and generic typing for consistent behavior.

-        const response = await apiClient.patch(
-          `v2/payouts/recurring/${input.externalPaymentId}`,
+        const response = await apiClient.patch<UpdateRecurringPaymentResponse>(
+          `/v2/payouts/recurring/${input.externalPaymentId}`,
           {
             action: input.action,
           },
         );

195-200: Remove redundant status check here as well.

Same rationale as above; let axios + toTRPCError handle failures.

-        if (response.status !== 200) {
-          throw new TRPCError({
-            code: "INTERNAL_SERVER_ERROR",
-            message: "Failed to update recurring payment",
-          });
-        }
+        // No-op: axios throws for non-2xx; reaching here implies success.
src/server/routers/subscription-plan.ts (3)

56-69: Optional: detect no-op deletes and return NOT_FOUND.

Currently, deactivating a non-existent or non-owned plan silently succeeds. Consider checking affected rows and throwing NOT_FOUND to improve API ergonomics.

Example (outside this hunk):

const res = await db.update(...).where(...).returning({ id: subscriptionPlanTable.id });
if (res.length === 0) throw new TRPCError({ code: "NOT_FOUND", message: "Plan not found" });

146-211: LGTM: getAllPayments composes SubscriptionPayment DTOs and maps errors.

Minor nit: paymentNumber is derived from local array index; if payments are not guaranteed sorted, you might want to sort by date first for deterministic numbering.


257-282: Filter out cancelled entries for “active subscriptions.”

Given the name, returning cancelled entries may surprise clients. Filter status to exclude "cancelled" (or include only "active") for clarity.

-      const userSubscriptions = await db.query.recurringPaymentTable.findMany({
-        where: and(
-          eq(recurringPaymentTable.userId, user.id),
-          isNotNull(recurringPaymentTable.subscriptionId),
-        ),
+      const userSubscriptions = await db.query.recurringPaymentTable.findMany({
+        where: and(
+          eq(recurringPaymentTable.userId, user.id),
+          isNotNull(recurringPaymentTable.subscriptionId),
+          not(eq(recurringPaymentTable.status, "cancelled")),
+        ),
         orderBy: desc(recurringPaymentTable.createdAt),
         with: {
           subscription: {
             columns: {
               id: true,
               label: true,
               trialDays: true,
             },
           },
         },
       });
src/server/routers/payment.ts (5)

15-20: Authorization checks are redundant under protectedProcedure

protectedProcedure already enforces auth. These explicit user checks are duplicate and add noise.

Apply these diffs to remove the redundant checks:

@@
-        if (!user) {
-          throw new TRPCError({
-            code: "UNAUTHORIZED",
-            message: "You must be logged in to pay",
-          });
-        }
@@
-        if (!user) {
-          throw new TRPCError({
-            code: "UNAUTHORIZED",
-            message: "You must be logged in to pay",
-          });
-        }

Also applies to: 53-58


40-45: Simplify catch: toTRPCError already passes through TRPCError

toTRPCError returns the original TRPCError instance. The explicit instanceof check is unnecessary.

-      } catch (error) {
-        if (error instanceof TRPCError) {
-          throw error;
-        }
-        throw toTRPCError(error);
-      }
+      } catch (error) {
+        throw toTRPCError(error);
+      }

76-81: Same catch simplification applies here

Mirror the simplified error handling in batchPay.

-      } catch (error) {
-        if (error instanceof TRPCError) {
-          throw error;
-        }
-        throw toTRPCError(error);
-      }
+      } catch (error) {
+        throw toTRPCError(error);
+      }

22-24: Trim env vars to avoid sending empty fee fields

Empty strings from envs evaluate truthy and would incorrectly include fee fields (in pay). Trim to avoid this edge case.

-        const feePercentage = process.env.FEE_PERCENTAGE_FOR_PAYMENT;
-        const feeAddress = process.env.FEE_ADDRESS_FOR_PAYMENT;
+        const feePercentage = process.env.FEE_PERCENTAGE_FOR_PAYMENT?.trim();
+        const feeAddress = process.env.FEE_ADDRESS_FOR_PAYMENT?.trim();

60-73: Align fee field handling with pay; omit fee keys unless both envs are present

Currently, batchPay always includes feePercentage/feeAddress keys (potentially undefined), which is inconsistent with pay. Conditionally include them only when both envs exist.

-        const response = await apiClient.post("v2/payouts/batch", {
-          requests: input.payouts
-            ? input.payouts.map((payout) => ({
-                amount: payout.amount.toString(),
-                payee: payout.payee,
-                invoiceCurrency: payout.invoiceCurrency,
-                paymentCurrency: payout.paymentCurrency,
-                feePercentage: process.env.FEE_PERCENTAGE_FOR_PAYMENT,
-                feeAddress: process.env.FEE_ADDRESS_FOR_PAYMENT,
-              }))
-            : undefined,
-          requestIds: input.requestIds,
-          payer: input.payer,
-        });
+        const feePercentage = process.env.FEE_PERCENTAGE_FOR_PAYMENT?.trim();
+        const feeAddress = process.env.FEE_ADDRESS_FOR_PAYMENT?.trim();
+
+        const response = await apiClient.post("v2/payouts/batch", {
+          requests: input.payouts
+            ? input.payouts.map((payout) => ({
+                amount: payout.amount.toString(),
+                payee: payout.payee,
+                invoiceCurrency: payout.invoiceCurrency,
+                paymentCurrency: payout.paymentCurrency,
+                ...(feePercentage && feeAddress
+                  ? { feePercentage, feeAddress }
+                  : {}),
+              }))
+            : undefined,
+          requestIds: input.requestIds,
+          payer: input.payer,
+        });
src/app/api/trpc/[trpc]/route.ts (2)

25-43: Robust translator for both string and numeric error codes

Handling both TRPC_ERROR_CODES_BY_KEY and raw numeric codes ensures compatibility across adapters and versions. Defaulting to 500 is reasonable.

Optional follow-up: consider extracting getHttpStatusFromTrpcError into a small shared util if you anticipate using it outside this route.


70-78: Batch semantics: using the first error to set status is acceptable, but note trade-offs

For batched calls, this sets a single HTTP status based on the first error. This matches typical tRPC patterns, but the "worst" error might be later in the list.

No action required; just documenting the trade-off.

src/server/routers/invoice-me.ts (4)

19-24: Redundant auth checks under protectedProcedure

protectedProcedure already guarantees an authenticated user in ctx. These explicit checks are not needed and add duplication.

@@
-        if (!user) {
-          throw new TRPCError({
-            code: "UNAUTHORIZED",
-            message: "You must be logged in to create an invoice me link",
-          });
-        }
@@
-        if (!user) {
-          throw new TRPCError({
-            code: "UNAUTHORIZED",
-            message: "You must be logged in to delete an invoice me link",
-          });
-        }

Also applies to: 60-66


26-31: Return created link ID to the client

As written, create returns undefined. Returning the new ID improves UX (e.g., redirect/copy link) and avoids an immediate follow-up query.

-        await db.insert(invoiceMeTable).values({
-          id: ulid(),
-          label: input.label,
-          userId: user.id,
-        });
+        const newId = ulid();
+        await db.insert(invoiceMeTable).values({
+          id: newId,
+          label: input.label,
+          userId: user.id,
+        });
+        return { id: newId };

If the client already expects no return value, ignore this. Otherwise, consider adding this now to avoid an extra read.


84-93: Public endpoint exposes user email; verify PII requirements

getById is a publicProcedure (which aligns with prior decisions for shareable endpoints), but it returns the user’s email. If the link is shared publicly, this might be an unnecessary PII exposure.

           user: {
             columns: {
               name: true,
-              email: true,
               id: true,
             },
           },

Confirm whether the UI needs the email. If not, removing it reduces exposure surface.


68-76: Optionally report NOT_FOUND when delete affects no rows

Verified that .returning() is already in use across the codebase (e.g., in src/server/routers/recurring-payment.ts, src/server/routers/invoice.ts, src/app/api/webhook/route.ts), so the DB driver supports it. You can enhance the delete path in src/server/routers/invoice-me.ts to throw a NOT_FOUND error when nothing is removed:

• File: src/server/routers/invoice-me.ts
Lines: 68–76

-        await db
-          .delete(invoiceMeTable)
-          .where(
-            and(
-              eq(invoiceMeTable.id, input),
-              eq(invoiceMeTable.userId, user.id),
-            ),
-          );
+        const deleted = await db
+          .delete(invoiceMeTable)
+          .where(
+            and(
+              eq(invoiceMeTable.id, input),
+              eq(invoiceMeTable.userId, user.id),
+            ),
+          )
+          .returning({ id: invoiceMeTable.id });
+        if (deleted.length === 0) {
+          throw new TRPCError({ code: "NOT_FOUND", message: "Invoice link not found" });
+        }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f022688 and 54d1750.

📒 Files selected for processing (8)
  • src/app/api/trpc/[trpc]/route.ts (2 hunks)
  • src/lib/errors.ts (1 hunks)
  • src/server/routers/compliance.ts (6 hunks)
  • src/server/routers/invoice-me.ts (3 hunks)
  • src/server/routers/invoice.ts (3 hunks)
  • src/server/routers/payment.ts (3 hunks)
  • src/server/routers/recurring-payment.ts (5 hunks)
  • src/server/routers/subscription-plan.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-02-14T12:48:42.125Z
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#8
File: src/app/invoice-me/page.tsx:21-21
Timestamp: 2025-02-14T12:48:42.125Z
Learning: In the easy-invoice codebase, error handling for TRPC queries is not required at the component level as confirmed by the maintainer.

Applied to files:

  • src/server/routers/invoice.ts
  • src/server/routers/invoice-me.ts
  • src/server/routers/payment.ts
📚 Learning: 2025-02-12T12:40:14.742Z
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/server/routers/invoice.ts:88-109
Timestamp: 2025-02-12T12:40:14.742Z
Learning: The payRequest endpoint in src/server/routers/invoice.ts is intentionally kept public (using publicProcedure) to allow invoice sharing and payment by anyone with the payment reference, similar to how payment links work in other payment systems.

Applied to files:

  • src/server/routers/invoice-me.ts
  • src/server/routers/payment.ts
🧬 Code Graph Analysis (6)
src/server/routers/recurring-payment.ts (3)
src/server/db/schema.ts (2)
  • recurringPaymentTable (218-257)
  • subscriptionPlanTable (283-300)
src/lib/errors.ts (1)
  • toTRPCError (37-78)
src/lib/axios.ts (1)
  • apiClient (3-8)
src/server/routers/invoice.ts (1)
src/lib/errors.ts (1)
  • toTRPCError (37-78)
src/server/routers/invoice-me.ts (3)
src/server/db/schema.ts (1)
  • invoiceMeTable (272-281)
src/lib/errors.ts (1)
  • toTRPCError (37-78)
src/server/trpc.ts (2)
  • protectedProcedure (39-39)
  • publicProcedure (12-12)
src/server/routers/payment.ts (4)
src/lib/axios.ts (1)
  • apiClient (3-8)
src/lib/errors.ts (1)
  • toTRPCError (37-78)
src/server/trpc.ts (1)
  • protectedProcedure (39-39)
src/lib/schemas/payment.ts (1)
  • batchPaymentApiSchema (55-67)
src/server/routers/compliance.ts (1)
src/lib/errors.ts (1)
  • toTRPCError (37-78)
src/server/routers/subscription-plan.ts (4)
src/server/db/schema.ts (2)
  • subscriptionPlanTable (283-300)
  • recurringPaymentTable (218-257)
src/lib/errors.ts (1)
  • toTRPCError (37-78)
src/server/trpc.ts (1)
  • protectedProcedure (39-39)
src/lib/types/index.ts (1)
  • SubscriptionPayment (22-35)
🔇 Additional comments (20)
src/lib/errors.ts (1)

57-69: LGTM on custom status propagation for generic Errors.

Gracefully handling Error instances with status/statusCode covers common server-side patterns.

src/server/routers/invoice.ts (2)

2-2: Good move: centralizing error translation via toTRPCError.

This aligns the router with the new error handling approach.


186-193: LGTM on createFromInvoiceMe error mapping.

Standardized catch-to-TRPC behavior looks good.

src/server/routers/compliance.ts (4)

131-134: LGTM: updateAgreementStatus now standardizes error mapping.

Error propagation is now uniform and will pick up AxiosError status mapping.


470-473: LGTM: getPaymentDetails now delegates to toTRPCError.

Good consistency with the centralized error handling approach.


533-534: LGTM: getPaymentDetailsById catch path standardized.

Rethrowing TRPCError and otherwise delegating to toTRPCError is solid.


565-567: LGTM: getUserByEmail adopts toTRPCError for non-TRPCError failures.

This brings the procedure in line with the new error handling strategy.

src/server/routers/recurring-payment.ts (2)

29-44: LGTM: centralized error handling on listing.

Wrapping the DB query with toTRPCError ensures consistent error propagation.


52-82: LGTM: createRecurringPayment now properly normalizes failures.

The try/catch with toTRPCError is correct.

src/server/routers/subscription-plan.ts (6)

1-1: LGTM: importing toTRPCError to standardize error propagation.

Good step toward uniform error handling across these procedures.


16-31: LGTM: create wrapped with toTRPCError.

Insert path is clear and errors are normalized.


36-49: LGTM: getAll now returns errors consistently via toTRPCError.

Looks good.


76-105: LGTM: getById preserves NOT_FOUND semantics and normalizes other failures.

Good.


141-145: LGTM: getAllSubscribers error handling standardized.

Consistent with the rest of the router.


217-256: LGTM: owner check and NOT_FOUND behavior are correct; errors centralized.

No issues spotted.

src/server/routers/payment.ts (2)

102-114: LGTM: Consistent error translation and axios handling

The try/catch pattern with toTRPCError aligns with the centralized error handling objective and leverages axios to surface non-2xx responses correctly.


60-74: Confirm external API payload property for batch payouts

The internal schema and client-side validation use payouts (and requestIds), but the outgoing call sends a requests array. I didn’t find any other API client calls using requests for this endpoint. Please verify against the v2/payouts/batch contract that the endpoint expects a requests field; if it actually expects payouts, update the payload accordingly to prevent 4xx errors.

Locations to check:

  • src/server/routers/payment.ts: lines 60–74
src/app/api/trpc/[trpc]/route.ts (1)

8-23: Good, explicit TRPC→HTTP mapping

The mapping covers the standard tRPC error keys, including UNPROCESSABLE_CONTENT and CLIENT_CLOSED_REQUEST. This makes responses predictable for clients.

src/server/routers/invoice-me.ts (2)

45-53: LGTM: Query and error translation are consistent

The query is scoped to the authenticated user and wrapped in the centralized error handler.


97-102: LGTM: Proper NOT_FOUND signal for missing links

Correct use of TRPCError with NOT_FOUND here; it will map to 404 via the new responseMeta.

@aimensahnoun aimensahnoun merged commit 41acd52 into main Aug 21, 2025
5 checks passed
@aimensahnoun aimensahnoun deleted the 111-easyinvoice---improve-trpc-error-propogation-we-_do_-propogate-but-with-generic-error-messages branch August 21, 2025 08:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EasyInvoice - Improve tRPC Error Propogation (we _do_ propogate but with generic error messages)

3 participants