Skip to content

feat: sponsor cart payments#857

Open
santipalenque wants to merge 4 commits intomasterfrom
feature/sponsor-cart-payments
Open

feat: sponsor cart payments#857
santipalenque wants to merge 4 commits intomasterfrom
feature/sponsor-cart-payments

Conversation

@santipalenque
Copy link
Copy Markdown

@santipalenque santipalenque commented Apr 6, 2026

https://app.clickup.com/t/86b66n5kw
https://app.clickup.com/t/86b66n5ku

Summary by CodeRabbit

  • New Features

    • Offline invoice and online card payments for sponsor carts, with payment confirmation and redirect to orders; cart updates include more detailed item/form data.
  • Refactor

    • Cart editing now uses route-based flows; save/cancel navigate via routes. Cart notes only display when a cart exists.
  • Localization

    • Added translations for invoice/payment views and new navigation labels.
  • Chores

    • Added payment SDKs and updated example env scopes; i18n initialization adjusted.
  • Tests

    • Updated navigation-focused tests to assert route behavior.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added offline and online sponsor-cart payment workflows: new action types and thunks, expanded cart API payloads, reducer state for payment/profile/intent/offline payments, cart sub-routing and views (invoice/payment), Stripe integration, helpers/constants, i18n keys, and dependency updates.

Changes

Cohort / File(s) Summary
Actions
src/actions/sponsor-cart-actions.js
Added action constants (CART_STATUS_UPDATED, RECEIVE_PAYMENT_PROFILE, OFFLINE_PAYMENT_CREATED, PAYMENT_INTENT_CREATED, PAYMENT_INTENT_UPDATED, PAYMENT_CONFIRMED) and thunks: checkoutCart, payWithInvoice, getPaymentProfile, updatePaymentIntent, confirmPayment; expanded API expand params and updated error handler behavior.
Reducers
src/reducers/sponsors/sponsor-page-cart-list-reducer.js
Added paymentProfile, paymentIntent, offlinePayment to default state; handle new payment actions; CART_STATUS_UPDATED falls through to RECEIVE_SPONSOR_CART to rebuild derived cart fields.
Cart UI & Wiring
src/pages/sponsors/.../cart-view.js
Removed onEdit prop, added checkoutCart/payWithInvoice wiring, history-based edit navigation, skip checkout when PENDING_PAYMENT, moved notes rendering under cart existence, minor layout tweaks.
Payment & Invoice Views
src/pages/sponsors/.../invoice-view.js, src/pages/sponsors/.../payment-view.js
Added InvoiceView (navigation card) and connected PaymentView (loads payment profile, displays order summary, embeds MuiStripePayment, dispatches update/confirm).
Tab Routing / Orchestration
src/pages/sponsors/.../sponsor-cart-tab/index.js, src/pages/sponsors/sponsor-page/tabDefs.js
SponsorCartTab now uses sub-routes (match.url) for forms/payment/invoice, removed internal edit state; /cart route no longer exact.
Edit Form & Tests
src/pages/sponsors/.../edit-form/edit-cart-form.js, src/pages/sponsors/.../edit-form/__tests__/edit-cart-form.test.js
EditCartForm now reads formId from match.params, uses history-based back navigation; tests updated to mock/assert history.push.
Helpers & Constants
src/pages/sponsors/.../helpers.js, src/utils/constants.js
Added mapCartData(cart, showItemDescription) to flatten/enrich cart items and render metafields; added SPONSOR_FORMS_METAFIELD_CLASS and SPONSOR_CART_STATUS.
i18n & Env
src/i18n/en.json, .env.example
Added payment/invoice i18n keys and small general keys; added payment-profile/read scope to PURCHASES_API_SCOPES.
App init & deps
src/app.js, package.json
Switched i18n init to setAppTexts(...) import; added Stripe libs (@stripe/react-stripe-js, @stripe/stripe-js) and bumped openstack-uicore-foundation.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant CartView as CartView
    participant Redux as Redux
    participant API as Sponsor API
    participant PaymentView as PaymentView
    participant Stripe as Stripe

    User->>CartView: Click "Pay with Invoice" or "Pay by Card"
    CartView->>Redux: dispatch(checkoutCart()) (skipped if PendingPayment)
    Redux->>API: PUT /sponsor/.../carts/current/checkout?expand=...
    API-->>Redux: CART_STATUS_UPDATED
    Redux-->>CartView: state updated (cart)
    alt Invoice flow
        CartView->>Redux: dispatch(payWithInvoice(cart_id))
        Redux->>API: POST /sponsor/.../payments {type: "Offline", cart_id}
        API-->>Redux: OFFLINE_PAYMENT_CREATED
        Redux-->>User: navigate -> /.../cart/invoice
    else Online card flow
        PaymentView->>Redux: dispatch(getPaymentProfile(cart.id))
        Redux->>API: GET /sponsor/.../payment-profiles?cart_id=...
        API-->>Redux: RECEIVE_PAYMENT_PROFILE
        Redux->>API: POST/PUT -> create/update payment_intent
        API-->>Redux: PAYMENT_INTENT_CREATED/UPDATED
        PaymentView->>Stripe: collect payment details
        Stripe->>API: confirm -> returns success
        API-->>Redux: PAYMENT_CONFIRMED
        Redux-->>User: navigate -> success/redirect URL
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • romanetar
  • smarcet

Poem

🐰
I hopped through code to stitch the cart so neat,
Thunks carried carrots down the API street,
Invoice blooms, Stripe hums a tender tune,
Payments confirmed beneath the glowing moon—
Hooray, the cart's complete; let's nibble a sweet treat! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: sponsor cart payments' directly and clearly summarizes the main objective of this PR, which implements sponsor cart payment functionality including offline invoice payments and online Stripe payments.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/sponsor-cart-payments

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/reducers/sponsors/sponsor-page-cart-list-reducer.js (1)

80-106: ⚠️ Potential issue | 🟠 Major

Harden cart normalization for CART_STATUS_UPDATED responses.

Line 83 assumes payload.response.forms is always present. checkoutCart uses a different endpoint (/carts/current/checkout) than RECEIVE_SPONSOR_CART (/carts/current); if that response is partial or lacks a forms array, .map() will throw and break checkout rendering.

🔧 Proposed defensive fix
     case CART_STATUS_UPDATED:
     case RECEIVE_SPONSOR_CART: {
-      const cart = payload.response;
-      cart.forms = cart.forms.map((form) => {
+      const rawCart = payload.response || {};
+      const forms = Array.isArray(rawCart.forms) ? rawCart.forms : [];
+      const cart = {
+        ...rawCart,
+        forms: forms.map((form) => {
           const discountAmount = amountFromCents(form.discount_amount);
           const discountStr =
             form.discount_type === DISCOUNT_TYPES.RATE
               ? `${discountAmount} %`
               : `$${discountAmount}`;
           const discount = form.discount_amount ? discountStr : "";

           return {
             ...form,
             addon_name: form.addon_name || "None",
             amount: currencyAmountFromCents(form.net_amount),
             discount,
             item_count: `${form.items.length} ${
               form.items.length === 1 ? "item" : "items"
             }`,
             items: form.items.map((it) => ({
               ...it,
               custom_rate: currencyAmountFromCents(it.custom_rate || 0)
             }))
           };
-      });
-      cart.total = currencyAmountFromCents(cart.net_amount);
+        }),
+        total: currencyAmountFromCents(rawCart.net_amount || 0)
+      };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/reducers/sponsors/sponsor-page-cart-list-reducer.js` around lines 80 -
106, The reducer handling CART_STATUS_UPDATED and RECEIVE_SPONSOR_CART assumes
payload.response.forms exists and calls .map, which will throw if forms is
missing; update the normalization in that case (inside the case block handling
CART_STATUS_UPDATED / RECEIVE_SPONSOR_CART) to defensively read forms from
payload.response (e.g., use an empty array when payload.response.forms is
null/undefined or not an array) before mapping, so cart.forms is always a safe
array for the subsequent mapping and downstream rendering; keep the same mapping
logic for each form and ensure cart.total still derives from cart.net_amount as
before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/actions/sponsor-cart-actions.js`:
- Around line 481-505: The payWithInvoice thunk dereferences
sponsorPageCartListState.cart without a null guard, swallows errors with
.catch(console.log) and never guarantees stopLoading(); fix payWithInvoice by
first checking that sponsorPageCartListState.cart exists (guard cart before
using cart.id), ensure postRequest errors are not swallowed (remove or replace
the silent .catch and rethrow or return the rejected promise so callers can
handle failures), and always call stopLoading() in a finally path (use
Promise.finally or try/catch/finally around the async flow) after dispatching
startLoading(); keep existing calls to postRequest,
createAction(OFFLINE_PAYMENT_CREATED), getSponsorCart()(dispatch, getState) and
snackbarErrorHandler but make sure errors propagate to callers instead of being
logged locally.

In `@src/pages/sponsors/sponsor-cart-tab/index.js`:
- Around line 37-39: The UI hides CartView when payCCView is truthy but no
credit-card view is rendered; remove payCCView from the cart-visibility logic
and stop toggling it until a CC/ACH view is implemented: update showCartView to
not include payCCView (remove "!payCCView"), and remove or noop any
setPayCCView(true) calls (e.g., the handler around where setPayCCView is set to
true in the click flow lines ~82-88) so clicking "pay with credit card or ach"
does not hide the cart until a real CC view is added; keep the payCCView state
or handlers only if they'll be implemented later.

---

Outside diff comments:
In `@src/reducers/sponsors/sponsor-page-cart-list-reducer.js`:
- Around line 80-106: The reducer handling CART_STATUS_UPDATED and
RECEIVE_SPONSOR_CART assumes payload.response.forms exists and calls .map, which
will throw if forms is missing; update the normalization in that case (inside
the case block handling CART_STATUS_UPDATED / RECEIVE_SPONSOR_CART) to
defensively read forms from payload.response (e.g., use an empty array when
payload.response.forms is null/undefined or not an array) before mapping, so
cart.forms is always a safe array for the subsequent mapping and downstream
rendering; keep the same mapping logic for each form and ensure cart.total still
derives from cart.net_amount as before.
🪄 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: 7bde1bc9-44c7-43aa-9620-165845d71aa6

📥 Commits

Reviewing files that changed from the base of the PR and between 37de874 and 8ab9218.

📒 Files selected for processing (6)
  • src/actions/sponsor-cart-actions.js
  • src/i18n/en.json
  • src/pages/sponsors/sponsor-cart-tab/components/cart-view.js
  • src/pages/sponsors/sponsor-cart-tab/components/invoice-view.js
  • src/pages/sponsors/sponsor-cart-tab/index.js
  • src/reducers/sponsors/sponsor-page-cart-list-reducer.js

@santipalenque santipalenque force-pushed the feature/sponsor-cart-payments branch from 8ab9218 to 90aff95 Compare April 7, 2026 19:50
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: 1

🧹 Nitpick comments (2)
src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js (1)

80-86: Consider inlining these pass-through handlers.

The handlers handlePayCreditCard and handlePayInvoice are single-line pass-throughs to the props. They could be inlined directly in the onClick handlers for conciseness.

♻️ Optional simplification
-  const handlePayCreditCard = () => {
-    onPayCC();
-  };
-
-  const handlePayInvoice = () => {
-    onPayInvoice();
-  };
@@
               <Button
                 variant="contained"
                 color="primary"
                 style={{ minWidth: 250 }}
                 disabled={cart?.net_amount === 0}
-                onClick={handlePayCreditCard}
+                onClick={onPayCC}
               >
@@
               <Button
                 variant="contained"
                 color="primary"
                 style={{ minWidth: 250 }}
-                onClick={handlePayInvoice}
+                onClick={onPayInvoice}
               >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js`
around lines 80 - 86, The two functions handlePayCreditCard and handlePayInvoice
are trivial pass-throughs; remove these two small wrappers and replace their
usages by calling the prop handlers directly (onPayCC and onPayInvoice) in the
corresponding onClick handlers where handlePayCreditCard and handlePayInvoice
are currently referenced; update any references to handlePayCreditCard and
handlePayInvoice to use onPayCC and onPayInvoice respectively to keep behavior
identical while simplifying the component.
src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js (1)

44-48: Remove unnecessary Redux connection.

The component only uses the onCancel prop passed from its parent. The mapStateToProps spreads showAccessState but none of those props are used in the component. This adds unnecessary Redux subscription overhead and potential re-renders.

♻️ Proposed simplification
-const mapStateToProps = ({ showAccessState }) => ({
-  ...showAccessState
-});
-
-export default connect(mapStateToProps, {})(InvoiceView);
+export default InvoiceView;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js`
around lines 44 - 48, mapStateToProps and the connect wrapper are unnecessary
because InvoiceView only uses the onCancel prop; remove the mapStateToProps
function and the connect import/wrapper and instead export the component
directly (export default InvoiceView), ensuring InvoiceView still accepts
onCancel from its parent and removing any references to showAccessState; also
remove any unused imports (connect) to avoid extra Redux subscription and
re-renders.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/index.js`:
- Around line 49-55: handlePayInvoice currently proceeds to setInvoiceView(true)
regardless of failures because checkoutCart and payWithInvoice swallow errors;
change handlePayInvoice to await both operations inside a try/catch and only
call setInvoiceView(true) if both succeed, and in the catch branch propagate or
handle the error (e.g., show snackbar) without advancing the UI. Also update the
action thunks checkoutCart and payWithInvoice (in
src/actions/sponsor-cart-actions.js) to re-throw or reject when they encounter
failures so callers like handlePayInvoice can correctly detect and react to
errors.

---

Nitpick comments:
In
`@src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js`:
- Around line 80-86: The two functions handlePayCreditCard and handlePayInvoice
are trivial pass-throughs; remove these two small wrappers and replace their
usages by calling the prop handlers directly (onPayCC and onPayInvoice) in the
corresponding onClick handlers where handlePayCreditCard and handlePayInvoice
are currently referenced; update any references to handlePayCreditCard and
handlePayInvoice to use onPayCC and onPayInvoice respectively to keep behavior
identical while simplifying the component.

In
`@src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js`:
- Around line 44-48: mapStateToProps and the connect wrapper are unnecessary
because InvoiceView only uses the onCancel prop; remove the mapStateToProps
function and the connect import/wrapper and instead export the component
directly (export default InvoiceView), ensuring InvoiceView still accepts
onCancel from its parent and removing any references to showAccessState; also
remove any unused imports (connect) to avoid extra Redux subscription and
re-renders.
🪄 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: 92b50c27-8e1d-4044-8e1e-dea84531b580

📥 Commits

Reviewing files that changed from the base of the PR and between 8ab9218 and 90aff95.

📒 Files selected for processing (6)
  • src/actions/sponsor-cart-actions.js
  • src/i18n/en.json
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/index.js
  • src/reducers/sponsors/sponsor-page-cart-list-reducer.js
✅ Files skipped from review due to trivial changes (1)
  • src/i18n/en.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/reducers/sponsors/sponsor-page-cart-list-reducer.js

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

🧹 Nitpick comments (1)
src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/__tests__/edit-cart-form.test.js (1)

303-314: Assert navigation target, not just call count.

Both tests only verify history.push was called once. Please assert the destination path too, so route regressions are caught.

Proposed assertion tightening
-      expect(mockHistoryPush).toHaveBeenCalledTimes(1);
+      expect(mockHistoryPush).toHaveBeenCalledWith(
+        "/app/events/1/sponsors/2/cart"
+      );

Also applies to: 373-388

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

In
`@src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/__tests__/edit-cart-form.test.js`
around lines 303 - 314, The tests "clicking CANCEL navigates back" and the other
similar test only assert mockHistoryPush was called once; update them to also
assert the navigation destination by checking mockHistoryPush was called with
the expected path (e.g., the route string or route object your app uses) instead
of just verifying call count. Locate the tests where mockHistoryPush is asserted
(references: the test named "clicking CANCEL navigates back" and the other test
around lines asserting mockHistoryPush) and change the assertion to
expect(mockHistoryPush).toHaveBeenCalledWith(<expectedPathOrRoute>), or
toHaveBeenCalledWith(expect.stringContaining(...)) if using partial matching, so
the destination is verified.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/__tests__/edit-cart-form.test.js`:
- Around line 176-179: The test helper buildMatch is using a default URL that
includes `/edit` which doesn't match the registered parent route
`/forms/:form_id`; update buildMatch so its default url is
`/app/events/1/sponsors/2/cart/forms/${formId}` (no `/edit`) to reflect the
actual route shape, and leave callers that need the edit path to pass an
explicit url containing `/edit`; adjust tests that relied on the old default to
either pass the explicit edit url or assert against the parent route as
appropriate.

In
`@src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/edit-cart-form.js`:
- Around line 35-44: The backToCart function is brittle because it slices the
URL by a fixed depth (slice(0, -2)); instead, derive the parent URL by removing
the trailing "/forms/:form_id" segment so it works regardless of added segments.
Replace the split/filter/slice/join logic with a robust transformation such as
using match.url.replace(/\/forms\/[^/]+$/, '') (or equivalent) and then
history.push to `/${newUrl}`; reference the backToCart function, match.url and
history.push when making the change.

In `@src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/index.js`:
- Around line 85-89: The route in SponsorCartTab registers EditCartForm at path
`${match.url}/forms/:form_id` but the navigation in components/cart-view.js
pushes `cart/forms/${form.id}`, causing a doubled "cart" segment; update either
the navigator to push a path relative to the mounted tab (e.g.
`forms/${form.id}` and likewise for the invoice link) or change the navigation
to build the absolute path using match.url (e.g.
`${match.url}/forms/${form.id}`) so the pushed URL and the Route (EditCartForm
and the invoice route) resolve to the same pattern.

---

Nitpick comments:
In
`@src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/__tests__/edit-cart-form.test.js`:
- Around line 303-314: The tests "clicking CANCEL navigates back" and the other
similar test only assert mockHistoryPush was called once; update them to also
assert the navigation destination by checking mockHistoryPush was called with
the expected path (e.g., the route string or route object your app uses) instead
of just verifying call count. Locate the tests where mockHistoryPush is asserted
(references: the test named "clicking CANCEL navigates back" and the other test
around lines asserting mockHistoryPush) and change the assertion to
expect(mockHistoryPush).toHaveBeenCalledWith(<expectedPathOrRoute>), or
toHaveBeenCalledWith(expect.stringContaining(...)) if using partial matching, so
the destination is verified.
🪄 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: 11e0826c-3c71-4e0e-bbbd-7136340c0ff1

📥 Commits

Reviewing files that changed from the base of the PR and between 90aff95 and cb72c39.

📒 Files selected for processing (8)
  • src/i18n/en.json
  • src/pages/sponsors/sponsor-page/tabDefs.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/__tests__/edit-cart-form.test.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/edit-cart-form.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/index.js
💤 Files with no reviewable changes (1)
  • src/pages/sponsors/sponsor-page/tabDefs.js
✅ Files skipped from review due to trivial changes (1)
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js

Comment on lines +176 to +179
const buildMatch = (formId, url) => ({
params: { form_id: formId },
url: url || `/app/events/1/sponsors/2/cart/forms/${formId}/edit`
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Test route shape does not match the actual registered edit route.

buildMatch defaults to /forms/${formId}/edit, but the parent route is /forms/:form_id. This can hide real navigation regressions.

Proposed fix
 const buildMatch = (formId, url) => ({
   params: { form_id: formId },
-  url: url || `/app/events/1/sponsors/2/cart/forms/${formId}/edit`
+  url: url || `/app/events/1/sponsors/2/cart/forms/${formId}`
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const buildMatch = (formId, url) => ({
params: { form_id: formId },
url: url || `/app/events/1/sponsors/2/cart/forms/${formId}/edit`
});
const buildMatch = (formId, url) => ({
params: { form_id: formId },
url: url || `/app/events/1/sponsors/2/cart/forms/${formId}`
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/__tests__/edit-cart-form.test.js`
around lines 176 - 179, The test helper buildMatch is using a default URL that
includes `/edit` which doesn't match the registered parent route
`/forms/:form_id`; update buildMatch so its default url is
`/app/events/1/sponsors/2/cart/forms/${formId}` (no `/edit`) to reflect the
actual route shape, and leave callers that need the edit path to pass an
explicit url containing `/edit`; adjust tests that relied on the old default to
either pass the explicit edit url or assert against the parent route as
appropriate.

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: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/reducers/sponsors/sponsor-page-cart-list-reducer.js (1)

88-112: ⚠️ Potential issue | 🟠 Major

Avoid mutating payload.response inside the reducer.

Lines 89-112 mutate the object coming from action.payload. Reducers should be immutable to avoid subtle state bugs.

♻️ Proposed immutable fix
-      const cart = payload.response;
-      cart.forms = cart.forms.map((form) => {
+      const rawCart = payload.response;
+      const forms = rawCart.forms.map((form) => {
         const discountAmount = amountFromCents(form.discount_amount); // this works also for rate from bps
         const discountStr =
           form.discount_type === DISCOUNT_TYPES.RATE
             ? `${discountAmount} %`
             : `$${discountAmount}`;
         const discount = form.discount_amount ? discountStr : "";
@@
-      });
-      cart.total = currencyAmountFromCents(cart.net_amount);
+      });
+      const cart = {
+        ...rawCart,
+        forms,
+        total: currencyAmountFromCents(rawCart.net_amount)
+      };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/reducers/sponsors/sponsor-page-cart-list-reducer.js` around lines 88 -
112, The reducer currently mutates the action payload via the local const cart =
payload.response and then assigns cart.forms = ... and cart.total = ..., which
should be avoided; instead create a newCart object (e.g., const newCart = {
...cart, forms: cart.forms.map(...), total:
currencyAmountFromCents(cart.net_amount) }) and perform immutable maps for forms
and items (reuse amountFromCents, currencyAmountFromCents, DISCOUNT_TYPES logic
inside the maps) so no properties of payload.response are reassigned; finally
use newCart when returning state rather than mutating cart.
♻️ Duplicate comments (1)
src/actions/sponsor-cart-actions.js (1)

575-587: ⚠️ Potential issue | 🟠 Major

Silent error swallowing hides failures from callers.

.catch(console.log) logs errors but converts rejections to resolved promises. Callers cannot distinguish success from failure, which may leave the UI in an inconsistent state (e.g., no payment intent but no error displayed).

Consider propagating the error or handling it explicitly:

🔧 Proposed fix
   return getRequest(
     null,
     createAction(RECEIVE_PAYMENT_PROFILE),
     `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/payment-profiles/SponsorServices`,
     (err, res) => (dispatch) =>
       customErrorHandler(err, res, PaymentProfileNotFound)(dispatch, getState)
   )(params)(dispatch)
     .then(() => createPaymentIntent()(dispatch, getState))
-    .catch(console.log)
+    .catch((err) => {
+      console.error(err);
+      return Promise.reject(err);
+    })
     .finally(() => {
       dispatch(stopLoading());
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/sponsor-cart-actions.js` around lines 575 - 587, The current
chain swallows errors via .catch(console.log) making callers think the operation
succeeded; update the promise handling in the flow that calls
getRequest(...)(params)(dispatch) so errors are propagated instead of absorbed:
remove the trivial .catch(console.log) and either rethrow the caught error after
logging or dispatch an error action (e.g., via customErrorHandler or a new
failure action) so callers and UI can respond; keep the existing .finally(() =>
dispatch(stopLoading())) to ensure loading state is cleared while allowing
createPaymentIntent, customErrorHandler, and callers to receive the rejection.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.env.example:
- Around line 15-17: Reorder the environment keys in .env.example so
PURCHASES_API_SCOPES appears before PURCHASES_API_URL to satisfy dotenv-linter's
UnorderedKey rule; specifically move the PURCHASES_API_SCOPES line above the
PURCHASES_API_URL line (leave the value unchanged) and ensure the surrounding
keys (e.g., SPONSOR_USERS_API_URL) retain their relative order.

In `@package.json`:
- Line 95: The package "openstack-uicore-foundation" was bumped to 5.0.7-beta.1
which requires React ^17; update your dependencies by either (A) upgrading
"react" and "react-dom" in package.json to a ^17.x range and regenerate lockfile
(npm/yarn) and run the app/tests, or (B) revert "openstack-uicore-foundation" to
a stable 4.x version that supports React 16; target the approach you choose and
ensure the package name "openstack-uicore-foundation" and the peer deps "react"
and "react-dom" are consistent across package.json and the generated lockfile.

In `@src/actions/sponsor-cart-actions.js`:
- Around line 619-645: The confirmPayment thunk dereferences paymentIntent.id
from sponsorPageCartListState without checking for null, which can throw if no
payment intent exists; update confirmPayment to first read paymentIntent from
sponsorPageCartListState, check if it is present (and has an id), and if not
either dispatch an appropriate snackbar error (or return a rejected Promise)
instead of proceeding to call putRequest; ensure references to paymentIntent.id
are guarded so the URL is only built when paymentIntent && paymentIntent.id are
truthy.
- Around line 478-491: The current error handler for the putRequest chain uses
.catch(() => new Promise(() => {})) which returns a never-settling promise and
prevents the .finally() block (which dispatches stopLoading()) from running;
update the catch on the putRequest(..., snackbarErrorHandler)(params)(dispatch)
call to return a settled promise (for example Promise.resolve()) or otherwise
swallow the error in a way that resolves, so that .finally() always executes and
stopLoading() is dispatched; locate the chain involving putRequest,
createAction(CART_STATUS_UPDATED), snackbarErrorHandler and stopLoading to apply
this change.
- Around line 59-71: The customErrorHandler function currently dispatches
stopLoading() unconditionally causing duplicate stopLoading calls when callers
(e.g., getPaymentProfile) also call stopLoading() in their .finally(); remove
the stopLoading() dispatch from customErrorHandler and let callers manage
loading state in their .finally() blocks so error paths only call
authErrorHandler(err, res)(dispatch, getState) (and optional callback) without
altering loading state.
- Around line 526-550: The createPaymentIntent thunk assumes
sponsorPageCartListState.cart exists and calls cart.id; add a null-check for
sponsorPageCartListState.cart at the start of createPaymentIntent and fail
gracefully if missing (e.g., dispatch an error action or return a rejected
Promise with a clear message like "No cart available for sponsor" or throw a
descriptive Error). Update the function that builds payload (payload.cart_id =
cart.id) to only run after the guard and ensure any early-return path provides
the same shape (rejected promise or dispatched PAYMENT_INTENT_FAILED/other error
action) so callers handle the failure consistently.
- Around line 589-617: The updatePaymentIntent thunk dereferences cart.id and
paymentIntent.id without null checks which can throw if getState() returns
missing values; update the function (updatePaymentIntent) to guard cart and
paymentIntent (from sponsorPageCartListState) after getState(), and if either is
null/undefined return early or dispatch an error action (or call
snackbarErrorHandler) instead of calling putRequest; ensure the URL construction
uses the validated currentSummit.id, sponsor.id and the safely accessed cart.id
and paymentIntent.id before invoking putRequest.

In `@src/i18n/en.json`:
- Line 2564: Update the user-facing label for the payment table entry keyed by
"code" in the en.json localization: replace the incorrect string "From code"
with the correct "Form code" so the label reads "Form code" in the UI.

In
`@src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js`:
- Around line 93-107: The current handlers (the cartIsPendingPayment logic in
the anonymous checkout flow and handlePayInvoice using checkoutCart and
payWithInvoice) leave potential unhandled promise rejections; modify both flows
to handle failures explicitly by awaiting checkoutCart and payWithInvoice inside
try/catch blocks (or chaining .catch()) so navigation via
history.push("cart/payment") and history.push("cart/invoice") only happens on
success, and surface errors (e.g., call an error handler, set error state, or
show a toast) in the catch so failures are handled and logged instead of left
unhandled.

In
`@src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js`:
- Around line 52-53: The render assumes nested state like currentSummit,
sponsor.company, and cart.notes are always present causing crashes; inside the
PaymentView component (e.g., where redirectUrl is built and where cart/sponsor
fields are referenced) add null-safe guards or an early return (e.g., if
(!currentSummit || !sponsor || !sponsor.company || !cart) return null/loading)
and replace direct property reads with optional chaining (currentSummit?.id,
sponsor?.company, cart?.notes) so the component won't access undefined values;
apply the same pattern to the other referenced blocks that read cart or sponsor
fields.
- Around line 80-83: The success handler handlePaymentSuccess currently calls
confirmPayment(paymentData.id) and assumes it always resolves; if confirmPayment
rejects the error is dropped—wrap the confirmation in a proper rejection path
(either convert handlePaymentSuccess to async and use try/catch around await
confirmPayment(paymentData.id) or add a .catch(...) to the returned promise) and
handle failures by reporting the error (e.g., call the existing error
handler/state updater or logger) and avoid calling history.push(redirectUrl) on
failure; keep references to confirmPayment, handlePaymentSuccess, history.push,
and redirectUrl when implementing the fix.

In `@src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/helpers.js`:
- Around line 16-28: The code assumes it.meta_fields and mf.values exist which
can throw if payloads are null/undefined; update the logic around computing
formMetaFields and mapping mf.values so they use null-safe guards (e.g., treat
it.meta_fields as an empty array, use optional chaining or default empty arrays
for mf.values) before filtering/mapping, and ensure mf.current_value lookup uses
safe access (mf.values?.find(...) and fallback to mf.current_value) so item_name
construction in the block referencing showItemDescription is resilient to
missing metafields or values.

---

Outside diff comments:
In `@src/reducers/sponsors/sponsor-page-cart-list-reducer.js`:
- Around line 88-112: The reducer currently mutates the action payload via the
local const cart = payload.response and then assigns cart.forms = ... and
cart.total = ..., which should be avoided; instead create a newCart object
(e.g., const newCart = { ...cart, forms: cart.forms.map(...), total:
currencyAmountFromCents(cart.net_amount) }) and perform immutable maps for forms
and items (reuse amountFromCents, currencyAmountFromCents, DISCOUNT_TYPES logic
inside the maps) so no properties of payload.response are reassigned; finally
use newCart when returning state rather than mutating cart.

---

Duplicate comments:
In `@src/actions/sponsor-cart-actions.js`:
- Around line 575-587: The current chain swallows errors via .catch(console.log)
making callers think the operation succeeded; update the promise handling in the
flow that calls getRequest(...)(params)(dispatch) so errors are propagated
instead of absorbed: remove the trivial .catch(console.log) and either rethrow
the caught error after logging or dispatch an error action (e.g., via
customErrorHandler or a new failure action) so callers and UI can respond; keep
the existing .finally(() => dispatch(stopLoading())) to ensure loading state is
cleared while allowing createPaymentIntent, customErrorHandler, and callers to
receive the rejection.
🪄 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: 37ed0b67-4548-41a5-98b0-fd2b514a0fa5

📥 Commits

Reviewing files that changed from the base of the PR and between cb72c39 and 484dd24.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (11)
  • .env.example
  • package.json
  • src/actions/sponsor-cart-actions.js
  • src/app.js
  • src/i18n/en.json
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/helpers.js
  • src/reducers/sponsors/sponsor-page-cart-list-reducer.js
  • src/utils/constants.js
✅ Files skipped from review due to trivial changes (1)
  • src/utils/constants.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js

@santipalenque santipalenque force-pushed the feature/sponsor-cart-payments branch from e0ec413 to 353dfa7 Compare April 10, 2026 16:29
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.

♻️ Duplicate comments (1)
src/actions/sponsor-cart-actions.js (1)

478-490: ⚠️ Potential issue | 🔴 Critical

Fix the thunk to resolve properly after errors so awaiting callers can complete their logic.

checkoutCart() returns a never-settling promise on error (line 490), which causes await checkoutCart() in cart-view.js (lines 94, 103) to hang forever—the callers' catch blocks never run. Since snackbarErrorHandler already handles the error, either let the promise reject naturally or resolve after error handling, but do not return new Promise(() => {}).

Error path issue in checkoutCart()
// Current code (lines 478-490)
return (
  putRequest(
    null,
    createAction(CART_STATUS_UPDATED),
    `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/carts/current/checkout`,
    {},
    snackbarErrorHandler
  )(params)(dispatch)
    .finally(() => {
      dispatch(stopLoading());
    })
    // this swallows the error neither rejecting or resolving, so we don't need to handle it down the pipe
    .catch(() => new Promise(() => {}))
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/sponsor-cart-actions.js` around lines 478 - 490, The checkoutCart
thunk currently swallows errors by returning a never-settling promise in the
final .catch, causing awaiting callers to hang; remove the trailing .catch(() =>
new Promise(() => {})) so the promise returned by the putRequest chain can
either reject normally (allowing callers to catch) or resolve after
snackbarErrorHandler runs—keep the existing finally that dispatches
stopLoading() and leave snackbarErrorHandler in place; locate this behavior in
the checkoutCart thunk (references: checkoutCart, putRequest,
createAction(CART_STATUS_UPDATED), snackbarErrorHandler, stopLoading) and delete
or replace the swallowing catch so errors propagate to callers.
🧹 Nitpick comments (1)
src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js (1)

82-85: Make this callback match confirmPayment's actual API.

confirmPayment is defined as a zero-arg thunk in src/actions/sponsor-cart-actions.js, so paymentData.id is ignored here. Either drop the argument at the call site or thread the identifier into the thunk explicitly so the success flow is not misleading.

♻️ Proposed cleanup
-  const handlePaymentSuccess = (paymentData) =>
-    confirmPayment(paymentData.id).then(() => {
+  const handlePaymentSuccess = () =>
+    confirmPayment().then(() => {
       history.push(redirectUrl);
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js`
around lines 82 - 85, handlePaymentSuccess currently passes paymentData.id into
confirmPayment but confirmPayment is a zero-arg thunk (so the id is ignored);
either remove the unused parameter at the call site or update the thunk to
accept and forward the id. Fix option A: change handlePaymentSuccess to call
confirmPayment() (no args) and keep the existing zero-arg action; Fix option B:
modify the action creator confirmPayment to accept an id parameter and use it
inside the thunk, then call confirmPayment(paymentData.id) from
handlePaymentSuccess so the identifier is actually threaded through; ensure
history.push(redirectUrl) remains after the promise resolves.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/actions/sponsor-cart-actions.js`:
- Around line 478-490: The checkoutCart thunk currently swallows errors by
returning a never-settling promise in the final .catch, causing awaiting callers
to hang; remove the trailing .catch(() => new Promise(() => {})) so the promise
returned by the putRequest chain can either reject normally (allowing callers to
catch) or resolve after snackbarErrorHandler runs—keep the existing finally that
dispatches stopLoading() and leave snackbarErrorHandler in place; locate this
behavior in the checkoutCart thunk (references: checkoutCart, putRequest,
createAction(CART_STATUS_UPDATED), snackbarErrorHandler, stopLoading) and delete
or replace the swallowing catch so errors propagate to callers.

---

Nitpick comments:
In
`@src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js`:
- Around line 82-85: handlePaymentSuccess currently passes paymentData.id into
confirmPayment but confirmPayment is a zero-arg thunk (so the id is ignored);
either remove the unused parameter at the call site or update the thunk to
accept and forward the id. Fix option A: change handlePaymentSuccess to call
confirmPayment() (no args) and keep the existing zero-arg action; Fix option B:
modify the action creator confirmPayment to accept an id parameter and use it
inside the thunk, then call confirmPayment(paymentData.id) from
handlePaymentSuccess so the identifier is actually threaded through; ensure
history.push(redirectUrl) remains after the promise resolves.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a5422c40-ba0f-48e1-9a7a-cd36c91befce

📥 Commits

Reviewing files that changed from the base of the PR and between 484dd24 and e0ec413.

📒 Files selected for processing (6)
  • src/actions/sponsor-cart-actions.js
  • src/i18n/en.json
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/edit-cart-form.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/helpers.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/helpers.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/edit-cart-form.js

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