Skip to content

Feature/rea2 60 build api key auth and idempotency foundation#52

Open
bomberkill wants to merge 4 commits intodevelopfrom
feature/rea2-60-build-api-key-auth-and-idempotency-foundation
Open

Feature/rea2 60 build api key auth and idempotency foundation#52
bomberkill wants to merge 4 commits intodevelopfrom
feature/rea2-60-build-api-key-auth-and-idempotency-foundation

Conversation

@bomberkill
Copy link
Copy Markdown
Collaborator

No description provided.

- add normalized payment session, transaction and webhook models
- implement Flutterwave primary and Stripe fallback providers
- add payment session creation and lookup endpoints
- add secure provider webhook endpoints with status normalization
- activate workspace plan or credits on confirmed payments
- document payment endpoints in OpenAPI
- add backend integration coverage for sessions and webhooks
- move billing source of truth to organization
- add organization plan, credits, verification status and senderId
- add contact channel validation flags
- support experimental plan quotas for sms and email
- enforce billing and sender rules for campaign and direct sends
- expose workspace billing summary in API and settings UI
@linear
Copy link
Copy Markdown

linear bot commented Apr 7, 2026

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 7, 2026

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

Project Deployment Actions Updated (UTC)
reachdem Ready Ready Preview, Comment Apr 7, 2026 6:14pm
reachdem-web Ready Ready Preview, Comment Apr 7, 2026 6:14pm

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a Public API foundation featuring API key authentication and idempotency management, while refactoring the billing system to support currency-based wallet balances and tiered message pricing. It also implements contact unsubscription checks during campaign launches and updates the database schema to include API keys, idempotency records, and quota tracking timestamps. Feedback highlights critical race conditions in the entitlement services where usage counters are updated non-atomically, which could lead to undercounting and billing discrepancies.

Comment on lines +101 to 109
...(channel === "sms"
? {
smsQuotaPeriodStartedAt: smsPeriodStart,
smsQuotaUsed: currentUsage + units,
}
: {
emailQuotaUsed: {
increment: units,
},
emailQuotaPeriodStartedAt: emailPeriodStart,
emailQuotaUsed: currentUsage + units,
}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The update of smsQuotaUsed and emailQuotaUsed using currentUsage + units is not atomic and is prone to race conditions. When multiple requests occur simultaneously, they may all read the same currentUsage value, leading to undercounted usage. Furthermore, since the billing calculation (chargeAmountMinor) also depends on this potentially stale currentUsage, users could be undercharged. Using Prisma's increment operation for the usage counters is safer for the increment case. However, to fully prevent billing discrepancies, a row-level lock (e.g., SELECT ... FOR UPDATE) on the organization record is recommended during the entitlement check and reservation process.

        ...(channel === "sms"
          ? {
              smsQuotaPeriodStartedAt: smsPeriodStart,
              smsQuotaUsed: isSameInstant(organization.smsQuotaPeriodStartedAt, smsPeriodStart)
                ? { increment: units }
                : units,
            }
          : {
              emailQuotaPeriodStartedAt: emailPeriodStart,
              emailQuotaUsed: isSameInstant(organization.emailQuotaPeriodStartedAt, emailPeriodStart)
                ? { increment: units }
                : units,
            }),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@bomberkill I found it usefull. can you implement this fix ?

Comment on lines +163 to 171
...(campaign.channel === "sms"
? {
smsQuotaPeriodStartedAt: smsPeriodStart,
smsQuotaUsed: currentUsage + eligibleTargetCount,
}
: {
emailQuotaUsed: {
increment: eligibleTargetCount,
},
emailQuotaPeriodStartedAt: emailPeriodStart,
emailQuotaUsed: currentUsage + eligibleTargetCount,
}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Similar to the issue in MessagingEntitlementsService, updating usage counters with currentUsage + eligibleTargetCount is not atomic. In a concurrent environment, this can lead to incorrect quota tracking and billing. Even within a transaction, standard isolation levels may not prevent race conditions between the read and the write. Using atomic increments and row-level locking is recommended for sensitive billing logic. This approach also aligns with repository guidelines to use bulk database actions for performance and consistency during bulk operations like campaign launches.

          ...(campaign.channel === "sms"
            ? {
                smsQuotaPeriodStartedAt: smsPeriodStart,
                smsQuotaUsed: isSameInstant(organization.smsQuotaPeriodStartedAt, smsPeriodStart)
                  ? { increment: eligibleTargetCount }
                  : eligibleTargetCount,
              }
            : {
                emailQuotaPeriodStartedAt: emailPeriodStart,
                emailQuotaUsed: isSameInstant(organization.emailQuotaPeriodStartedAt, emailPeriodStart)
                  ? { increment: eligibleTargetCount }
                  : eligibleTargetCount,
              }),
References
  1. For bulk operations, fetch all necessary data in a single query and use bulk database actions instead of iterating and querying for each item individually to avoid performance issues and database overload.

Copy link
Copy Markdown
Contributor

@latioms latioms Apr 7, 2026

Choose a reason for hiding this comment

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

@copilot can you sugeeest me a commit to fix it ?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

To address the atomicity issue in request-campaign-launch.usecase.ts, you should use Prisma's atomic update syntax. This ensures the database handles the increment as a single, atomic operation, preventing race conditions even under high concurrency. Here is the suggested change for the tx.organization.update call:

Suggested change
...(campaign.channel === "sms"
? {
smsQuotaPeriodStartedAt: smsPeriodStart,
smsQuotaUsed: currentUsage + eligibleTargetCount,
}
: {
emailQuotaUsed: {
increment: eligibleTargetCount,
},
emailQuotaPeriodStartedAt: emailPeriodStart,
emailQuotaUsed: currentUsage + eligibleTargetCount,
}),
...(campaign.channel === "sms"
? {
smsQuotaPeriodStartedAt: smsPeriodStart,
smsQuotaUsed: { increment: eligibleTargetCount },
}
: {
emailQuotaPeriodStartedAt: emailPeriodStart,
emailQuotaUsed: { increment: eligibleTargetCount },
}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@bomberkill For bulk operations, fetch all necessary data in a single query and use bulk database actions instead of iterating and querying for each item individually to avoid performance issues and database overload.

@bomberkill
Copy link
Copy Markdown
Collaborator Author

bomberkill commented Apr 7, 2026 via email

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants