+
Skip to content

Conversation

devkiran
Copy link
Collaborator

@devkiran devkiran commented Jul 31, 2025

Summary by CodeRabbit

  • New Features

    • Introduced automated daily renewal payment and reminder emails for expiring domains, with new cron jobs for both.
    • Added support for domain auto-renewal management, including UI controls to enable or disable auto-renewal and a dedicated modal.
    • Users now receive email notifications for domain renewal reminders, successful renewals, failed renewals, and expired domains.
    • New PDF invoice generation for domain renewals and partner payouts.
    • Expanded billing invoices page with new invoice types and improved filtering.
  • Improvements

    • Domain cards now display expiration and auto-renewal status, with direct controls for managing renewal settings.
    • Enhanced Stripe webhook handling for domain renewal payments, including retries and refund logic.
    • Improved workspace billing and payout invoice filtering and navigation.
  • Bug Fixes

    • Fixed invoice type selection and navigation in billing and payout areas to ensure correct invoice categories are shown.
  • Chores

    • Updated database schema to support domain renewal features, including tracking auto-renewal status and renewal fees.

Copy link
Contributor

vercel bot commented Jul 31, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
dub ✅ Ready (Inspect) Visit Preview Aug 6, 2025 8:48pm

Copy link
Contributor

coderabbitai bot commented Jul 31, 2025

Warning

Rate limit exceeded

@steven-tey has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 2 minutes and 57 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 73b9a16 and 0f1432c.

📒 Files selected for processing (2)
  • apps/web/app/(ee)/api/cron/invoices/retry-failed/route.ts (1 hunks)
  • apps/web/app/(ee)/api/stripe/webhook/charge-failed.ts (4 hunks)

Walkthrough

This update introduces comprehensive support for domain renewal billing and automation. It adds new cron jobs for sending renewal reminders and processing renewal payments, implements invoice management for domain renewals, and enhances Stripe webhook handling for various invoice events. The UI is updated to display and manage domain auto-renewal, and several new React email templates are provided for domain renewal notifications. Schema changes support tracking renewal fees and auto-renewal status.

Changes

Cohort / File(s) Change Summary
Domain Renewal Automation (Cron Jobs & API)
apps/web/app/(ee)/api/cron/domains/renewal-payments/route.ts, apps/web/app/(ee)/api/cron/domains/renewal-reminders/route.ts, apps/web/vercel.json
Adds daily cron jobs and API routes for sending renewal reminders and initiating renewal payments for .link domains, with signature verification and error handling.
Invoice & Stripe Webhook Handling
apps/web/app/(ee)/api/cron/invoices/retry-failed/route.ts, apps/web/app/(ee)/api/stripe/webhook/charge-failed.ts, apps/web/app/(ee)/api/stripe/webhook/charge-refunded.ts, apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts, apps/web/app/(ee)/api/stripe/webhook/route.ts
Implements retry logic for failed invoices, refactors webhook handlers to process partner payouts and domain renewals distinctly, adds refund support, and modularizes post-payment processing.
Invoice PDF Generation
apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/domain-renewal-invoice.tsx, apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/partner-payout-invoice.tsx, apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/route.tsx
Introduces dedicated React PDF components for domain renewal and partner payout invoices; refactors route to delegate PDF rendering based on invoice type.
Domain Auto-Renewal Management (UI & API)
apps/web/ui/domains/domain-card.tsx, apps/web/ui/modals/domain-auto-renewal-modal.tsx, apps/web/app/api/domains/[domain]/route.ts, apps/web/lib/dynadot/set-renew-option.ts
Adds UI and modal for toggling domain auto-renewal, extends API and Dynadot integration for updating auto-renewal status, and displays renewal/expiration status in domain cards.
Workspace Billing & Invoice Types
apps/web/app/api/workspaces/[idOrSlug]/billing/invoices/route.ts, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/invoices/page-client.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/success/page-client.tsx
Expands invoice type support to include domain renewals and partner payouts, updates UI for invoice filtering, and adjusts links to reflect new invoice types.
Stripe Payment Intent Utility
apps/web/lib/stripe/create-payment-intent.ts
Adds a utility for creating Stripe payment intents with idempotency and error logging.
Schema & Types Updates
packages/prisma/schema/domain.prisma, packages/prisma/schema/invoice.prisma, apps/web/lib/types.ts, apps/web/lib/zod/schemas/domains.ts
Updates Prisma schema and Zod schemas to support tracking of auto-renewal status, renewal fees, and new invoice types; adds relevant fields to TypeScript types.
Email Templates for Domain Renewal Events
packages/email/src/templates/domain-expired.tsx, packages/email/src/templates/domain-renewal-failed.tsx, packages/email/src/templates/domain-renewal-reminder.tsx, packages/email/src/templates/domain-renewed.tsx
Adds new React email templates for domain expiration, renewal failure, renewal reminders, and successful renewals.
Admin Payouts Filtering
apps/web/app/(ee)/api/admin/payouts/route.ts
Updates invoice filtering logic to ensure program is not null and improves mapping for invoice display.

Sequence Diagram(s)

Domain Renewal Payment Cron Flow

sequenceDiagram
    participant Vercel Cron
    participant API /renewal-payments
    participant DB
    participant Stripe

    Vercel Cron->>API /renewal-payments: GET /api/cron/domains/renewal-payments
    API /renewal-payments->>API /renewal-payments: Verify signature
    API /renewal-payments->>DB: Query domains expiring in 14 days
    API /renewal-payments->>DB: Group domains by workspace, create invoices
    loop For each invoice
        API /renewal-payments->>Stripe: Create payment intent (if Stripe customer)
    end
    API /renewal-payments-->>Vercel Cron: JSON response (OK or error)
Loading

Domain Auto-Renewal Toggle (UI & API)

sequenceDiagram
    participant User
    participant DomainCard UI
    participant DomainAutoRenewalModal
    participant API /domains/[domain]
    participant Dynadot API

    User->>DomainCard UI: Click "Enable/Disable Auto-Renew"
    DomainCard UI->>DomainAutoRenewalModal: Open modal
    User->>DomainAutoRenewalModal: Confirm action
    DomainAutoRenewalModal->>API /domains/[domain]: PATCH autoRenew
    API /domains/[domain]->>Dynadot API: Update renew option
    Dynadot API-->>API /domains/[domain]: Success/failure
    API /domains/[domain]-->>DomainAutoRenewalModal: Response
    DomainAutoRenewalModal-->>DomainCard UI: Update status
Loading

Stripe Webhook: Charge Succeeded (Domain Renewal)

sequenceDiagram
    participant Stripe
    participant Webhook Handler
    participant DB
    participant Email Service

    Stripe->>Webhook Handler: charge.succeeded event
    Webhook Handler->>DB: Fetch invoice by ID
    Webhook Handler->>DB: Update invoice status, payment info
    Webhook Handler->>DB: Extend domain expiration, enable auto-renew
    Webhook Handler->>Email Service: Send "Domain Renewed" email
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90 minutes

Suggested reviewers

  • devkiran

Poem

🐇✨
Domains renew, reminders fly,
Invoices print as time goes by.
Webhooks dance, Stripe takes the lead,
Auto-renew toggles with lightning speed.
Cron jobs hum at break of day,
Emails hop along the way.
Dub’s domains, now here to stay!

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch domain-renewal

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
  • 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 src/utils.ts and explain its main purpose.
    • @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 comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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

Documentation and Community

  • 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.

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

♻️ Duplicate comments (19)
apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/route.tsx (2)

45-55: Add error handling for PDF generation

The PDF generation functions could throw errors that aren't currently handled. Consider wrapping these calls in try-catch blocks to provide better error reporting.

  let pdf: Buffer | null = null;

-  if (invoice.type === "partnerPayout") {
-    pdf = await PartnerPayoutInvoice({
-      invoice,
-      workspace: invoice.workspace,
-    });
-  } else if (invoice.type === "domainRenewal") {
-    pdf = await DomainRenewalInvoice({
-      invoice,
-      workspace: invoice.workspace,
-    });
-  }
+  try {
+    if (invoice.type === "partnerPayout") {
+      pdf = await PartnerPayoutInvoice({
+        invoice,
+        workspace: invoice.workspace,
+      });
+    } else if (invoice.type === "domainRenewal") {
+      pdf = await DomainRenewalInvoice({
+        invoice,
+        workspace: invoice.workspace,
+      });
+    }
+  } catch (error) {
+    throw new DubApiError({
+      code: "internal_server_error",
+      message: `Failed to generate PDF for invoice ${invoiceId}: ${error.message}`,
+    });
+  }

57-62: Improve error message accuracy

The error message "Invoice not found" is misleading since the invoice was successfully retrieved from the database. The actual issue is that the PDF generation failed or the invoice type is unsupported.

  if (!pdf) {
    throw new DubApiError({
      code: "bad_request",
-     message: `Invoice ${invoiceId} not found in workspace.`,
+     message: `Failed to generate PDF for invoice ${invoiceId}. Unsupported invoice type: ${invoice.type}`,
    });
  }
apps/web/lib/dynadot/set-renew-option.ts (1)

52-60: Document fire-and-forget error handling behavior

The function catches errors without re-throwing them, making this a fire-and-forget operation. Consider documenting this behavior or re-throwing critical errors after logging.

Add a comment explaining the fire-and-forget behavior:

  } catch (error) {
+    // Log error but don't throw - this is a fire-and-forget operation
+    // to avoid blocking the main workflow if Dynadot API is unavailable
    await log({
      message: `Failed to set renew option for ${domain}: ${error.message}`,
      type: "errors",
      mention: true,
    });

    console.error(error);
  }
apps/web/app/(ee)/api/stripe/webhook/charge-refunded.ts (1)

35-46: Consider type safety for registeredDomains field

The casting of invoice.registeredDomains as string[] could be unsafe if the database contains invalid data. Consider adding runtime validation.

async function processDomainRenewalInvoice({ invoice }: { invoice: Invoice }) {
- const domains = invoice.registeredDomains as string[];
+ const domains = Array.isArray(invoice.registeredDomains) 
+   ? invoice.registeredDomains.filter((domain): domain is string => typeof domain === 'string')
+   : [];

+ if (domains.length === 0) {
+   console.log(`No valid domains found in invoice ${invoice.id}`);
+   return;
+ }

  await Promise.allSettled(
    domains.map((domain) =>
      setRenewOption({
        domain,
        autoRenew: false,
      }),
    ),
  );
}
packages/email/src/templates/domain-renewal-failed.tsx (1)

80-85: Fix domain link URLs

The domain links only use the slug as the href, which won't form valid URLs. They should include the protocol.

                     <Link
-                      href={domain.slug}
+                      href={`https://${domain.slug}`}
                       className="font-semibold text-black underline"
                     >
                       {domain.slug}
                     </Link>
packages/email/src/templates/domain-renewed.tsx (1)

68-73: Fix domain link URLs

Similar to the renewal failed template, the domain links only use the slug without the protocol, which won't form valid URLs.

                      <Link
-                        href={domain.slug}
+                        href={`https://${domain.slug}`}
                        className="font-semibold text-black underline"
                      >
                        {domain.slug}
                      </Link>
apps/web/app/(ee)/api/cron/domains/renewal-reminders/route.ts (2)

38-46: Use addDays for clarity

Using subDays with a negative value is confusing. Use addDays for better readability.

    const targetDates = REMINDER_WINDOWS.map((days) => {
-      const date = subDays(now, -days);
+      const date = addDays(now, days);

      return {
        start: startOfDay(date),
        end: endOfDay(date),
        days,
      };
    });

111-125: Add error handling for batch email sending

The batch send operation could partially fail. Consider adding error handling to track which emails failed.

    for (const reminderDomainsChunk of reminderDomainsChunks) {
-      await resend.batch.send(
-        reminderDomainsChunk.map(({ workspace, user, domain }) => ({
-          from: VARIANT_TO_FROM_MAP.notifications,
-          to: user.email!,
-          subject: "Your domain is expiring soon",
-          variant: "notifications",
-          react: DomainRenewalReminder({
-            email: user.email!,
-            workspace,
-            domain,
-          }),
-        })),
-      );
+      try {
+        await resend.batch.send(
+          reminderDomainsChunk.map(({ workspace, user, domain }) => ({
+            from: VARIANT_TO_FROM_MAP.notifications,
+            to: user.email!,
+            subject: "Your domain is expiring soon",
+            variant: "notifications",
+            react: DomainRenewalReminder({
+              email: user.email!,
+              workspace,
+              domain,
+            }),
+          })),
+        );
+      } catch (error) {
+        await log({
+          message: `Failed to send batch reminder emails: ${error.message}`,
+          type: "errors",
+        });
+        // Continue with next batch
+      }
    }
packages/email/src/templates/domain-expired.tsx (1)

83-83: Fix the domain link href to use absolute URL

The href attribute should use an absolute URL with protocol, not just the domain slug. This will create a relative link instead of linking to the actual domain.

-                        href={domain.slug}
+                        href={`https://${domain.slug}`}
packages/email/src/templates/domain-renewal-reminder.tsx (1)

62-62: Fix the domain link href to use absolute URL

The href attribute should use an absolute URL with protocol for proper linking to the domain.

-                href={domain.slug}
+                href={`https://${domain.slug}`}
apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/domain-renewal-invoice.tsx (2)

37-47: Add type validation for registeredDomains

The code assumes invoice.registeredDomains is a string array, but the schema defines it as Json?. Consider adding runtime validation or type assertions.

+  // Validate registeredDomains is an array of strings
+  const domainSlugs = Array.isArray(invoice.registeredDomains) 
+    ? invoice.registeredDomains.filter((d): d is string => typeof d === 'string')
+    : [];
+
   const domains = await prisma.registeredDomain.findMany({
     where: {
       slug: {
-        in: invoice.registeredDomains as string[],
+        in: domainSlugs,
       },
     },
     select: {
       slug: true,
       renewalFee: true,
     },
   });

111-111: Prevent division by zero error

The platform fee percentage calculation could throw a division by zero error if invoice.amount is 0.

-      label: `Platform fees (${Math.round((invoice.fee / invoice.amount) * 100)}%)`,
+      label: `Platform fees${invoice.amount > 0 ? ` (${Math.round((invoice.fee / invoice.amount) * 100)}%)` : ''}`,
apps/web/ui/modals/domain-auto-renewal-modal.tsx (2)

66-68: Add runtime guard for optional registeredDomain before using expiresAt and renewalFee

The DomainProps definition marks registeredDomain as optional, so it's possible for domain.registeredDomain to be undefined. Your current code extracts properties without first checking that registeredDomain exists.

Add a guard before extracting properties:

+  if (!domain.registeredDomain) {
+    return (
+      <Modal
+        showModal={showDomainAutoRenewalModal}
+        setShowModal={setShowDomainAutoRenewalModal}
+      >
+        <div className="p-6 text-center">
+          <p className="text-sm text-neutral-600">
+            This domain is not registered and cannot be renewed.
+          </p>
+        </div>
+      </Modal>
+    );
+  }
+
   const isEnabling = enableAutoRenewal;
   const expiresAt = domain.registeredDomain?.expiresAt;
   const renewalFee = domain.registeredDomain?.renewalFee;

82-100: Remove non-null assertions after adding proper null checks

Lines 86-87 and 93 use non-null assertions (!) which could cause runtime errors if the values are undefined. After adding the null check for registeredDomain suggested above, these assertions become safe to use.

Once the guard is in place, update the code to remove the assertions:

           {isEnabling ? (
             <>
               By enabling auto-renewal, Dub will automatically renew your domain
-              on <strong>{formatDate(expiresAt!)}</strong> for{" "}
-              {currencyFormatter(renewalFee! / 100)}.
+              on <strong>{formatDate(expiresAt)}</strong> for{" "}
+              {currencyFormatter(renewalFee / 100)}.
             </>
           ) : (
             <>
               By disabling auto-renewal, your domain{" "}
               <strong>{domain.slug}</strong> will expire on{" "}
-              <strong>{formatDate(expiresAt!)}</strong>.
+              <strong>{formatDate(expiresAt)}</strong>.
apps/web/lib/stripe/create-payment-intent.ts (1)

31-41: Improve error handling consistency

The current implementation returns early without proper error responses, while the main try-catch block uses structured logging. This creates inconsistent error handling.

   if (cards.data.length === 0 && links.data.length === 0) {
-    console.error(`No valid payment methods found for customer ${stripeId}.`);
-    return;
+    const errorMsg = `No valid payment methods found for customer ${stripeId}.`;
+    console.error(errorMsg);
+    await log({
+      message: errorMsg,
+      type: "errors",
+      mention: true,
+    });
+    throw new Error(errorMsg);
   }

   const paymentMethod = cards.data[0] || links.data[0];

   if (!paymentMethod) {
-    console.error(`No valid payment method found for customer ${stripeId}.`);
-    return;
+    const errorMsg = `No valid payment method found for customer ${stripeId}.`;
+    console.error(errorMsg);
+    await log({
+      message: errorMsg,
+      type: "errors", 
+      mention: true,
+    });
+    throw new Error(errorMsg);
   }
apps/web/app/(ee)/api/cron/domains/renewal-payments/route.ts (1)

141-146: Ensure safe error message extraction

The error might not have a message property. Use a type guard or fallback.

   } catch (error) {
     await log({
-      message: "Domains renewal cron failed. Error: " + error.message,
+      message: "Domains renewal cron failed. Error: " + (error instanceof Error ? error.message : String(error)),
       type: "errors",
     });
apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/partner-payout-invoice.tsx (1)

151-151: Prevent potential division by zero

The platform fee percentage calculation could result in division by zero if invoice.amount is 0.

-      label: `Platform fees (${Math.round((invoice.fee / invoice.amount) * 100)}%)`,
+      label: `Platform fees${invoice.amount > 0 ? ` (${Math.round((invoice.fee / invoice.amount) * 100)}%)` : ''}`,
apps/web/app/(ee)/api/stripe/webhook/charge-failed.ts (1)

182-182: Use consistent amount source for payout emails

The email uses charge.amount instead of invoice.amount. This could lead to inconsistencies if there are currency conversions or processing fees.

apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts (1)

155-169: Add error handling for email sending

The email sending operation lacks error handling, which could cause silent failures.

🧹 Nitpick comments (4)
apps/web/lib/stripe/create-payment-intent.ts (1)

58-63: Simplify idempotency key handling

The conditional object spread can be simplified for better readability.

       },
-      {
-        ...(idempotencyKey
-          ? {
-              idempotencyKey,
-            }
-          : {}),
-      },
+      idempotencyKey ? { idempotencyKey } : undefined,
apps/web/app/(ee)/api/cron/domains/renewal-payments/route.ts (1)

129-131: Remove unnecessary non-null assertion.

The stripeId is already verified to exist before this point, so the non-null assertion is redundant.

       await createPaymentIntent({
-        stripeId: workspace.stripeId!,
+        stripeId: workspace.stripeId,
         amount: invoice.total,
packages/prisma/schema/domain.prisma (1)

34-34: Consider making the renewal fee configurable.

The renewal fee is hardcoded to 1200 ($12). This might need to be configurable in the future to support different pricing tiers or promotional rates.

apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/partner-payout-invoice.tsx (1)

77-93: Simplify period calculation logic.

The reduce operations for finding earliest and latest periods could be simplified for better readability.

-  const earliestPeriodStart = payouts.reduce(
-    (acc, payout) => {
-      if (!acc) return payout.periodStart;
-      if (!payout.periodStart) return acc;
-      return payout.periodStart < (acc as Date) ? payout.periodStart : acc;
-    },
-    null as Date | null,
-  );
+  const validPeriodStarts = payouts
+    .map(p => p.periodStart)
+    .filter((d): d is Date => d !== null);
+  const earliestPeriodStart = validPeriodStarts.length > 0
+    ? new Date(Math.min(...validPeriodStarts.map(d => d.getTime())))
+    : null;

-  const latestPeriodEnd = payouts.reduce(
-    (acc, payout) => {
-      if (!acc) return payout.periodEnd;
-      if (!payout.periodEnd) return acc;
-      return payout.periodEnd > (acc as Date) ? payout.periodEnd : acc;
-    },
-    null as Date | null,
-  );
+  const validPeriodEnds = payouts
+    .map(p => p.periodEnd)
+    .filter((d): d is Date => d !== null);
+  const latestPeriodEnd = validPeriodEnds.length > 0
+    ? new Date(Math.max(...validPeriodEnds.map(d => d.getTime())))
+    : null;
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between dc74803 and fa7bfce.

📒 Files selected for processing (28)
  • apps/web/app/(ee)/api/admin/payouts/route.ts (2 hunks)
  • apps/web/app/(ee)/api/cron/domains/renewal-payments/route.ts (1 hunks)
  • apps/web/app/(ee)/api/cron/domains/renewal-reminders/route.ts (1 hunks)
  • apps/web/app/(ee)/api/cron/invoices/retry-failed/route.ts (1 hunks)
  • apps/web/app/(ee)/api/stripe/webhook/charge-failed.ts (3 hunks)
  • apps/web/app/(ee)/api/stripe/webhook/charge-refunded.ts (1 hunks)
  • apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts (2 hunks)
  • apps/web/app/(ee)/api/stripe/webhook/route.ts (3 hunks)
  • apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/domain-renewal-invoice.tsx (1 hunks)
  • apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/partner-payout-invoice.tsx (1 hunks)
  • apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/route.tsx (2 hunks)
  • apps/web/app/api/domains/[domain]/route.ts (4 hunks)
  • apps/web/app/api/workspaces/[idOrSlug]/billing/invoices/route.ts (4 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/invoices/page-client.tsx (1 hunks)
  • apps/web/lib/dynadot/set-renew-option.ts (1 hunks)
  • apps/web/lib/stripe/create-payment-intent.ts (1 hunks)
  • apps/web/lib/types.ts (1 hunks)
  • apps/web/lib/zod/schemas/domains.ts (1 hunks)
  • apps/web/ui/domains/add-edit-domain-form.tsx (6 hunks)
  • apps/web/ui/domains/domain-card.tsx (9 hunks)
  • apps/web/ui/modals/domain-auto-renewal-modal.tsx (1 hunks)
  • apps/web/vercel.json (1 hunks)
  • packages/email/src/templates/domain-expired.tsx (1 hunks)
  • packages/email/src/templates/domain-renewal-failed.tsx (1 hunks)
  • packages/email/src/templates/domain-renewal-reminder.tsx (1 hunks)
  • packages/email/src/templates/domain-renewed.tsx (1 hunks)
  • packages/prisma/schema/domain.prisma (1 hunks)
  • packages/prisma/schema/invoice.prisma (1 hunks)
🧰 Additional context used
🧠 Learnings (12)
📚 Learning: paypal webhook verification in the dub codebase is handled at the route level in `apps/web/app/(ee)/...
Learnt from: steven-tey
PR: dubinc/dub#0
File: :0-0
Timestamp: 2025-06-19T01:46:45.723Z
Learning: PayPal webhook verification in the Dub codebase is handled at the route level in `apps/web/app/(ee)/api/paypal/webhook/route.ts` using the `verifySignature` function. Individual webhook handlers like `payoutsItemFailed` don't need to re-verify signatures since they're only called after successful verification.

Applied to files:

  • apps/web/app/(ee)/api/stripe/webhook/route.ts
  • apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/route.tsx
  • apps/web/app/(ee)/api/stripe/webhook/charge-refunded.ts
  • apps/web/app/(ee)/api/cron/invoices/retry-failed/route.ts
  • apps/web/app/(ee)/api/stripe/webhook/charge-failed.ts
  • apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts
📚 Learning: in the singular integration (apps/web/app/(ee)/api/singular/webhook/route.ts), the event names in th...
Learnt from: devkiran
PR: dubinc/dub#2637
File: apps/web/app/(ee)/api/singular/webhook/route.ts:0-0
Timestamp: 2025-07-17T06:41:45.620Z
Learning: In the Singular integration (apps/web/app/(ee)/api/singular/webhook/route.ts), the event names in the singularToDubEvent object have intentionally different casing: "Copy GAID" and "copy IDFA". This casing difference is valid and should not be changed, as these are the correct event names expected from Singular.

Applied to files:

  • apps/web/app/(ee)/api/stripe/webhook/route.ts
  • apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts
📚 Learning: in apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward conditi...
Learnt from: TWilson023
PR: dubinc/dub#2673
File: apps/web/ui/partners/rewards/rewards-logic.tsx:268-275
Timestamp: 2025-07-30T15:29:54.131Z
Learning: In apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward condition, dependent fields (attribute, operator, value) should be reset rather than preserved because different entities (customer vs sale) have different available attributes. Maintaining existing fields when the entity changes would create invalid state combinations and confusing UX.

Applied to files:

  • apps/web/app/(ee)/api/admin/payouts/route.ts
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/invoices/page-client.tsx
  • apps/web/ui/modals/domain-auto-renewal-modal.tsx
  • apps/web/ui/domains/add-edit-domain-form.tsx
📚 Learning: in the partnerprogramsummary email template (packages/email/src/templates/partner-program-summary.ts...
Learnt from: devkiran
PR: dubinc/dub#2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/invoices/page-client.tsx
📚 Learning: in the dub codebase, multiple payout records can now share the same stripetransferid because payouts...
Learnt from: devkiran
PR: dubinc/dub#2635
File: packages/prisma/schema/payout.prisma:24-25
Timestamp: 2025-07-11T16:28:55.693Z
Learning: In the Dub codebase, multiple payout records can now share the same stripeTransferId because payouts are grouped by partner and processed as single Stripe transfers. This is why the unique constraint was removed from the stripeTransferId field in the Payout model - a single transfer can include multiple payouts for the same partner.

Applied to files:

  • apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/route.tsx
  • apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/partner-payout-invoice.tsx
  • apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts
📚 Learning: the `/api/workspaces/${slug}/billing/payment-methods` post endpoint in the billing api returns eithe...
Learnt from: devkiran
PR: dubinc/dub#2433
File: apps/web/ui/modals/add-payment-method-modal.tsx:60-62
Timestamp: 2025-05-29T09:49:19.604Z
Learning: The `/api/workspaces/${slug}/billing/payment-methods` POST endpoint in the billing API returns either an error (handled by response.ok check) or a response object containing a `url` property for successful requests.

Applied to files:

  • apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/route.tsx
  • apps/web/app/(ee)/api/cron/invoices/retry-failed/route.ts
  • apps/web/app/api/workspaces/[idOrSlug]/billing/invoices/route.ts
📚 Learning: when zod enum validation is used (e.g., `z.enum(payment_method_types)`), it ensures the validated va...
Learnt from: devkiran
PR: dubinc/dub#2433
File: apps/web/app/api/workspaces/[idOrSlug]/billing/payment-methods/route.ts:74-76
Timestamp: 2025-05-29T09:47:33.583Z
Learning: When Zod enum validation is used (e.g., `z.enum(PAYMENT_METHOD_TYPES)`), it ensures the validated value is one of the valid enum values, making subsequent type casts to the corresponding TypeScript types safe without additional validation.

Applied to files:

  • apps/web/app/api/workspaces/[idOrSlug]/billing/invoices/route.ts
📚 Learning: in apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition ob...
Learnt from: TWilson023
PR: dubinc/dub#2673
File: apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx:56-66
Timestamp: 2025-07-30T15:25:13.936Z
Learning: In apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition objects to allow users to add empty/unconfigured condition fields without type errors, while submission validation uses strict schemas to ensure data integrity. This two-stage validation pattern improves UX by allowing progressive completion of complex forms.

Applied to files:

  • apps/web/app/api/workspaces/[idOrSlug]/billing/invoices/route.ts
  • apps/web/ui/domains/add-edit-domain-form.tsx
📚 Learning: in apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined res...
Learnt from: devkiran
PR: dubinc/dub#2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.

Applied to files:

  • packages/email/src/templates/domain-renewal-failed.tsx
  • packages/email/src/templates/domain-renewed.tsx
📚 Learning: in apps/web/ui/partners/design/previews/lander-preview.tsx, the ellipsis wave animation delay calcul...
Learnt from: TWilson023
PR: dubinc/dub#2614
File: apps/web/ui/partners/design/previews/lander-preview.tsx:181-181
Timestamp: 2025-07-09T20:52:56.592Z
Learning: In apps/web/ui/partners/design/previews/lander-preview.tsx, the ellipsis wave animation delay calculation `3 - i * -0.15` is intentionally designed to create negative delays that offset each dot's animation cycle. This pattern works correctly for the intended ellipsis effect and should not be changed to positive incremental delays.

Applied to files:

  • apps/web/ui/modals/domain-auto-renewal-modal.tsx
  • apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/partner-payout-invoice.tsx
📚 Learning: in the dub codebase, components that use workspace data (workspaceid, defaultprogramid) are wrapped ...
Learnt from: TWilson023
PR: dubinc/dub#2538
File: apps/web/ui/partners/overview/blocks/commissions-block.tsx:16-27
Timestamp: 2025-06-18T20:26:25.177Z
Learning: In the Dub codebase, components that use workspace data (workspaceId, defaultProgramId) are wrapped in `WorkspaceAuth` which ensures these values are always available, making non-null assertions safe. This is acknowledged as a common pattern in their codebase, though not ideal.

Applied to files:

  • apps/web/ui/domains/add-edit-domain-form.tsx
📚 Learning: in the dub codebase, it's acceptable to keep `partners.dub.co` hardcoded rather than making it confi...
Learnt from: devkiran
PR: dubinc/dub#2448
File: packages/email/src/templates/partner-program-summary.tsx:254-254
Timestamp: 2025-05-29T04:49:42.842Z
Learning: In the Dub codebase, it's acceptable to keep `partners.dub.co` hardcoded rather than making it configurable for different environments.

Applied to files:

  • apps/web/ui/domains/domain-card.tsx
🧬 Code Graph Analysis (7)
apps/web/app/(ee)/api/stripe/webhook/route.ts (1)
apps/web/app/(ee)/api/stripe/webhook/charge-refunded.ts (1)
  • chargeRefunded (6-33)
apps/web/lib/dynadot/set-renew-option.ts (1)
packages/utils/src/functions/log.ts (1)
  • log (9-45)
packages/email/src/templates/domain-renewal-reminder.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (1-11)
apps/web/lib/stripe/create-payment-intent.ts (3)
apps/web/lib/stripe/index.ts (1)
  • stripe (3-9)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (1-11)
packages/utils/src/functions/log.ts (1)
  • log (9-45)
apps/web/app/api/domains/[domain]/route.ts (3)
apps/web/lib/zod/schemas/domains.ts (1)
  • updateDomainBodySchema (166-166)
apps/web/lib/api/errors.ts (1)
  • DubApiError (75-92)
apps/web/lib/dynadot/set-renew-option.ts (1)
  • setRenewOption (12-61)
apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/partner-payout-invoice.tsx (3)
apps/web/lib/stripe/index.ts (1)
  • stripe (3-9)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (1-11)
packages/utils/src/constants/misc.ts (1)
  • OG_AVATAR_URL (29-29)
apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts (2)
apps/web/lib/dynadot/set-renew-option.ts (1)
  • setRenewOption (12-61)
packages/email/src/templates/domain-renewed.tsx (1)
  • DomainRenewed (19-119)
🔇 Additional comments (23)
apps/web/app/(ee)/api/stripe/webhook/route.ts (3)

6-6: LGTM: Import follows established pattern.

The import of chargeRefunded handler is consistent with other webhook handler imports in this file.


16-16: LGTM: Event type correctly added to relevant events.

Adding "charge.refunded" to the relevantEvents set follows the established pattern for webhook event filtering.


54-56: LGTM: Switch case implementation is correct.

The new case for "charge.refunded" follows the same pattern as other webhook event handlers, properly calling the chargeRefunded function with the event parameter.

apps/web/vercel.json (1)

7-14: LGTM: Cron job configurations are correct.

The new cron jobs for domain renewal reminders and payments are properly configured with valid cron syntax. Both jobs are scheduled to run daily at 8:00 AM UTC, which appears intentional for coordinated domain renewal processing.

The paths follow the established pattern and align with the new API endpoints introduced in this PR.

apps/web/lib/types.ts (1)

235-238: LGTM: Type additions support domain renewal functionality.

The new autoRenewalDisabledAt and renewalFee properties are well-designed:

  • autoRenewalDisabledAt as nullable Date correctly represents the auto-renewal state (null = enabled, date = when disabled)
  • renewalFee as number properly types the renewal cost

These additions align with the corresponding schema updates and support the domain renewal features introduced in this PR.

apps/web/app/(ee)/api/admin/payouts/route.ts (2)

45-56: LGTM: Improved filtering logic prevents null reference errors.

The updated filtering logic is a good defensive programming improvement:

  • The AND condition ensures both programId is not ACME_PROGRAM_ID AND program is not null
  • This prevents potential null reference errors when accessing program.name and program.logo

The approach is safer than the previous single condition filter.


102-104: LGTM: Non-null assertions are now safe.

The non-null assertions for program.name and program.logo are justified by the filtering logic above that ensures program is not null. The explanatory comment makes the reasoning clear.

apps/web/lib/zod/schemas/domains.ts (1)

12-18: LGTM: Well-structured schema additions for domain renewal.

The new fields are properly defined:

  • autoRenewalDisabledAt as nullable date correctly models the auto-renewal state
  • renewalFee as number appropriately types the renewal cost
  • Both fields have clear, descriptive documentation

These schema additions align perfectly with the corresponding TypeScript interface changes and support the domain renewal functionality.

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/invoices/page-client.tsx (1)

45-46: LGTM! Invoice type tabs align with backend changes.

The addition of the "Domain Renewals" tab and the renaming of the partner payout ID from "payout" to "partnerPayout" correctly reflects the expanded invoice types supported by the system.

apps/web/app/(ee)/api/cron/invoices/retry-failed/route.ts (1)

13-81: Well-structured retry logic with proper safeguards

The implementation includes all necessary validation checks:

  • Request signature verification via Qstash
  • Invoice existence and status validation
  • Retry attempt limits (max 3)
  • Invoice type restriction to domain renewals only
  • Stripe ID requirement
  • Idempotency key to prevent duplicate charges
packages/prisma/schema/invoice.prisma (2)

7-10: LGTM! Well-structured enum for invoice types.

The InvoiceType enum cleanly separates partner payouts from domain renewals, enabling proper handling of different invoice workflows.


14-14: LGTM! Schema changes support domain renewal invoices well.

The changes are well-designed to support the new domain renewal functionality:

  • Making programId nullable allows domain renewal invoices without programs
  • The type field with default partnerPayout ensures backward compatibility
  • registeredDomains JSON field provides flexibility for storing domain data
  • failedAttempts field enables retry logic for failed payments
  • Nullable program relation aligns with the nullable programId

These changes maintain backward compatibility while enabling the new domain renewal features.

Also applies to: 18-18, 24-26, 31-31

packages/email/src/templates/domain-renewal-failed.tsx (1)

20-44: LGTM! Well-structured email template.

The email template has a good structure with proper default props, appropriate pluralization handling, and clean React Email component usage.

apps/web/app/api/domains/[domain]/route.ts (2)

22-24: LGTM! Clean schema extension.

The extended schema properly adds the optional autoRenew field while maintaining backward compatibility with the existing updateDomainBodySchema.


151-176: LGTM! Well-implemented auto-renewal sync logic.

The auto-renewal synchronization is well-designed:

  • Only processes when domain is registered and autoRenew is provided
  • Correctly determines when an update is needed to avoid unnecessary operations
  • Properly updates autoRenewalDisabledAt field (null for enabled, current date for disabled)
  • Uses waitUntil to asynchronously sync with Dynadot API without blocking the response

The logic is sound and follows good practices for external API integration.

packages/email/src/templates/domain-renewed.tsx (1)

19-42: LGTM! Clean email template structure.

The template has a well-designed structure with proper props, pluralization handling, and clear messaging for successful domain renewals.

apps/web/app/(ee)/api/cron/domains/renewal-reminders/route.ts (1)

18-30: LGTM! Well-documented cron job with clear reminder windows.

The cron job is well-structured with clear documentation of the reminder intervals and proper configuration.

apps/web/ui/domains/add-edit-domain-form.tsx (1)

65-67: LGTM! Well-structured auto-renewal feature implementation.

The auto-renewal functionality is cleanly integrated into the existing form structure. The conditional rendering ensures it only appears for Dub-provisioned domains, and the state management correctly reflects the current auto-renewal status.

Also applies to: 130-138, 154-156, 380-491, 619-624

apps/web/app/api/workspaces/[idOrSlug]/billing/invoices/route.ts (1)

10-14: LGTM! Clean refactoring to support multiple invoice types.

The consolidation of invoice handling into a generic otherInvoices function is well-implemented. The type safety through Zod validation ensures only valid invoice types are processed.

Also applies to: 24-30, 56-88

apps/web/ui/domains/domain-card.tsx (4)

104-119: LGTM! Clean implementation of expiration and auto-renewal state management.

The logic correctly derives the auto-renewal status from the autoRenewalDisabledAt field and properly checks for expired domains using isPast.


139-164: Well-structured renewal status UI with clear visual indicators.

The implementation provides excellent user feedback through color-coded states and appropriate icons. The conditional text clearly communicates whether the domain will auto-renew or expire.


334-339: Good refactoring: Hooks properly organized at the component top.

Following React best practices by placing all hooks at the beginning of the component improves code maintainability and ensures compliance with the Rules of Hooks.


495-515: Clean implementation of the auto-renewal toggle with intuitive UX.

The toggle button provides clear action labels and appropriate color coding (green for enable, red for disable) that enhances user understanding of the current state and available action.

@steven-tey
Copy link
Collaborator

@coderabbitai full review

Copy link
Contributor

coderabbitai bot commented Aug 6, 2025

✅ Actions performed

Full review triggered.

@steven-tey
Copy link
Collaborator

@coderabbitai I made a few final changes, can you do a full review please 🙏

Copy link
Contributor

coderabbitai bot commented Aug 6, 2025

@steven-tey I'll conduct a comprehensive full review of your final changes to the .link domain renewals implementation. Thank you for the iterative improvements!

✅ Actions performed

Full review triggered.

@steven-tey
Copy link
Collaborator

@coderabbitai full review please

Copy link
Contributor

coderabbitai bot commented Aug 6, 2025

@steven-tey I'll conduct a comprehensive full review of your .link domain renewals implementation. This looks like a well-structured feature with cron jobs, Stripe integration, email notifications, and UI components for auto-renewal management.

✅ Actions performed

Full review triggered.

@steven-tey
Copy link
Collaborator

@coderabbitai there was no review, can you do a full review please 🙏

Copy link
Contributor

coderabbitai bot commented Aug 6, 2025

@steven-tey I apologize for the issue with the previous review attempts. I'll trigger a comprehensive full review of your .link domain renewals implementation right now. This will cover all the components including the cron jobs, Stripe integration, email notifications, database changes, and UI components for auto-renewal management.

✅ Actions performed

Full review triggered.

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

♻️ Duplicate comments (1)
apps/web/app/(ee)/api/stripe/webhook/charge-failed.ts (1)

161-161: Use consistent amount source for payout emails

The email uses charge.amount instead of invoice.amount. This could lead to inconsistencies if there are currency conversions or processing fees.

-          amount: charge.amount,
+          amount: invoice.amount,
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b68c3fa and 73b9a16.

📒 Files selected for processing (7)
  • apps/web/app/(ee)/api/stripe/webhook/charge-failed.ts (4 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/success/page-client.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/invoices/page-client.tsx (3 hunks)
  • apps/web/lib/stripe/create-payment-intent.ts (1 hunks)
  • apps/web/lib/types.ts (1 hunks)
  • apps/web/ui/modals/domain-auto-renewal-modal.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/success/page-client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/web/lib/types.ts
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/invoices/page-client.tsx
  • apps/web/lib/stripe/create-payment-intent.ts
  • apps/web/ui/modals/domain-auto-renewal-modal.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: paypal webhook verification in the dub codebase is handled at the route level in `apps/web/app/(ee)/...
Learnt from: steven-tey
PR: dubinc/dub#0
File: :0-0
Timestamp: 2025-06-19T01:46:45.723Z
Learning: PayPal webhook verification in the Dub codebase is handled at the route level in `apps/web/app/(ee)/api/paypal/webhook/route.ts` using the `verifySignature` function. Individual webhook handlers like `payoutsItemFailed` don't need to re-verify signatures since they're only called after successful verification.

Applied to files:

  • apps/web/app/(ee)/api/stripe/webhook/charge-failed.ts
📚 Learning: the `/api/workspaces/${slug}/billing/payment-methods` post endpoint in the billing api returns eithe...
Learnt from: devkiran
PR: dubinc/dub#2433
File: apps/web/ui/modals/add-payment-method-modal.tsx:60-62
Timestamp: 2025-05-29T09:49:19.604Z
Learning: The `/api/workspaces/${slug}/billing/payment-methods` POST endpoint in the billing API returns either an error (handled by response.ok check) or a response object containing a `url` property for successful requests.

Applied to files:

  • apps/web/app/(ee)/api/stripe/webhook/charge-failed.ts
🧬 Code Graph Analysis (1)
apps/web/app/(ee)/api/stripe/webhook/charge-failed.ts (6)
packages/utils/src/functions/log.ts (1)
  • log (9-45)
apps/web/lib/stripe/create-payment-intent.ts (1)
  • createPaymentIntent (4-76)
apps/web/lib/partners/constants.ts (1)
  • PAYOUT_FAILURE_FEE_CENTS (7-7)
apps/web/lib/dynadot/set-renew-option.ts (1)
  • setRenewOption (12-61)
packages/email/src/templates/domain-expired.tsx (1)
  • DomainExpired (19-124)
packages/email/src/templates/domain-renewal-failed.tsx (1)
  • DomainRenewalFailed (20-124)
⏰ 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
🔇 Additional comments (3)
apps/web/app/(ee)/api/stripe/webhook/charge-failed.ts (3)

1-18: LGTM on the new imports.

The additional imports properly support the new domain renewal functionality and follow the established patterns in the codebase.


20-59: Excellent refactor improving code organization.

The separation of concerns between partner payouts and domain renewals makes the code more maintainable. The early return pattern and type-based delegation are well implemented.


192-192: Confirm registeredDomains nullability and usage

Before trusting the as string[] assertion, please verify how registeredDomains is defined in your Prisma schema and how it’s populated:

  • Check the Invoice model in your Prisma schema: is registeredDomains declared as a non-nullable String[]?
  • If it’s non-nullable: you can remove the redundant as string[] cast – Prisma will already type it as string[].
  • If it’s optional (String[]?): add a runtime guard (e.g. default to [] or throw) to avoid unexpected null/undefined at runtime.

@steven-tey steven-tey merged commit 31a72b8 into main Aug 6, 2025
9 checks passed
@steven-tey steven-tey deleted the domain-renewal branch August 6, 2025 20:54
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.

2 participants

点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载