Skip to content

feat: add balance_id to create/update balance endpoints + delete balance endpoint#862

Open
joejohnson123[bot] wants to merge 1 commit intodevfrom
jj/eng-1092-balance-id
Open

feat: add balance_id to create/update balance endpoints + delete balance endpoint#862
joejohnson123[bot] wants to merge 1 commit intodevfrom
jj/eng-1092-balance-id

Conversation

@joejohnson123
Copy link
Contributor

@joejohnson123 joejohnson123 bot commented Mar 2, 2026

ENG-1092

Changes

Schema:

  • Added external_id column to customerEntitlements table
  • Added external_id to CustomerEntitlementSchema
  • Added externalId to CustomerEntitlementFilters

Create balance (POST /balances/create):

  • Accepts optional balance_id param, stored as external_id on the customer entitlement

Update balance (POST /balances/update):

  • Accepts optional balance_id param to target a specific balance by its external ID
  • Flows through buildCustomerEntitlementFiltersfullCustomerToCustomerEntitlements

Delete balance (POST /balances/delete):

  • New endpoint accepting customer_id, feature_id, optional balance_id and entity_id
  • Marks matching customer entitlements as expired (expires_at = now) rather than hard deleting
  • Also available as POST /balances.delete (RPC style)

API response:

  • breakdown[].id now returns external_id when set, falling back to internal id

Note

  • external_id column needs to be pushed via db:push before deploying

Summary by cubic

Implements ENG-1092 by adding support for external balance IDs and a soft-delete endpoint. Create/update can target balances by balance_id; API returns external IDs in balance breakdowns.

  • New Features

    • Added external_id to customer entitlements and filters.
    • POST /balances/create: accepts balance_id (stored as external_id).
    • POST /balances/update: accepts balance_id to target by external_id.
    • POST /balances/delete and RPC /balances.delete: expires matching entitlements; accepts customer_id, feature_id, optional balance_id and entity_id.
    • API: breakdown[].id returns external_id when present, else internal id.
  • Migration

    • Run db:push to add the external_id column before deploy.

Written for commit 7c6580c. Summary will update on new commits.

Greptile Summary

Adds support for external balance IDs (balance_id) to enable API consumers to reference specific balances across create, update, and delete operations. The balance_id is stored as external_id in the database and returned in API responses.

Key changes:

  • API changes: Added optional balance_id parameter to create/update endpoints, and new delete endpoint (POST /balances/delete)
  • API changes: API responses now return external_id (when set) instead of internal id in breakdown[].id
  • Improvements: Delete operation marks balances as expired rather than hard deleting (preserves audit trail)
  • Improvements: External IDs enable idempotent balance management and easier integration for API consumers

Note: The external_id column does not enforce uniqueness. If multiple balances share the same external_id, update/delete operations will affect all of them.

Confidence Score: 4/5

  • Safe to merge with minor considerations around external_id uniqueness behavior
  • Implementation is solid and follows existing patterns. The main consideration is that external_id has no uniqueness constraint, which means operations can affect multiple balances. This appears intentional but should be verified. Consider adding a database index on external_id for query performance.
  • Consider adding index to external_id in shared/models/cusProductModels/cusEntModels/cusEntTable.ts for performance

Important Files Changed

Filename Overview
shared/models/cusProductModels/cusEntModels/cusEntTable.ts Added external_id text column for API consumers to reference balances
server/src/internal/balances/utils/buildCustomerEntitlementFilters.ts Extended filter builder to include externalId from balance_id parameter
shared/utils/cusUtils/fullCusUtils/fullCustomerToCustomerEntitlements.ts Added filtering logic to match customer entitlements by externalId
server/src/internal/balances/handlers/handleDeleteBalance.ts New endpoint to mark balances as expired; affects all balances matching external_id
server/src/internal/customers/cusUtils/apiCusUtils/getApiBalance/getApiBalance.ts Returns external_id as breakdown item id when set, falling back to internal id

Sequence Diagram

sequenceDiagram
    participant Client
    participant handleDeleteBalance
    participant CusService
    participant fullCustomerToCustomerEntitlements
    participant CusEntService
    participant Cache
    
    Client->>handleDeleteBalance: POST /balances/delete<br/>{customer_id, feature_id, balance_id?}
    handleDeleteBalance->>CusService: getFull(customer_id, entity_id)
    CusService-->>handleDeleteBalance: fullCustomer
    
    handleDeleteBalance->>fullCustomerToCustomerEntitlements: Filter by feature_id<br/>and external_id (balance_id)
    fullCustomerToCustomerEntitlements-->>handleDeleteBalance: matching cusEnts[]
    
    alt No matches found
        handleDeleteBalance-->>Client: 404 Not Found
    else Matches found
        loop For each cusEnt
            handleDeleteBalance->>CusEntService: update(id, expires_at=now)
            CusEntService-->>handleDeleteBalance: updated
        end
        
        handleDeleteBalance->>Cache: deleteCachedApiCustomer
        Cache-->>handleDeleteBalance: cleared
        
        handleDeleteBalance-->>Client: {success: true, deleted_count}
    end
Loading

Last reviewed commit: 7c6580c

…nce endpoint

- Add external_id column to cusEntTable
- Accept balance_id param in create balance (stored as external_id)
- Accept balance_id param in update balance (filters by external_id)
- Add externalId to CustomerEntitlementFilters
- New POST /balances/delete endpoint (marks as expired rather than deleting)
- API balance breakdown.id returns external_id when set

Closes ENG-1092

Co-authored-by: Joe Johnson (JJ) <joejohnson123[bot]@users.noreply.github.com>
@joejohnson123 joejohnson123 bot requested review from ay-rod and johnyeocx as code owners March 2, 2026 15:10
@vercel
Copy link

vercel bot commented Mar 2, 2026

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

Project Deployment Actions Updated (UTC)
autumn-vite Building Building Preview, Comment Mar 2, 2026 3:10pm

Request Review

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

cubic analysis

No issues found across 11 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Linked issue analysis

Linked issue: ENG-1092: Add balance_id to create/update balance endpoints + delete balance endpoint

Status Acceptance criteria Notes
Add `external_id` column to `cusEntTable` Added external_id column in customerEntTable
Add `external_id` to CustomerEntitlement schema/model CustomerEntitlementSchema now includes external_id
Add `externalId` to CustomerEntitlementFilters Filters schema extended with externalId
In getApiBalance, use breakdown.id: cusEnt.external_id ?? cusEnt.id API balance id now falls back to internal id
Create balance: accept `balance_id` param and store as `external_id` Create params include balance_id and insertion sets external_id
Update balance: accept `balance_id` param (add to customerEntitlementFilters) to target specific balance Update params include balance_id; filters use externalId
Delete balance endpoint: accept `customer_id`, `feature_id`, optional `balance_id` and `entity_id` New delete handler validates and accepts required/optional params
Delete behavior: mark matching customer entitlements as expired (set expires_at = now) rather than hard deleting Handler updates expires_at for each matched entitlement
Expose delete endpoint as RPC style `/balances.delete` as well RPC router includes balances.delete route
fullCustomerToCustomerEntitlements should filter by externalId when provided Full-customer conversion now filters by externalId

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

11 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

feature_id: text("feature_id"),

// External ID for API consumers to reference this balance
external_id: text("external_id"),
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider adding a database index on external_id since it's used for filtering in update/delete operations

Suggested change
external_id: text("external_id"),
// External ID for API consumers to reference this balance
external_id: text("external_id").index("idx_customer_entitlements_external_id"),
Prompt To Fix With AI
This is a comment left during a code review.
Path: shared/models/cusProductModels/cusEntModels/cusEntTable.ts
Line: 52

Comment:
Consider adding a database index on `external_id` since it's used for filtering in update/delete operations

```suggestion
		// External ID for API consumers to reference this balance
		external_id: text("external_id").index("idx_customer_entitlements_external_id"),
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +51 to +60
// Mark as expired rather than deleting
const now = Date.now();
for (const cusEnt of cusEnts) {
await CusEntService.update({
ctx,
id: cusEnt.id,
updates: {
expires_at: now,
},
});
Copy link
Contributor

Choose a reason for hiding this comment

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

If multiple customer entitlements share the same external_id, all will be expired - verify this is the intended behavior

Prompt To Fix With AI
This is a comment left during a code review.
Path: server/src/internal/balances/handlers/handleDeleteBalance.ts
Line: 51-60

Comment:
If multiple customer entitlements share the same `external_id`, all will be expired - verify this is the intended behavior

How can I resolve this? If you propose a fix, please make it concise.

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