Add Workspace Created and Payment Received ClickHouse events (#20277)

## Summary
- Adds two new workspace audit events for the AARRR funnel tracked in
ClickHouse
- **Workspace Created**: emitted in `signUpOnNewWorkspace` after the
transaction commits, capturing every new workspace creation
- **Payment Received**: emitted in `processInvoicePaid` on every Stripe
`invoice.paid` webhook, with `stripeInvoiceId`, `amountPaid`, and
`billingReason` properties. First payment per workspace can be derived
at query time via `min(timestamp)` grouped by `workspaceId`

## Test plan
- [x] Verify `Workspace Created` event appears in ClickHouse after
signing up on a new workspace
- [x] Verify `Payment Received` event appears in ClickHouse after a
Stripe `invoice.paid` webhook fires
- [x] Confirm no event is emitted if the billing customer cannot be
resolved from `stripeCustomerId`
- [x] Run existing `SignInUpService` unit tests pass with the new
`AuditService` mock


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Thomas Trompette
2026-05-07 09:57:30 +02:00
committed by GitHub
parent be4b466234
commit eddd2c979a
7 changed files with 72 additions and 11 deletions
@@ -100,6 +100,11 @@ const createSignInUpServiceForTests = () => {
{
isValid: jest.fn().mockReturnValue(false),
} as any,
{
createContext: jest.fn().mockReturnValue({
insertWorkspaceEvent: jest.fn(),
}),
} as any,
{
createQueryRunner: jest.fn(() => queryRunnerMock),
} as any,
@@ -9,6 +9,8 @@ import { Repository, type DataSource, type QueryRunner } from 'typeorm';
import { v4 } from 'uuid';
import { USER_SIGNUP_EVENT_NAME } from 'src/engine/api/graphql/workspace-query-runner/constants/user-signup-event-name.constants';
import { AuditService } from 'src/engine/core-modules/audit/services/audit.service';
import { WORKSPACE_CREATED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/workspace/workspace-created';
import { type AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import {
@@ -67,6 +69,7 @@ export class SignInUpService {
private readonly applicationService: ApplicationService,
private readonly fileCorePictureService: FileCorePictureService,
private readonly enterprisePlanService: EnterprisePlanService,
private readonly auditService: AuditService,
@InjectDataSource()
private readonly dataSource: DataSource,
) {}
@@ -594,6 +597,10 @@ export class SignInUpService {
await queryRunner.commitTransaction();
this.auditService
.createContext({ workspaceId })
.insertWorkspaceEvent(WORKSPACE_CREATED_EVENT, {});
return { user, workspace };
} catch (error) {
if (queryRunner.isTransactionActive) {