feat(billing): credit signupGrant on org creation post-signup#3663
Conversation
Adds BillingSignupGrantService.grantOnSignup (best-effort, never throws) and hooks it into createOrganizationForUser outside the rollback catch block. Free orgs receive 500 compute at signup via the idempotent creditGrant method. Warns on duplicate grant (idempotency replay) and missing plan config.
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (4)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #3663 +/- ##
==========================================
+ Coverage 89.43% 89.54% +0.10%
==========================================
Files 137 138 +1
Lines 4714 4733 +19
Branches 1466 1472 +6
==========================================
+ Hits 4216 4238 +22
+ Misses 389 388 -1
+ Partials 109 107 -2
Flags with carried forward coverage won't be shown. Click here to find out more. Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR implements the N2 “one-shot signup grant” hook by crediting an organization’s extra balance immediately after a successful org+membership creation during signup, without allowing billing failures to trigger org rollback.
Changes:
- Added
BillingSignupGrantService.grantOnSignup({ orgId, planId })as a best-effort wrapper around the idempotentcreditGrantrepository call. - Hooked the signup-grant credit into
createOrganizationForUserafter org/membership/user updates succeed (outside rollback handling). - Added unit + integration coverage for the signup-grant behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| modules/organizations/services/organizations.service.js | Calls the billing signup-grant service after successfully creating an org/membership during signup. |
| modules/billing/services/billing.signupGrant.service.js | Introduces a best-effort service to credit a configured signup grant via creditGrant. |
| modules/billing/tests/billing.signupGrant.service.unit.tests.js | Adds unit tests for success, no-op, missing plan, duplicate grant, and error-swallow behavior. |
| modules/organizations/tests/organizations.integration.tests.js | Adds an integration test asserting the signup grant is credited and a ledger entry exists post-signup. |
| test('credits 500 compute to the org ExtraBalance ledger at signup', async () => { | ||
| // Resolve the org created for this user | ||
| const memberships = await MembershipRepository.list({ userId: grantUser._id || grantUser.id }); | ||
| expect(memberships.length).toBeGreaterThan(0); | ||
| const orgId = (memberships[0].organizationId._id || memberships[0].organizationId).toString(); | ||
|
|
||
| const balance = await BillingExtraBalanceRepository.getBalance(orgId); | ||
| expect(balance).toBe(500); | ||
|
|
||
| const ledger = await BillingExtraBalanceRepository.findLedgerByOrg(orgId); | ||
| expect(ledger).not.toBeNull(); | ||
| const grantEntry = ledger.find((e) => e.source === 'signup_grant'); | ||
| expect(grantEntry).toBeDefined(); | ||
| expect(grantEntry.amount).toBe(500); |
| afterAll(async () => { | ||
| await cleanupUser(grantUser); | ||
| }); |
| if (result?.applied === false) { | ||
| // Idempotent no-op — org already has a signup_grant entry (e.g. admin re-ran or replay). | ||
| logger.warn('[billing.signupGrant] duplicate grant skipped (idempotent)', { orgId, planId, reason: result.reason }); |
Summary
BillingSignupGrantService.grantOnSignup({ orgId, planId })— best-effort, always resolves, never throwscreateOrganizationForUseroutside the rollback catch block (billing failure cannot trigger org/membership cleanup)creditGrantmethod (N2 plan)Test plan
/api/auth/signup→ org created → balance=500, ledger hassignup_grantentrylistLedger.perffailure unrelated — MongoDB$sortArrayversion compat issue)Notes
createOrganizationForUser, so hardcodingplanId: 'free'is correct.