Update Stripe SDK to v18 and dependencies (#236)
* Update Stripe SDK and dependencies 1. Upgrade `stripe` package from version 17.7.0 to 18.0.0 in `package.json`. 2. Update `STRIPE_API_VERSION` in `stripe-sdk.ts` to '2025-03-31.basil'. 3. Refactor `StripeWebhookHandlerService` to retrieve subscription details using Supabase client, ensuring compatibility with the new Stripe version. 4. Introduce helper methods `getPeriodStartsAt` and `getPeriodEndsAt` for better handling of subscription periods based on the Stripe API changes. These changes enhance the integration with the latest Stripe API and improve the overall reliability of the billing service. * Refactor billing payload builders to remove config dependency Removed direct dependency on `BillingConfig` in subscription payload builders. Introduced `PlanTypeMap` to streamline plan type resolutions. Updated webhook handlers and event processing functions to handle plan types more efficiently and improve extensibility. * Refactor Stripe subscription handling for improved accuracy
This commit is contained in:
committed by
GitHub
parent
4f41304be4
commit
903ef6dc08
@@ -27,6 +27,10 @@ import { createLemonSqueezyBillingPortalSession } from './create-lemon-squeezy-b
|
||||
import { createLemonSqueezyCheckout } from './create-lemon-squeezy-checkout';
|
||||
import { createLemonSqueezySubscriptionPayloadBuilderService } from './lemon-squeezy-subscription-payload-builder.service';
|
||||
|
||||
/**
|
||||
* @name LemonSqueezyBillingStrategyService
|
||||
* @description This class is used to create a billing strategy for Lemon Squeezy
|
||||
*/
|
||||
export class LemonSqueezyBillingStrategyService
|
||||
implements BillingStrategyProviderService
|
||||
{
|
||||
@@ -405,6 +409,8 @@ export class LemonSqueezyBillingStrategyService
|
||||
quantity: subscription.first_subscription_item?.quantity ?? 1,
|
||||
// not anywhere in the API
|
||||
priceAmount: 0,
|
||||
// we cannot retrieve this from the API, user should retrieve from the billing configuration if needed
|
||||
type: '' as never,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BillingConfig, getLineItemTypeById } from '@kit/billing';
|
||||
import { BillingConfig } from '@kit/billing';
|
||||
import { UpsertSubscriptionParams } from '@kit/billing/types';
|
||||
|
||||
type SubscriptionStatus =
|
||||
@@ -25,17 +25,6 @@ export function createLemonSqueezySubscriptionPayloadBuilderService() {
|
||||
class LemonSqueezySubscriptionPayloadBuilderService {
|
||||
private config?: BillingConfig;
|
||||
|
||||
/**
|
||||
* @name withBillingConfig
|
||||
* @description Set the billing config for the subscription payload
|
||||
* @param config
|
||||
*/
|
||||
withBillingConfig(config: BillingConfig) {
|
||||
this.config = config;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name build
|
||||
* @description Build the subscription payload for Lemon Squeezy
|
||||
@@ -48,6 +37,7 @@ class LemonSqueezySubscriptionPayloadBuilderService {
|
||||
product: string;
|
||||
variant: string;
|
||||
priceAmount: number;
|
||||
type: 'flat' | 'per_seat' | 'metered';
|
||||
},
|
||||
>(params: {
|
||||
id: string;
|
||||
@@ -85,9 +75,7 @@ class LemonSqueezySubscriptionPayloadBuilderService {
|
||||
product_id: item.product,
|
||||
variant_id: item.variant,
|
||||
price_amount: item.priceAmount,
|
||||
type: this.config
|
||||
? getLineItemTypeById(this.config, item.variant)
|
||||
: undefined,
|
||||
type: item.type,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
getVariant,
|
||||
} from '@lemonsqueezy/lemonsqueezy.js';
|
||||
|
||||
import { BillingConfig, BillingWebhookHandlerService } from '@kit/billing';
|
||||
import { BillingWebhookHandlerService, type PlanTypeMap } from '@kit/billing';
|
||||
import { getLogger } from '@kit/shared/logger';
|
||||
import { Database, Enums } from '@kit/supabase/database';
|
||||
|
||||
@@ -48,7 +48,7 @@ export class LemonSqueezyWebhookHandlerService
|
||||
|
||||
private readonly namespace = 'billing.lemon-squeezy';
|
||||
|
||||
constructor(private readonly config: BillingConfig) {}
|
||||
constructor(private readonly planTypesMap: PlanTypeMap) {}
|
||||
|
||||
/**
|
||||
* @description Verifies the webhook signature - should throw an error if the signature is invalid
|
||||
@@ -116,48 +116,84 @@ export class LemonSqueezyWebhookHandlerService
|
||||
|
||||
switch (eventName) {
|
||||
case 'order_created': {
|
||||
return this.handleOrderCompleted(
|
||||
const result = await this.handleOrderCompleted(
|
||||
event as OrderWebhook,
|
||||
params.onCheckoutSessionCompleted,
|
||||
);
|
||||
|
||||
// handle user-supplied handler
|
||||
if (params.onEvent) {
|
||||
await params.onEvent(event);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case 'subscription_created': {
|
||||
return this.handleSubscriptionCreatedEvent(
|
||||
const result = await this.handleSubscriptionCreatedEvent(
|
||||
event as SubscriptionWebhook,
|
||||
params.onSubscriptionUpdated,
|
||||
);
|
||||
|
||||
// handle user-supplied handler
|
||||
if (params.onEvent) {
|
||||
await params.onEvent(event);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case 'subscription_updated': {
|
||||
return this.handleSubscriptionUpdatedEvent(
|
||||
const result = await this.handleSubscriptionUpdatedEvent(
|
||||
event as SubscriptionWebhook,
|
||||
params.onSubscriptionUpdated,
|
||||
);
|
||||
|
||||
// handle user-supplied handler
|
||||
if (params.onEvent) {
|
||||
await params.onEvent(event);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case 'subscription_expired': {
|
||||
return this.handleSubscriptionDeletedEvent(
|
||||
const result = await this.handleSubscriptionDeletedEvent(
|
||||
event as SubscriptionWebhook,
|
||||
params.onSubscriptionDeleted,
|
||||
);
|
||||
|
||||
// handle user-supplied handler
|
||||
if (params.onEvent) {
|
||||
await params.onEvent(event);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case 'subscription_payment_success': {
|
||||
return this.handleInvoicePaid(
|
||||
const result = await this.handleInvoicePaid(
|
||||
event as SubscriptionInvoiceWebhook,
|
||||
params.onInvoicePaid,
|
||||
);
|
||||
|
||||
// handle user-supplied handler
|
||||
if (params.onEvent) {
|
||||
await params.onEvent(event);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
default: {
|
||||
// handle user-supplied handler
|
||||
if (params.onEvent) {
|
||||
return params.onEvent(event);
|
||||
}
|
||||
|
||||
const logger = await getLogger();
|
||||
|
||||
logger.info(
|
||||
logger.debug(
|
||||
{
|
||||
eventType: eventName,
|
||||
name: this.namespace,
|
||||
@@ -212,6 +248,7 @@ export class LemonSqueezyWebhookHandlerService
|
||||
variant_id: attrs.first_order_item.variant_id.toString(),
|
||||
price_amount: attrs.first_order_item.price,
|
||||
quantity: 1,
|
||||
type: this.getLineItemType(attrs.first_order_item.variant_id),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -268,6 +305,7 @@ export class LemonSqueezyWebhookHandlerService
|
||||
variant: variantId.toString(),
|
||||
quantity: firstSubscriptionItem.quantity,
|
||||
priceAmount,
|
||||
type: this.getLineItemType(variantId),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -276,7 +314,7 @@ export class LemonSqueezyWebhookHandlerService
|
||||
const payloadBuilderService =
|
||||
createLemonSqueezySubscriptionPayloadBuilderService();
|
||||
|
||||
const payload = payloadBuilderService.withBillingConfig(this.config).build({
|
||||
const payload = payloadBuilderService.build({
|
||||
customerId,
|
||||
id: subscriptionId,
|
||||
accountId,
|
||||
@@ -361,6 +399,8 @@ export class LemonSqueezyWebhookHandlerService
|
||||
const payloadBuilderService =
|
||||
createLemonSqueezySubscriptionPayloadBuilderService();
|
||||
|
||||
const lineItemType = this.getLineItemType(variantId);
|
||||
|
||||
const lineItems = [
|
||||
{
|
||||
id: subscription.order_item_id.toString(),
|
||||
@@ -368,10 +408,11 @@ export class LemonSqueezyWebhookHandlerService
|
||||
variant: variantId.toString(),
|
||||
quantity: subscription.first_subscription_item?.quantity ?? 1,
|
||||
priceAmount: attrs.total,
|
||||
type: lineItemType,
|
||||
},
|
||||
];
|
||||
|
||||
const payload = payloadBuilderService.withBillingConfig(this.config).build({
|
||||
const payload = payloadBuilderService.build({
|
||||
customerId,
|
||||
id: subscriptionId,
|
||||
accountId,
|
||||
@@ -390,6 +431,22 @@ export class LemonSqueezyWebhookHandlerService
|
||||
return onInvoicePaidCallback(payload);
|
||||
}
|
||||
|
||||
private getLineItemType(variantId: number) {
|
||||
const type = this.planTypesMap.get(variantId.toString());
|
||||
|
||||
if (!type) {
|
||||
console.warn(
|
||||
{
|
||||
variantId,
|
||||
},
|
||||
'Line item type not found. Will be defaulted to "flat"',
|
||||
);
|
||||
|
||||
return 'flat' as const;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
private getOrderStatus(status: OrderStatus) {
|
||||
switch (status) {
|
||||
case 'paid':
|
||||
|
||||
Reference in New Issue
Block a user