-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Improve discount validation #2937
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughRefactors 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45–90 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (5)
💤 Files with no reviewable changes (2)
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. Comment |
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this 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 linksnext 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 moduleStripe 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 discountStripe 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 monthsAdd 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 typesAvoid 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 fieldsAccessing 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
📒 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 environmentsThis 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’samount_offexpects the smallest currency unit (cents). Confirm thatparsedInput.amountis already in cents (e.g. viahandleMoneyInputChange) or multiply by 100 indubDiscountToStripeCouponto prevent $0.10 vs $10 errors.apps/web/lib/stripe/coupon-discount-converter.ts (1)
7-12: No mapping needed for RewardStructure
Prisma enumRewardStructuredefines lowercasepercentageandflat, which exactly match the code’s string‐literal checks.
Summary by CodeRabbit
New Features
Bug Fixes
Refactor
Chores