Add error management to the Billing Package (#10343)
Solves https://github.com/twentyhq/core-team-issues/issues/403 **TLDR:** Enhance error management in Billing and when a customer is updated it updates automatically the Stripecustomer id in the entitlements. - Add Billing exceptions to filter. - Add onUpdate for billing customer and entitlement. - Remember to run the migrations with is BILLING_ENABLED set to true. **In order to test (a simple test case)** - Ensure that the environment variables for Sentry and Billing are set, ensuring that SENTRY_ENVIRONMENT=staging - Run the server, the worker and the stripe cli - Do a database reset with IS_BILLING_ENABLED set to true - Go to stripe in test mode and update a random price description, this causes an exception because you are trying to write a price of. a product that doesn't exists in the database - You should see an error in Sentry: 
This commit is contained in:
committed by
GitHub
parent
cc0d892de0
commit
1b64f87d75
@ -0,0 +1,25 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AddOnUpdateInEntitlements1739981257356
|
||||||
|
implements MigrationInterface
|
||||||
|
{
|
||||||
|
name = 'AddOnUpdateInEntitlements1739981257356';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."billingEntitlement" DROP CONSTRAINT "FK_766a1918aa3dbe0d67d3df62356"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."billingEntitlement" ADD CONSTRAINT "FK_766a1918aa3dbe0d67d3df62356" FOREIGN KEY ("stripeCustomerId") REFERENCES "core"."billingCustomer"("stripeCustomerId") ON DELETE CASCADE ON UPDATE CASCADE`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."billingEntitlement" DROP CONSTRAINT "FK_766a1918aa3dbe0d67d3df62356"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."billingEntitlement" ADD CONSTRAINT "FK_766a1918aa3dbe0d67d3df62356" FOREIGN KEY ("stripeCustomerId") REFERENCES "core"."billingCustomer"("stripeCustomerId") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -47,23 +47,34 @@ export class BillingController {
|
|||||||
@Res() res: Response,
|
@Res() res: Response,
|
||||||
) {
|
) {
|
||||||
if (!req.rawBody) {
|
if (!req.rawBody) {
|
||||||
res.status(400).end();
|
throw new BillingException(
|
||||||
|
'Missing request body',
|
||||||
return;
|
BillingExceptionCode.BILLING_MISSING_REQUEST_BODY,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const event = this.stripeWebhookService.constructEventFromPayload(
|
|
||||||
signature,
|
|
||||||
req.rawBody,
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const event = this.stripeWebhookService.constructEventFromPayload(
|
||||||
|
signature,
|
||||||
|
req.rawBody,
|
||||||
|
);
|
||||||
const result = await this.handleStripeEvent(event);
|
const result = await this.handleStripeEvent(event);
|
||||||
|
|
||||||
res.status(200).send(result).end();
|
res.status(200).send(result).end();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof BillingException) {
|
if (
|
||||||
res.status(404).end();
|
error instanceof BillingException ||
|
||||||
|
error instanceof Stripe.errors.StripeError
|
||||||
|
) {
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
|
const errorMessage =
|
||||||
|
error instanceof Error ? error.message : JSON.stringify(error);
|
||||||
|
|
||||||
|
throw new BillingException(
|
||||||
|
errorMessage,
|
||||||
|
BillingExceptionCode.BILLING_UNHANDLED_ERROR,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,4 +16,7 @@ export enum BillingExceptionCode {
|
|||||||
BILLING_SUBSCRIPTION_EVENT_WORKSPACE_NOT_FOUND = 'BILLING_SUBSCRIPTION_EVENT_WORKSPACE_NOT_FOUND',
|
BILLING_SUBSCRIPTION_EVENT_WORKSPACE_NOT_FOUND = 'BILLING_SUBSCRIPTION_EVENT_WORKSPACE_NOT_FOUND',
|
||||||
BILLING_ACTIVE_SUBSCRIPTION_NOT_FOUND = 'BILLING_ACTIVE_SUBSCRIPTION_NOT_FOUND',
|
BILLING_ACTIVE_SUBSCRIPTION_NOT_FOUND = 'BILLING_ACTIVE_SUBSCRIPTION_NOT_FOUND',
|
||||||
BILLING_METER_EVENT_FAILED = 'BILLING_METER_EVENT_FAILED',
|
BILLING_METER_EVENT_FAILED = 'BILLING_METER_EVENT_FAILED',
|
||||||
|
BILLING_MISSING_REQUEST_BODY = 'BILLING_MISSING_REQUEST_BODY',
|
||||||
|
BILLING_UNHANDLED_ERROR = 'BILLING_UNHANDLED_ERROR',
|
||||||
|
BILLING_STRIPE_ERROR = 'BILLING_STRIPE_ERROR',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,6 +54,7 @@ export class BillingEntitlement {
|
|||||||
(billingCustomer) => billingCustomer.billingEntitlements,
|
(billingCustomer) => billingCustomer.billingEntitlements,
|
||||||
{
|
{
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
|
onUpdate: 'CASCADE',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@JoinColumn({
|
@JoinColumn({
|
||||||
|
|||||||
@ -17,22 +17,41 @@ export class BillingRestApiExceptionFilter implements ExceptionFilter {
|
|||||||
private readonly httpExceptionHandlerService: HttpExceptionHandlerService,
|
private readonly httpExceptionHandlerService: HttpExceptionHandlerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
catch(exception: BillingException, host: ArgumentsHost) {
|
catch(
|
||||||
|
exception: BillingException | Stripe.errors.StripeError,
|
||||||
|
host: ArgumentsHost,
|
||||||
|
) {
|
||||||
const ctx = host.switchToHttp();
|
const ctx = host.switchToHttp();
|
||||||
const response = ctx.getResponse<Response>();
|
const response = ctx.getResponse<Response>();
|
||||||
|
|
||||||
|
if (exception instanceof Stripe.errors.StripeError) {
|
||||||
|
return this.httpExceptionHandlerService.handleError(
|
||||||
|
{
|
||||||
|
code: BillingExceptionCode.BILLING_STRIPE_ERROR,
|
||||||
|
message: exception.message,
|
||||||
|
name: 'StripeError',
|
||||||
|
},
|
||||||
|
response,
|
||||||
|
400,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
switch (exception.code) {
|
switch (exception.code) {
|
||||||
case BillingExceptionCode.BILLING_CUSTOMER_NOT_FOUND:
|
case BillingExceptionCode.BILLING_CUSTOMER_NOT_FOUND:
|
||||||
|
case BillingExceptionCode.BILLING_ACTIVE_SUBSCRIPTION_NOT_FOUND:
|
||||||
|
case BillingExceptionCode.BILLING_SUBSCRIPTION_EVENT_WORKSPACE_NOT_FOUND:
|
||||||
|
case BillingExceptionCode.BILLING_PRODUCT_NOT_FOUND:
|
||||||
|
case BillingExceptionCode.BILLING_PLAN_NOT_FOUND:
|
||||||
return this.httpExceptionHandlerService.handleError(
|
return this.httpExceptionHandlerService.handleError(
|
||||||
exception,
|
exception,
|
||||||
response,
|
response,
|
||||||
404,
|
404,
|
||||||
);
|
);
|
||||||
case BillingExceptionCode.BILLING_PRODUCT_NOT_FOUND:
|
case BillingExceptionCode.BILLING_METER_EVENT_FAILED:
|
||||||
return this.httpExceptionHandlerService.handleError(
|
return this.httpExceptionHandlerService.handleError(
|
||||||
exception,
|
exception,
|
||||||
response,
|
response,
|
||||||
404,
|
400,
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return this.httpExceptionHandlerService.handleError(
|
return this.httpExceptionHandlerService.handleError(
|
||||||
|
|||||||
Reference in New Issue
Block a user