From e93187fded4de43e0c4ed66844d3eaace39711f3 Mon Sep 17 00:00:00 2001 From: Etienne <45695613+etiennejouan@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:57:56 +0100 Subject: [PATCH] Billing - Fix subscription_schedule release (#16669) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At subscription_schedule released, a subscription_schedule.update event is received in webhook stripe controller but not handled. [Sentry event example ](https://twenty-v7.sentry.io/issues/6606854024/events/e737c528a55048e5981756a4fad9028f/) Payload example ⬇️ ``` { "object": { "id": "sub_sched_1SfeABHDlIZyMfEDBhgJUCN2", "object": "subscription_schedule", .... "phases": [ { "add_invoice_items": [], "application_fee_percent": null, "automatic_tax": { "disabled_reason": null, "enabled": false, "liability": null }, "billing_cycle_anchor": null, "billing_thresholds": { "amount_gte": 10000, "reset_billing_cycle_anchor": false }, "collection_method": "charge_automatically", "coupon": null, "currency": "usd", "default_payment_method": null, "default_tax_rates": [], "description": null, "discounts": [], "end_date": 1768731003, "invoice_settings": { "account_tax_ids": null, "days_until_due": null, "issuer": { "type": "self" } }, "items": [ { "billing_thresholds": null, "discounts": [], "metadata": {}, "plan": "price_1SChAfHDlIZyMfEDCP3pGHHv", "price": "price_1SChAfHDlIZyMfEDCP3pGHHv", "quantity": 1, "tax_rates": [] }, { "billing_thresholds": null, "discounts": [], "metadata": {}, "plan": "price_1SChAaHDlIZyMfEDkGCDIIjm", "price": "price_1SChAaHDlIZyMfEDkGCDIIjm", "tax_rates": [] } ], "metadata": {}, "on_behalf_of": null, "proration_behavior": "none", "start_date": 1766053422, "transfer_data": null, "trial_end": null } ], "released_at": 1768731003, "released_subscription": "sub_1Sfe0oHDlIZyMfEDs1lYui6v", // here ! "subscription": null, "test_clock": "clock_1SfeT1HDlIZyMfEDNnlhPmOM" }, "previous_attributes": { "released_subscription": null, "subscription": "sub_1Sfe0oHDlIZyMfEDs1lYui6v" } } ``` related to https://github.com/twentyhq/twenty/issues/16573 --- ...g-webhook-subscription-schedule.service.ts | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/twenty-server/src/engine/core-modules/billing-webhook/services/billing-webhook-subscription-schedule.service.ts b/packages/twenty-server/src/engine/core-modules/billing-webhook/services/billing-webhook-subscription-schedule.service.ts index 90d02727e4..897ce6a4d6 100644 --- a/packages/twenty-server/src/engine/core-modules/billing-webhook/services/billing-webhook-subscription-schedule.service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing-webhook/services/billing-webhook-subscription-schedule.service.ts @@ -3,13 +3,13 @@ import { Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; import Stripe from 'stripe'; import { isDefined } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; +import { transformStripeSubscriptionScheduleEventToDatabaseSubscriptionPhase } from 'src/engine/core-modules/billing-webhook/utils/transform-stripe-subscription-schedule-event-to-database-subscription-phase.util'; import { BillingSubscriptionEntity } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; import { StripeSubscriptionScheduleService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription-schedule.service'; -import { transformStripeSubscriptionScheduleEventToDatabaseSubscriptionPhase } from 'src/engine/core-modules/billing-webhook/utils/transform-stripe-subscription-schedule-event-to-database-subscription-phase.util'; @Injectable() export class BillingWebhookSubscriptionScheduleService { @@ -30,6 +30,24 @@ export class BillingWebhookSubscriptionScheduleService { ) { const schedule = data.object as Stripe.SubscriptionSchedule; + if ( + isDefined(schedule.released_subscription) && + schedule.status === 'released' + ) { + await this.billingSubscriptionRepository.update( + { stripeSubscriptionId: schedule.released_subscription }, + { + phases: [], + }, + ); + + return { + stripeSubscriptionId: schedule.released_subscription, + phasesCount: 0, + scheduleId: schedule.id, + }; + } + if (!isDefined(schedule.subscription)) { throw new Error('Subscription is not defined'); }