forked from twentyhq/twenty
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add billing meter event service (twentyhq#9865)
Solves : twentyhq/private-issues#241 twentyhq/private-issues#254 **TLDR:** - Add BillingMeterEventService and StripeBillingMeterEventService in order to send billing meter events to stripe. - Plugged the service into workflow node execution for testing purposes (more improvements on this area will be done in the next PR's) **In order to test:** - Have the environment variable IS_BILLING_ENABLED set to true and add the other required environment variables for Billing to work - Do a database reset (to ensure that the new feature flag is properly added and that the billing tables are created) - Run the command: npx nx run twenty-server:command billing:sync-plans-data (if you don't do that the products and prices will not be present in the database) - Run the server , the frontend, the worker, and the stripe listen command (stripe listen --forward-to http://localhost:3000/billing/webhooks) - Buy a subscription for the Acme workspace - Create a workflow and run it - After the run has been finished check in sprite the quantity of events in the CreditMeter, you should see that there is a new occurence with value one. **Take into consideration:** - I used an eventName that I have made a long time ago, so it hasn't a significant naming. I'm updating the meters and associated prices in stripe to use the correct meter with a more clearer eventName. - I put some error handling in the execution of the workflow nodes, this is still incomplete and needs some refinement, I would like the feedback of the workflows track for a more cleaner approach
- Loading branch information
Showing
14 changed files
with
251 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
...ver/src/engine/core-modules/billing/constants/billing-execute-billed-function.constant.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export const BILLING_EXECUTE_BILLED_FUNCTION = | ||
'billing_execute_billed_function'; |
5 changes: 5 additions & 0 deletions
5
packages/twenty-server/src/engine/core-modules/billing/enums/billing-meter-event-names.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export enum BillingMeterEventName { | ||
WORKFLOW_NODE_RUN = 'creditexecutiontest1', | ||
} | ||
//this is a test event name (no conventions) would you want camel case?, snake case, or all caps? | ||
//Something like workflowNodeRunBillingMeterEvent ? |
39 changes: 39 additions & 0 deletions
39
...ver/src/engine/core-modules/billing/listeners/billing-execute-billed-function.listener.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
|
||
import { OnCustomBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-custom-batch-event.decorator'; | ||
import { BILLING_EXECUTE_BILLED_FUNCTION } from 'src/engine/core-modules/billing/constants/billing-execute-billed-function.constant'; | ||
import { BillingUsageService } from 'src/engine/core-modules/billing/services/billing-usage.service'; | ||
import { BillingUsageEvent } from 'src/engine/core-modules/billing/types/billing-usage-event.type'; | ||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; | ||
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type'; | ||
|
||
@Injectable() | ||
export class BillingExecuteBilledFunctionListener { | ||
constructor( | ||
private readonly billingUsageService: BillingUsageService, | ||
private readonly environmentService: EnvironmentService, | ||
) {} | ||
|
||
@OnCustomBatchEvent(BILLING_EXECUTE_BILLED_FUNCTION) | ||
async handleExecuteBilledFunctionEvent( | ||
payload: WorkspaceEventBatch<BillingUsageEvent>, | ||
) { | ||
if (!this.environmentService.get('IS_BILLING_ENABLED')) { | ||
return; | ||
} | ||
|
||
const canExecuteBilledFunction = | ||
await this.billingUsageService.canExecuteBilledFunction( | ||
payload.workspaceId, | ||
); | ||
|
||
if (!canExecuteBilledFunction) { | ||
return; | ||
} | ||
|
||
await this.billingUsageService.billUsage({ | ||
workspaceId: payload.workspaceId, | ||
billingEvents: payload.events, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
90 changes: 90 additions & 0 deletions
90
packages/twenty-server/src/engine/core-modules/billing/services/billing-usage.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import { Injectable, Logger } from '@nestjs/common'; | ||
import { InjectRepository } from '@nestjs/typeorm'; | ||
|
||
import { Repository } from 'typeorm'; | ||
|
||
import { | ||
BillingException, | ||
BillingExceptionCode, | ||
} from 'src/engine/core-modules/billing/billing.exception'; | ||
import { BillingCustomer } from 'src/engine/core-modules/billing/entities/billing-customer.entity'; | ||
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service'; | ||
import { StripeBillingMeterEventService } from 'src/engine/core-modules/billing/stripe/services/stripe-billing-meter-event.service'; | ||
import { BillingUsageEvent } from 'src/engine/core-modules/billing/types/billing-usage-event.type'; | ||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; | ||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; | ||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; | ||
|
||
@Injectable() | ||
export class BillingUsageService { | ||
protected readonly logger = new Logger(BillingUsageService.name); | ||
constructor( | ||
@InjectRepository(BillingCustomer, 'core') | ||
private readonly billingCustomerRepository: Repository<BillingCustomer>, | ||
private readonly featureFlagService: FeatureFlagService, | ||
private readonly billingSubscriptionService: BillingSubscriptionService, | ||
private readonly environmentService: EnvironmentService, | ||
private readonly stripeBillingMeterEventService: StripeBillingMeterEventService, | ||
) {} | ||
|
||
async canExecuteBilledFunction(workspaceId: string): Promise<boolean> { | ||
const isBillingEnabled = this.environmentService.get('IS_BILLING_ENABLED'); | ||
const isBillingPlansEnabled = | ||
await this.featureFlagService.isFeatureEnabled( | ||
FeatureFlagKey.IsBillingPlansEnabled, | ||
workspaceId, | ||
); | ||
|
||
if (!isBillingPlansEnabled || !isBillingEnabled) { | ||
return true; | ||
} | ||
|
||
const billingSubscription = | ||
await this.billingSubscriptionService.getCurrentBillingSubscriptionOrThrow( | ||
{ | ||
workspaceId, | ||
}, | ||
); | ||
|
||
if (!billingSubscription) { | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
async billUsage({ | ||
workspaceId, | ||
billingEvents, | ||
}: { | ||
workspaceId: string; | ||
billingEvents: BillingUsageEvent[]; | ||
}) { | ||
const workspaceStripeCustomer = | ||
await this.billingCustomerRepository.findOne({ | ||
where: { | ||
workspaceId, | ||
}, | ||
}); | ||
|
||
if (!workspaceStripeCustomer) { | ||
throw new BillingException( | ||
'Stripe customer not found', | ||
BillingExceptionCode.BILLING_CUSTOMER_NOT_FOUND, | ||
); | ||
} | ||
|
||
try { | ||
await this.stripeBillingMeterEventService.sendBillingMeterEvent({ | ||
eventName: billingEvents[0].eventName, | ||
value: billingEvents[0].value, | ||
stripeCustomerId: workspaceStripeCustomer.stripeCustomerId, | ||
}); | ||
} catch (error) { | ||
throw new BillingException( | ||
'Failed to send billing meter events to Cache Service', | ||
BillingExceptionCode.BILLING_METER_EVENT_FAILED, | ||
); | ||
} | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
...ver/src/engine/core-modules/billing/stripe/services/stripe-billing-meter-event.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { Injectable, Logger } from '@nestjs/common'; | ||
|
||
import Stripe from 'stripe'; | ||
|
||
import { BillingMeterEventName } from 'src/engine/core-modules/billing/enums/billing-meter-event-names'; | ||
import { StripeSDKService } from 'src/engine/core-modules/billing/stripe/stripe-sdk/services/stripe-sdk.service'; | ||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; | ||
|
||
@Injectable() | ||
export class StripeBillingMeterEventService { | ||
protected readonly logger = new Logger(StripeBillingMeterEventService.name); | ||
private readonly stripe: Stripe; | ||
|
||
constructor( | ||
private readonly environmentService: EnvironmentService, | ||
private readonly stripeSDKService: StripeSDKService, | ||
) { | ||
if (!this.environmentService.get('IS_BILLING_ENABLED')) { | ||
return; | ||
} | ||
this.stripe = this.stripeSDKService.getStripe( | ||
this.environmentService.get('BILLING_STRIPE_API_KEY'), | ||
); | ||
} | ||
|
||
async sendBillingMeterEvent({ | ||
eventName, | ||
value, | ||
stripeCustomerId, | ||
}: { | ||
eventName: BillingMeterEventName; | ||
value: number; | ||
stripeCustomerId: string; | ||
}) { | ||
await this.stripe.billing.meterEvents.create({ | ||
event_name: eventName, | ||
payload: { | ||
value: value.toString(), | ||
stripe_customer_id: stripeCustomerId, | ||
}, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
packages/twenty-server/src/engine/core-modules/billing/types/billing-usage-event.type.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { NonNegative } from 'type-fest'; | ||
|
||
import { BillingMeterEventName } from 'src/engine/core-modules/billing/enums/billing-meter-event-names'; | ||
|
||
export type BillingUsageEvent = { | ||
eventName: BillingMeterEventName; | ||
value: NonNegative<number>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters