Skip to content

BILLING - #124

Open
kanashenko wants to merge 1 commit into
mainfrom
billing
Open

BILLING#124
kanashenko wants to merge 1 commit into
mainfrom
billing

Conversation

@kanashenko

@kanashenko kanashenko commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

https://claude.ai/code/session_01CRtdbVqb77ef3JY398kRhB

Summary by CodeRabbit

  • Bug Fixes
    • Custom subscription quantities now respect product-specific minimums.
    • Improved validation messages distinguish quantities below the minimum from quantities that do not match the required unit size.
    • Custom quantity defaults now start at the applicable minimum when selecting custom packages or billing periods.
    • Invalid custom quantities now prevent checkout submission.

@kanashenko
kanashenko requested review from a team as code owners July 31, 2026 10:31
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c5b75f98-79da-44a8-b346-a11ca86a62d1

📥 Commits

Reviewing files that changed from the base of the PR and between b882b01 and 87daa9e.

📒 Files selected for processing (4)
  • src/app/(app)/settings/billing-usage/subscription/components/product-subscription-card.tsx
  • src/app/(app)/settings/billing-usage/subscription/components/subscription-submit-button.tsx
  • src/app/(app)/settings/billing-usage/subscription/hooks/use-product-selection.ts
  • src/app/(app)/settings/billing-usage/subscription/utils/subscription.utils.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/app/(app)/settings/billing-usage/subscription/utils/subscription.utils.ts
  • src/app/(app)/settings/billing-usage/subscription/components/product-subscription-card.tsx
  • src/app/(app)/settings/billing-usage/subscription/hooks/use-product-selection.ts
  • src/app/(app)/settings/billing-usage/subscription/components/subscription-submit-button.tsx

📝 Walkthrough

Walkthrough

Custom subscription quantity validation now enforces product-specific minimums. Selection defaults use the greater of the unit size and minimum quantity. The subscription card shows minimum-quantity errors before divisibility errors, and checkout disables invalid quantities.

Changes

Custom quantity validation

Layer / File(s) Summary
Product minimum quantity rules
src/app/(app)/settings/billing-usage/subscription/utils/subscription.utils.ts
Defines product-specific minimum quantities and exports minCustomQuantity and validCustomQuantity.
Custom selection defaults
src/app/(app)/settings/billing-usage/subscription/hooks/use-product-selection.ts
Validates custom quantities with validCustomQuantity and uses the product minimum when selecting custom packages or billing periods.
Validation feedback and checkout gating
src/app/(app)/settings/billing-usage/subscription/components/product-subscription-card.tsx, src/app/(app)/settings/billing-usage/subscription/components/subscription-submit-button.tsx
Displays minimum-quantity errors before unit-size errors and disables checkout for invalid custom quantities.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title identifies the billing area but does not describe the specific custom-quantity validation changes. Replace "BILLING" with a concise title that describes enforcing minimum custom quantities and disabling invalid billing actions.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch billing

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.

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

🧹 Nitpick comments (1)
src/app/(app)/settings/billing-usage/subscription/utils/subscription.utils.ts (1)

149-154: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add boundary tests for the shared validator.

validCustomQuantity now controls update validity, checkout quantity, and package updates. Add tests for null, below-minimum, exact-minimum, non-multiple, and valid-multiple quantities.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/app/`(app)/settings/billing-usage/subscription/utils/subscription.utils.ts
around lines 149 - 154, Add boundary-focused tests for validCustomQuantity
covering null, below-minimum, exact-minimum, non-multiple, and valid-multiple
custom quantities. Use product fixtures that exercise the minimum and required
multiple, and assert null for invalid inputs while preserving the entered
quantity for valid multiples.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/app/`(app)/settings/billing-usage/subscription/components/product-subscription-card.tsx:
- Around line 241-245: Update the custom quantity validation around
customBelowMin and customNotDivisible to use the core Input/FieldWrapper error
mechanism instead of rendering a standalone paragraph. Pass the selected
validation message through fieldState.error?.message or the Input error prop,
preserving the existing minimum and divisibility messages while ensuring the
error is associated with the quantity field for screen readers.

In
`@src/app/`(app)/settings/billing-usage/subscription/hooks/use-product-selection.ts:
- Around line 73-74: Update the defaultCustomQuantity calculation so
minCustomQuantity(product) is rounded up to the next multiple of unitSize before
applying the existing lower-bound logic, ensuring the result satisfies
validCustomQuantity. Preserve the current behavior of selecting at least
unitSize and the product’s minimum quantity.

---

Nitpick comments:
In
`@src/app/`(app)/settings/billing-usage/subscription/utils/subscription.utils.ts:
- Around line 149-154: Add boundary-focused tests for validCustomQuantity
covering null, below-minimum, exact-minimum, non-multiple, and valid-multiple
custom quantities. Use product fixtures that exercise the minimum and required
multiple, and assert null for invalid inputs while preserving the entered
quantity for valid multiples.
🪄 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 Plus

Run ID: df2c06e4-6607-4e37-b6b0-a0c17abfc8a1

📥 Commits

Reviewing files that changed from the base of the PR and between 1516bce and 8fe55f5.

📒 Files selected for processing (3)
  • src/app/(app)/settings/billing-usage/subscription/components/product-subscription-card.tsx
  • src/app/(app)/settings/billing-usage/subscription/hooks/use-product-selection.ts
  • src/app/(app)/settings/billing-usage/subscription/utils/subscription.utils.ts

Comment on lines +241 to +245
{customBelowMin ? (
<p className="text-h6 text-ods-error">
{`A minimum of ${minQty} ${packageUnitLabel} is required.`}
</p>
) : customNotDivisible ? (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 3 'aria-(invalid|describedby)|<Input' --glob '*.{ts,tsx}' .

Repository: flamingo-stack/openframe-oss-frontend

Length of output: 50393


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant component section and nearby imports.
sed -n '1,280p' 'src/app/(app)/settings/billing-usage/subscription/components/product-subscription-card.tsx' | cat -n

# Inspect the core Input component to identify the documented error API if present.
printf '\n--- candidate Input component locations ---\n'
fd -i 'input' 'src' --ext ts --ext tsx | sed -n '1,80p'

printf '\n--- Input props / aria / error usage in core files ---\n'
for f in $(fd -i 'input' 'src' --ext ts --ext tsx | sed -n '1,20p'); do
  echo "### $f"
  rg -n -C 3 'error|aria-invalid|aria-describedby|children|forwardRef|React\.Children|useId' "$f" || true
done

Repository: flamingo-stack/openframe-oss-frontend

Length of output: 11963


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- package manifests mentioning core library package ---'
rg -n '"`@flamingo-stack/openframe-frontend-core`"|openframe-frontend-core' package.json package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null || true

printf '%s\n' '--- Input component files ---'
fd -i 'input.*\.(ts|tsx)$' 'src' --extension ts --extension tsx | sort

printf '%s\n' '--- core library imports for Input ---'
rg -n "from ['\"]`@flamingo-stack/openframe-frontend-core/components/ui`['\"]|from \['\"]`@flamingo-stack/openframe-frontend-core/components/ui`['\"]" --glob '*.{ts,tsx}' src | sed -n '1,80p'

printf '%s\n' '--- core package files ---'
fd -i '.+\.(ts|tsx|json)$' 'node_modules/@flamingo-stack/openframe-frontend-core' | sed -n '1,120p' 2>/dev/null || true

printf '%s\n' '--- Input component body ---'
input_file="$(fd -i '^input.*\.(ts|tsx)$' 'src' --extension ts --extension tsx | grep -F 'input.ts' | head -n 1 || true)"
if [ -n "$input_file" ]; then
  echo "### $input_file"
  cat -n "$input_file" | sed -n '1,240p'
fi

printf '%s\n' '--- docs/types for Input error props ---'
rg -n 'InputProps|interface .*Input|type .*Input|aria-invalid|aria-describedby|error:|errorMessage' src --glob '*.{ts,tsx}' | sed -n '1,200p'

Repository: flamingo-stack/openframe-oss-frontend

Length of output: 44017


🌐 Web query:

@flamingo-stack/openframe-frontend-core 0.0.502 components/ui Input error aria-describedby source

💡 Result:

The @flamingo-stack/openframe-frontend-core library, version 0.0.502, provides base UI components for the Flamingo OpenFrame platform [1][2]. Within this package, the Input component (located at components/ui/input.tsx) is designed to support standard web accessibility attributes, including aria-describedby [1][3]. In the context of this library, aria-describedby is typically used to programmatically associate an input field with its error message or helper text [3][4]. When implementing an error state, the standard pattern involves: 1. Generating a unique ID for the error message container (often using a utility hook or useId). 2. Setting the aria-describedby attribute on the Input element to match the error container's ID. 3. Conditionally rendering the error text when the validation state (usually handled via react-hook-form and zod in the consuming applications) is present [2]. If you are encountering an error related to aria-describedby or its source, verify that your implementation is correctly passing the ID to the aria-describedby prop of the Input component and that the DOM element displaying the error message possesses the corresponding id. The library relies on standard HTML/ARIA practices for these components [1][2]. For the most accurate implementation details specific to version 0.0.502, you can inspect the source code directly in your node_modules/@flamingo-stack/openframe-frontend-core/components/ui/input.tsx file or refer to the openframe-oss-lib repository, which contains the design system source [1][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "--- core Input package metadata ---"
cat node_modules/@flamingo-stack/openframe-frontend-core/package.json | jq '{version, exports}' 2>/dev/null || sed -n '1,160p' node_modules/@flamingo-stack/openframe-frontend-core/package.json

echo "--- core components/ui files ---"
fd -i 'input.*\.(ts|tsx)$' node_modules/@flamingo-stack/openframe-frontend-core --extension ts --extension tsx

echo "--- core Input source if present ---"
while IFS= read -r f; do
  echo "### $f"
  sed -n '1,260p' "$f"
done < <(fd -i 'input.*\.(ts|tsx)$' node_modules/@flamingo-stack/openframe-frontend-core --extension ts --extension tsx)

echo "--- Input implementations using error props in core package ---"
rg -n -C 3 'error:|errorMessage|aria-invalid|aria-describedby|id=".*error|role="alert|Text\("error|children|children:' node_modules/@flamingo-stack/openframe-frontend-core/src/components --glob '*.{ts,tsx}' | sed -n '1,240p' || true

echo "--- local error prop usage around react-hook-form Input ---"
rg -n -C 3 'fieldState|Input\(\s*\{\.\.\.field|error=\{"?\{\*fieldState|error=\{"\{\*fieldState|error={fieldState.*error|aria-describedby' --glob '*.{ts,tsx}' src/app/src/components | sed -n '1,240p'

Repository: flamingo-stack/openframe-oss-frontend

Length of output: 11448


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "--- core Input source ---"
sed -n '1,340p' node_modules/@flamingo-stack/openframe-frontend-core/src/components/ui/input.tsx | cat -n

echo "--- Input story error examples ---"
sed -n '1,260p' node_modules/@flamingo-stack/openframe-frontend-core/src/stories/Input.stories.tsx | cat -n

echo "--- UI index exports ---"
sed -n '1,160p' node_modules/@flamingo-stack/openframe-frontend-core/src/components/ui/index.tsx | cat -n

echo "--- local react-hook-form Input error patterns ---"
rg -n -C 4 'fieldState\.error|error:\s*fieldState\.error|Input\(' --glob '*.{ts,tsx}' src/app src/components | sed -n '1,220p'

echo "--- focused Input with fieldState.error ---"
rg -n -C 6 'render=\{\{\s*field,\s*fieldState\s*\}.*Input|fieldState\.error|error=\{\*\*fieldState.error|error=\{\*fieldState.error' --glob '*.{ts,tsx}' src/app src/components | sed -n '1,220p'

Repository: flamingo-stack/openframe-oss-frontend

Length of output: 15702


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "--- FieldWrapper source ---"
rg -l 'function FieldWrapper|const FieldWrapper|export function FieldWrapper|export const FieldWrapper' node_modules/@flamingo-stack/openframe-frontend-core/src/components/ui | xargs -r cat -n | sed -n '1,220p'

echo "--- UI index export path/source ---"
fd -i 'index.*\.(ts|tsx)$' node_modules/@flamingo-stack/openframe-frontend-core/src/components/ui --extension ts --extension tsx
for f in $(fd -i 'index.*\.(ts|tsx)$' node_modules/@flamingo-stack/openframe-frontend-core/src/components/ui --extension ts --extension tsx); do
  echo "### $f"
  sed -n '1,200p' "$f" | cat -n
done

Repository: flamingo-stack/openframe-oss-frontend

Length of output: 18204


Use the core Input error mechanism for the custom quantity validation.

The standalone <p> is not linked to <Input>, and this case does not use the core Input invalid/error props supported by FieldWrapper. Pass one of the status strings via fieldState.error?.message or render the chosen message through the core error prop so screen readers receive the error with the field.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/app/`(app)/settings/billing-usage/subscription/components/product-subscription-card.tsx
around lines 241 - 245, Update the custom quantity validation around
customBelowMin and customNotDivisible to use the core Input/FieldWrapper error
mechanism instead of rendering a standalone paragraph. Pass the selected
validation message through fieldState.error?.message or the Input error prop,
preserving the existing minimum and divisibility messages while ensuring the
error is associated with the quantity field for screen readers.

Source: Coding guidelines

Comment on lines +73 to +74
// What the Custom input pre-fills with: the smallest valid amount.
const defaultCustomQuantity = Math.max(unitSize, minCustomQuantity(product));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 'MANAGED_DEVICES|unitSize|minCustomQuantity' \
  --glob '*.{ts,tsx,json,graphql}' .

Repository: flamingo-stack/openframe-oss-frontend

Length of output: 34185


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== subscription.utils relevant section =="
sed -n '1,220p' src/app/\(app\)/settings/billing-usage/subscription/utils/subscription.utils.ts

echo
echo "== product selection hook relevant section =="
sed -n '1,115p' src/app/\(app\)/settings/billing-usage/subscription/hooks/use-product-selection.ts

echo
echo "== search for product defaults/catalog unitSize declarations =="
rg -n --glob '*.{ts,tsx,graphql,md,txt,yml,yaml,json}' 'AI_ASSISTANCE|MANAGED_DEVICES|unitSize|minCustomQuantity|validCustomQuantity' .

echo
echo "== behavioral probe for current formula vs validator semantics =="
python3 - <<'PY'
from typing import Optional

def min_custom_quantity(name: str) -> int:
    return 10 if name == 'MANAGED_DEVICES' else 1

def valid_custom_quantity(name: str, unit_size: Optional[int], custom_quantity: Optional[int]) -> Optional[int]:
    unit_size = (unit_size or 1) or 1
    if custom_quantity is None:
        return None
    if custom_quantity < min_custom_quantity(name):
        return None
    if custom_quantity % unit_size != 0:
        return None
    return custom_quantity

def default_custom_quantity(name: str, unit_size: Optional[int] = None) -> int:
    unit_size = (unit_size or 1) or 1
    return max(unit_size, min_custom_quantity(name))

cases = [
    ('MANAGED_DEVICES', 1),
    ('AI_ASSISTANCE', None),
    ('MANAGED_DEVICES', None),
    ('AI_ASSISTANCE', 100_000),
    # edge case for the original invariant claim
    ('MANAGED_DEVICES', 6),
]
for name, unit_size in cases:
    default = default_custom_quantity(name, unit_size)
    validator = valid_custom_quantity(name, unit_size, default)
    print({'product': name, 'unitSize': unit_size, 'default': default, 'valid': validator})
PY

Repository: flamingo-stack/openframe-oss-frontend

Length of output: 26630


🌐 Web query:

Openframe frontend product unitSize 100000 AI_ASSISTANCE

💡 Result:

In the context of the Flamingo Stack's OpenFrame OSS tenant, the unitSize property in the frontend billing and subscription module has been deprecated for its previous role in calculating billable units [1][2]. Previously, unitSize was used to perform unit conversions for AI and device quantities. Following a refactor to support real-token billing, the system now treats these quantities as real product counts end-to-end [1][2]. Consequently: * Scaling: All multiply/divide operations previously used to convert quantities (e.g., tier.from * unitSize) have been removed [1][2]. * Validation: unitSize is now used exclusively to validate that a custom token amount is a whole multiple of the defined unit [1][2]. * Billing Logic: Allocations and pending-plan quantities are now read directly from the backend quantity field [1]. The mention of "100000" in this context refers to a warning condition regarding the deployment of these billing changes [1]. Because the backend and frontend were updated to use real token counts rather than scaled units, there was a risk that if the backend had not yet been updated, the quantity sent to payment processors (like Stripe) would be off by a factor of 100,000 [1]. This is distinct from other technical contexts where unitSize might represent physical product dimensions or packaging quantities in retail scrapers [3][4][5][6].

Citations:


🌐 Web query:

site:github.com/flamingo-stack/openframe-oss-tenant unitSize 100000 AI_ASSISTANCE

💡 Result:

In the context of the Flamingo Stack open-source project (specifically openframe-oss-tenant), unitSize was a billing parameter previously used to handle AI/device quantity scaling [1][2]. Historically, this system used unitSize to manage billable units—where a single unit might represent a package of items (e.g., a batch of tokens or conversations). Recent refactoring has moved the system toward using real product counts (such as raw token amounts) directly [1]. As part of this transition, unitSize was relegated to a validation role, ensuring that user-provided custom token amounts are whole multiples of the defined unit size [1][2]. If you encounter unitSize 100000 in legacy configurations or code, it refers to a legacy scaling factor where 1 unit represented 100,000 of the underlying metric (likely AI tokens) [1][2]. The backend logic has been updated to treat these metrics as direct counts, and multiplying or dividing by this unitSize for quantity calculations is no longer the standard architectural approach [1].

Citations:


🌐 Web query:

site:github.com/flamingo-stack/openframe-oss Tenant unitSize 100000 AI_ASSISTANCE

💡 Result:

The repository appears to be flamingo-stack/openframe-oss-tenant, not openframe-oss. I couldn’t find an indexed match for all three terms (Tenant, unitSize: 100000, and AI_ASSISTANCE). The relevant OpenFrame tenant repository is referenced in Flamingo’s documentation. [1]

(flamingo.run)

Citations:


🌐 Web query:

github "unitSize" "AI_ASSISTANCE" "100000"

💡 Result:

There is no widely recognized technical parameter, library feature, or standard configuration named "unitSize" associated with the specific value "100000" in the context of generic AI assistant software [1][2][3][4]. Research indicates that "AI_ASSISTANCE" and "ai_assistant" are common names for various software projects, including: - OrlandoLuque/ai_assistant: A Rust-based library for LLM integration that uses various "Known Model Context Sizes" (e.g., 128K tokens for many models) [1][2], but does not contain a "unitSize" parameter set to 100,000. - Unity AI Assistant: A generative AI tool integrated into the Unity Editor [4][5]. - RapidKit AI Assistant Module: A provider-agnostic Python runtime module [6]. It is possible that "unitSize: 100000" refers to a custom configuration variable, a specific buffer size, or an indexing threshold within a private or specialized codebase not indexed in public technical documentation [7][8]. If this relates to a specific project, it is likely a user-defined constant for memory allocation, token window processing, or data chunking [7][3].

Citations:


Make the custom default satisfy validCustomQuantity.

Math.max(unitSize, minCustomQuantity(product)) can pre-fill an invalid custom quantity, for example 10 when MANAGED_DEVICES.unitSize is less than 10 but not a divisor of 10. Round minCustomQuantity(product) up to the next multiple of unitSize before using it as the Custom Amount default.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/app/`(app)/settings/billing-usage/subscription/hooks/use-product-selection.ts
around lines 73 - 74, Update the defaultCustomQuantity calculation so
minCustomQuantity(product) is rounded up to the next multiple of unitSize before
applying the existing lower-bound logic, ensuring the result satisfies
validCustomQuantity. Preserve the current behavior of selecting at least
unitSize and the product’s minimum quantity.

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