Skip to content

Stripe integration - account for multiple line items for a given invoice#3824

Open
devkiran wants to merge 12 commits into
mainfrom
support-multiple-products
Open

Stripe integration - account for multiple line items for a given invoice#3824
devkiran wants to merge 12 commits into
mainfrom
support-multiple-products

Conversation

@devkiran
Copy link
Copy Markdown
Collaborator

@devkiran devkiran commented Apr 29, 2026

Summary by CodeRabbit

  • New Features

    • Support for multiple products per Stripe transaction.
    • Partner commissions can be calculated per-product within a sale.
    • Reward context now accepts a products array for sale events.
  • Bug Fixes

    • Partner reward and earnings correctly handle multiple invoice or checkout line items.
    • Webhook logs use event-specific prefixes for checkout.session.completed and invoice.paid.

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 29, 2026

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

Project Deployment Actions Updated (UTC)
dub Ready Ready Preview May 6, 2026 4:22am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

📝 Walkthrough

Walkthrough

Webhook handlers, product-extraction utils, reward schemas, and partner commission logic were updated to extract and propagate multiple sale products (id, amount, quantity) and compute partner commissions per-product when reward modifiers reference sale.productId. Log prefixes for webhook early-return paths were adjusted.

Changes

Cohort / File(s) Summary
Webhook handlers
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts, apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts
Handlers now call the product extractor and set context.sale.products (array of {id, amount, quantity}) before createPartnerCommission. Adjusted log prefixes for "already processed" early returns; preserved skip behavior and response payloads.
Stripe webhook utils
apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-checkout-session-product-id.ts, apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-checkout-session-products.ts
Removed single-ID helper; added getCheckoutSessionProducts returning [{id, amount, quantity}] and productIdFromLineItemPrice. Functions return [] on missing/invalid data and log failures.
Partner commission logic
apps/web/lib/partners/create-partner-commission.ts
Detects product-dependent modifiers and, when present, iterates products calling determinePartnerReward per product (setting sale.productId/sale.amount), aggregates per-product rewards/earnings, and uses the first computed reward for commission record fields.
Reward evaluation
apps/web/lib/partners/evaluate-reward-conditions.ts
Rewrote evaluateCondition from switch to an ordered if/return chain with the same operator semantics and added type guards/fallbacks.
Schema
apps/web/lib/zod/schemas/rewards.ts
Extended rewardContextSchema.sale to allow optional `products?: Array<{ id: string; amount: number; quantity: number }>

Sequence Diagram

sequenceDiagram
    participant Stripe as Stripe Webhook
    participant Handler as Webhook Handler
    participant Utils as Product Utils
    participant Commission as Partner Commission
    participant Rewards as Reward Evaluator

    Stripe->>Handler: checkout.session.completed / invoice.paid event
    Handler->>Utils: getCheckoutSessionProducts(checkoutSessionId, stripeAccountId)
    Utils-->>Handler: [{id, amount, quantity}, ...] or []
    Handler->>Commission: createPartnerCommission(context with sale.products)
    
    alt reward conditions reference sale.productId
        Commission->>Rewards: evaluateConditions(productId) per product
        Rewards-->>Commission: condition results per product
        Commission->>Commission: compute per-product rewards & earnings
        Commission->>Commission: aggregate earnings across products
    else no product-dependent conditions
        Commission->>Rewards: evaluateConditions(single sale/context)
        Rewards-->>Commission: condition result
        Commission->>Commission: compute single reward & earnings
    end

    Commission-->>Handler: commission recorded (total earnings)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • steven-tey
  • pepeladeira

Poem

🐰 I hopped through webhooks with nimble paws,

Found many products hidden in carts and laws.
Per-item carrots now split every share,
Rabbity sums hop, precise and fair,
Cheers for commissions handled with care! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly aligns with the primary objective of the PR—updating Stripe integration to handle multiple line items per invoice instead of a single product.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch support-multiple-products

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
Review rate limit: 6/8 reviews remaining, refill in 13 minutes and 19 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (1)
apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-checkout-session-product-id.ts (1)

47-56: Consider pagination for checkout sessions with many line items.

The limit: 10 may miss products when a checkout session has more than 10 line items. For high-volume e-commerce scenarios with many products per checkout, this could lead to incomplete reward calculations.

Consider either increasing the limit or implementing pagination via auto_pagination_each:

💡 Optional: Support pagination for larger checkouts
-    const lineItems = await stripeApp.checkout.sessions.listLineItems(
-      checkoutSessionId,
-      {
-        expand: ["data.price.product"],
-        limit: 10,
-      },
-      {
-        stripeAccount: stripeAccountId,
-      },
-    );
+    const lineItems: Stripe.LineItem[] = [];
+    for await (const item of stripeApp.checkout.sessions.listLineItems(
+      checkoutSessionId,
+      {
+        expand: ["data.price.product"],
+        limit: 100,
+      },
+      {
+        stripeAccount: stripeAccountId,
+      },
+    )) {
+      lineItems.push(item);
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/app/`(ee)/api/stripe/integration/webhook/utils/get-checkout-session-product-id.ts
around lines 47 - 56, The current call to
stripeApp.checkout.sessions.listLineItems using limit: 10 can miss items for
sessions with >10 line items; update the implementation in
get-checkout-session-product-id to paginate through all line items (e.g., use
Stripe's auto-pagination or repeatedly call listLineItems with starting_after)
against stripeApp.checkout.sessions.listLineItems using checkoutSessionId and
stripeAccountId, aggregating expanded "data.price.product" results before
extracting product IDs so reward calculations include every line item rather
than only the first 10.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@apps/web/app/`(ee)/api/stripe/integration/webhook/utils/get-checkout-session-product-id.ts:
- Around line 47-56: The current call to
stripeApp.checkout.sessions.listLineItems using limit: 10 can miss items for
sessions with >10 line items; update the implementation in
get-checkout-session-product-id to paginate through all line items (e.g., use
Stripe's auto-pagination or repeatedly call listLineItems with starting_after)
against stripeApp.checkout.sessions.listLineItems using checkoutSessionId and
stripeAccountId, aggregating expanded "data.price.product" results before
extracting product IDs so reward calculations include every line item rather
than only the first 10.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fd25aa3a-dcf1-4f2b-b49e-afd596ec95eb

📥 Commits

Reviewing files that changed from the base of the PR and between 8f4b0f5 and 168a09f.

📒 Files selected for processing (6)
  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
  • apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-checkout-session-product-id.ts
  • apps/web/lib/partners/create-partner-commission.ts
  • apps/web/lib/partners/evaluate-reward-conditions.ts
  • apps/web/lib/zod/schemas/rewards.ts

@devkiran devkiran marked this pull request as ready for review April 30, 2026 04:02
Copy link
Copy Markdown
Contributor

@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: 3

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

Inline comments:
In `@apps/web/app/`(ee)/api/stripe/integration/webhook/invoice-paid.ts:
- Around line 247-262: The code that builds products from invoice.lines.data
must handle Stripe's pagination and nullable quantities: if
invoice.lines.has_more is true, page through the invoice lines (using the Stripe
SDK invoice line list API for the given invoice id) to collect all lines before
mapping; when mapping each line in the products creation (the block that
references invoice.lines.data and returns { id: productId, amount, quantity }),
coerce nullable line.quantity to a safe numeric value (e.g., const qty =
line.quantity ?? 1) so the created product objects always have a number for
quantity and will be accepted by createPartnerCommission; update the type guard
to reflect quantity: number and ensure you iterate the fully-aggregated lines
array rather than the possibly partial invoice.lines.data.

In
`@apps/web/app/`(ee)/api/stripe/integration/webhook/utils/get-checkout-session-products.ts:
- Around line 73-79: The mapped product creation is passing nullable Stripe
line.quantity directly (in the map that returns { id, amount, quantity }) and
the subsequent type predicate only checks for null objects, not for missing
quantity; update the mapping logic in get-checkout-session-products so it either
filters out line items with null/undefined quantity or converts nullable
quantities to a safe number (e.g., default 1) and then update the type predicate
used after .filter to assert quantity is a number (e.g., p is { id: string;
amount: number; quantity: number }) so callers of the resulting array can safely
rely on a non-null quantity without changing other code paths.
- Around line 47-52: The current call in get-checkout-session-products.ts to
stripeApp.checkout.sessions.listLineItems uses a hard limit of 10 and stops,
truncating results; update the logic to paginate and collect all line items for
the given checkoutSessionId (e.g., use Stripe's auto-pagination or iterate pages
from stripeApp.checkout.sessions.listLineItems while following
has_more/starting_after) and keep expand: ["data.price.product"] so every page
expands product details; ensure the final returned products array aggregates
items from all pages instead of only the first 10.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8a83afca-816a-4917-858e-50edfbc58939

📥 Commits

Reviewing files that changed from the base of the PR and between 168a09f and a262613.

📒 Files selected for processing (3)
  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
  • apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-checkout-session-products.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts

Comment thread apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts
Comment on lines +73 to +79
amount: line.amount_total,
quantity: line.quantity,
};
})
.filter(
(p): p is { id: string; amount: number; quantity: number } =>
p !== null,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n apps/web/app/\(ee\)/api/stripe/integration/webhook/utils/get-checkout-session-products.ts | head -100

Repository: dubinc/dub

Length of output: 2800


🌐 Web query:

Stripe LineItem quantity field type nullable definition

💡 Result:

In Stripe's API, "LineItem" refers to different objects depending on context, but the query likely targets the Checkout Session line item (object type "item" in checkout.sessions.line_items.data), as it's commonly used for checkout line items. For Checkout Session LineItem: - quantity: integer, nullable As per official Stripe API docs for recent API versions (e.g., 2026-03-25.dahlia, 2025-03-31.basil, 2024-06-20): "line_items.data.quantity (integer, nullable) - The quantity of products being purchased." Examples always show a positive integer value (e.g., "quantity": 2), but docs explicitly mark it nullable. For comparison, Invoice LineItem (invoice.line_items.data): - quantity: integer, nullable (deprecated in favor of quantity_decimal: decimal string, nullable) When creating Checkout Sessions, line_items[].quantity is "integer, required conditionally" (required except for metered recurring usage_type). Sources confirm consistency across API versions up to 2026.

Citations:


🏁 Script executed:

# Check Stripe types definition in the project or node_modules
rg "LineItem|quantity" --type ts -A 3 -B 1 | head -100

Repository: dubinc/dub

Length of output: 7977


🏁 Script executed:

# Look for stripe types definition
find node_modules -name "*.d.ts" -path "*stripe*" -type f 2>/dev/null | head -20

Repository: dubinc/dub

Length of output: 36


🏁 Script executed:

# Check package.json for stripe version
cat package.json | grep -A 5 -B 5 stripe

Repository: dubinc/dub

Length of output: 36


🏁 Script executed:

# Find package.json files
find . -maxdepth 2 -name "package.json" -type f | head -5

Repository: dubinc/dub

Length of output: 69


🏁 Script executed:

grep -A 2 -B 2 '"stripe"' package.json

Repository: dubinc/dub

Length of output: 36


🏁 Script executed:

rg "stripe" package.json -i

Repository: dubinc/dub

Length of output: 36


🏁 Script executed:

find . -name "package.json" -type f -exec grep -l stripe {} \;

Repository: dubinc/dub

Length of output: 113


🏁 Script executed:

grep "stripe" apps/web/package.json | head -5

Repository: dubinc/dub

Length of output: 114


Ensure quantity field is non-null before returning products.

Line 74 passes line.quantity directly, but Stripe line-item quantity is nullable. The type predicate on lines 78-79 only checks if the object is null, not if individual fields are null, creating a type safety gap.

Suggested fix
         return {
           id: productId,
           amount: line.amount_total,
-          quantity: line.quantity,
+          quantity: line.quantity ?? 1,
         };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/app/`(ee)/api/stripe/integration/webhook/utils/get-checkout-session-products.ts
around lines 73 - 79, The mapped product creation is passing nullable Stripe
line.quantity directly (in the map that returns { id, amount, quantity }) and
the subsequent type predicate only checks for null objects, not for missing
quantity; update the mapping logic in get-checkout-session-products so it either
filters out line items with null/undefined quantity or converts nullable
quantities to a safe number (e.g., default 1) and then update the type predicate
used after .filter to assert quantity is a number (e.g., p is { id: string;
amount: number; quantity: number }) so callers of the resulting array can safely
rely on a non-null quantity without changing other code paths.

Copy link
Copy Markdown
Contributor

@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: 1

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

Inline comments:
In `@apps/web/lib/partners/create-partner-commission.ts`:
- Around line 193-195: The code currently assumes rewards[0] is the canonical
reward which makes eligibility checks (e.g., firstCommission.rewardId and
maxDuration) order-dependent; instead, iterate each computed reward from rewards
(do not assign a single `reward = rewards[0]`) and validate eligibility for each
reward individually, applying the `firstCommission.rewardId` and `maxDuration`
checks per reward (or persist a separate commission per reward/product) before
aggregating earnings or setting commission fields so downstream logic is based
on per-reward validation rather than list order.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 62c948f9-23ba-45a2-8993-af287118bcfa

📥 Commits

Reviewing files that changed from the base of the PR and between a262613 and 6a888ad.

📒 Files selected for processing (1)
  • apps/web/lib/partners/create-partner-commission.ts

Comment thread apps/web/lib/partners/create-partner-commission.ts
@devkiran
Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review please

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 30, 2026

@devkiran Sure! I'll kick off a full review of the PR now.


🧠 Learnings used
Learnt from: steven-tey
Repo: dubinc/dub PR: 3679
File: apps/web/lib/stripe/disable-stripe-discount-code.ts:2-2
Timestamp: 2026-04-01T17:57:43.749Z
Learning: In dubinc/dub, there is a known StripeMode type divergence between packages: `apps/web/lib/types.ts` defines `StripeMode = "test" | "sandbox" | "live"`, while `packages/stripe-app/src/utils/types.ts` defines `StripeMode = "test" | "live"` (missing "sandbox"). This is intentional for now; the Stripe app package needs to be updated separately to support "sandbox" mode. Do not flag this divergence as a blocking issue in future reviews.
✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@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: 1

♻️ Duplicate comments (2)
apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-checkout-session-products.ts (1)

71-79: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Coerce checkout line-item quantity to a number.

line.quantity is nullable, so the current filter can still return a product with a non-numeric quantity while asserting quantity: number. Flat per-unit rewards then compute against bad data.

Suggested fix
         return {
           id: productId,
           amount: line.amount_total,
-          quantity: line.quantity,
+          quantity: line.quantity ?? 1,
         };
Stripe Checkout Session line item `quantity` field nullable official docs
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/app/`(ee)/api/stripe/integration/webhook/utils/get-checkout-session-products.ts
around lines 71 - 79, The returned product objects currently use line.quantity
which is nullable; update the mapping in getCheckoutSessionProducts (the map
that returns { id: productId, amount: line.amount_total, quantity: line.quantity
}) to coerce quantity to a number (e.g., Number(line.quantity) or a default like
1) and then tighten the subsequent filter to only allow products where quantity
is a finite positive number (use Number.isFinite and > 0) so the type predicate
(p is { id: string; amount: number; quantity: number }) is correct and
downstream reward calculations receive a valid numeric quantity.
apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts (1)

247-262: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize nullable Stripe invoice quantities before building products.

line.quantity can be null here, but the type predicate only filters out null objects. That lets products claim quantity: number while still carrying a null at runtime, which underpays flat per-unit commissions for matched line items.

Suggested fix
     return {
       id: productId,
       amount: line.amount,
-      quantity: line.quantity,
+      quantity: line.quantity ?? 1,
     };
Stripe Invoice Line Item `quantity` field nullable official docs
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/`(ee)/api/stripe/integration/webhook/invoice-paid.ts around
lines 247 - 262, When building products from invoice.lines.data, normalize
nullable Stripe line.quantity before asserting the product shape: update the
mapping in the products creation (the block referencing invoice.lines.data,
line.pricing?.price_details?.product, and line.quantity) to coerce
null/undefined quantities to a safe numeric default (e.g., use line.quantity ??
1 or Number(line.quantity ?? 1)) so the resulting objects truly satisfy { id:
string; amount: number; quantity: number } and avoid underpaying per-unit
commissions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/web/app/`(ee)/api/stripe/integration/webhook/utils/get-checkout-session-products.ts:
- Around line 90-95: The catch in getCheckoutSessionProducts currently swallows
Stripe errors and returns an empty array, which conflates transient failures
with a legitimately empty cart; change the error handling in
getCheckoutSessionProducts so it preserves failure signals (either rethrow the
caught error or return null instead of []) and update callers (e.g.,
checkout-session-completed.ts and create-partner-commission logic that checks
products.length) to treat null as "fetch failed" and skip or retry commission
creation rather than falling back to the generic sale path.

---

Duplicate comments:
In `@apps/web/app/`(ee)/api/stripe/integration/webhook/invoice-paid.ts:
- Around line 247-262: When building products from invoice.lines.data, normalize
nullable Stripe line.quantity before asserting the product shape: update the
mapping in the products creation (the block referencing invoice.lines.data,
line.pricing?.price_details?.product, and line.quantity) to coerce
null/undefined quantities to a safe numeric default (e.g., use line.quantity ??
1 or Number(line.quantity ?? 1)) so the resulting objects truly satisfy { id:
string; amount: number; quantity: number } and avoid underpaying per-unit
commissions.

In
`@apps/web/app/`(ee)/api/stripe/integration/webhook/utils/get-checkout-session-products.ts:
- Around line 71-79: The returned product objects currently use line.quantity
which is nullable; update the mapping in getCheckoutSessionProducts (the map
that returns { id: productId, amount: line.amount_total, quantity: line.quantity
}) to coerce quantity to a number (e.g., Number(line.quantity) or a default like
1) and then tighten the subsequent filter to only allow products where quantity
is a finite positive number (use Number.isFinite and > 0) so the type predicate
(p is { id: string; amount: number; quantity: number }) is correct and
downstream reward calculations receive a valid numeric quantity.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 41fbbb45-7a63-4064-b21f-800ddd3ba7f7

📥 Commits

Reviewing files that changed from the base of the PR and between be182cf and 0b01a27.

📒 Files selected for processing (7)
  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
  • apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-checkout-session-product-id.ts
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-checkout-session-products.ts
  • apps/web/lib/partners/create-partner-commission.ts
  • apps/web/lib/partners/evaluate-reward-conditions.ts
  • apps/web/lib/zod/schemas/rewards.ts
💤 Files with no reviewable changes (1)
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-checkout-session-product-id.ts

Comment on lines +90 to +95
} catch (error) {
console.log(
"[getCheckoutSessionProducts] Failed to get checkout session products:",
error,
);
return [];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't collapse Stripe fetch failures into an empty product list.

Returning [] here makes a transient Stripe failure indistinguishable from a real empty cart. In apps/web/lib/partners/create-partner-commission.ts, products.length === 0 falls back to the generic sale path, so programs that use sale.productId modifiers can get the wrong commission instead of a safe skip/retry.

One way to preserve the failure signal
-export async function getCheckoutSessionProducts({
+export async function getCheckoutSessionProducts({
   checkoutSessionId,
   stripeAccountId,
   mode,
 }: {
   checkoutSessionId: string;
   stripeAccountId?: string | null;
   mode: StripeMode;
-}) {
+}): Promise<{ id: string; amount: number; quantity: number }[] | null> {
@@
   } catch (error) {
     console.log(
       "[getCheckoutSessionProducts] Failed to get checkout session products:",
       error,
     );
-    return [];
+    return null;
   }
 }

Then have checkout-session-completed.ts skip or retry commission creation when the result is null.

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

In
`@apps/web/app/`(ee)/api/stripe/integration/webhook/utils/get-checkout-session-products.ts
around lines 90 - 95, The catch in getCheckoutSessionProducts currently swallows
Stripe errors and returns an empty array, which conflates transient failures
with a legitimately empty cart; change the error handling in
getCheckoutSessionProducts so it preserves failure signals (either rethrow the
caught error or return null instead of []) and update callers (e.g.,
checkout-session-completed.ts and create-partner-commission logic that checks
products.length) to treat null as "fetch failed" and skip or retry commission
creation rather than falling back to the generic sale path.

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