Skip to content

feat: add support for promotion codes and coupons#287

Open
Nika0000 wants to merge 5 commits intostripe:mainfrom
Nika0000:main
Open

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

Conversation

@Nika0000
Copy link
Copy Markdown

@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
Copy Markdown

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
Copy Markdown

@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

Copilot AI review requested due to automatic review settings March 22, 2026 16:57
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds Stripe webhook + persistence support for coupons and promotion codes in the sync engine, extending the set of Stripe objects that can be reliably mirrored into Postgres.

Changes:

  • Handle coupon.* and promotion_code.* webhook events and persist/upsert the corresponding entities.
  • Introduce schemas and a new stripe.promotion_codes table migration for promotion code storage.
  • Extend Fastify webhook tests/fixtures and update README event support list.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/sync-engine/src/types.ts Extends revalidation entity union to include coupon and promotion_code.
packages/sync-engine/src/stripeSync.ts Adds webhook handlers + upsert/backfill helpers for coupons and promotion codes; adds promo_ support in single-entity sync.
packages/sync-engine/src/schemas/promotion_code.ts Defines persisted fields for promotion codes.
packages/sync-engine/src/schemas/coupon.ts Defines persisted fields for coupons.
packages/sync-engine/src/database/migrations/0043_promotion_codes.sql Adds stripe.promotion_codes table + indexes + updated_at trigger.
packages/fastify-app/src/test/webhooks.test.ts Adds webhook upsert coverage for coupon/promotion_code “created” events.
packages/fastify-app/src/test/stripe/promotion_code_created.json Adds fixture for promotion_code.created.
packages/fastify-app/src/test/stripe/coupon_created.json Adds fixture for coupon.created.
packages/fastify-app/src/test/helpers/mockStripe.ts Mocks Stripe coupons.retrieve and promotionCodes.retrieve for tests.
README.md Documents additional supported webhook events.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


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

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

promotion_code.created/updated webhooks call upsertPromotionCodes(..., false, ...), which disables the related-entity backfill logic in upsertPromotionCodes. Since promotion code webhooks can reference coupons/customers that were created before this service started (and may never emit new coupon.* / customer.* events), this can leave promotion_codes.coupon / .customer pointing to missing rows and undermines the PR goal of syncing coupon data used by promotion codes. Consider either letting upsertPromotionCodes use config.backfillRelatedEntities here (omit the false), or explicitly persisting the embedded promotionCode.coupon (and customer when expanded) alongside the promotion code without extra Stripe API calls.

Suggested change
false,
this.config.backfillRelatedEntities,

Copilot uses AI. Check for mistakes.
Comment on lines +1568 to +1587
const couponIds = promotionCodes
.map((promotionCode) => {
const coupon =
(promotionCode as Stripe.PromotionCode & { coupon?: string | Stripe.Coupon | null })
.coupon ?? null

if (!coupon) return null

return typeof coupon === 'string' ? coupon : coupon.id.toString()
})
.filter((id): id is string => Boolean(id))
const customerIds = promotionCodes
.map((promotionCode) => {
if (!promotionCode.customer) return null

return typeof promotionCode.customer === 'string'
? promotionCode.customer
: promotionCode.customer.id.toString()
})
.filter((id): id is string => Boolean(id))
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

In upsertPromotionCodes, couponIds and customerIds are collected without deduplication, but fetchMissingEntities fetches sequentially and will re-fetch duplicates. Deduplicate these ID lists (e.g., via Set/getUniqueIds) before calling backfillCoupons/backfillCustomers to avoid redundant DB checks and Stripe API calls.

Suggested change
const couponIds = promotionCodes
.map((promotionCode) => {
const coupon =
(promotionCode as Stripe.PromotionCode & { coupon?: string | Stripe.Coupon | null })
.coupon ?? null
if (!coupon) return null
return typeof coupon === 'string' ? coupon : coupon.id.toString()
})
.filter((id): id is string => Boolean(id))
const customerIds = promotionCodes
.map((promotionCode) => {
if (!promotionCode.customer) return null
return typeof promotionCode.customer === 'string'
? promotionCode.customer
: promotionCode.customer.id.toString()
})
.filter((id): id is string => Boolean(id))
const couponIds = getUniqueIds(
promotionCodes
.map((promotionCode) => {
const coupon =
(promotionCode as Stripe.PromotionCode & { coupon?: string | Stripe.Coupon | null })
.coupon ?? null
if (!coupon) return null
return {
id: typeof coupon === 'string' ? coupon : coupon.id.toString(),
}
})
.filter((entry): entry is { id: string } => Boolean(entry)),
'id'
)
const customerIds = getUniqueIds(
promotionCodes
.map((promotionCode) => {
if (!promotionCode.customer) return null
return {
id:
typeof promotionCode.customer === 'string'
? promotionCode.customer
: promotionCode.customer.id.toString(),
}
})
.filter((entry): entry is { id: string } => Boolean(entry)),
'id'
)

Copilot uses AI. Check for mistakes.
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