@forklaunch/implementation-billing-stripe 0.5.12 → 0.5.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/eject/services/webhook.service.ts +133 -14
- package/lib/services/index.d.mts +7 -0
- package/lib/services/index.d.ts +7 -0
- package/lib/services/index.js +99 -8
- package/lib/services/index.mjs +99 -8
- package/package.json +12 -12
|
@@ -102,6 +102,34 @@ export class StripeWebhookService<
|
|
|
102
102
|
this.subscriptionService = subscriptionService;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Extract features from Stripe product metadata.
|
|
107
|
+
* Features can be stored as:
|
|
108
|
+
* - metadata.features: comma-separated string (e.g., "feature1,feature2,feature3")
|
|
109
|
+
* - metadata.features: JSON array string (e.g., '["feature1","feature2"]')
|
|
110
|
+
*/
|
|
111
|
+
private extractFeaturesFromProduct(product: Stripe.Product): string[] {
|
|
112
|
+
const featuresStr = product.metadata?.features;
|
|
113
|
+
if (!featuresStr) {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Try parsing as JSON array first
|
|
118
|
+
try {
|
|
119
|
+
const parsed = JSON.parse(featuresStr);
|
|
120
|
+
if (Array.isArray(parsed)) {
|
|
121
|
+
return parsed.filter((f): f is string => typeof f === 'string');
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
// Not JSON, treat as comma-separated
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return featuresStr
|
|
128
|
+
.split(',')
|
|
129
|
+
.map((f) => f.trim())
|
|
130
|
+
.filter((f) => f.length > 0);
|
|
131
|
+
}
|
|
132
|
+
|
|
105
133
|
async handleWebhookEvent(event: Stripe.Event): Promise<void> {
|
|
106
134
|
if (this.openTelemetryCollector) {
|
|
107
135
|
this.openTelemetryCollector.info('Handling webhook event', event);
|
|
@@ -185,22 +213,26 @@ export class StripeWebhookService<
|
|
|
185
213
|
|
|
186
214
|
case 'plan.created': {
|
|
187
215
|
if (
|
|
188
|
-
typeof event.data.object.product === 'object' &&
|
|
189
216
|
event.data.object.product != null &&
|
|
190
217
|
event.data.object.amount != null
|
|
191
218
|
) {
|
|
219
|
+
const productId =
|
|
220
|
+
typeof event.data.object.product === 'string'
|
|
221
|
+
? event.data.object.product
|
|
222
|
+
: event.data.object.product.id;
|
|
223
|
+
const product = await this.stripeClient.products.retrieve(productId);
|
|
224
|
+
const features = this.extractFeaturesFromProduct(product);
|
|
225
|
+
|
|
192
226
|
await this.planService.basePlanService.createPlan({
|
|
193
227
|
id: event.data.object.id,
|
|
194
228
|
billingProvider: BillingProviderEnum.STRIPE,
|
|
195
229
|
cadence: event.data.object.interval as PlanCadenceEnum,
|
|
196
230
|
currency: event.data.object.currency as CurrencyEnum,
|
|
197
|
-
active:
|
|
198
|
-
name:
|
|
199
|
-
typeof event.data.object.product === 'string'
|
|
200
|
-
? event.data.object.product
|
|
201
|
-
: event.data.object.product?.id,
|
|
231
|
+
active: product.active,
|
|
232
|
+
name: product.name,
|
|
202
233
|
price: event.data.object.amount,
|
|
203
|
-
externalId: event.data.object.id
|
|
234
|
+
externalId: event.data.object.id,
|
|
235
|
+
features
|
|
204
236
|
});
|
|
205
237
|
} else {
|
|
206
238
|
throw new Error('Invalid plan');
|
|
@@ -210,22 +242,26 @@ export class StripeWebhookService<
|
|
|
210
242
|
|
|
211
243
|
case 'plan.updated': {
|
|
212
244
|
if (
|
|
213
|
-
typeof event.data.object.product === 'object' &&
|
|
214
245
|
event.data.object.product != null &&
|
|
215
246
|
event.data.object.amount != null
|
|
216
247
|
) {
|
|
248
|
+
const productId =
|
|
249
|
+
typeof event.data.object.product === 'string'
|
|
250
|
+
? event.data.object.product
|
|
251
|
+
: event.data.object.product.id;
|
|
252
|
+
const product = await this.stripeClient.products.retrieve(productId);
|
|
253
|
+
const features = this.extractFeaturesFromProduct(product);
|
|
254
|
+
|
|
217
255
|
await this.planService.basePlanService.updatePlan({
|
|
218
256
|
id: event.data.object.id,
|
|
219
257
|
billingProvider: BillingProviderEnum.STRIPE,
|
|
220
258
|
cadence: event.data.object.interval as PlanCadenceEnum,
|
|
221
259
|
currency: event.data.object.currency as CurrencyEnum,
|
|
222
|
-
active:
|
|
223
|
-
name:
|
|
224
|
-
typeof event.data.object.product === 'string'
|
|
225
|
-
? event.data.object.product
|
|
226
|
-
: event.data.object.product?.id,
|
|
260
|
+
active: product.active,
|
|
261
|
+
name: product.name,
|
|
227
262
|
price: event.data.object.amount,
|
|
228
|
-
externalId: event.data.object.id
|
|
263
|
+
externalId: event.data.object.id,
|
|
264
|
+
features
|
|
229
265
|
});
|
|
230
266
|
} else {
|
|
231
267
|
throw new Error('Invalid plan');
|
|
@@ -240,6 +276,89 @@ export class StripeWebhookService<
|
|
|
240
276
|
break;
|
|
241
277
|
}
|
|
242
278
|
|
|
279
|
+
case 'product.created':
|
|
280
|
+
case 'product.updated': {
|
|
281
|
+
// When a product is created/updated, sync features to all associated plans
|
|
282
|
+
const product = event.data.object;
|
|
283
|
+
const features = this.extractFeaturesFromProduct(product);
|
|
284
|
+
|
|
285
|
+
// Update all legacy plans (iterates through all pages)
|
|
286
|
+
await this.stripeClient.plans
|
|
287
|
+
.list({ product: product.id })
|
|
288
|
+
.autoPagingEach(async (plan) => {
|
|
289
|
+
try {
|
|
290
|
+
await this.planService.basePlanService.updatePlan({
|
|
291
|
+
id: plan.id,
|
|
292
|
+
features,
|
|
293
|
+
active: product.active,
|
|
294
|
+
name: product.name
|
|
295
|
+
});
|
|
296
|
+
} catch (error) {
|
|
297
|
+
this.openTelemetryCollector.warn(
|
|
298
|
+
`Failed to update plan ${plan.id} with product features`,
|
|
299
|
+
error
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Update all price-based plans (iterates through all pages)
|
|
305
|
+
await this.stripeClient.prices
|
|
306
|
+
.list({ product: product.id })
|
|
307
|
+
.autoPagingEach(async (price) => {
|
|
308
|
+
try {
|
|
309
|
+
await this.planService.basePlanService.updatePlan({
|
|
310
|
+
id: price.id,
|
|
311
|
+
features,
|
|
312
|
+
active: price.active && product.active,
|
|
313
|
+
name: product.name
|
|
314
|
+
});
|
|
315
|
+
} catch (error) {
|
|
316
|
+
this.openTelemetryCollector.warn(
|
|
317
|
+
`Failed to update price-based plan ${price.id} with product features`,
|
|
318
|
+
error
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Handle Stripe Prices API (newer alternative to Plans)
|
|
326
|
+
case 'price.created':
|
|
327
|
+
case 'price.updated': {
|
|
328
|
+
const price = event.data.object;
|
|
329
|
+
if (
|
|
330
|
+
price.product != null &&
|
|
331
|
+
price.unit_amount != null &&
|
|
332
|
+
price.recurring
|
|
333
|
+
) {
|
|
334
|
+
const productId =
|
|
335
|
+
typeof price.product === 'string'
|
|
336
|
+
? price.product
|
|
337
|
+
: price.product.id;
|
|
338
|
+
const product = await this.stripeClient.products.retrieve(productId);
|
|
339
|
+
const features = this.extractFeaturesFromProduct(product);
|
|
340
|
+
|
|
341
|
+
const planData = {
|
|
342
|
+
id: price.id,
|
|
343
|
+
billingProvider: BillingProviderEnum.STRIPE,
|
|
344
|
+
cadence: price.recurring.interval as PlanCadenceEnum,
|
|
345
|
+
currency: price.currency as CurrencyEnum,
|
|
346
|
+
active: price.active && product.active,
|
|
347
|
+
name: product.name,
|
|
348
|
+
price: price.unit_amount,
|
|
349
|
+
externalId: price.id,
|
|
350
|
+
features
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
if (event.type === 'price.created') {
|
|
354
|
+
await this.planService.basePlanService.createPlan(planData);
|
|
355
|
+
} else {
|
|
356
|
+
await this.planService.basePlanService.updatePlan(planData);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
|
|
243
362
|
case 'customer.subscription.created': {
|
|
244
363
|
if (
|
|
245
364
|
!event.data.object.items?.data ||
|
package/lib/services/index.d.mts
CHANGED
|
@@ -168,6 +168,13 @@ declare class StripeWebhookService<SchemaValidator extends AnySchemaValidator, S
|
|
|
168
168
|
protected readonly planService: StripePlanService<SchemaValidator, PlanEntities>;
|
|
169
169
|
protected readonly subscriptionService: StripeSubscriptionService<SchemaValidator, PartyEnum, SubscriptionEntities>;
|
|
170
170
|
constructor(stripeClient: stripe__default, em: EntityManager, schemaValidator: SchemaValidator, openTelemetryCollector: OpenTelemetryCollector<MetricsDefinition>, billingPortalService: StripeBillingPortalService<SchemaValidator, BillingPortalEntities>, checkoutSessionService: StripeCheckoutSessionService<SchemaValidator, StatusEnum, CheckoutSessionEntities>, paymentLinkService: StripePaymentLinkService<SchemaValidator, StatusEnum, PaymentLinkEntities>, planService: StripePlanService<SchemaValidator, PlanEntities>, subscriptionService: StripeSubscriptionService<SchemaValidator, PartyEnum, SubscriptionEntities>);
|
|
171
|
+
/**
|
|
172
|
+
* Extract features from Stripe product metadata.
|
|
173
|
+
* Features can be stored as:
|
|
174
|
+
* - metadata.features: comma-separated string (e.g., "feature1,feature2,feature3")
|
|
175
|
+
* - metadata.features: JSON array string (e.g., '["feature1","feature2"]')
|
|
176
|
+
*/
|
|
177
|
+
private extractFeaturesFromProduct;
|
|
171
178
|
handleWebhookEvent(event: stripe__default.Event): Promise<void>;
|
|
172
179
|
}
|
|
173
180
|
|
package/lib/services/index.d.ts
CHANGED
|
@@ -168,6 +168,13 @@ declare class StripeWebhookService<SchemaValidator extends AnySchemaValidator, S
|
|
|
168
168
|
protected readonly planService: StripePlanService<SchemaValidator, PlanEntities>;
|
|
169
169
|
protected readonly subscriptionService: StripeSubscriptionService<SchemaValidator, PartyEnum, SubscriptionEntities>;
|
|
170
170
|
constructor(stripeClient: stripe__default, em: EntityManager, schemaValidator: SchemaValidator, openTelemetryCollector: OpenTelemetryCollector<MetricsDefinition>, billingPortalService: StripeBillingPortalService<SchemaValidator, BillingPortalEntities>, checkoutSessionService: StripeCheckoutSessionService<SchemaValidator, StatusEnum, CheckoutSessionEntities>, paymentLinkService: StripePaymentLinkService<SchemaValidator, StatusEnum, PaymentLinkEntities>, planService: StripePlanService<SchemaValidator, PlanEntities>, subscriptionService: StripeSubscriptionService<SchemaValidator, PartyEnum, SubscriptionEntities>);
|
|
171
|
+
/**
|
|
172
|
+
* Extract features from Stripe product metadata.
|
|
173
|
+
* Features can be stored as:
|
|
174
|
+
* - metadata.features: comma-separated string (e.g., "feature1,feature2,feature3")
|
|
175
|
+
* - metadata.features: JSON array string (e.g., '["feature1","feature2"]')
|
|
176
|
+
*/
|
|
177
|
+
private extractFeaturesFromProduct;
|
|
171
178
|
handleWebhookEvent(event: stripe__default.Event): Promise<void>;
|
|
172
179
|
}
|
|
173
180
|
|
package/lib/services/index.js
CHANGED
|
@@ -646,6 +646,26 @@ var StripeWebhookService = class {
|
|
|
646
646
|
this.planService = planService;
|
|
647
647
|
this.subscriptionService = subscriptionService;
|
|
648
648
|
}
|
|
649
|
+
/**
|
|
650
|
+
* Extract features from Stripe product metadata.
|
|
651
|
+
* Features can be stored as:
|
|
652
|
+
* - metadata.features: comma-separated string (e.g., "feature1,feature2,feature3")
|
|
653
|
+
* - metadata.features: JSON array string (e.g., '["feature1","feature2"]')
|
|
654
|
+
*/
|
|
655
|
+
extractFeaturesFromProduct(product) {
|
|
656
|
+
const featuresStr = product.metadata?.features;
|
|
657
|
+
if (!featuresStr) {
|
|
658
|
+
return [];
|
|
659
|
+
}
|
|
660
|
+
try {
|
|
661
|
+
const parsed = JSON.parse(featuresStr);
|
|
662
|
+
if (Array.isArray(parsed)) {
|
|
663
|
+
return parsed.filter((f) => typeof f === "string");
|
|
664
|
+
}
|
|
665
|
+
} catch {
|
|
666
|
+
}
|
|
667
|
+
return featuresStr.split(",").map((f) => f.trim()).filter((f) => f.length > 0);
|
|
668
|
+
}
|
|
649
669
|
async handleWebhookEvent(event) {
|
|
650
670
|
if (this.openTelemetryCollector) {
|
|
651
671
|
this.openTelemetryCollector.info("Handling webhook event", event);
|
|
@@ -714,16 +734,20 @@ var StripeWebhookService = class {
|
|
|
714
734
|
break;
|
|
715
735
|
}
|
|
716
736
|
case "plan.created": {
|
|
717
|
-
if (
|
|
737
|
+
if (event.data.object.product != null && event.data.object.amount != null) {
|
|
738
|
+
const productId = typeof event.data.object.product === "string" ? event.data.object.product : event.data.object.product.id;
|
|
739
|
+
const product = await this.stripeClient.products.retrieve(productId);
|
|
740
|
+
const features = this.extractFeaturesFromProduct(product);
|
|
718
741
|
await this.planService.basePlanService.createPlan({
|
|
719
742
|
id: event.data.object.id,
|
|
720
743
|
billingProvider: BillingProviderEnum.STRIPE,
|
|
721
744
|
cadence: event.data.object.interval,
|
|
722
745
|
currency: event.data.object.currency,
|
|
723
|
-
active:
|
|
724
|
-
name:
|
|
746
|
+
active: product.active,
|
|
747
|
+
name: product.name,
|
|
725
748
|
price: event.data.object.amount,
|
|
726
|
-
externalId: event.data.object.id
|
|
749
|
+
externalId: event.data.object.id,
|
|
750
|
+
features
|
|
727
751
|
});
|
|
728
752
|
} else {
|
|
729
753
|
throw new Error("Invalid plan");
|
|
@@ -731,16 +755,20 @@ var StripeWebhookService = class {
|
|
|
731
755
|
break;
|
|
732
756
|
}
|
|
733
757
|
case "plan.updated": {
|
|
734
|
-
if (
|
|
758
|
+
if (event.data.object.product != null && event.data.object.amount != null) {
|
|
759
|
+
const productId = typeof event.data.object.product === "string" ? event.data.object.product : event.data.object.product.id;
|
|
760
|
+
const product = await this.stripeClient.products.retrieve(productId);
|
|
761
|
+
const features = this.extractFeaturesFromProduct(product);
|
|
735
762
|
await this.planService.basePlanService.updatePlan({
|
|
736
763
|
id: event.data.object.id,
|
|
737
764
|
billingProvider: BillingProviderEnum.STRIPE,
|
|
738
765
|
cadence: event.data.object.interval,
|
|
739
766
|
currency: event.data.object.currency,
|
|
740
|
-
active:
|
|
741
|
-
name:
|
|
767
|
+
active: product.active,
|
|
768
|
+
name: product.name,
|
|
742
769
|
price: event.data.object.amount,
|
|
743
|
-
externalId: event.data.object.id
|
|
770
|
+
externalId: event.data.object.id,
|
|
771
|
+
features
|
|
744
772
|
});
|
|
745
773
|
} else {
|
|
746
774
|
throw new Error("Invalid plan");
|
|
@@ -753,6 +781,69 @@ var StripeWebhookService = class {
|
|
|
753
781
|
});
|
|
754
782
|
break;
|
|
755
783
|
}
|
|
784
|
+
case "product.created":
|
|
785
|
+
case "product.updated": {
|
|
786
|
+
const product = event.data.object;
|
|
787
|
+
const features = this.extractFeaturesFromProduct(product);
|
|
788
|
+
await this.stripeClient.plans.list({ product: product.id }).autoPagingEach(async (plan) => {
|
|
789
|
+
try {
|
|
790
|
+
await this.planService.basePlanService.updatePlan({
|
|
791
|
+
id: plan.id,
|
|
792
|
+
features,
|
|
793
|
+
active: product.active,
|
|
794
|
+
name: product.name
|
|
795
|
+
});
|
|
796
|
+
} catch (error) {
|
|
797
|
+
this.openTelemetryCollector.warn(
|
|
798
|
+
`Failed to update plan ${plan.id} with product features`,
|
|
799
|
+
error
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
await this.stripeClient.prices.list({ product: product.id }).autoPagingEach(async (price) => {
|
|
804
|
+
try {
|
|
805
|
+
await this.planService.basePlanService.updatePlan({
|
|
806
|
+
id: price.id,
|
|
807
|
+
features,
|
|
808
|
+
active: price.active && product.active,
|
|
809
|
+
name: product.name
|
|
810
|
+
});
|
|
811
|
+
} catch (error) {
|
|
812
|
+
this.openTelemetryCollector.warn(
|
|
813
|
+
`Failed to update price-based plan ${price.id} with product features`,
|
|
814
|
+
error
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
break;
|
|
819
|
+
}
|
|
820
|
+
// Handle Stripe Prices API (newer alternative to Plans)
|
|
821
|
+
case "price.created":
|
|
822
|
+
case "price.updated": {
|
|
823
|
+
const price = event.data.object;
|
|
824
|
+
if (price.product != null && price.unit_amount != null && price.recurring) {
|
|
825
|
+
const productId = typeof price.product === "string" ? price.product : price.product.id;
|
|
826
|
+
const product = await this.stripeClient.products.retrieve(productId);
|
|
827
|
+
const features = this.extractFeaturesFromProduct(product);
|
|
828
|
+
const planData = {
|
|
829
|
+
id: price.id,
|
|
830
|
+
billingProvider: BillingProviderEnum.STRIPE,
|
|
831
|
+
cadence: price.recurring.interval,
|
|
832
|
+
currency: price.currency,
|
|
833
|
+
active: price.active && product.active,
|
|
834
|
+
name: product.name,
|
|
835
|
+
price: price.unit_amount,
|
|
836
|
+
externalId: price.id,
|
|
837
|
+
features
|
|
838
|
+
};
|
|
839
|
+
if (event.type === "price.created") {
|
|
840
|
+
await this.planService.basePlanService.createPlan(planData);
|
|
841
|
+
} else {
|
|
842
|
+
await this.planService.basePlanService.updatePlan(planData);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
756
847
|
case "customer.subscription.created": {
|
|
757
848
|
if (!event.data.object.items?.data || event.data.object.items.data.length === 0 || !event.data.object.items.data[0]?.plan?.id) {
|
|
758
849
|
throw new Error(
|
package/lib/services/index.mjs
CHANGED
|
@@ -614,6 +614,26 @@ var StripeWebhookService = class {
|
|
|
614
614
|
this.planService = planService;
|
|
615
615
|
this.subscriptionService = subscriptionService;
|
|
616
616
|
}
|
|
617
|
+
/**
|
|
618
|
+
* Extract features from Stripe product metadata.
|
|
619
|
+
* Features can be stored as:
|
|
620
|
+
* - metadata.features: comma-separated string (e.g., "feature1,feature2,feature3")
|
|
621
|
+
* - metadata.features: JSON array string (e.g., '["feature1","feature2"]')
|
|
622
|
+
*/
|
|
623
|
+
extractFeaturesFromProduct(product) {
|
|
624
|
+
const featuresStr = product.metadata?.features;
|
|
625
|
+
if (!featuresStr) {
|
|
626
|
+
return [];
|
|
627
|
+
}
|
|
628
|
+
try {
|
|
629
|
+
const parsed = JSON.parse(featuresStr);
|
|
630
|
+
if (Array.isArray(parsed)) {
|
|
631
|
+
return parsed.filter((f) => typeof f === "string");
|
|
632
|
+
}
|
|
633
|
+
} catch {
|
|
634
|
+
}
|
|
635
|
+
return featuresStr.split(",").map((f) => f.trim()).filter((f) => f.length > 0);
|
|
636
|
+
}
|
|
617
637
|
async handleWebhookEvent(event) {
|
|
618
638
|
if (this.openTelemetryCollector) {
|
|
619
639
|
this.openTelemetryCollector.info("Handling webhook event", event);
|
|
@@ -682,16 +702,20 @@ var StripeWebhookService = class {
|
|
|
682
702
|
break;
|
|
683
703
|
}
|
|
684
704
|
case "plan.created": {
|
|
685
|
-
if (
|
|
705
|
+
if (event.data.object.product != null && event.data.object.amount != null) {
|
|
706
|
+
const productId = typeof event.data.object.product === "string" ? event.data.object.product : event.data.object.product.id;
|
|
707
|
+
const product = await this.stripeClient.products.retrieve(productId);
|
|
708
|
+
const features = this.extractFeaturesFromProduct(product);
|
|
686
709
|
await this.planService.basePlanService.createPlan({
|
|
687
710
|
id: event.data.object.id,
|
|
688
711
|
billingProvider: BillingProviderEnum.STRIPE,
|
|
689
712
|
cadence: event.data.object.interval,
|
|
690
713
|
currency: event.data.object.currency,
|
|
691
|
-
active:
|
|
692
|
-
name:
|
|
714
|
+
active: product.active,
|
|
715
|
+
name: product.name,
|
|
693
716
|
price: event.data.object.amount,
|
|
694
|
-
externalId: event.data.object.id
|
|
717
|
+
externalId: event.data.object.id,
|
|
718
|
+
features
|
|
695
719
|
});
|
|
696
720
|
} else {
|
|
697
721
|
throw new Error("Invalid plan");
|
|
@@ -699,16 +723,20 @@ var StripeWebhookService = class {
|
|
|
699
723
|
break;
|
|
700
724
|
}
|
|
701
725
|
case "plan.updated": {
|
|
702
|
-
if (
|
|
726
|
+
if (event.data.object.product != null && event.data.object.amount != null) {
|
|
727
|
+
const productId = typeof event.data.object.product === "string" ? event.data.object.product : event.data.object.product.id;
|
|
728
|
+
const product = await this.stripeClient.products.retrieve(productId);
|
|
729
|
+
const features = this.extractFeaturesFromProduct(product);
|
|
703
730
|
await this.planService.basePlanService.updatePlan({
|
|
704
731
|
id: event.data.object.id,
|
|
705
732
|
billingProvider: BillingProviderEnum.STRIPE,
|
|
706
733
|
cadence: event.data.object.interval,
|
|
707
734
|
currency: event.data.object.currency,
|
|
708
|
-
active:
|
|
709
|
-
name:
|
|
735
|
+
active: product.active,
|
|
736
|
+
name: product.name,
|
|
710
737
|
price: event.data.object.amount,
|
|
711
|
-
externalId: event.data.object.id
|
|
738
|
+
externalId: event.data.object.id,
|
|
739
|
+
features
|
|
712
740
|
});
|
|
713
741
|
} else {
|
|
714
742
|
throw new Error("Invalid plan");
|
|
@@ -721,6 +749,69 @@ var StripeWebhookService = class {
|
|
|
721
749
|
});
|
|
722
750
|
break;
|
|
723
751
|
}
|
|
752
|
+
case "product.created":
|
|
753
|
+
case "product.updated": {
|
|
754
|
+
const product = event.data.object;
|
|
755
|
+
const features = this.extractFeaturesFromProduct(product);
|
|
756
|
+
await this.stripeClient.plans.list({ product: product.id }).autoPagingEach(async (plan) => {
|
|
757
|
+
try {
|
|
758
|
+
await this.planService.basePlanService.updatePlan({
|
|
759
|
+
id: plan.id,
|
|
760
|
+
features,
|
|
761
|
+
active: product.active,
|
|
762
|
+
name: product.name
|
|
763
|
+
});
|
|
764
|
+
} catch (error) {
|
|
765
|
+
this.openTelemetryCollector.warn(
|
|
766
|
+
`Failed to update plan ${plan.id} with product features`,
|
|
767
|
+
error
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
await this.stripeClient.prices.list({ product: product.id }).autoPagingEach(async (price) => {
|
|
772
|
+
try {
|
|
773
|
+
await this.planService.basePlanService.updatePlan({
|
|
774
|
+
id: price.id,
|
|
775
|
+
features,
|
|
776
|
+
active: price.active && product.active,
|
|
777
|
+
name: product.name
|
|
778
|
+
});
|
|
779
|
+
} catch (error) {
|
|
780
|
+
this.openTelemetryCollector.warn(
|
|
781
|
+
`Failed to update price-based plan ${price.id} with product features`,
|
|
782
|
+
error
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
break;
|
|
787
|
+
}
|
|
788
|
+
// Handle Stripe Prices API (newer alternative to Plans)
|
|
789
|
+
case "price.created":
|
|
790
|
+
case "price.updated": {
|
|
791
|
+
const price = event.data.object;
|
|
792
|
+
if (price.product != null && price.unit_amount != null && price.recurring) {
|
|
793
|
+
const productId = typeof price.product === "string" ? price.product : price.product.id;
|
|
794
|
+
const product = await this.stripeClient.products.retrieve(productId);
|
|
795
|
+
const features = this.extractFeaturesFromProduct(product);
|
|
796
|
+
const planData = {
|
|
797
|
+
id: price.id,
|
|
798
|
+
billingProvider: BillingProviderEnum.STRIPE,
|
|
799
|
+
cadence: price.recurring.interval,
|
|
800
|
+
currency: price.currency,
|
|
801
|
+
active: price.active && product.active,
|
|
802
|
+
name: product.name,
|
|
803
|
+
price: price.unit_amount,
|
|
804
|
+
externalId: price.id,
|
|
805
|
+
features
|
|
806
|
+
};
|
|
807
|
+
if (event.type === "price.created") {
|
|
808
|
+
await this.planService.basePlanService.createPlan(planData);
|
|
809
|
+
} else {
|
|
810
|
+
await this.planService.basePlanService.updatePlan(planData);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
break;
|
|
814
|
+
}
|
|
724
815
|
case "customer.subscription.created": {
|
|
725
816
|
if (!event.data.object.items?.data || event.data.object.items.data.length === 0 || !event.data.object.items.data[0]?.plan?.id) {
|
|
726
817
|
throw new Error(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forklaunch/implementation-billing-stripe",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.14",
|
|
4
4
|
"description": "Stripe implementation for forklaunch billing",
|
|
5
5
|
"homepage": "https://github.com/forklaunch/forklaunch-js#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -42,23 +42,23 @@
|
|
|
42
42
|
"lib/**"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@forklaunch/common": "^0.6.
|
|
46
|
-
"@forklaunch/core": "^0.18.
|
|
47
|
-
"@forklaunch/internal": "^0.3.
|
|
48
|
-
"@forklaunch/validator": "^0.10.
|
|
49
|
-
"@mikro-orm/core": "^6.6.
|
|
45
|
+
"@forklaunch/common": "^0.6.30",
|
|
46
|
+
"@forklaunch/core": "^0.18.3",
|
|
47
|
+
"@forklaunch/internal": "^0.3.30",
|
|
48
|
+
"@forklaunch/validator": "^0.10.30",
|
|
49
|
+
"@mikro-orm/core": "^6.6.8",
|
|
50
50
|
"@sinclair/typebox": "^0.34.48",
|
|
51
|
-
"ajv": "^8.
|
|
52
|
-
"stripe": "^20.
|
|
51
|
+
"ajv": "^8.18.0",
|
|
52
|
+
"stripe": "^20.4.0",
|
|
53
53
|
"zod": "^4.3.6",
|
|
54
|
-
"@forklaunch/implementation-billing-base": "0.8.
|
|
55
|
-
"@forklaunch/interfaces-billing": "0.8.
|
|
54
|
+
"@forklaunch/implementation-billing-base": "0.8.14",
|
|
55
|
+
"@forklaunch/interfaces-billing": "0.8.14"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
58
|
+
"@typescript/native-preview": "7.0.0-dev.20260302.1",
|
|
59
59
|
"depcheck": "^1.4.7",
|
|
60
60
|
"prettier": "^3.8.1",
|
|
61
|
-
"typedoc": "^0.28.
|
|
61
|
+
"typedoc": "^0.28.17"
|
|
62
62
|
},
|
|
63
63
|
"scripts": {
|
|
64
64
|
"build": "tsgo --noEmit && tsup domain/schemas/index.ts services/index.ts domain/enum/index.ts domain/types/index.ts --format cjs,esm --no-splitting --dts --tsconfig tsconfig.json --out-dir lib --clean && if [ -f eject-package.bash ]; then pnpm package:eject; fi",
|