feat: add support for promotion codes and coupons#287
feat: add support for promotion codes and coupons#287Nika0000 wants to merge 5 commits intostripe:mainfrom
Conversation
📝 WalkthroughSummary by CodeRabbitRelease Notes
WalkthroughThis 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 DiagramsequenceDiagram
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
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). 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 |
There was a problem hiding this comment.
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
📒 Files selected for processing (10)
README.mdpackages/fastify-app/src/test/helpers/mockStripe.tspackages/fastify-app/src/test/stripe/coupon_created.jsonpackages/fastify-app/src/test/stripe/promotion_code_created.jsonpackages/fastify-app/src/test/webhooks.test.tspackages/sync-engine/src/database/migrations/0043_promotion_codes.sqlpackages/sync-engine/src/schemas/coupon.tspackages/sync-engine/src/schemas/promotion_code.tspackages/sync-engine/src/stripeSync.tspackages/sync-engine/src/types.ts
There was a problem hiding this comment.
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.*andpromotion_code.*webhook events and persist/upsert the corresponding entities. - Introduce schemas and a new
stripe.promotion_codestable 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.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
| false, | |
| this.config.backfillRelatedEntities, |
| 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)) |
There was a problem hiding this comment.
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.
| 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' | |
| ) |
Adds support for coupon and promotion code syncing by:
coupon.created,coupon.updated, andcoupon.deletedpromotion_code.createdandpromotion_code.updatedpromotion_codestable