Skip to content

feat: add support for promotion codes and coupons#287

Open
Nika0000 wants to merge 2 commits intosupabase:mainfrom
Nika0000:main
Open

feat: add support for promotion codes and coupons#287
Nika0000 wants to merge 2 commits intosupabase:mainfrom
Nika0000:main

Conversation

@Nika0000
Copy link

@Nika0000 Nika0000 commented Mar 9, 2026

Adds support for coupon and promotion code syncing by:

  • handling coupon.created, coupon.updated, and coupon.deleted
  • handling promotion_code.created and promotion_code.updated
  • persisting promotion codes in a new promotion_codes table
  • syncing coupon data used by promotion codes

@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Added webhook support for coupon events (created, updated, and deleted)
    • Added webhook support for promotion code events (created and updated)
    • Enabled syncing and management of coupons and promotion codes via webhooks
  • Tests

    • Added test fixtures and coverage for new coupon and promotion code webhook events
  • Documentation

    • Updated webhook support documentation with newly supported events

Walkthrough

This pull request extends the Stripe sync engine to support coupons and promotion codes. Changes include adding database schema for promotion codes, defining entity schemas for both resource types, implementing webhook event handlers for coupon and promotion code events (created, updated, deleted), adding corresponding sync and CRUD methods to the sync engine, updating type definitions to include the new entities, extending test fixtures and mocks, and updating documentation. No existing functionality is modified or removed.

Sequence Diagram

sequenceDiagram
    actor Stripe as Stripe API
    participant Handler as Webhook Handler
    participant Engine as Sync Engine
    participant DB as Database
    
    Stripe->>Handler: Send promotion_code.created event
    Handler->>Engine: processWebhookEvent(event)
    Engine->>Stripe: Fetch promotion code details
    Stripe-->>Engine: PromotionCode object
    
    alt Backfill related entities
        Engine->>Stripe: Fetch associated coupon
        Stripe-->>Engine: Coupon object
        Engine->>DB: upsertCoupons(coupon)
        DB-->>Engine: Success
    end
    
    Engine->>DB: upsertPromotionCodes(promotionCode)
    DB-->>Engine: Success
    
    Engine-->>Handler: Event processed
    Handler-->>Stripe: Webhook acknowledged
Loading

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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.

Copy link

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/sync-engine/src/stripeSync.ts`:
- Around line 709-712: The code only handles webhook writes and single-entity
promo lookups via the promo_ branch calling this.upsertPromotionCodes, but omits
any bulk import/backfill paths for historical coupons and promotion codes; add
implementations for syncCoupons and syncPromotionCodes (or a generic
syncBackfill branch) inside the StripeSync class in stripeSync.ts that call the
Stripe list APIs, page through results, transform them into the same shape as
upsertPromotionCodes/upsertCoupons, and call the existing upsert methods to seed
a fresh database; ensure these methods are invoked from whatever backfill/sync
dispatcher currently handles other entities so historical coupons/promotion
codes are imported on initial sync.
- Around line 439-463: The webhook handler for promotion_code.created/updated
passes false into upsertPromotionCodes, which prevents the backfill logic from
running and therefore skips syncing referenced coupons when Stripe sends a
coupon ID string; change the call in the promotion code webhook branch to invoke
upsertPromotionCodes with backfill=true (or compute a boolean that enables
backfill for webhook events) so the backfill block in upsertPromotionCodes
(lines handling coupon backfill) runs; also ensure that upsertCoupons is invoked
or backfill logic will fetch by ID when coupon is a string—refer to
fetchOrUseWebhookData, getSyncTimestamp, upsertCoupons, and upsertPromotionCodes
to locate and adjust the behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 53f989de-5534-4325-95db-a2decadbefe7

📥 Commits

Reviewing files that changed from the base of the PR and between 976a837 and 3c51372.

📒 Files selected for processing (10)
  • README.md
  • packages/fastify-app/src/test/helpers/mockStripe.ts
  • packages/fastify-app/src/test/stripe/coupon_created.json
  • packages/fastify-app/src/test/stripe/promotion_code_created.json
  • packages/fastify-app/src/test/webhooks.test.ts
  • packages/sync-engine/src/database/migrations/0043_promotion_codes.sql
  • packages/sync-engine/src/schemas/coupon.ts
  • packages/sync-engine/src/schemas/promotion_code.ts
  • packages/sync-engine/src/stripeSync.ts
  • packages/sync-engine/src/types.ts

Comment on lines +439 to +463
case 'promotion_code.created':
case 'promotion_code.updated': {
const { entity: promotionCode, refetched } = await this.fetchOrUseWebhookData(
event.data.object as Stripe.PromotionCode,
(id) => this.stripe.promotionCodes.retrieve(id)
)

const syncTimestamp = this.getSyncTimestamp(event, refetched)

this.config.logger?.info(
`Received webhook ${event.id}: ${event.type} for promotionCode ${promotionCode.id}`
)

const coupon =
(promotionCode as Stripe.PromotionCode & { coupon?: string | Stripe.Coupon | null })
.coupon ??
promotionCode.promotion?.coupon ??
null

if (coupon && typeof coupon !== 'string') {
await this.upsertCoupons([coupon], syncTimestamp)
}

await this.upsertPromotionCodes([promotionCode], false, syncTimestamp)
break
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Promotion code webhooks skip the related coupon/customer sync in the common case.

Line 462 passes false into upsertPromotionCodes, so the backfill logic on Lines 1566-1591 never runs for webhook events. The manual coupon sync on Lines 458-460 only executes when coupon is already an expanded object; when Stripe sends the usual string ID, the promotion code row is stored without syncing the referenced coupon row, which breaks the stated “sync coupon data used by promotion codes” behavior. As per coding guidelines, "Highlight only issues that could cause runtime errors, data loss, or severe maintainability issues."

Also applies to: 1566-1591

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sync-engine/src/stripeSync.ts` around lines 439 - 463, The webhook
handler for promotion_code.created/updated passes false into
upsertPromotionCodes, which prevents the backfill logic from running and
therefore skips syncing referenced coupons when Stripe sends a coupon ID string;
change the call in the promotion code webhook branch to invoke
upsertPromotionCodes with backfill=true (or compute a boolean that enables
backfill for webhook events) so the backfill block in upsertPromotionCodes
(lines handling coupon backfill) runs; also ensure that upsertCoupons is invoked
or backfill logic will fetch by ID when coupon is a string—refer to
fetchOrUseWebhookData, getSyncTimestamp, upsertCoupons, and upsertPromotionCodes
to locate and adjust the behavior.

Comment on lines +709 to +712
} else if (stripeId.startsWith('promo_')) {
return this.stripe.promotionCodes
.retrieve(stripeId)
.then((it) => this.upsertPromotionCodes([it]))
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Historical coupons/promotion codes still cannot be synced.

These additions only cover webhook writes/backfills and promo_ single-entity lookup. I don’t see any syncCoupons / syncPromotionCodes implementation or any syncBackfill branch for either entity in this file, so a fresh database will never import existing coupons or promotion codes from Stripe. That leaves the new feature incomplete unless every record arrives via webhook after deploy. As per coding guidelines, "Highlight only issues that could cause runtime errors, data loss, or severe maintainability issues."

Also applies to: 1420-1439, 1561-1627

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sync-engine/src/stripeSync.ts` around lines 709 - 712, The code only
handles webhook writes and single-entity promo lookups via the promo_ branch
calling this.upsertPromotionCodes, but omits any bulk import/backfill paths for
historical coupons and promotion codes; add implementations for syncCoupons and
syncPromotionCodes (or a generic syncBackfill branch) inside the StripeSync
class in stripeSync.ts that call the Stripe list APIs, page through results,
transform them into the same shape as upsertPromotionCodes/upsertCoupons, and
call the existing upsert methods to seed a fresh database; ensure these methods
are invoked from whatever backfill/sync dispatcher currently handles other
entities so historical coupons/promotion codes are imported on initial sync.

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