Skip to content

improvement(promos): promo codes should be only stripe codes#3591

Merged
icecrasher321 merged 2 commits intostagingfrom
improvement/referral-campaigns
Mar 14, 2026
Merged

improvement(promos): promo codes should be only stripe codes#3591
icecrasher321 merged 2 commits intostagingfrom
improvement/referral-campaigns

Conversation

@icecrasher321
Copy link
Collaborator

@icecrasher321 icecrasher321 commented Mar 14, 2026

Summary

Moving promo code system to be only based on stripe coupons. Update admin route as well. Cleanup old code.

Type of Change

  • Other: UX Improvement

Testing

Tested using Stripe sandbox env

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel
Copy link

vercel bot commented Mar 14, 2026

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Mar 14, 2026 11:16pm

Request Review

@cursor
Copy link

cursor bot commented Mar 14, 2026

PR Summary

High Risk
Replaces a DB-backed referral/bonus-credit system with Stripe coupon/promotion-code management and drops the underlying tables, so mistakes could break promo workflows or cause irreversible data loss. Also removes runtime attribution/redemption endpoints and UI paths, which may impact existing integrations relying on those routes.

Overview
Switches the Admin referral-campaigns API from CRUD on referral_campaigns rows to Stripe-backed promotion code management: GET now lists Stripe promotion codes via cursor pagination, and POST creates a Stripe coupon + promotion code with validation, Stripe error handling, and coupon cleanup on failure.

Removes the legacy referral/bonus-credit system end-to-end: deletes /api/attribution and /api/referral-code/redeem, drops the ReferralCode UI and associated React Query hook, removes the UTM cookie capture + client attribution hook, deletes applyBonusCredits, and adds a migration to drop referral_attribution and referral_campaigns tables (plus related admin types/routes).

Written by Cursor Bugbot for commit 3cedb74. Configure here.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 14, 2026

Greptile Summary

This PR migrates the promo/referral code system from an internal DB-backed implementation to Stripe-native promotion codes. All custom referral infrastructure (DB tables, API routes for UTM attribution and code redemption, the client-side attribution hook, and the UTM cookie logic in the proxy) is deleted, and the admin endpoint is rewritten to create and list Stripe coupons and promotion codes directly.

Key changes:

  • Deleted: referral_campaigns and referral_attribution DB tables (via DROP TABLE … CASCADE migration), applyBonusCredits helper, UTM cookie middleware, and the /api/attribution + /api/referral-code/redeem endpoints.
  • Rewritten: /api/v1/admin/referral-campaigns now wraps stripe.promotionCodes.list() and stripe.coupons.create() + stripe.promotionCodes.create().
  • Removed from UI: The inline referral code redemption widget in subscription settings.
  • Critical gap: When creating a promo code, stripe.coupons.create() runs first. If the subsequent stripe.promotionCodes.create() call fails (e.g. code already exists), the coupon is permanently orphaned in Stripe with no cleanup logic.
  • Type safety: listParams is typed as Record<string, unknown> instead of Stripe.PromotionCodeListParams, bypassing SDK-level type checking.
  • Stripe error handling: Uses manual duck-typing ('type' in error) rather than error instanceof Stripe.errors.StripeInvalidRequestError.

Confidence Score: 3/5

  • Mostly safe to merge, but the orphaned-coupon race condition and irreversible data-dropping migration warrant fixes before production.
  • The architectural direction is sound and the cleanup is thorough. However, there is one clear logic bug (orphaned Stripe coupon on partial failure), a type-safety regression in the Stripe API call, and a destructive no-rollback DB migration. None of these are show-stoppers in isolation, but the orphaned-coupon issue should be addressed before merging to avoid polluting the Stripe account with stray coupons.
  • apps/sim/app/api/v1/admin/referral-campaigns/route.ts (orphaned coupon + type safety) and packages/db/migrations/0174_whole_lyja.sql (irreversible data loss).

Important Files Changed

Filename Overview
apps/sim/app/api/v1/admin/referral-campaigns/route.ts Rewrites the admin referral campaigns endpoint to use Stripe promotion codes directly. Contains a critical logic gap: if coupon creation succeeds but promotion code creation fails, the coupon is orphaned in Stripe. Also uses Record<string, unknown> instead of the proper Stripe SDK type for list params, and duck-types Stripe errors instead of using the SDK's error classes.
packages/db/migrations/0174_whole_lyja.sql Drops referral_attribution and referral_campaigns tables with CASCADE — a permanently destructive, irreversible migration with no data archival or rollback path.
apps/sim/app/api/attribution/route.ts Deleted — the UTM-based automatic attribution endpoint is fully removed as part of the shift to Stripe-managed promo codes.
apps/sim/app/api/referral-code/redeem/route.ts Deleted — the manual code-redemption endpoint (with its enterprise/org restrictions and DB writes) is removed, with redemption now delegated entirely to Stripe.
apps/sim/lib/billing/credits/bonus.ts Deleted — the applyBonusCredits helper (which added credits to user/org balances after referral redemption) is removed cleanly; no remaining callers were found.
apps/sim/proxy.ts Removes UTM cookie injection (setUtmCookie) from the middleware proxy — cleans up the tracking infrastructure no longer needed.
apps/sim/app/workspace/[workspaceId]/settings/components/subscription/subscription.tsx Removes the ReferralCode inline redemption widget from the subscription settings UI; the ReferralCode import and its conditional render block are cleanly deleted.
apps/sim/app/api/v1/admin/types.ts Removes DbReferralCampaign, AdminReferralCampaign, and toAdminReferralCampaign types/helpers that were tied to the old DB-backed campaign system.

Sequence Diagram

sequenceDiagram
    participant Admin as Admin Client
    participant API as /api/v1/admin/referral-campaigns
    participant Stripe as Stripe API

    Note over Admin,Stripe: Before this PR — DB-backed system
    Admin->>API: POST (name, bonusCreditAmount, code, utm*)
    API->>API: Insert into referral_campaigns table
    API-->>Admin: { id, code, bonusCreditAmount, ... }

    Admin->>API: POST /api/referral-code/redeem (code)
    API->>API: Lookup campaign in DB
    API->>API: Insert referralAttribution + apply bonus credits
    API-->>Admin: { redeemed: true, bonusAmount }

    Note over Admin,Stripe: After this PR — Stripe-only system
    Admin->>API: POST (name, percentOff, code, duration, ...)
    API->>Stripe: stripe.coupons.create(...)
    Stripe-->>API: Coupon object
    API->>Stripe: stripe.promotionCodes.create(coupon, code, ...)
    Stripe-->>API: PromotionCode object
    API-->>Admin: { id, code, couponId, percentOff, ... }

    Admin->>API: GET (?limit, ?starting_after, ?active)
    API->>Stripe: stripe.promotionCodes.list(...)
    Stripe-->>API: Paginated PromotionCode list
    API-->>Admin: { data[], hasMore, nextCursor }

    Note over API,Stripe: ⚠️ If promotionCodes.create fails,<br/>the already-created coupon is orphaned in Stripe
Loading

Comments Outside Diff (3)

  1. apps/sim/app/api/v1/admin/referral-campaigns/route.ts, line 789-803 (link)

    Orphaned Stripe coupon on promo code creation failure

    If stripe.coupons.create() succeeds but stripe.promotionCodes.create() subsequently fails (e.g., the requested code already exists in Stripe, or max_redemptions / expires_at violates Stripe constraints), the newly-created coupon is left dangling in Stripe with no associated promotion code. There is no cleanup or rollback path.

    const coupon = await stripe.coupons.create({ ... })
    // ^^^ If this succeeds but the line below fails, `coupon` is orphaned in Stripe
    
    const promoCode = await stripe.promotionCodes.create(promoParams)

    Consider deleting the coupon if promotion code creation fails:

    const coupon = await stripe.coupons.create({ ... })
    
    let promoCode: Stripe.PromotionCode
    try {
      promoCode = await stripe.promotionCodes.create(promoParams)
    } catch (promoError) {
      // Clean up the orphaned coupon
      try {
        await stripe.coupons.del(coupon.id)
      } catch (cleanupError) {
        logger.error('Admin API: Failed to clean up orphaned coupon', { couponId: coupon.id, cleanupError })
      }
      throw promoError
    }
  2. apps/sim/app/api/v1/admin/referral-campaigns/route.ts, line 671-679 (link)

    Record<string, unknown> bypasses Stripe type safety

    listParams is typed as Record<string, unknown> instead of the SDK's Stripe.PromotionCodeListParams. This means TypeScript will not catch misspelled or invalid parameters at compile time, and the Stripe SDK may not accept this type (it would be a type error in strict mode). Use the proper Stripe type instead:

  3. apps/sim/app/api/v1/admin/referral-campaigns/route.ts, line 818-825 (link)

    Prefer Stripe SDK error class over manual duck-typing

    The manual check 'type' in error && (error as { type: string }).type === 'StripeInvalidRequestError' is fragile — it relies on checking a string property rather than using the proper instanceof check. The Stripe Node.js SDK exports a concrete error class for this:

    This also removes the need for the error instanceof Error guard since Stripe.errors.StripeInvalidRequestError extends Error.

Last reviewed commit: 007a6e6

@icecrasher321
Copy link
Collaborator Author

bugbot run

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

@icecrasher321 icecrasher321 merged commit 75bdf46 into staging Mar 14, 2026
12 checks passed
@icecrasher321 icecrasher321 deleted the improvement/referral-campaigns branch March 14, 2026 23:28
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.

1 participant