Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@steven-tey
Copy link
Collaborator

@steven-tey steven-tey commented Oct 6, 2025

Summary by CodeRabbit

  • New Features

    • Discount creation now supports using an existing Stripe coupon or creating one automatically, with validation and resolved coupon association.
    • Partner invite emails redirect recipients to the relevant program after registration.
  • Bug Fixes

    • Clearer, user-friendly error messages for Stripe-related issues and missing coupons.
  • Refactor

    • Unified Stripe-integrated discount flow for consistent behavior.
  • Chores

    • Removed internal performance timing from group retrieval.

@vercel
Copy link
Contributor

vercel bot commented Oct 6, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Oct 6, 2025 8:04pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 6, 2025

Walkthrough

Refactors partner discount creation to a unified Stripe-integrated flow that requires a Stripe connection, validates and converts between Stripe coupons and local Dub discounts, removes a legacy coupon-creation utility, removes console.time telemetry in get-groups, and adds a next parameter to the partner invite URL.

Changes

Cohort / File(s) Summary
Unified discount creation & Stripe conversion
apps/web/lib/actions/partners/create-discount.ts, apps/web/lib/stripe/coupon-discount-converter.ts, apps/web/lib/stripe/create-stripe-coupon.ts
Integrates Stripe into main discount creation flow: initializes Stripe client per workspace, accepts existing couponId (retrieves, validates, converts) or creates a new Stripe coupon from Dub attributes, maps Stripe errors to user-friendly messages, persists resolved Stripe coupon id; adds a new converter module; removes the old create-stripe-coupon utility.
Telemetry cleanup
apps/web/lib/api/groups/get-groups.ts
Removes console.time / console.timeEnd instrumentation around getGroups query.
Partner invite link
packages/email/src/templates/partner-invite.tsx
Adds next=/programs/{program.slug} query param to the Accept Invite register URL.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Web as Web App (createDiscountAction)
  participant Stripe as Stripe Connect

  User->>Web: createDiscountAction({ workspace, couponId?, discountAttrs, couponTestId? })
  Note right of Web: init stripe client for workspace.stripeConnectId
  alt couponId provided
    Web->>Stripe: Retrieve Coupon(couponId)
    Stripe-->>Web: Stripe.Coupon / error
    alt coupon valid for Dub
      Web->>Web: stripeCouponToDubDiscount -> update local attrs
    else invalid
      Web-->>User: Validation error (mapped message)
    end
  else no couponId
    Web->>Web: dubDiscountToStripeCoupon(discountAttrs)
    Web->>Stripe: Create Coupon(params)
    Stripe-->>Web: Stripe.Coupon / error
  end

  alt success
    Web->>Web: Persist discount with resolved Stripe coupon id (or original couponId)
    Web-->>User: Discount created
  else error
    Note over Web: Map Stripe errors (permissions/resource_missing/not_found)
    Web-->>User: Mapped error message
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–90 minutes

Possibly related PRs

Suggested reviewers

  • TWilson023

Poem

A rabbit hops through Stripe and code,
Coupons found or freshly sowed.
Links now point where users go,
Logs trimmed down, the flow can grow.
Thump-thump—deploy, we cheer the road. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “Improve discount validation” accurately reflects a real and significant aspect of the changeset—namely, the enhanced validation of Stripe coupons against local discount models—though it does not explicitly mention the broader unified Stripe integration and coupon mapping.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch discount-validation

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b1d29b3 and 232bf6e.

📒 Files selected for processing (5)
  • apps/web/lib/actions/partners/create-discount.ts (3 hunks)
  • apps/web/lib/api/groups/get-groups.ts (0 hunks)
  • apps/web/lib/stripe/coupon-discount-converter.ts (1 hunks)
  • apps/web/lib/stripe/create-stripe-coupon.ts (0 hunks)
  • packages/email/src/templates/partner-invite.tsx (1 hunks)
💤 Files with no reviewable changes (2)
  • apps/web/lib/stripe/create-stripe-coupon.ts
  • apps/web/lib/api/groups/get-groups.ts

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@steven-tey
Copy link
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 6, 2025

✅ 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: 1

🧹 Nitpick comments (6)
packages/email/src/templates/partner-invite.tsx (1)

66-70: Encode the next param (or build query with URLSearchParams) to avoid malformed links

next contains slashes; encode it or use URLSearchParams for safety.

Apply minimal fix:

-                href={`https://partners.dub.co/${program.slug}/register?email=${encodeURIComponent(email)}&next=/programs/${program.slug}`}
+                href={`https://partners.dub.co/${program.slug}/register?email=${encodeURIComponent(email)}&next=${encodeURIComponent(`/programs/${program.slug}`)}`}

Optionally:

-                href={`https://partners.dub.co/${program.slug}/register?email=${encodeURIComponent(email)}&next=/programs/${program.slug}`}
+                href={`https://partners.dub.co/${program.slug}/register?${new URLSearchParams({
+                  email,
+                  next: `/programs/${program.slug}`,
+                }).toString()}`}

Please confirm the partners app consumes next as a URL-encoded value.

apps/web/lib/stripe/coupon-discount-converter.ts (3)

2-2: Use a type-only import to avoid bundling Stripe runtime into this module

Stripe is only used for types here; make it a type import.

-import { Stripe } from "stripe";
+import type { Stripe } from "stripe";

76-81: Ensure unit symmetry when converting Stripe coupon back to Dub discount

Stripe amount_off is in cents. If your Dub flat amount is in dollars, convert back to dollars; otherwise keep as-is.

-  return {
-    amount,
-    type,
-    maxDuration,
-    description: stripeCoupon.name || null,
-  };
+  return {
+    amount:
+      type === "flat"
+        ? Math.round((amount || 0) as number) / 100 // convert cents → dollars
+        : (amount as number),
+    type,
+    maxDuration,
+    description: stripeCoupon.name || null,
+  };

Please confirm the expected unit for Discount.amount in your schema to finalize this.


92-105: Strengthen validation: percent range, positive amounts, repeating duration months

Add basic guards to catch invalid coupons early.

   // Check if coupon has either percent_off or amount_off
   if (!stripeCoupon.percent_off && !stripeCoupon.amount_off) {
     errors.push("Coupon must have either percent_off or amount_off");
   }
 
   // Check if coupon has both percent_off and amount_off (invalid)
   if (stripeCoupon.percent_off && stripeCoupon.amount_off) {
     errors.push("Coupon cannot have both percent_off and amount_off");
   }
 
+  // Validate ranges
+  if (stripeCoupon.percent_off !== null && stripeCoupon.percent_off !== undefined) {
+    if (!(stripeCoupon.percent_off > 0 && stripeCoupon.percent_off <= 100)) {
+      errors.push("percent_off must be > 0 and <= 100");
+    }
+  }
+  if (stripeCoupon.amount_off !== null && stripeCoupon.amount_off !== undefined) {
+    if (!(stripeCoupon.amount_off > 0)) {
+      errors.push("amount_off must be a positive integer (in cents)");
+    }
+  }
+
   // Check currency for amount_off coupons
   if (stripeCoupon.amount_off && stripeCoupon.currency !== "usd") {
     errors.push("Amount-based coupons must use USD currency");
   }
 
+  // If repeating, ensure duration_in_months is present and > 0
+  if (stripeCoupon.duration === "repeating") {
+    if (!stripeCoupon.duration_in_months || stripeCoupon.duration_in_months <= 0) {
+      errors.push("Repeating coupons must include duration_in_months > 0");
+    }
+  }

Also applies to: 108-115

apps/web/lib/actions/partners/create-discount.ts (2)

18-18: Use a type-only import for Stripe types

Avoid pulling Stripe into the bundle from this file when only using its types.

-import { Stripe } from "stripe";
+import type { Stripe } from "stripe";

85-92: Narrow error type in catch; avoid accessing possibly-undefined fields

Accessing error.code/message on unknown is unsafe. Extract safely first.

-    } catch (error) {
-      throw new Error(
-        error.code === "more_permissions_required_for_application"
-          ? "STRIPE_APP_UPGRADE_REQUIRED: Your connected Stripe account doesn't have the permissions needed to create discount codes. Please upgrade your Stripe integration in settings or reach out to our support team for help."
-          : error.code === "resource_missing"
-            ? `The coupon ID you provided (${couponId}) was not found in your Stripe account. Please check the coupon ID and try again.`
-            : error.message,
-      );
+    } catch (err) {
+      const e = err as { code?: string; message?: string };
+      const code = e?.code;
+      const message = e?.message ?? "An unexpected error occurred";
+      throw new Error(
+        code === "more_permissions_required_for_application"
+          ? "STRIPE_APP_UPGRADE_REQUIRED: Your connected Stripe account doesn't have the permissions needed to create discount codes. Please upgrade your Stripe integration in settings or reach out to our support team for help."
+          : code === "resource_missing"
+            ? `The coupon ID you provided (${couponId}) was not found in your Stripe account. Please check the coupon ID and try again.`
+            : message,
+      );
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b1d29b3 and 232bf6e.

📒 Files selected for processing (5)
  • apps/web/lib/actions/partners/create-discount.ts (3 hunks)
  • apps/web/lib/api/groups/get-groups.ts (0 hunks)
  • apps/web/lib/stripe/coupon-discount-converter.ts (1 hunks)
  • apps/web/lib/stripe/create-stripe-coupon.ts (0 hunks)
  • packages/email/src/templates/partner-invite.tsx (1 hunks)
💤 Files with no reviewable changes (2)
  • apps/web/lib/api/groups/get-groups.ts
  • apps/web/lib/stripe/create-stripe-coupon.ts
🧰 Additional context used
🧬 Code graph analysis (2)
apps/web/lib/stripe/coupon-discount-converter.ts (1)
packages/prisma/client.ts (1)
  • RewardStructure (27-27)
apps/web/lib/actions/partners/create-discount.ts (1)
apps/web/lib/stripe/coupon-discount-converter.ts (3)
  • validateStripeCouponForDubDiscount (87-116)
  • stripeCouponToDubDiscount (52-82)
  • dubDiscountToStripeCoupon (17-47)
⏰ 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/lib/actions/partners/create-discount.ts (2)

21-23: Verify livemode flag logic for non-prod environments

This sets livemode whenever VERCEL_ENV is defined (including preview). Ensure this matches your intended behavior for test vs live on connected accounts.

Would you prefer checking VERCEL_ENV === "production" (or a workspace flag) instead?


74-80: Verify amount units before creating Stripe coupon
Stripe’s amount_off expects the smallest currency unit (cents). Confirm that parsedInput.amount is already in cents (e.g. via handleMoneyInputChange) or multiply by 100 in dubDiscountToStripeCoupon to prevent $0.10 vs $10 errors.

apps/web/lib/stripe/coupon-discount-converter.ts (1)

7-12: No mapping needed for RewardStructure
Prisma enum RewardStructure defines lowercase percentage and flat, which exactly match the code’s string‐literal checks.

@steven-tey steven-tey merged commit 27c3ee1 into main Oct 6, 2025
9 checks passed
@steven-tey steven-tey deleted the discount-validation branch October 6, 2025 20:40
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