Skip to content

Epic/ba 2017 stripe payments#349

Open
lbjunq wants to merge 22 commits into
masterfrom
epic/BA-2017-stripe-payments
Open

Epic/ba 2017 stripe payments#349
lbjunq wants to merge 22 commits into
masterfrom
epic/BA-2017-stripe-payments

Conversation

@lbjunq

@lbjunq lbjunq commented Mar 9, 2026

Copy link
Copy Markdown
Contributor
  • __package_name__ package update - v __package_version__
    • changelog_info
    • changelog_info

Summary by CodeRabbit

  • New Features

    • Full payments UI: add/manage/select/remove cards, payment dropdown, Add Card modal, and Stripe setup flow.
    • Checkout & subscriptions: end-to-end checkout, subscribe/update plans, confirmation modal, plan change/cancel flows.
    • Subscription browsing: monthly/yearly toggle and responsive subscription cards.
    • Invoices: paginated, responsive invoice list with mobile rows and receipt links.
  • UI polish

    • Masked emails, formatted prices, new credit-card icons, optional cancel button in confirmations.
  • Documentation

    • Payments module README describing platform structure and import rules.

mathieubouhelier and others added 16 commits July 29, 2025 05:25
Implements the first phase of the subscription checkout experience using Stripe.
Includes payment method selection (saved or new), billing address input, and "Place Order" flow.
Displays confirmation modal on success and error feedback on failure.
Uses Stripe invoice ID as order reference.
Tax calculation and Stripe UI caching will be addressed in follow-up stories.
Avoids storing Stripe data in Admin.
✅ Stripe Checkout - Payment Methods Management
Description

As a User, on the BaseApp Profile Page,I would like to add, view,
update, and remove my saved payment methods in the settings menu, In
order to manage my payment options easily and ensure uninterrupted
payments.

 
Acceptance Criteria 
Frontend

    Add a Payment Methods section to the settings menu.

    Display a list of saved payment methods, including:

        Card type and masked card number (e.g., Visa **** 1234).

        Expiration date.

        "Default" label for the primary payment method.

        Include actions for each payment method via a hamburger menu:

Set as Default: Mark a selected payment method as the primary option for
transactions.

Remove: Open a confirmation modal to delete the selected payment method.

    Include a "+ Add Payment Method" button that:

        Opens a modal for entering card details.


https://www.figma.com/design/XRD6wSl1m8Kz6XUcAy5CLp/BaseApp---WEB?node-id=7279-10539&t=wyIQdMiNfVhcs5LQ-4

Calls the backend to securely store the payment method via Stripe.

    On selecting "Remove" from the dropdown:

        Display a confirmation modal with the message:

"Are you sure you want to remove this payment method? If this is your
only payment method, your payments will be interrupted until a new
method is added."

        Include "Cancel" and "Remove" buttons.

Trigger a backend call to delete the payment method from Stripe and
update the user's saved methods.

Backend

    Use Stripe's API to retrieve the user's saved payment methods.

        Ensure each method includes:

        Card type, masked number, and expiration date.

        Default status.

    Trigger a Stripe API call to delete the selected payment method.

    Handle cases where:

The removed method is the default (prompt the user to select another
default method or display a warning).

No payment methods remain (prompt the user to add a new payment method).

        Set as Default

    Use Stripe's API to update the default payment method for the user. 

Design Link:
https://www.figma.com/design/XRD6wSl1m8Kz6XUcAy5CLp/BaseApp---WEB?node-id=7279-191921&t=EqObBvkTBQ7zD0Fr-4

Approvd 
https://app.approvd.io/silverlogic/BA/stories/38019 

Demo: https://www.loom.com/share/50196b35fcd04202a50742f59d8a7745

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a comprehensive Stripe payments module with components for
checkout, payment method management, and subscription confirmation
modals.
- Added support for adding, selecting, and managing payment methods,
including card input and billing address forms.
- Integrated responsive UI elements for payment flows, including modals,
dropdowns, and styled buttons.
- Provided visual feedback with new credit card icons (Visa, Mastercard,
generic) and email masking for privacy.
- Enabled product and subscription management with real-time updates and
notifications.
  - Added a utility to initialize Stripe with a publishable key.
- Included a hook encapsulating Stripe API interactions for streamlined
data fetching and mutations.

- **Chores**
- Added Stripe dependencies and updated package exports to support new
payment features.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Mathieu Bouhelier <mathieubouhelier@gmail.com>
Co-authored-by: Lúcio BJ <lb@tsl.io>
Co-authored-by: Lúcio BJ <lb@tsl.com>
Description

As a User, on the BaseApp Profile Page,I would like to manage my
subscription plan, payment methods, and receive notifications for failed
payment attempts, In order to easily update my subscription and payment
details and ensure uninterrupted access.

 
Acceptance Criteria 
Frontend

Add a Subscription section to the settings menu.

Display the following details:

Current subscription plan.

Plan features.

Next billing date and amount.

Current payment method.

Include actions:

Change Plan button to navigate to the subscription options page.

Cancel Subscription button that opens a confirmation modal.

Display a dropdown menu under Payment Method:

Show all saved payment methods.

Allow users to select a different payment method.

If you change the payment method then display a toast informing the user
that the change was successful.

Include an "Add New Payment Method" option.

Clicking on button displays new payment method modal from checkout story

Failed payment attempts will be covered in a follow-up story

On clicking Cancel Subscription, show a confirmation modal:
Text: "Are you sure you want to cancel your subscription? You will
retain access to your current plan until the end of the billing period.
After that, your account will be downgraded to the free plan."

Buttons: "Back" and "Cancel Subscription."

Subscription Management Story
APIs for Frontend

Build an API to:

fetch subscription plan details directly from Stripe or the local
database (if cached).

Identify the user’s active plan by querying Stripe’s Customer API.

Expose endpoints for actions like subscribing, upgrading, or canceling
plans, routed through Stripe’s API.

User Subscription Status

Use Stripe's Customer API to determine:

The user's active subscription plan.

Billing cycle and renewal dates.

Any upcoming invoices or changes in plan.

Stripe Integration

Use Stripe's API to retrieve:

The user's active subscription details.

Billing information, including the default payment method.

Select and Update Payment Method

Trigger Stripe API to update the payment method for the subscription.

Update the selected default payment method for future billing.

Payment Attempt Notifications

Trigger a Stripe API call to cancel the subscription.

Ensure the user retains access until the end of the billing cycle.

Downgrade the user's account to the free plan after the billing cycle
ends.

Backend Requirements

Create an endpoint to fetch subscription details from Stripe:

Include the active plan, billing amount, next payment date, and payment
method(s).

Keep in mind only 1 subscription can be active at a time, and we need to
display the active subs. correctly

Create an endpoint to update the payment method:

Accept a new payment method ID and update it in Stripe.

Create an endpoint to cancel subscriptions:

Trigger Stripe API to cancel the subscription.

Update the user's account status to reflect the cancellation.

Flag failed payment attempts in the backend for display in the UI.
*Database Updates

Update user account details upon subscription or payment method changes.

Design Link:
https://www.figma.com/design/XRD6wSl1m8Kz6XUcAy5CLp/BaseApp---WEB?node-id=7279-189782&t=IMDSKi4lrWcYru1G-4

## Installation guidance:
BE: USee the master branch
In FE-Template

* Use the branch "wip-stripe-for-test-purpose"
* Update the following value according to your configuration
   * subscriptionId="sub_1RYuyQGYYYYYY"
   * customerId="cus_Rz7mAKnYYYY"
* Add "NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY:"your_value_here"" to the .env
file
* Then run
   * nvm use
   * pnpm replace-baseapp-catalogs
   * pnpm clean && pnpm i && pnpm dev
* access to http://localhost:3000/checkout
Demo
https://www.loom.com/share/9c1b83a7a3364ce3adb43ca19ede57fa



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a comprehensive Stripe payments module, enabling
subscription checkout, payment method management, and card addition via
modals.
- Added responsive checkout and subscription management interfaces with
support for viewing, adding, and selecting payment methods.
- Implemented confirmation dialogs and cancellation flows for
subscriptions.
- Integrated branded credit card icons (Visa, Mastercard, generic) for
enhanced payment method display.

- **Enhancements**
- Added utility functions for masking email addresses and improved user
feedback with toast notifications.
- Centralized and streamlined exports for easier integration and usage.

- **Styling**
- Introduced new styled components for modals, buttons, and containers
to ensure a consistent and responsive user interface.

- **Dependencies**
  - Added Stripe SDK dependencies for seamless payment integration.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
As a User, on the BaseApp Profile Page,I would like to View a detailed
history of my transactions and access receipts for my subscription
payments, In order to Keep track of my subscription expenses, verify
payments, and maintain accurate financial records.

 
Acceptance Criteria 

Given the user navigates to the "Transaction History" section within
settings, the user views a list of all historical payments, sorted by
date with the most recent payment listed first.

Use the Material UI table component

Ensure the transaction history is responsive and provides a consistent
user experience on desktop, tablet, and mobile devices.

Design Link:
https://www.figma.com/design/XRD6wSl1m8Kz6XUcAy5CLp/BaseApp---WEB?node-id=7279-192622&t=pkTsTldiYoAVvZuw-4

Approvd 
https://app.approvd.io/projects/BA/stories/39485 

Demo
https://www.loom.com/share/d293f0dec8a045a7aa1167a45d56df1d

---------

Co-authored-by: Lúcio BJ <lb@tsl.com>
Implements the first phase of the subscription checkout experience using Stripe.
Includes payment method selection (saved or new), billing address input, and "Place Order" flow.
Displays confirmation modal on success and error feedback on failure.
Uses Stripe invoice ID as order reference.
Tax calculation and Stripe UI caching will be addressed in follow-up stories.
Avoids storing Stripe data in Admin.
✅ Stripe Checkout - Payment Methods Management
Description

As a User, on the BaseApp Profile Page,I would like to add, view,
update, and remove my saved payment methods in the settings menu, In
order to manage my payment options easily and ensure uninterrupted
payments.

 
Acceptance Criteria 
Frontend

    Add a Payment Methods section to the settings menu.

    Display a list of saved payment methods, including:

        Card type and masked card number (e.g., Visa **** 1234).

        Expiration date.

        "Default" label for the primary payment method.

        Include actions for each payment method via a hamburger menu:

Set as Default: Mark a selected payment method as the primary option for
transactions.

Remove: Open a confirmation modal to delete the selected payment method.

    Include a "+ Add Payment Method" button that:

        Opens a modal for entering card details.


https://www.figma.com/design/XRD6wSl1m8Kz6XUcAy5CLp/BaseApp---WEB?node-id=7279-10539&t=wyIQdMiNfVhcs5LQ-4

Calls the backend to securely store the payment method via Stripe.

    On selecting "Remove" from the dropdown:

        Display a confirmation modal with the message:

"Are you sure you want to remove this payment method? If this is your
only payment method, your payments will be interrupted until a new
method is added."

        Include "Cancel" and "Remove" buttons.

Trigger a backend call to delete the payment method from Stripe and
update the user's saved methods.

Backend

    Use Stripe's API to retrieve the user's saved payment methods.

        Ensure each method includes:

        Card type, masked number, and expiration date.

        Default status.

    Trigger a Stripe API call to delete the selected payment method.

    Handle cases where:

The removed method is the default (prompt the user to select another
default method or display a warning).

No payment methods remain (prompt the user to add a new payment method).

        Set as Default

    Use Stripe's API to update the default payment method for the user. 

Design Link:
https://www.figma.com/design/XRD6wSl1m8Kz6XUcAy5CLp/BaseApp---WEB?node-id=7279-191921&t=EqObBvkTBQ7zD0Fr-4

Approvd 
https://app.approvd.io/silverlogic/BA/stories/38019 

Demo: https://www.loom.com/share/50196b35fcd04202a50742f59d8a7745

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a comprehensive Stripe payments module with components for
checkout, payment method management, and subscription confirmation
modals.
- Added support for adding, selecting, and managing payment methods,
including card input and billing address forms.
- Integrated responsive UI elements for payment flows, including modals,
dropdowns, and styled buttons.
- Provided visual feedback with new credit card icons (Visa, Mastercard,
generic) and email masking for privacy.
- Enabled product and subscription management with real-time updates and
notifications.
  - Added a utility to initialize Stripe with a publishable key.
- Included a hook encapsulating Stripe API interactions for streamlined
data fetching and mutations.

- **Chores**
- Added Stripe dependencies and updated package exports to support new
payment features.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Mathieu Bouhelier <mathieubouhelier@gmail.com>
Co-authored-by: Lúcio BJ <lb@tsl.io>
Co-authored-by: Lúcio BJ <lb@tsl.com>
Description

As a User, on the BaseApp Profile Page,I would like to manage my
subscription plan, payment methods, and receive notifications for failed
payment attempts, In order to easily update my subscription and payment
details and ensure uninterrupted access.

 
Acceptance Criteria 
Frontend

Add a Subscription section to the settings menu.

Display the following details:

Current subscription plan.

Plan features.

Next billing date and amount.

Current payment method.

Include actions:

Change Plan button to navigate to the subscription options page.

Cancel Subscription button that opens a confirmation modal.

Display a dropdown menu under Payment Method:

Show all saved payment methods.

Allow users to select a different payment method.

If you change the payment method then display a toast informing the user
that the change was successful.

Include an "Add New Payment Method" option.

Clicking on button displays new payment method modal from checkout story

Failed payment attempts will be covered in a follow-up story

On clicking Cancel Subscription, show a confirmation modal:
Text: "Are you sure you want to cancel your subscription? You will
retain access to your current plan until the end of the billing period.
After that, your account will be downgraded to the free plan."

Buttons: "Back" and "Cancel Subscription."

Subscription Management Story
APIs for Frontend

Build an API to:

fetch subscription plan details directly from Stripe or the local
database (if cached).

Identify the user’s active plan by querying Stripe’s Customer API.

Expose endpoints for actions like subscribing, upgrading, or canceling
plans, routed through Stripe’s API.

User Subscription Status

Use Stripe's Customer API to determine:

The user's active subscription plan.

Billing cycle and renewal dates.

Any upcoming invoices or changes in plan.

Stripe Integration

Use Stripe's API to retrieve:

The user's active subscription details.

Billing information, including the default payment method.

Select and Update Payment Method

Trigger Stripe API to update the payment method for the subscription.

Update the selected default payment method for future billing.

Payment Attempt Notifications

Trigger a Stripe API call to cancel the subscription.

Ensure the user retains access until the end of the billing cycle.

Downgrade the user's account to the free plan after the billing cycle
ends.

Backend Requirements

Create an endpoint to fetch subscription details from Stripe:

Include the active plan, billing amount, next payment date, and payment
method(s).

Keep in mind only 1 subscription can be active at a time, and we need to
display the active subs. correctly

Create an endpoint to update the payment method:

Accept a new payment method ID and update it in Stripe.

Create an endpoint to cancel subscriptions:

Trigger Stripe API to cancel the subscription.

Update the user's account status to reflect the cancellation.

Flag failed payment attempts in the backend for display in the UI.
*Database Updates

Update user account details upon subscription or payment method changes.

Design Link:
https://www.figma.com/design/XRD6wSl1m8Kz6XUcAy5CLp/BaseApp---WEB?node-id=7279-189782&t=IMDSKi4lrWcYru1G-4

## Installation guidance:
BE: USee the master branch
In FE-Template

* Use the branch "wip-stripe-for-test-purpose"
* Update the following value according to your configuration
   * subscriptionId="sub_1RYuyQGYYYYYY"
   * customerId="cus_Rz7mAKnYYYY"
* Add "NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY:"your_value_here"" to the .env
file
* Then run
   * nvm use
   * pnpm replace-baseapp-catalogs
   * pnpm clean && pnpm i && pnpm dev
* access to http://localhost:3000/checkout
Demo
https://www.loom.com/share/9c1b83a7a3364ce3adb43ca19ede57fa



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a comprehensive Stripe payments module, enabling
subscription checkout, payment method management, and card addition via
modals.
- Added responsive checkout and subscription management interfaces with
support for viewing, adding, and selecting payment methods.
- Implemented confirmation dialogs and cancellation flows for
subscriptions.
- Integrated branded credit card icons (Visa, Mastercard, generic) for
enhanced payment method display.

- **Enhancements**
- Added utility functions for masking email addresses and improved user
feedback with toast notifications.
- Centralized and streamlined exports for easier integration and usage.

- **Styling**
- Introduced new styled components for modals, buttons, and containers
to ensure a consistent and responsive user interface.

- **Dependencies**
  - Added Stripe SDK dependencies for seamless payment integration.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
As a User, on the BaseApp Profile Page,I would like to View a detailed
history of my transactions and access receipts for my subscription
payments, In order to Keep track of my subscription expenses, verify
payments, and maintain accurate financial records.

 
Acceptance Criteria 

Given the user navigates to the "Transaction History" section within
settings, the user views a list of all historical payments, sorted by
date with the most recent payment listed first.

Use the Material UI table component

Ensure the transaction history is responsive and provides a consistent
user experience on desktop, tablet, and mobile devices.

Design Link:
https://www.figma.com/design/XRD6wSl1m8Kz6XUcAy5CLp/BaseApp---WEB?node-id=7279-192622&t=pkTsTldiYoAVvZuw-4

Approvd 
https://app.approvd.io/projects/BA/stories/39485 

Demo
https://www.loom.com/share/d293f0dec8a045a7aa1167a45d56df1d

---------

Co-authored-by: Lúcio BJ <lb@tsl.com>
…/baseapp-frontend into epic/BA-2017-stripe-payments
After a major refactor in the BE, this changes were necessary to align
with it, and make sure the flow is working as intended

---------

Co-authored-by: Lúcio BJ <lb@tsl.com>
✅ Stripe Checkout - Subscription Plans Page
Description

As a User, on the BaseApp Profile Page,I would like to view and manage
available subscription plans on a responsive page tailored for different
devices, In order to select or manage a plan that meets my needs
effectively.

 
Acceptance Criteria 
Frontend

    Responsive Layouts

        Desktop (Horizontal layout):

Display plans in a row, account for multiple rows if more than 3 plans
exist.

Ensure proper alignment and consistent card widths regardless of the
number of plans.

        Mobile & Tablet (Vertical layout):

Stack plans vertically to ensure usability and readability on smaller
screens.

CoP comments

     

        Its ok to reuse Stripe Web Elements. 

Check if its feasible to replace the BaseApp Components with the Stripe
Payment Elements

Check so that we can customize the individual component so that the end
result is as similar as possible to BaseApp components in the designs:
https://docs.stripe.com/elements/appearance-api#rules

We have to think about what customizations are we going to be enabled so
that components can be re used in other projects.

            Probably a good idea to talk to CoP when thinking of this.

Plan Cards

    Reusable plan card component:

Title, pricing, description, and features dynamically populated via
backend.

Call-to-Action Buttons

    Highlight the “Subscribe” button for available plans.

Use of the button to redirect user to Checkout Page will be covered in a
follow-up story

Design Link:
https://www.figma.com/design/XRD6wSl1m8Kz6XUcAy5CLp/BaseApp---WEB?node-id=7170-121730&t=EqObBvkTBQ7zD0Fr-4

Approvd 
https://app.approvd.io/projects/BA/stories/38018 

Demo: https://www.loom.com/share/b024bb2566c34682bd0c182616248275

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- New Features
- Browse available subscriptions, toggle monthly/yearly, view pricing,
features, and active status.
- Complete Stripe-based checkout: address entry, payment method
selection/addition, error handling, and success confirmation.
- Manage payment methods: list, set default, remove, and add new via
modal.
- Subscription management: view plan details, change plan, update
default payment method, and cancel with confirmation; includes Free plan
view.
- Invoices table with pagination, mobile-friendly rows, and receipt
links.
- New payment card icons (Visa/Mastercard) and improved dialog with
optional hidden Cancel button.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Mathieu Bouhelier <mathieubouhelier@gmail.com>
Co-authored-by: Lúcio BJ <lb@tsl.io>
Co-authored-by: Lúcio BJ <lb@tsl.com>
Co-authored-by: Alisson Patricio <eu@alisson.net>
Co-authored-by: Mathieu Bouhelier <62220320+mathieubouhelier@users.noreply.github.com>
❌ Subscription Page Component Integration
Description fields
Description

Acceptance Criteria 

Implement components to the subscription page so that everything is
integrated with existing components in baseapp.

Approvd 
https://app.approvd.io/silverlogic/BA/stories/42883 

Demo:
https://www.loom.com/share/0f76db0716b6466ebae525250630c2c6

---------

Co-authored-by: Mathieu Bouhelier <mathieubouhelier@gmail.com>
Co-authored-by: Lúcio BJ <lb@tsl.io>
Co-authored-by: Lúcio BJ <lb@tsl.com>
@coderabbitai

coderabbitai Bot commented Mar 9, 2026

Copy link
Copy Markdown

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

Adds a complete web payments module: Stripe API wrapper, React Query hooks, Stripe Elements flows (add card, checkout, subscription management), invoice UI, utilities/icons/styled components, and package export mappings for payments/web.

Changes

Cohort / File(s) Summary
Core Stripe API & Hooks
packages/components/modules/payments/web/services/stripe.ts, packages/components/modules/payments/web/hooks/useStripeHook.tsx
New StripeApi wrapper, STRIPE_API_KEY query key helpers, and centralized useStripeHook exposing React Query hooks for customers, setup intents, payment methods, products, subscriptions, and invoices with cache invalidation and toast/error handling.
Domain Types & Shared Utils
packages/components/modules/payments/web/types.ts, packages/components/modules/payments/web/utils/index.tsx, packages/components/modules/payments/web/utils/stripe.ts, packages/components/modules/payments/web/CheckoutComponent/utils.tsx
Adds payment-related types (Customer, Product, Subscription, Invoice, request bodies), formatPrice, card-brand constants/icons, getStripePromise, maskEmail/getCardIcon utilities.
Checkout Flow
packages/components/modules/payments/web/CheckoutComponent/index.tsx, .../styled.tsx, .../types.ts, .../utils.tsx
New CheckoutComponent with Elements wrapper: subscription create/update flows, AddressElement/payment confirmation, confirmation modal, styled components and related types/utilities.
Add Card & Dropdown
packages/components/modules/payments/web/AddCardModal/*, packages/components/modules/payments/web/PaymentDropDown/*
AddCardModal (PaymentElement + AddressElement + stripe.confirmSetup) and PaymentDropdown to list/select/add payment methods (creates SetupIntent and opens modal).
Payment Methods Management
packages/components/modules/payments/web/PaymentMethodsManagementComponent/*
UI to list/add/set-default/delete payment methods, menu/confirm flows, Elements wrapper, and query invalidation integration.
Subscriptions UI
packages/components/modules/payments/web/AvailableSubscriptions/*, packages/components/modules/payments/web/SubscriptionManagement/*
AvailableSubscriptions, SubscriptionCard, SubscriptionManagement (change/cancel flows), FreePlanComponent, CancelSubscriptionModal, and status→chip mapping util with styled/types.
Invoices
packages/components/modules/payments/web/InvoiceListTable/*
Responsive, paginated InvoiceListTable with header, footer, mobile/desktop row variants, receipt links, and related types.
Design System Icons & ConfirmDialog
packages/design-system/components/web/icons/*, packages/design-system/components/web/dialogs/ConfirmDialog/*
Adds Check, CreditCard, Visa, Mastercard icons and exposes hideCancelButton prop/type on ConfirmDialog.
Exports, Package & Docs
packages/components/modules/payments/web/index.ts, packages/components/package.json, packages/components/modules/payments/README.md
Barrel export for payments/web, package.json export mappings and Stripe deps, and README documenting module structure.
Misc & Constants
packages/utils/constants/date.ts, packages/components/modules/messages/.../GroupDetailsQuery.ts
Add ISO date format constant; small GraphQL selection reorder (participantIds moved).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as CheckoutComponent
    participant Dropdown as PaymentDropdown
    participant Modal as AddCardModal
    participant Elements as Stripe Elements
    participant API as StripeApi/Backend

    User->>UI: Open checkout page
    UI->>API: listProducts(), getCustomer(), listPaymentMethods()
    API-->>UI: products, customer, paymentMethods
    User->>Dropdown: Choose "Add New" card
    Dropdown->>API: createSetupIntent(entityId)
    API-->>Dropdown: setupIntent.client_secret
    Dropdown->>Modal: open(clientSecret)
    User->>Elements: Fill PaymentElement & AddressElement
    User->>Modal: Confirm
    Modal->>API: confirmSetup (via Stripe or backend)
    API-->>Modal: payment_method id
    Modal->>UI: handleSetupSuccess(payment_method)
    User->>UI: Click Subscribe
    UI->>API: createSubscription(productId, paymentMethodId)
    API-->>UI: subscription created
    UI->>UI: invalidate queries, show confirmation modal
Loading
sequenceDiagram
    participant User
    participant SubMgmt as SubscriptionManagement
    participant Dropdown as PaymentDropdown
    participant API as StripeApi/Backend

    User->>SubMgmt: Load subscription management
    SubMgmt->>API: getSubscription(), listPaymentMethods()
    API-->>SubMgmt: subscription, paymentMethods
    User->>Dropdown: Select different default method
    Dropdown->>API: updatePaymentMethod / updateSubscription
    API-->>Dropdown: success
    Dropdown->>SubMgmt: trigger refetch/invalidate
    User->>SubMgmt: Click Cancel
    SubMgmt->>User: show CancelSubscriptionModal
    User->>SubMgmt: Confirm cancel
    SubMgmt->>API: cancelSubscription(subscriptionId)
    API-->>SubMgmt: canceled
    SubMgmt->>SubMgmt: invalidate subscription, update UI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

needs review

Suggested reviewers

  • matheusysd
  • priscilladeroode
  • anicioalexandre

Poem

🐰 I hop through code with carrot zeal,

Cards and invoices snugly feel.
Stripe threads stitched and modals bright,
Subscriptions waltz into the night.
Hooray — payments ready, what a delight!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is entirely a template with placeholder values (package_name, package_version, changelog_info) and contains no actual information about the changes made. Replace the template with substantive details describing the payments module addition, key components (CheckoutComponent, SubscriptionManagement, etc.), and specific features implemented.
Title check ❓ Inconclusive The title uses a placeholder format that doesn't clearly describe the actual changes; 'Epic/ba 2017' is not informative about the specific feature or scope. Replace with a descriptive title such as 'Add Stripe payments integration with subscription and checkout flows' to clarify the main changes.
✅ Passed checks (1 passed)
Check name Status Explanation
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 epic/BA-2017-stripe-payments

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (20)
packages/components/modules/payments/web/SubscriptionManagement/utils.ts-1-20 (1)

1-20: ⚠️ Potential issue | 🟠 Major

incomplete is misclassified as "Active" and paused status is unhandled.

Stripe treats incomplete as a pending-payment state (first invoice not yet successfully paid), not an active subscription. The current mapping incorrectly shows pending subscriptions as "Active" with green success color. Additionally, paused is a valid Stripe subscription status (occurs when trial ends without a default payment method, or via the Pause API), but the function returns blank { label: '', color: '' } for it, leaving a valid state unlabeled in the UI.

Suggested fix
-export const getChipLabelAndColorByStatus = (status: string) => {
-  let label = ''
-  let color: 'success' | 'error' | 'warning' | 'info' | '' = ''
-  if (status === 'active' || status === 'trialing' || status === 'incomplete') {
-    label = 'Active'
-    color = 'success'
-  }
-  if (status === 'canceled' || status === 'incomplete_expired') {
-    label = 'Canceled'
-    color = 'error'
-  }
-  if (status === 'past_due') {
-    label = 'Past Due'
-    color = 'warning'
-  }
-  if (status === 'unpaid') {
-    label = 'Unpaid'
-    color = 'error'
-  }
-  return { label, color }
-}
+type SubscriptionStatus =
+  | 'active'
+  | 'trialing'
+  | 'incomplete'
+  | 'incomplete_expired'
+  | 'past_due'
+  | 'canceled'
+  | 'unpaid'
+  | 'paused'
+
+export const getChipLabelAndColorByStatus = (status: SubscriptionStatus) => {
+  switch (status) {
+    case 'active':
+    case 'trialing':
+      return { label: 'Active', color: 'success' as const }
+    case 'incomplete':
+      return { label: 'Pending Payment', color: 'warning' as const }
+    case 'incomplete_expired':
+      return { label: 'Expired', color: 'error' as const }
+    case 'past_due':
+      return { label: 'Past Due', color: 'warning' as const }
+    case 'canceled':
+      return { label: 'Canceled', color: 'error' as const }
+    case 'unpaid':
+      return { label: 'Unpaid', color: 'error' as const }
+    case 'paused':
+      return { label: 'Paused', color: 'info' as const }
+    default:
+      return { label: 'Unknown', color: 'info' as const }
+  }
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/components/modules/payments/web/SubscriptionManagement/utils.ts`
around lines 1 - 20, The getChipLabelAndColorByStatus function incorrectly
classifies 'incomplete' as Active and omits 'paused'; update
getChipLabelAndColorByStatus so statuses are matched exclusively (use if/else-if
or a switch) and map 'incomplete' to a pending state (e.g., label "Pending" with
color 'warning' or 'info') and add handling for 'paused' (e.g., label "Paused"
with color 'info' or 'warning'); keep existing mappings for 'active',
'trialing', 'canceled', 'past_due', and 'unpaid' but ensure no overlapping
branches cause wrong labels.
packages/design-system/components/web/icons/MastercardCreditCardIcon/index.tsx-5-6 (1)

5-6: ⚠️ Potential issue | 🟠 Major

Merge sx as an array to handle all prop forms.

SvgIconProps["sx"] can be an object, function, or array. Spreading sx directly will silently fail with non-object forms. Normalize to an array instead.

Suggested fix
 const MastercardCreditCardIcon: FC<SvgIconProps> = ({ sx, ...props }) => (
-  <SvgIcon sx={{ fontSize: 24, color: 'action.active', ...sx }} {...props}>
+  <SvgIcon
+    sx={[
+      { fontSize: 24, color: 'action.active' },
+      ...(Array.isArray(sx) ? sx : sx ? [sx] : []),
+    ]}
+    {...props}
+  >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/design-system/components/web/icons/MastercardCreditCardIcon/index.tsx`
around lines 5 - 6, The component MastercardCreditCardIcon spreads sx directly
which fails for non-object forms; update the SvgIcon sx prop to normalize sx
into an array by merging the default style object ({ fontSize: 24, color:
'action.active' }) with the incoming sx converted to an array (treat undefined
as empty, if Array.isArray(sx) use it, else wrap sx in [sx]) so SvgIcon receives
a combined array-style sx that supports object, function, or array forms; update
MastercardCreditCardIcon to compute this mergedSx and pass it to <SvgIcon
sx={mergedSx} ...props>.
packages/design-system/components/web/icons/MastercardCreditCardIcon/index.tsx-6-31 (1)

6-31: ⚠️ Potential issue | 🟠 Major

Move the viewBox to SvgIcon and remove the nested <svg> element.

SvgIcon already renders the outer SVG element. Wrapping SVG content in a nested <svg> with explicit width and height attributes prevents MUI's sizing and fontSize props from controlling the artwork correctly. Per MUI's API, pass drawing elements (<rect>, <defs>, etc.) directly as children of SvgIcon.

For this non-24×24 icon (36×24), set the viewBox prop on SvgIcon to "0 0 36 24" rather than on a nested element.

Suggested fix
-  <SvgIcon sx={{ fontSize: 24, color: 'action.active', ...sx }} {...props}>
-    <svg
-      width="36"
-      height="24"
-      viewBox="0 0 36 24"
-      fill="none"
-      xmlns="http://www.w3.org/2000/svg"
-      xmlnsXlink="http://www.w3.org/1999/xlink"
-    >
+  <SvgIcon
+    viewBox="0 0 36 24"
+    sx={{ fontSize: 24, color: 'action.active', ...sx }}
+    {...props}
+  >
       <rect x="5" y="3" width="26" height="18" fill="url(`#pattern0_0_8810`)" />
       <defs>
         <pattern id="pattern0_0_8810" patternContentUnits="objectBoundingBox" width="1" height="1">
           <use
             xlinkHref="#image0_0_8810"
             transform="matrix(0.00713719 0 0 0.0103093 -0.0138779 0)"
           />
         </pattern>
         <image
           id="image0_0_8810"
           width="144"
           height="97"
           preserveAspectRatio="none"
           xlinkHref="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAABhCAYAAAA5iCgEAAAACXBIWXMAACxLAAAsSwGlPZapAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAnESURBVHgB7Z3/bhNXFsfPuRNn86PVOtolUDaE2dJGaP8okar9r1KcJyA8QfDyAMATJHkC4AGqhCcoPAHu/7va8EdXLLDVJLRbkewqrgoJJZ57es4EO4kztsf2eMY+no9kQWYmhnv1vefXvXMvQEZGRkZGRkbG8IHQR+y5bv4QJl3w38/zj9f4kweCeUDMHz1B7vHT6H245hFgmVvylJ/dRCfnTXvfbUKfQOtufh/8All0HQPX+P+a/9COfPhvBO0qS7ushS3HmM0x4HYVvTL0IakLaNe9WiDfv85/FdEUID5KhPitMaZ0zntWgoQQwbwFf8kBXCBpD4ELcYCwyd/HA4MeT4BT6hdBpSKgqmj4H79JDUdirHj8KYEz+qAX1ikQjbU3jcHrQFSAJEB8ZK19/NGtVxuQIokJSNxTxY7eJqClwC2lh8eftelXLzagSw7W3QK35zaxaDCZgRCGRwglA2ZtvOh5kDA9F1BVODwy71B6nRyGBx0K6YNwVhKzNhFhIW0kLaSeCmh3dm6lD4VTjwcRhfRm3Z03QPf6TTj1JCmkngjoKMax66ezpr7HyznO4pT3zKu/ITHOAVgeDHAHBgfPEq31OkaKVUBH7iq3QoPV0adAxNVz28/Xqj8H7opkMMSUTSUNYgkBi72yRrEJaM+96h769smAWZ1wuJ6UG3FujK3sL/NgWIXBp2fWKBYB7czOLSPR/T6PdSKDOYDRv/rlka/8PE4SaAERVseL22sQI10LaHdmboWQVkEJOM7i+dIHHDv62XxqwfzJghq4fjTOLi2uQmRXAtIunipmlkV0WZOIeGoEzI044qKOBTQs4qmiTkQcFyGaxW5FZKADhk08gt02YLc66q5+xSWw30iJArqg7R4ZRvFUCUT0WpGIeErpAOgb6IK2euP1pStLmsQjjF6LJp4q9nuuRb/tq1Uw3cFV9f312XvQIZEFJHUeLo+vgyJycwT4EbRHhUX0Lyf4Uw1c+N3/eraj4m+koRQs9PJH/6miSPgB5yJB7i+dB8X4ewLnCx8UUR7hoHq06LW13CWSBZLpCU3ikbhn5NPuMir6GcH+qCqozleo/aC6ZQ8EE6MDPLcVhoinnbinERJU06+K4iHOzIJJ4zZoKaCjWXU9iOtyPolpekLioX+rskJBPPR+3Y284K9p6yVl1+S6hG5dVz3iyuSjiYqseYpIQwFJ1kUIN0ERYn3icF312OcOqIJTe1nGEuXRhgKqVOxyZn2iQe/4o6nACOLJKFIsFNrqzPq0jwTUqohohUJbfej7hcz6tEdghZTFQlGsUINhg22lcv2OmYKeWp8qyiZbI1mhMy3enbla0GZ9nIvJLMMIMjJddSHwyS41u39GQIT+MigCRyC+uk8E6LUuAbFAllvcrwOhqeIGDTOd7JpmbdkYk2/mxk61NnBfpGNhfBXnXMICkmB6iNzYKQGR8VVZHwGnkn+rgv6vzo1db3LvGLSwAIowHx/FQElDZV0CAplkXXfdsBs1AcmaHy4eprlrRuykYX0EbfUgwbdSGzxLTUCVypgq8Qjm45ReCqzoi4MAMVQfNQH5htQJKIniYUPe6BIQNtgbwBz/xbqgDEzLAoFYIFAFHu1ZeYbjIJrCHxhUJHhOI4Cu8Q604YZdrAkIldV/ZN1zmlBFXyAdlonVBES1rXSVkKb1EZS5MMEJ2X3lRB1I1wRq6rxTmMo3F5AucETPvj79gh0mAUEOMmKGrD9EAsqIHcc4Xv21YwEh9OVZDJ1CBynHIMpe1GjEiToQqhJQ6uSGIwY7UQciXRYo7UKeTgvk1V844cLQA00cQqrgmD4LFLYd3olCIm2BIqhy9EmNNCdye4MXdrEmIAvGA22kGEhr2l86oIGHqgnIsX7fnPIXF/ZNJqC4sERPw67XBDQyUtEnoF8gFYJ1SGnPxcUMoimFXa8JaMrzyjwjr0pEtJeSBdLmviAQymaD68eQgW9BEWKB0gikzR90CYgANxttSF73VgY9AmXY3RRma/K6BGSQGnqmU7177oeXJW1TGnYPEgUn+fM7bS7MPGx4J+Raw4cHEbuDiboxvKDqPA2Qc+zZfZUa3T0jIG1uLCgoJhhMo7L4B5FKze6fEZBGN1bZTkZAyLGPQvfV9IC60AiTLD4ARVi2QPRL70WU9E4gPQex1Oo4qFABjY68v6/OCv3UWwFJ8RDP64p/2J62NCShApKiojor9N/eBtNyIJ0u0Jssei3j4YZFEm1WSMTjf9+bmpBG64OEkQ7nbdijYoUsUKwn/KaNBNPUg9dt1FkfOZj3lrcR5dGmQ/LC9kuxQqrmxw6/i1dAeJ70WR/Au1GfbWnTuS4U+csGAcnI/N34RKTsIF4ggrV2DuKN1JM7M1fYEpnboATZdGH0K7/rzRfMFQvmoiYBoTfxt60/t/MbkaLK3Ii/qsmVSUB9+LS7gFoqzprEw464jIiL0CaRelECat84NzRlZYEr67BCLVmXWB9NINFaJ2fIRx6Gn3jPPEO2CIo4fG4CIbWDuD3zha9qykLinolb2/ehA9qy43989Z9HRLpSe3Fl7aT2KMdlaprv4pR98tb2KnRI24HA+R9ermoSkcRD7/8RTUSScRlVKTtujgN25VU6zmdfz3y2ykGXmlN9ZEez0S9twxcCA/GoKhiyeDhoxqLXVVzbVUFkWESUiacxXeWy6tzZwWl3FgTMc7rEwy15GJd4hFhKsv+7dGXJonOPw3kXFCDCybElyi1UVL0gKNlWNwFzGLFMT0t25huzqGWDBg6sS3t/96dgQod1PSoSmsW4xSPEPjU9yHERcqHU92ntwo8vazUR2dqW3fSTgd2EFLHEk6PFToqEkb4eesBP7lXXsfbJgLm0ku84RSmYht18+/UsDwwYmIEhVscQ3o26LKNTerrOc+fS5zdBrFE/CwnBI7J3z7MbbvWoWCNLdhVbHAOZJiIc7u8HE8bcjytQbkYiryv0pZBYONzba9OvXmxAm/SjkJIWTpVEdx/oEyGVCOyDKBanFVUhcTdeR6BUdvpPSzhVUtm+Ysf9fJ58uMPB9kIyYkJxUw855SwF773FDK27+bfgLzlgljngLkCPCeIblBdAzcNmb40mQer78e/OfFawxhSQaIGD1HmK4dAXyab4ezY5dX1MxpamvReJrWUSMe0DFEAOqkW8xu2K5xw2zqZ4sD3lNj0aA25bCtYmjNQFVI9YJ+Nb10dnnjv/MsgxQ8iiopDDYJDKSGxdgMqEuMXPe5xJlRplUmkggnoHPDAszANabgte5k7Py7EB9W4vsCwga67Isxa2yJAH4GxOAnj9IpiMjIyMjIyMjIyMjIyMjIyMjOHkNzyZDwpa4ewTAAAAAElFTkSuQmCC"
         />
       </defs>
-    </svg>
   </SvgIcon>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/design-system/components/web/icons/MastercardCreditCardIcon/index.tsx`
around lines 6 - 31, The nested <svg> is blocking MUI SvgIcon sizing; remove the
inner <svg> element and move its viewBox="0 0 36 24" up to the SvgIcon component
(e.g., <SvgIcon viewBox="0 0 36 24" sx={{ fontSize: 24, ...sx }} {...props}>),
then place the drawing children (rect, defs, pattern id="pattern0_0_8810", image
id="image0_0_8810", etc.) directly inside SvgIcon; also remove explicit
width/height attributes and unnecessary xmlns/xlink attributes from the removed
<svg> so MUI fontSize and sizing control the icon correctly.
packages/components/modules/payments/web/utils/stripe.ts-3-4 (1)

3-4: ⚠️ Potential issue | 🟠 Major

Return null instead of a rejected promise when the Stripe key is unavailable.

The Elements component from @stripe/react-stripe-js expects the stripe prop to be a Stripe instance, null, or a Promise that resolves to either—not a rejected promise. Returning Promise.reject(...) violates this contract and can cause unhandled rejections since the library doesn't expect to handle promise rejections.

All three call sites pass this helper directly to Elements without error boundaries, so a rejected promise will surface as an unhandled rejection. Use null instead, which is the pattern Stripe documents for cases where initialization isn't ready.

Recommended change
 import { loadStripe } from '@stripe/stripe-js'
 
-export const getStripePromise = (key: string) =>
-  key ? loadStripe(key) : Promise.reject(new Error('Stripe publishable key is not defined'))
+export const getStripePromise = (key?: string) => (key ? loadStripe(key) : null)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/components/modules/payments/web/utils/stripe.ts` around lines 3 - 4,
getStripePromise currently returns Promise.reject(...) when key is falsy, which
can cause unhandled rejections since `@stripe/react-stripe-js` expects a Stripe
instance, null, or a resolving Promise; change getStripePromise (the exported
function) to return null instead of a rejected Promise when the key is missing
so callers (and Elements) receive null per Stripe docs; keep the successful
branch using loadStripe(key) unchanged.
packages/utils/constants/date.ts-7-7 (1)

7-7: ⚠️ Potential issue | 🟠 Major

Escape the literal T in this timestamp format.

This constant is used with Luxon's toFormat() and fromFormat() methods, where T is a recognized token. To render a literal T, wrap it in single quotes: "yyyy-MM-dd'T'HH:mm:ss.SSS".

Proposed fix
-  iso: 'yyyy-MM-ddTHH:mm:ss.SSS',
+  iso: "yyyy-MM-dd'T'HH:mm:ss.SSS",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/utils/constants/date.ts` at line 7, The iso format string in the
constants file is using an unescaped T token which Luxon treats as a format
token; update the iso constant (in packages/utils/constants/date.ts, symbol:
iso) to escape the literal T so Luxon prints/parses it as a character (use
"yyyy-MM-dd'T'HH:mm:ss.SSS") and ensure any usages with
DateTime.toFormat()/fromFormat() rely on this corrected constant.
packages/components/modules/payments/web/CheckoutComponent/utils.tsx-22-24 (1)

22-24: ⚠️ Potential issue | 🟠 Major

Mask at least one character for short email usernames.

Line 22 currently leaves 1- to 3-character usernames fully visible, so values like ab@example.com come back unmasked. That defeats the purpose of this helper for a subset of customer emails in the checkout flow.

Proposed fix
-  const visibleLength = Math.min(visibleUsernameChars, username.length)
+  const visibleLength =
+    username.length <= 1
+      ? 0
+      : Math.min(visibleUsernameChars, username.length - 1)
   const maskedUsername =
     username.slice(0, visibleLength) + maskChar.repeat(username.length - visibleLength)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/components/modules/payments/web/CheckoutComponent/utils.tsx` around
lines 22 - 24, The current logic lets very short usernames remain fully visible;
update how visibleLength is computed so we always reserve at least one character
to mask (e.g., compute visibleLength as Math.min(visibleUsernameChars,
Math.max(0, username.length - 1))) and then build maskedUsername using that
visibleLength with maskChar.repeat(username.length - visibleLength); change the
variable visibleLength calculation (used by maskedUsername) accordingly to
ensure at least one character is masked for short usernames.
packages/components/modules/payments/web/InvoiceListTable/index.tsx-54-56 (1)

54-56: ⚠️ Potential issue | 🟠 Major

Invalid table structure: CircularProgress directly inside TableBody.

Placing CircularProgress directly inside <TableBody> produces invalid HTML table structure. It must be wrapped in <TableRow> and <TableCell>.

Proposed fix
             <TableBody>
-              {isFetching && <CircularProgress sx={{ margin: 'auto' }} />}
+              {isFetching && (
+                <TableRow>
+                  <TableCell colSpan={5} sx={{ textAlign: 'center' }}>
+                    <CircularProgress />
+                  </TableCell>
+                </TableRow>
+              )}
               {isResultsEmpty ? (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/components/modules/payments/web/InvoiceListTable/index.tsx` around
lines 54 - 56, The CircularProgress is rendered directly inside TableBody (see
TableBody and isFetching) which creates invalid table markup; wrap the loader
inside a TableRow and TableCell (use TableRow and TableCell components) and set
the TableCell to span the full width (e.g., colSpan equal to the number of table
columns or a safe value) and center the CircularProgress via sx or align
properties so the loader is validly contained within the table structure.
packages/components/modules/payments/web/hooks/useStripeHook.tsx-141-144 (1)

141-144: ⚠️ Potential issue | 🟠 Major

Separate the combined query key into individual invalidateQueries calls.

The queryKey format doesn't match the registered queries. useGetCustomer registers with a single-element key ([STRIPE_API_KEY.getCustomer(...)]), and useGetSubscription registers with a single-element key ([STRIPE_API_KEY.getSubscription(...)]). The invalidateQueries call creates a two-element key ([getCustomer(), getSubscription()]), which won't match either registered query due to React Query's exact key matching.

Replace with separate invalidateQueries calls for each query:

queryClient.invalidateQueries({
  queryKey: [STRIPE_API_KEY.getCustomer()],
})
queryClient.invalidateQueries({
  queryKey: [STRIPE_API_KEY.getSubscription()],
})

Also applies to: 167-169

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

In `@packages/components/modules/payments/web/hooks/useStripeHook.tsx` around
lines 141 - 144, The current invalidateQueries call passes a combined
two-element key which doesn't match the single-element keys registered by
useGetCustomer and useGetSubscription; replace the single call with two calls to
queryClient.invalidateQueries, one using queryKey:
[STRIPE_API_KEY.getCustomer()] and another using queryKey:
[STRIPE_API_KEY.getSubscription()], and apply the same change to the other
instance noted around the block referencing queryClient.invalidateQueries at
167-169 so both useGetCustomer and useGetSubscription are invalidated
individually.
packages/components/modules/payments/web/PaymentMethodsManagementComponent/PaymentMethodsItem/index.tsx-39-48 (1)

39-48: ⚠️ Potential issue | 🟠 Major

Use the clicked button as the menu anchor.

document.activeElement is not guaranteed to be this IconButton, so the menu can anchor to the wrong element after mouse/touch interactions. Capture the click event and use event.currentTarget; while touching this, please add an aria-label to the icon-only button.

💡 Suggested fix
         <IconButton
-          onClick={() => {
+          aria-label={`Actions for card ending in ${paymentMethod?.card?.last4 ?? ''}`}
+          onClick={(event) => {
             setIsMenuOpen(true)
             setSelectedPaymentMethodId(paymentMethod.id)
-            setAnchorEl(document.activeElement as HTMLElement)
+            setAnchorEl(event.currentTarget)
           }}
           sx={{ flexGrow: 0 }}
         >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/components/modules/payments/web/PaymentMethodsManagementComponent/PaymentMethodsItem/index.tsx`
around lines 39 - 48, The IconButton click handler should use the clicked
element as the menu anchor and include an accessible label: change the onClick
to accept the click event and call setIsMenuOpen(true),
setSelectedPaymentMethodId(paymentMethod.id), and
setAnchorEl(event.currentTarget as HTMLElement) instead of
document.activeElement; also add an aria-label (e.g. "more options") to the
IconButton so the icon-only button is accessible. Target identifiers:
IconButton, onClick handler, setIsMenuOpen, setSelectedPaymentMethodId,
setAnchorEl, and paymentMethod.id.
packages/components/modules/payments/web/hooks/useStripeHook.tsx-191-194 (1)

191-194: ⚠️ Potential issue | 🟠 Major

Add entityId to the invoice list key.

The query function uses both page and entityId to fetch data, but the cache key only includes page. This causes cache collisions where requests from different users/tenants with the same page number receive the cached result from another user's request, exposing incorrect data.

The queryKey should be:

queryKey: [STRIPE_API_KEY.listInvoices(page.toString(), entityId)]

or similar, to ensure the cache key varies by both parameters.

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

In `@packages/components/modules/payments/web/hooks/useStripeHook.tsx` around
lines 191 - 194, The cache key for useListInvoices doesn't include entityId,
causing collisions; update useListInvoices so the queryKey includes both page
and entityId (e.g., use STRIPE_API_KEY.listInvoices(page.toString(), entityId)
or otherwise incorporate entityId) to match the parameters passed to
StripeApi.listInvoices and ensure per-entity caching.
packages/components/modules/payments/web/AvailableSubscriptions/index.tsx-15-16 (1)

15-16: ⚠️ Potential issue | 🟠 Major

Add null guard to onChange and initialize selectedTerm based on available products.

Line 40: setSelectedTerm(value) can receive null from the exclusive toggle (when re-clicking the selected button), but the type signature requires 'monthly' | 'yearly'. Additionally, defaulting to 'monthly' on line 15 causes the first render to show no selected button and no cards if only yearly products exist.

Also, lines 47 and 50 have redundant guards: monthlySubs?.length && already evaluates to falsy when the length is 0, so the additional > 0 check is unnecessary.

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

In `@packages/components/modules/payments/web/AvailableSubscriptions/index.tsx`
around lines 15 - 16, The onChange handler for the exclusive toggle should guard
against null before calling setSelectedTerm (only call setSelectedTerm when
value !== null) and the selectedTerm state should be initialized based on
available products (e.g., set initial state to 'yearly' if yearlySubs exist,
otherwise 'monthly') so the first render selects an available term; also remove
the redundant length checks like monthlySubs?.length && monthlySubs.length > 0
(keep only monthlySubs?.length or monthlySubs && monthlySubs.length) to simplify
the guards.
packages/components/modules/payments/web/PaymentDropDown/index.tsx-31-34 (1)

31-34: ⚠️ Potential issue | 🟠 Major

Replace the @ts-ignore with a proper fix: create a new Elements instance instead of updating clientSecret.

elements.update() does not support changing clientSecret after initialization. The StripeElementsUpdateOptions type omits clientSecret entirely—it is only available during Elements construction via stripe.elements({ clientSecret }).

Calling elements.update({ clientSecret }) will silently fail (Stripe ignores the change), leaving the modal with stale Elements state during payment setup. This breaks the payment flow.

Create a new Elements instance with the new secret instead, either by remounting the <Elements> component with a different key or conditionally rendering it once the secret is available.

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

In `@packages/components/modules/payments/web/PaymentDropDown/index.tsx` around
lines 31 - 34, The onSuccess handler currently calls elements?.update({
clientSecret }) with a ts-ignore, but Stripe Elements cannot change clientSecret
after initialization; remove the elements.update call and instead create a fresh
Elements instance when the new SetupIntent arrives: store
setupIntent.clientSecret in state (e.g., paymentSetupClientSecret) in the
onSuccess callback alongside setIsAddCardModalOpen, and drive the <Elements>
mounting by that secret (either render <Elements stripe={stripe}
options={{clientSecret: paymentSetupClientSecret}}> only when the secret exists
or change the Elements component key to force remount); update references to
elements and the onSuccess function to rely on the newly constructed Elements
rather than elements.update.
packages/components/modules/payments/web/SubscriptionManagement/index.tsx-60-77 (1)

60-77: ⚠️ Potential issue | 🟠 Major

Wait for the customer query before falling back to the free-plan UI.

hasSubscription is derived from customer, but isLoading ignores useGetCustomer. A slow customer fetch can render FreePlanComponent for subscribed accounts.

🩹 Proposed fix
-  const { data: customer, refetch: refetchCustomer } = useGetCustomer(entityId)
+  const {
+    data: customer,
+    refetch: refetchCustomer,
+    isLoading: isLoadingCustomer,
+  } = useGetCustomer(entityId)
@@
-  const isLoading = isLoadingMethods || isLoadingSubscription
+  const isLoading = isLoadingCustomer || isLoadingMethods || isLoadingSubscription

Also applies to: 149-151

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

In `@packages/components/modules/payments/web/SubscriptionManagement/index.tsx`
around lines 60 - 77, The UI falls back to FreePlanComponent before the customer
query finishes because isLoading omits the useGetCustomer state; update the
loading logic to include the customer fetch and ensure any guard that derives
hasSubscription from customer waits for customer to be defined before rendering
the free-plan UI. Specifically, modify the isLoading calculation (used alongside
useGetCustomer, useGetSubscription, useListPaymentMethods) to include the
customer loading flag from useGetCustomer and ensure the component logic that
checks hasSubscription (and the FreePlanComponent fallback) only runs once
customer is loaded; apply the same change where hasSubscription is used around
the other related block (around the code referenced at lines 149-151).
packages/components/modules/payments/web/CheckoutComponent/index.tsx-219-223 (1)

219-223: ⚠️ Potential issue | 🟠 Major

Don't store 'empty' as a real payment-method id.

When there is no default card, this seeds selectedPaymentMethodId with 'empty', and the submit path will send that literal string unless the user changes the dropdown.

🩹 Proposed fix
   useEffect(() => {
     if (!paymentMethods || paymentMethods.length === 0) return
     const defaultPaymentMethod = paymentMethods?.find((pm) => pm.isDefault)
-    setSelectedPaymentMethodId(defaultPaymentMethod?.id ?? 'empty')
+    setSelectedPaymentMethodId(defaultPaymentMethod?.id ?? paymentMethods[0]?.id ?? '')
   }, [paymentMethods])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/components/modules/payments/web/CheckoutComponent/index.tsx` around
lines 219 - 223, The useEffect seeds selectedPaymentMethodId with the literal
string 'empty' which can be submitted as a real id; change the logic in the
useEffect that reads paymentMethods and sets selection (the block using
paymentMethods, defaultPaymentMethod, and setSelectedPaymentMethodId) to set a
null/undefined (or empty string) sentinel instead of the literal 'empty' when no
default exists, and update the submit path that reads selectedPaymentMethodId
(form submit handler) to treat null/undefined/empty-string as "no payment method
selected" and reject/handle accordingly so the string 'empty' is never sent to
the API.
packages/components/modules/payments/web/PaymentMethodsManagementComponent/index.tsx-71-96 (1)

71-96: ⚠️ Potential issue | 🟠 Major

Setting/removing a card should not depend on Elements initialization.

These handlers only hit your backend mutations. Guarding them with !elements makes existing payment-method management fail whenever Stripe.js is slow or blocked.

🩹 Proposed fix
   const handleUpdatePaymentMethod = async () => {
-    if (!elements) {
-      console.error('Stripe elements not initialized')
+    if (!selectedPaymentMethodId) {
       return
     }
@@
   const handleDeletePaymentMethod = async () => {
-    if (!elements) {
-      console.error('Stripe elements not initialized')
+    if (!selectedPaymentMethodId) {
       return
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/components/modules/payments/web/PaymentMethodsManagementComponent/index.tsx`
around lines 71 - 96, The handlers handleUpdatePaymentMethod and
handleDeletePaymentMethod incorrectly bail out when the Stripe `elements` object
is not initialized; remove the `if (!elements) { ... return }` guards so these
functions always call the backend mutations `updatePaymentMethod` and
`deletePaymentMethod` (and still perform post-mutation state updates like
setSelectedPaymentMethodId and setIsConfirmationDialogOpen in
handleDeletePaymentMethod), ensuring payment-method management does not depend
on Stripe Elements initialization.
packages/components/modules/payments/web/PaymentMethodsManagementComponent/index.tsx-1-1 (1)

1-1: ⚠️ Potential issue | 🟠 Major

Add 'use client' before Line 1.

This exported entrypoint uses React hooks (useState, useEffect) and Stripe hooks (useElements, useStripe), requiring a client boundary. Other payment screens in the same directory (SubscriptionManagement, AvailableSubscriptions, CheckoutComponent) all declare 'use client'. Without it, importing this component from an App Router server component through the public barrel export will break.

🩹 Proposed fix
+'use client'
+
 import { FC, useEffect, useState } from 'react'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/components/modules/payments/web/PaymentMethodsManagementComponent/index.tsx`
at line 1, This file exports a component (PaymentMethodsManagementComponent)
that uses React hooks (useState, useEffect) and Stripe hooks (useElements,
useStripe) but lacks a client boundary; add the module directive 'use client' at
the very top of the file (before the existing imports) so this component is
treated as a client component and can be safely imported from App Router server
components.
packages/components/modules/payments/web/CheckoutComponent/index.tsx-173-178 (1)

173-178: ⚠️ Potential issue | 🟠 Major

Invalidate the customer and payment-method caches with separate calls.

The current code wraps two different query keys in a single array, but React Query's prefix matching won't find either cache entry. Query keys are registered as [['stripe', 'listPaymentMethods', entityId]] and [['stripe', 'getCustomer', entityId]] respectively, so passing both as a single filter [[key1], [key2]] fails to match either one.

Proposed fix
-              queryClient.invalidateQueries({
-                queryKey: [
-                  STRIPE_API_KEY.listPaymentMethods(entityId),
-                  STRIPE_API_KEY.getCustomer(entityId),
-                ],
-              })
+              queryClient.invalidateQueries({
+                queryKey: STRIPE_API_KEY.listPaymentMethods(entityId),
+              })
+              queryClient.invalidateQueries({
+                queryKey: STRIPE_API_KEY.getCustomer(entityId),
+              })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/components/modules/payments/web/CheckoutComponent/index.tsx` around
lines 173 - 178, The invalidate logic is passing two distinct query keys inside
a single array to queryClient.invalidateQueries which won't match registered
keys; call queryClient.invalidateQueries separately for each key (use
STRIPE_API_KEY.listPaymentMethods(entityId) and
STRIPE_API_KEY.getCustomer(entityId) in two invalidateQueries calls) so each
registered query key is matched and invalidated correctly.
packages/components/modules/payments/web/SubscriptionManagement/index.tsx-137-141 (1)

137-141: ⚠️ Potential issue | 🟠 Major

Remove the immediate refetchSubscription() call from this effect.

setSubscriptionId() is asynchronous, so refetchSubscription() executes with the stale (empty) subscriptionId. The query will automatically refetch when the state updates and the new subscriptionId becomes non-empty, since the hook has enabled: !!subscriptionId.

🩹 Proposed fix
   useEffect(() => {
     if (customer?.subscriptions?.[0]?.id) {
       setSubscriptionId(customer.subscriptions[0].id)
-      refetchSubscription()
     }
   }, [customer])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/components/modules/payments/web/SubscriptionManagement/index.tsx`
around lines 137 - 141, The effect that checks customer.subscriptions should not
call refetchSubscription() immediately because setSubscriptionId is async and
the query is already configured with enabled: !!subscriptionId; remove the
explicit refetchSubscription() call from the useEffect that contains
setSubscriptionId (the effect referencing customer?.subscriptions?.[0]?.id) so
the subscription query will refetch naturally when subscriptionId state updates.
packages/components/modules/payments/web/PaymentMethodsManagementComponent/index.tsx-98-105 (1)

98-105: ⚠️ Potential issue | 🟠 Major

Include elements in the setup-intent effect dependencies.

If the setup intent resolves before useElements() does, the optional chaining prevents the elements from being updated with the clientSecret, and the effect will never rerun since elements is not listed as a dependency.

🩹 Proposed fix
   useEffect(() => {
-    if (isCreatingSetupIntent || isErrorCreatingSetupIntent) return
-    if (setupIntent) {
-      // `@ts-ignore`
-      elements?.update({ clientSecret: setupIntent.clientSecret })
-      setIsAddCardModalOpen(true)
-    }
-  }, [setupIntent, isCreatingSetupIntent, isErrorCreatingSetupIntent])
+    if (!elements || isCreatingSetupIntent || isErrorCreatingSetupIntent || !setupIntent) return
+    // `@ts-ignore`
+    elements.update({ clientSecret: setupIntent.clientSecret })
+    setIsAddCardModalOpen(true)
+  }, [elements, setupIntent, isCreatingSetupIntent, isErrorCreatingSetupIntent])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/components/modules/payments/web/PaymentMethodsManagementComponent/index.tsx`
around lines 98 - 105, The effect in PaymentMethodsManagementComponent that
updates Stripe Elements when a setupIntent arrives doesn't list elements (from
useElements()) in its dependency array, so if setupIntent resolves before
elements the elements.update call is skipped and the effect won't rerun; update
the useEffect to include elements in the dependency array and ensure you guard
for null (e.g., return early if isCreatingSetupIntent/isErrorCreatingSetupIntent
or if elements is falsy) so that when elements becomes available the effect
runs, performs elements.update({ clientSecret: setupIntent.clientSecret }) and
then calls setIsAddCardModalOpen(true).
packages/components/modules/payments/web/AddCardModal/index.tsx-52-54 (1)

52-54: ⚠️ Potential issue | 🟠 Major

Pass the entityId to ensure query invalidation matches the registered query key.

STRIPE_API_KEY.listPaymentMethods() is called without the entityId parameter, but the query is registered with it. Since invalidateQueries uses prefix matching on the flattened key, wrapping the result in brackets creates a structure that won't match the registered query pattern. Pass entityId and use the flat key.

🐛 Proposed fix
         await queryClient.invalidateQueries({
-          queryKey: [STRIPE_API_KEY.listPaymentMethods()],
+          queryKey: STRIPE_API_KEY.listPaymentMethods(entityId),
         })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/components/modules/payments/web/AddCardModal/index.tsx` around lines
52 - 54, The invalidateQueries call uses a wrapped array and omits entityId so
it doesn't match the registered query key; update the call to pass the entityId
into STRIPE_API_KEY.listPaymentMethods(entityId) and supply that flat key
directly to queryClient.invalidateQueries (i.e., use
queryClient.invalidateQueries({ queryKey:
STRIPE_API_KEY.listPaymentMethods(entityId) }) or the equivalent flat key form)
so the invalidation matches the registered query; adjust the code around
STRIPE_API_KEY.listPaymentMethods and queryClient.invalidateQueries to use the
entityId variable.
🟡 Minor comments (7)
packages/design-system/components/web/icons/MastercardCreditCardIcon/index.tsx-15-28 (1)

15-28: ⚠️ Potential issue | 🟡 Minor

Namespace the SVG defs IDs per instance.

These hardcoded IDs will repeat every time the icon is rendered. In views that show multiple cards at once, duplicate ids make the DOM invalid and can cross-wire the url(#...) / xlinkHref references.

Suggested fix
-import { FC } from 'react'
+import { FC, useId } from 'react'

-const MastercardCreditCardIcon: FC<SvgIconProps> = ({ sx, ...props }) => (
-  <SvgIcon ...>
-      <rect x="5" y="3" width="26" height="18" fill="url(`#pattern0_0_8810`)" />
+const MastercardCreditCardIcon: FC<SvgIconProps> = ({ sx, ...props }) => {
+  const patternId = useId()
+  const imageId = useId()
+
+  return (
+    <SvgIcon ...>
+      <rect x="5" y="3" width="26" height="18" fill={`url(#${patternId})`} />
       <defs>
-        <pattern id="pattern0_0_8810" patternContentUnits="objectBoundingBox" width="1" height="1">
+        <pattern id={patternId} patternContentUnits="objectBoundingBox" width="1" height="1">
           <use
-            xlinkHref="#image0_0_8810"
+            xlinkHref={`#${imageId}`}
             transform="matrix(0.00713719 0 0 0.0103093 -0.0138779 0)"
           />
         </pattern>
         <image
-          id="image0_0_8810"
+          id={imageId}
           width="144"
           height="97"
           preserveAspectRatio="none"
           xlinkHref="data:image/png;base64,..."
         />
       </defs>
-  </SvgIcon>
-)
+    </SvgIcon>
+  )
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/design-system/components/web/icons/MastercardCreditCardIcon/index.tsx`
around lines 15 - 28, The SVG defs IDs (pattern0_0_8810, image0_0_8810) are
static and will collide when multiple MastercardCreditCardIcon instances render;
update the component to generate a unique suffix (e.g., via React's useId() or
an optional id prop) and append it to the ids in the <defs> (pattern and image)
and to every reference (rect fill="url(#...)" and <use xlinkHref="#..."> and
image xlinkHref) so each instance has namespaced IDs (update the
MastercardCreditCardIcon component where pattern0_0_8810 and image0_0_8810 are
declared and referenced).
packages/components/modules/payments/web/InvoiceListTable/InvoiceListTableHeader/index.tsx-6-16 (1)

6-16: ⚠️ Potential issue | 🟡 Minor

Column count mismatch between mobile header and footer.

The mobile header renders 3 columns (Description, Info, empty), but the InvoiceListTableFooter component uses a hardcoded colSpan={5}. This mismatch may cause layout issues on mobile views where the footer cell spans more columns than exist.

Consider making colSpan dynamic based on the smDown prop to ensure proper alignment.

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

In
`@packages/components/modules/payments/web/InvoiceListTable/InvoiceListTableHeader/index.tsx`
around lines 6 - 16, The mobile header renders only 3 columns when smDown is
true, but InvoiceListTableFooter uses a hardcoded colSpan={5}; update the footer
to compute its colspan from the same responsive flag instead of hardcoding:
e.g., derive a columnsCount = smDown ? 3 : 5 (or compute from the header
structure) and use that for the InvoiceListTableFooter colSpan prop; ensure the
responsive prop (smDown) is passed into or read by InvoiceListTableFooter so
TableHead and footer stay in sync.
packages/components/modules/payments/web/InvoiceListTable/InvoiceListTableFooter/index.tsx-16-16 (1)

16-16: ⚠️ Potential issue | 🟡 Minor

Hardcoded colSpan={5} may cause layout issues on mobile.

As noted in the header component review, the mobile view uses 3 columns while this footer spans 5. Consider receiving colSpan as a prop or deriving it from smDown to maintain consistency.

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

In
`@packages/components/modules/payments/web/InvoiceListTable/InvoiceListTableFooter/index.tsx`
at line 16, The TableCell in InvoiceListTableFooter currently hardcodes
colSpan={5}, which breaks the mobile layout; update the InvoiceListTableFooter
component to accept an optional prop (e.g., colSpan?: number) and/or compute
colSpan based on the smDown media query (useTheme/useMediaQuery) so it uses 3
when smDown is true and 5 otherwise, and ensure any passed prop overrides the
computed value; change the component signature and the TableCell usage to use
the derivedOrPropColSpan to keep header/footer column counts consistent.
packages/design-system/components/web/icons/VisaCreditCardIcon/index.tsx-15-26 (1)

15-26: ⚠️ Potential issue | 🟡 Minor

SVG ID collision risk if same icon renders multiple times on a page.

The hardcoded IDs (pattern0_0_8928, image0_0_8928) are document-global. When multiple VisaCreditCardIcon instances render on the same page, they share the same ID references in the DOM, which could cause visual issues if the second instance's <defs> overwrites the first.

While the current usage pattern (conditional rendering per card type) makes this unlikely, consider using unique IDs via React 18's useId() hook or inlining the fill to eliminate the dependency on pattern references.

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

In `@packages/design-system/components/web/icons/VisaCreditCardIcon/index.tsx`
around lines 15 - 26, The SVG uses hardcoded IDs pattern0_0_8928 and
image0_0_8928 inside VisaCreditCardIcon which can collide when multiple
instances render; fix by generating unique IDs (e.g., use React's useId() inside
VisaCreditCardIcon) and replace the static IDs in <pattern id="..."> and <image
id="..."> as well as the references (use xlinkHref="#image..." and rect
fill="url(`#pattern`...)") with the generated unique values; ensure you import
useId from React and wire the unique id into all three places (pattern id, image
id, and the url/# references).
packages/components/modules/payments/web/SubscriptionManagement/styled.tsx-16-23 (1)

16-23: ⚠️ Potential issue | 🟡 Minor

Replace paddingY with explicit top/bottom padding.

paddingY is a MUI System prop supported only in sx or system style functions. Inside styled() callback objects, only plain CSS properties are recognized, so this shorthand is ignored and the vertical spacing is lost. Use paddingTop and paddingBottom instead.

Suggested fix
 export const PaymentMethodContainer = styled(Box)(({ theme }) => ({
   display: 'flex',
   flexDirection: 'column',
   alignItems: 'flex-start',
   alignSelf: 'stretch',
   gap: theme.spacing(4),
-  paddingY: theme.spacing(4),
+  paddingTop: theme.spacing(4),
+  paddingBottom: theme.spacing(4),
   flex: 1,
 }))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/components/modules/payments/web/SubscriptionManagement/styled.tsx`
around lines 16 - 23, The styled callback for PaymentMethodContainer
(styled(Box)) uses the unsupported shorthand paddingY which is ignored; replace
it with explicit paddingTop and paddingBottom using the same value
(theme.spacing(4)) so the vertical spacing is applied (i.e., remove paddingY and
add paddingTop: theme.spacing(4), paddingBottom: theme.spacing(4) inside the
styled object).
packages/components/modules/payments/web/InvoiceListTable/InvoiceItemWrapper/index.tsx-9-9 (1)

9-9: ⚠️ Potential issue | 🟡 Minor

Potential NaN and unnecessary nullish coalescing.

If row.amountDue is undefined or null, the division will produce NaN, and toFixed(2) will return "NaN". The ?? '' is also ineffective since toFixed() always returns a string.

Proposed fix
-  const amountDue = (row.amountDue / 100).toFixed(2) ?? ''
+  const amountDue = row.amountDue != null ? (row.amountDue / 100).toFixed(2) : ''
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/components/modules/payments/web/InvoiceListTable/InvoiceItemWrapper/index.tsx`
at line 9, The amountDue computation can produce "NaN" because row.amountDue may
be null/undefined and the ?? '' after toFixed() is ineffective; update the
amountDue calculation (the const amountDue variable) to first verify
row.amountDue is a finite number (e.g., using Number.isFinite(row.amountDue) or
typeof/check !== null) and only then compute (row.amountDue / 100).toFixed(2);
otherwise set amountDue to an empty string (''), and remove the unnecessary
nullish coalescing.
packages/components/modules/payments/web/AvailableSubscriptions/index.tsx-47-52 (1)

47-52: ⚠️ Potential issue | 🟡 Minor

These conditions can render a literal 0.

Use a single boolean guard instead of chaining length && length > 0.

💡 Suggested fix
-          {monthlySubs?.length && monthlySubs?.length > 0 && (
+          {(monthlySubs?.length ?? 0) > 0 && (
             <ToggleButton value="monthly">Monthly</ToggleButton>
           )}
-          {yearlySubs?.length && yearlySubs?.length > 0 && (
+          {(yearlySubs?.length ?? 0) > 0 && (
             <ToggleButton value="yearly">Yearly</ToggleButton>
           )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/components/modules/payments/web/AvailableSubscriptions/index.tsx`
around lines 47 - 52, The conditional checks for rendering ToggleButton
currently use "monthlySubs?.length && monthlySubs?.length > 0" (and similarly
for yearlySubs) which can render a literal 0; replace each chain with a single
boolean guard such as "monthlySubs?.length > 0" (or
"Boolean(monthlySubs?.length)") and similarly "yearlySubs?.length > 0" to avoid
rendering 0 and simplify the logic around the ToggleButton components.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e1605fc4-5bb9-483a-a70a-93764d6b541e

📥 Commits

Reviewing files that changed from the base of the PR and between be3e85e and 0149745.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (50)
  • packages/components/modules/payments/web/AddCardModal/index.tsx
  • packages/components/modules/payments/web/AddCardModal/styled.tsx
  • packages/components/modules/payments/web/AddCardModal/types.ts
  • packages/components/modules/payments/web/AvailableSubscriptions/SubscriptionCard/index.tsx
  • packages/components/modules/payments/web/AvailableSubscriptions/index.tsx
  • packages/components/modules/payments/web/AvailableSubscriptions/styled.tsx
  • packages/components/modules/payments/web/AvailableSubscriptions/types.ts
  • packages/components/modules/payments/web/CheckoutComponent/ConfirmationSubscriptionModal/index.tsx
  • packages/components/modules/payments/web/CheckoutComponent/ConfirmationSubscriptionModal/styled.tsx
  • packages/components/modules/payments/web/CheckoutComponent/ConfirmationSubscriptionModal/types.ts
  • packages/components/modules/payments/web/CheckoutComponent/index.tsx
  • packages/components/modules/payments/web/CheckoutComponent/styled.tsx
  • packages/components/modules/payments/web/CheckoutComponent/types.ts
  • packages/components/modules/payments/web/CheckoutComponent/utils.tsx
  • packages/components/modules/payments/web/InvoiceListTable/InvoiceItemTableRow/index.tsx
  • packages/components/modules/payments/web/InvoiceListTable/InvoiceItemWrapper/index.tsx
  • packages/components/modules/payments/web/InvoiceListTable/InvoiceListTableFooter/index.tsx
  • packages/components/modules/payments/web/InvoiceListTable/InvoiceListTableHeader/index.tsx
  • packages/components/modules/payments/web/InvoiceListTable/MobileInvoiceItemTableRow/index.tsx
  • packages/components/modules/payments/web/InvoiceListTable/index.tsx
  • packages/components/modules/payments/web/InvoiceListTable/types.ts
  • packages/components/modules/payments/web/PaymentDropDown/AddPaymentMethodItem/index.tsx
  • packages/components/modules/payments/web/PaymentDropDown/PaymentMethodDisplay/index.tsx
  • packages/components/modules/payments/web/PaymentDropDown/index.tsx
  • packages/components/modules/payments/web/PaymentDropDown/styled.tsx
  • packages/components/modules/payments/web/PaymentDropDown/types.ts
  • packages/components/modules/payments/web/PaymentMethodsManagementComponent/PaymentMethodsItem/index.tsx
  • packages/components/modules/payments/web/PaymentMethodsManagementComponent/index.tsx
  • packages/components/modules/payments/web/PaymentMethodsManagementComponent/types.tsx
  • packages/components/modules/payments/web/SubscriptionManagement/CancelSubscriptionModal/index.tsx
  • packages/components/modules/payments/web/SubscriptionManagement/FreePlanComponent/index.tsx
  • packages/components/modules/payments/web/SubscriptionManagement/index.tsx
  • packages/components/modules/payments/web/SubscriptionManagement/styled.tsx
  • packages/components/modules/payments/web/SubscriptionManagement/types.ts
  • packages/components/modules/payments/web/SubscriptionManagement/utils.ts
  • packages/components/modules/payments/web/hooks/useStripeHook.tsx
  • packages/components/modules/payments/web/index.ts
  • packages/components/modules/payments/web/services/stripe.ts
  • packages/components/modules/payments/web/types.ts
  • packages/components/modules/payments/web/utils/index.tsx
  • packages/components/modules/payments/web/utils/stripe.ts
  • packages/components/package.json
  • packages/design-system/components/web/dialogs/ConfirmDialog/index.tsx
  • packages/design-system/components/web/dialogs/ConfirmDialog/types.ts
  • packages/design-system/components/web/icons/CheckIcon/index.tsx
  • packages/design-system/components/web/icons/CreditCardIcon/index.tsx
  • packages/design-system/components/web/icons/MastercardCreditCardIcon/index.tsx
  • packages/design-system/components/web/icons/VisaCreditCardIcon/index.tsx
  • packages/design-system/components/web/icons/index.ts
  • packages/utils/constants/date.ts

Comment thread packages/components/package.json
@changeset-bot

changeset-bot Bot commented Mar 9, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: cb3bd2f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 `@packages/components/package.json`:
- Around line 102-103: The package.json exports include a broken mapping
"./pages/web" -> "./modules/pages/web/index.ts" which doesn't exist; either
remove that export entry or add the missing module. Fix by editing the exports
object in package.json to delete the "./pages/web" export line (leaving
"./pages/common") or create the directory modules/pages/web with an index.ts
that re-exports the intended symbols (matching the structure used by
"./modules/pages/common/index.ts") so imports of
`@baseapp-frontend/components/pages/web` resolve correctly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a7695c79-0d37-42c0-87d8-7b6174b34d2e

📥 Commits

Reviewing files that changed from the base of the PR and between 0149745 and 9e52611.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • packages/components/package.json
  • packages/design-system/components/web/icons/index.ts

Comment thread packages/components/package.json Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
packages/components/modules/profiles/common/types.ts (1)

4-4: Consider type-only imports for generated GraphQL types to clarify intent.

While the current regular imports work safely (TypeScript's verbatimModuleSyntax: false automatically elides unused imports), type-only imports make it explicit that these are compile-time dependencies. However, note that this codebase uses regular imports for generated GraphQL types as the dominant convention; only a small number of files use import type. Align this change with your team's style preference before applying.

♻️ Proposed cleanup
-import { AllProfilesListFragment$data } from '../../../__generated__/AllProfilesListFragment.graphql'
-import { ProfileComponentFragment$data } from '../../../__generated__/ProfileComponentFragment.graphql'
+import type { AllProfilesListFragment$data } from '../../../__generated__/AllProfilesListFragment.graphql'
+import type { ProfileComponentFragment$data } from '../../../__generated__/ProfileComponentFragment.graphql'

 export type { ProfileComponentFragment$data }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/components/modules/profiles/common/types.ts` at line 4, The file
currently uses a regular import/export for generated GraphQL types; change any
non-type imports of the generated fragment to explicit type-only imports/exports
so intent is clear — e.g., use "import type { ProfileComponentFragment$data }"
(or ensure the re-export stays as "export type { ProfileComponentFragment$data
}") and update any other generated GraphQL symbols similarly (search for
ProfileComponentFragment$data and other GraphQL-generated names) to use "import
type" / "export type".
🤖 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/components/modules/payments/web/InvoiceListTable/index.tsx`:
- Around line 37-40: The code treats missing API responses as an empty list by
using data?.results ?? [], which hides request failures; update InvoiceListTable
to treat undefined data differently by checking the error state from
useListInvoices (e.g., isError / error) and only derive receipts from
data.results when data is defined, rendering a distinct error message/UI when
isError is true (instead of showing "No receipts found"), keep existing loading
logic using isLoading/isFetching, and apply the same change to the other
occurrences referenced around the receipts/isResultsEmpty logic (lines using
receipts, isResultsEmpty, and any conditional rendering at 48-50 and 56-63) so
failed fetches surface an explicit error state rather than an empty history.
- Line 1: When the parent swaps entityId, reset the pagination state so old page
numbers aren't reused; inside the InvoiceListTable component add an effect that
watches entityId and calls setPage(initialPage) (e.g., setPage(1) or whatever
the component's initial page value is) to reset pagination. Locate the useState
hook for page/currentPage and add a useEffect(() => { setPage(initialPage) },
[entityId]) so changing entityId always resets the page and avoids requesting
non-existent pages.
- Around line 53-58: The spinner and empty fallback must render as table rows
and use the header's column count: wrap the isFetching spinner in a <TableRow>
with a <TableCell colSpan={colSpan}> instead of rendering CircularProgress
directly inside TableBody, and replace the hard-coded colSpan={5} on the empty
fallback row with a colSpan value derived from the responsive header props (use
headerProps to compute colSpan, e.g. headerProps.colSpan ||
headerProps.columnCount || headerProps.columns?.length, defaulting only as a
last resort), keeping InvoiceListTableHeader, isFetching, isResultsEmpty and
rowProps/ headerProps references to locate the change.
- Line 41: The wrapper currently strips or mis-signatures the footer onChange:
keep and forward the original onChange signature and pass the pageNumber when
invoking it; specifically, when destructuring footerProps (const { onChange,
...restFooterProps } = footerProps ?? {}), treat onChange as a callback that
accepts the original event plus the page number and invoke it as
onChange?.(event, pageNumber) instead of calling it without the page number or
casting the event unsafely; to satisfy TS, either augment the footerProps type
locally (e.g., declare onChange?: (event: ChangeEvent<HTMLTableSectionElement>,
pageNumber: number) => void) or narrow/footerProps to a union that includes that
signature so you don’t need the unsafe cast, and ensure the invocation occurs
where the TableFooter event is handled (replace the current casted call) so the
consumer receives (event, pageNumber) intact.

In `@packages/components/package.json`:
- Around line 125-126: The package.json dependency bump risks breaking imports
for PaymentElement and AddressElement; either keep "@stripe/react-stripe-js":
"^3.6.0" or, if you want to upgrade to v5+, update all imports of PaymentElement
and AddressElement in components modules to the checkout entrypoint: change
imports in AddCardModal
(packages/components/modules/payments/web/AddCardModal/index.tsx) and
CheckoutComponent
(packages/components/modules/payments/web/CheckoutComponent/index.tsx) to import
from "@stripe/react-stripe-js/checkout", then update package.json versions for
"@stripe/react-stripe-js" and "@stripe/stripe-js" accordingly.

---

Nitpick comments:
In `@packages/components/modules/profiles/common/types.ts`:
- Line 4: The file currently uses a regular import/export for generated GraphQL
types; change any non-type imports of the generated fragment to explicit
type-only imports/exports so intent is clear — e.g., use "import type {
ProfileComponentFragment$data }" (or ensure the re-export stays as "export type
{ ProfileComponentFragment$data }") and update any other generated GraphQL
symbols similarly (search for ProfileComponentFragment$data and other
GraphQL-generated names) to use "import type" / "export type".

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1904fb44-4c01-4564-b16f-5d7edff63c21

📥 Commits

Reviewing files that changed from the base of the PR and between 9e52611 and 26dda5f.

📒 Files selected for processing (5)
  • packages/components/modules/messages/common/graphql/queries/GroupDetailsQuery.ts
  • packages/components/modules/payments/README.md
  • packages/components/modules/payments/web/InvoiceListTable/index.tsx
  • packages/components/modules/profiles/common/types.ts
  • packages/components/package.json
💤 Files with no reviewable changes (1)
  • packages/components/modules/messages/common/graphql/queries/GroupDetailsQuery.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/components/modules/payments/README.md

Comment thread packages/components/modules/payments/web/InvoiceListTable/index.tsx Outdated
Comment thread packages/components/modules/payments/web/InvoiceListTable/index.tsx
Comment thread packages/components/modules/payments/web/InvoiceListTable/index.tsx Outdated
Comment thread packages/components/modules/payments/web/InvoiceListTable/index.tsx Outdated
Comment thread packages/components/package.json
@sonarqubecloud

Copy link
Copy Markdown

@priscilladeroode

Copy link
Copy Markdown
Contributor

Can you please fix the 3 failed checks?

@lbjunq lbjunq requested a review from matheusysd as a code owner April 15, 2026 16:15
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants