@forklaunch/implementation-billing-stripe 0.0.1

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.
Files changed (34) hide show
  1. package/LICENSE +21 -0
  2. package/lib/domain/index.d.mts +189 -0
  3. package/lib/domain/index.d.ts +189 -0
  4. package/lib/domain/index.js +231 -0
  5. package/lib/domain/index.mjs +201 -0
  6. package/lib/eject/domain/schemas/billingPortal.schema.ts +37 -0
  7. package/lib/eject/domain/schemas/checkoutSession.schema.ts +74 -0
  8. package/lib/eject/domain/schemas/index.ts +5 -0
  9. package/lib/eject/domain/schemas/paymentLink.schema.ts +62 -0
  10. package/lib/eject/domain/schemas/plan.schema.ts +68 -0
  11. package/lib/eject/domain/schemas/subscription.schema.ts +78 -0
  12. package/lib/eject/services/billingPortal.service.ts +185 -0
  13. package/lib/eject/services/checkoutSession.service.ts +179 -0
  14. package/lib/eject/services/index.ts +6 -0
  15. package/lib/eject/services/paymentLink.service.ts +239 -0
  16. package/lib/eject/services/plan.service.ts +219 -0
  17. package/lib/eject/services/subscription.service.ts +285 -0
  18. package/lib/eject/services/webhook.service.ts +272 -0
  19. package/lib/eject/types/index.ts +2 -0
  20. package/lib/eject/types/stripe.dto.types.ts +214 -0
  21. package/lib/eject/types/stripe.entity.types.ts +83 -0
  22. package/lib/schemas/index.d.mts +376 -0
  23. package/lib/schemas/index.d.ts +376 -0
  24. package/lib/schemas/index.js +704 -0
  25. package/lib/schemas/index.mjs +737 -0
  26. package/lib/services/index.d.mts +223 -0
  27. package/lib/services/index.d.ts +223 -0
  28. package/lib/services/index.js +788 -0
  29. package/lib/services/index.mjs +776 -0
  30. package/lib/types/index.d.mts +121 -0
  31. package/lib/types/index.d.ts +121 -0
  32. package/lib/types/index.js +18 -0
  33. package/lib/types/index.mjs +0 -0
  34. package/package.json +74 -0
@@ -0,0 +1,219 @@
1
+ import { IdDto, IdsDto, InstanceTypeRecord } from '@forklaunch/common';
2
+ import {
3
+ MetricsDefinition,
4
+ OpenTelemetryCollector,
5
+ TelemetryOptions
6
+ } from '@forklaunch/core/http';
7
+ import { BasePlanService } from '@forklaunch/implementation-billing-base/services';
8
+ import { PlanService } from '@forklaunch/interfaces-billing/interfaces';
9
+ import {
10
+ IdentityRequestMapper,
11
+ IdentityResponseMapper,
12
+ InternalMapper,
13
+ RequestMapperConstructor,
14
+ ResponseMapperConstructor,
15
+ transformIntoInternalMapper
16
+ } from '@forklaunch/internal';
17
+ import { AnySchemaValidator } from '@forklaunch/validator';
18
+ import { EntityManager } from '@mikro-orm/core';
19
+ import Stripe from 'stripe';
20
+ import { BillingProviderEnum } from '../domain/enums/billingProvider.enum';
21
+ import { CurrencyEnum } from '../domain/enums/currency.enum';
22
+ import { PlanCadenceEnum } from '../domain/enums/planCadence.enum';
23
+ import {
24
+ StripeCreatePlanDto,
25
+ StripePlanDto,
26
+ StripePlanDtos,
27
+ StripeUpdatePlanDto
28
+ } from '../types/stripe.dto.types';
29
+ import { StripePlanEntities } from '../types/stripe.entity.types';
30
+
31
+ export class StripePlanService<
32
+ SchemaValidator extends AnySchemaValidator,
33
+ Entities extends StripePlanEntities,
34
+ Dto extends StripePlanDtos = StripePlanDtos
35
+ > implements
36
+ PlanService<
37
+ typeof PlanCadenceEnum,
38
+ typeof CurrencyEnum,
39
+ typeof BillingProviderEnum,
40
+ {
41
+ CreatePlanDto: StripeCreatePlanDto;
42
+ UpdatePlanDto: StripeUpdatePlanDto;
43
+ PlanDto: StripePlanDto;
44
+ IdDto: IdDto;
45
+ IdsDto: IdsDto;
46
+ }
47
+ >
48
+ {
49
+ basePlanService: BasePlanService<
50
+ SchemaValidator,
51
+ typeof PlanCadenceEnum,
52
+ typeof CurrencyEnum,
53
+ typeof BillingProviderEnum,
54
+ Entities,
55
+ Entities
56
+ >;
57
+ protected _mappers: InternalMapper<InstanceTypeRecord<typeof this.mappers>>;
58
+
59
+ constructor(
60
+ protected readonly stripeClient: Stripe,
61
+ protected readonly em: EntityManager,
62
+ protected readonly openTelemetryCollector: OpenTelemetryCollector<MetricsDefinition>,
63
+ protected readonly schemaValidator: SchemaValidator,
64
+ protected readonly mappers: {
65
+ PlanMapper: ResponseMapperConstructor<
66
+ SchemaValidator,
67
+ Dto['PlanMapper'],
68
+ Entities['PlanMapper']
69
+ >;
70
+ CreatePlanMapper: RequestMapperConstructor<
71
+ SchemaValidator,
72
+ Dto['CreatePlanMapper'],
73
+ Entities['CreatePlanMapper'],
74
+ (
75
+ dto: Dto['CreatePlanMapper'],
76
+ em: EntityManager,
77
+ plan: Stripe.Plan
78
+ ) => Promise<Entities['CreatePlanMapper']>
79
+ >;
80
+ UpdatePlanMapper: RequestMapperConstructor<
81
+ SchemaValidator,
82
+ Dto['UpdatePlanMapper'],
83
+ Entities['UpdatePlanMapper'],
84
+ (
85
+ dto: Dto['UpdatePlanMapper'],
86
+ em: EntityManager,
87
+ plan: Stripe.Plan
88
+ ) => Promise<Entities['UpdatePlanMapper']>
89
+ >;
90
+ },
91
+ readonly options?: {
92
+ telemetry?: TelemetryOptions;
93
+ databaseTableName?: string;
94
+ }
95
+ ) {
96
+ this._mappers = transformIntoInternalMapper(mappers, schemaValidator);
97
+ this.basePlanService = new BasePlanService(
98
+ em,
99
+ openTelemetryCollector,
100
+ schemaValidator,
101
+ {
102
+ PlanMapper: IdentityResponseMapper<Entities['PlanMapper']>,
103
+ CreatePlanMapper: IdentityRequestMapper<Entities['CreatePlanMapper']>,
104
+ UpdatePlanMapper: IdentityRequestMapper<Entities['UpdatePlanMapper']>
105
+ },
106
+ options
107
+ );
108
+ }
109
+
110
+ async createPlan(
111
+ planDto: Dto['CreatePlanMapper'],
112
+ em?: EntityManager
113
+ ): Promise<Dto['PlanMapper']> {
114
+ const plan = await this.stripeClient.plans.create({
115
+ ...planDto.stripeFields,
116
+ interval: planDto.cadence,
117
+ product: planDto.name,
118
+ currency: planDto.currency as string
119
+ });
120
+
121
+ const planEntity = await this.basePlanService.createPlan(
122
+ await this._mappers.CreatePlanMapper.deserializeDtoToEntity(
123
+ {
124
+ ...planDto,
125
+ externalId: plan.id,
126
+ billingProvider: 'stripe'
127
+ },
128
+ em ?? this.em,
129
+ plan
130
+ ),
131
+ em
132
+ );
133
+
134
+ return this._mappers.PlanMapper.serializeEntityToDto(planEntity);
135
+ }
136
+
137
+ async getPlan(idDto: IdDto, em?: EntityManager): Promise<Dto['PlanMapper']> {
138
+ const plan = await this.stripeClient.plans.retrieve(idDto.id);
139
+ const id = (
140
+ await em?.findOne<{ id: string; externalId: string }>(
141
+ this.options?.databaseTableName ?? 'plan',
142
+ { externalId: idDto.id }
143
+ )
144
+ )?.id;
145
+ if (!id) {
146
+ throw new Error('Plan not found');
147
+ }
148
+ return {
149
+ ...(await this._mappers.PlanMapper.serializeEntityToDto(
150
+ await this.basePlanService.getPlan({ id }, em)
151
+ )),
152
+ stripeFields: plan
153
+ };
154
+ }
155
+
156
+ async updatePlan(
157
+ planDto: Dto['UpdatePlanMapper'],
158
+ em?: EntityManager
159
+ ): Promise<Dto['PlanMapper']> {
160
+ const existingPlan = await this.stripeClient.plans.retrieve(planDto.id);
161
+ const plan = await this.stripeClient.plans.del(planDto.id).then(() =>
162
+ this.stripeClient.plans.create({
163
+ ...planDto.stripeFields,
164
+ interval: planDto.cadence ?? existingPlan.interval,
165
+ product: planDto.name,
166
+ currency: planDto.currency ?? existingPlan.currency
167
+ })
168
+ );
169
+
170
+ const planEntity = await this.basePlanService.updatePlan(
171
+ await this._mappers.UpdatePlanMapper.deserializeDtoToEntity(
172
+ {
173
+ ...planDto,
174
+ externalId: plan.id,
175
+ billingProvider: 'stripe'
176
+ },
177
+ em ?? this.em,
178
+ plan
179
+ ),
180
+ em
181
+ );
182
+
183
+ return this._mappers.PlanMapper.serializeEntityToDto(planEntity);
184
+ }
185
+
186
+ async deletePlan(idDto: { id: string }, em?: EntityManager): Promise<void> {
187
+ await this.stripeClient.plans.del(idDto.id);
188
+ await this.basePlanService.deletePlan(idDto, em);
189
+ }
190
+
191
+ async listPlans(
192
+ idsDto?: IdsDto,
193
+ em?: EntityManager
194
+ ): Promise<Dto['PlanMapper'][]> {
195
+ const plans = await this.stripeClient.plans.list({
196
+ active: true
197
+ });
198
+ const ids = (
199
+ await em?.findAll<{ id: string; externalId: string }>(
200
+ this.options?.databaseTableName ?? 'plan',
201
+ { where: { externalId: { $in: plans.data.map((plan) => plan.id) } } }
202
+ )
203
+ )
204
+ ?.filter((s) => idsDto?.ids?.includes(s.id))
205
+ ?.map((s) => s.id);
206
+
207
+ if (!ids) {
208
+ throw new Error('Plans not found');
209
+ }
210
+ return await Promise.all(
211
+ (await this.basePlanService.listPlans({ ids }, em)).map(async (plan) => ({
212
+ ...(await this._mappers.PlanMapper.serializeEntityToDto(plan)),
213
+ stripeFields: plans.data.find(
214
+ (stripePlan) => stripePlan.id === plan.externalId
215
+ )
216
+ }))
217
+ );
218
+ }
219
+ }
@@ -0,0 +1,285 @@
1
+ import { IdDto, IdsDto, InstanceTypeRecord } from '@forklaunch/common';
2
+ import {
3
+ MetricsDefinition,
4
+ OpenTelemetryCollector,
5
+ TelemetryOptions
6
+ } from '@forklaunch/core/http';
7
+ import { BaseSubscriptionService } from '@forklaunch/implementation-billing-base/services';
8
+ import { SubscriptionService } from '@forklaunch/interfaces-billing/interfaces';
9
+ import {
10
+ IdentityRequestMapper,
11
+ IdentityResponseMapper,
12
+ InternalMapper,
13
+ RequestMapperConstructor,
14
+ ResponseMapperConstructor,
15
+ transformIntoInternalMapper
16
+ } from '@forklaunch/internal';
17
+ import { AnySchemaValidator } from '@forklaunch/validator';
18
+ import { EntityManager } from '@mikro-orm/core';
19
+ import Stripe from 'stripe';
20
+ import { BillingProviderEnum } from '../domain/enums/billingProvider.enum';
21
+ import {
22
+ StripeCreateSubscriptionDto,
23
+ StripeSubscriptionDto,
24
+ StripeSubscriptionDtos,
25
+ StripeUpdateSubscriptionDto
26
+ } from '../types/stripe.dto.types';
27
+ import { StripeSubscriptionEntities } from '../types/stripe.entity.types';
28
+
29
+ export class StripeSubscriptionService<
30
+ SchemaValidator extends AnySchemaValidator,
31
+ PartyType,
32
+ Entities extends StripeSubscriptionEntities<PartyType>,
33
+ Dto extends
34
+ StripeSubscriptionDtos<PartyType> = StripeSubscriptionDtos<PartyType>
35
+ > implements
36
+ SubscriptionService<
37
+ PartyType,
38
+ typeof BillingProviderEnum,
39
+ {
40
+ CreateSubscriptionDto: StripeCreateSubscriptionDto<PartyType>;
41
+ UpdateSubscriptionDto: StripeUpdateSubscriptionDto<PartyType>;
42
+ SubscriptionDto: StripeSubscriptionDto<PartyType>;
43
+ IdDto: IdDto;
44
+ IdsDto: IdsDto;
45
+ }
46
+ >
47
+ {
48
+ baseSubscriptionService: BaseSubscriptionService<
49
+ SchemaValidator,
50
+ PartyType,
51
+ typeof BillingProviderEnum,
52
+ Entities,
53
+ Entities
54
+ >;
55
+ protected _mappers: InternalMapper<InstanceTypeRecord<typeof this.mappers>>;
56
+
57
+ constructor(
58
+ protected readonly stripe: Stripe,
59
+ protected readonly em: EntityManager,
60
+ protected readonly openTelemetryCollector: OpenTelemetryCollector<MetricsDefinition>,
61
+ protected readonly schemaValidator: SchemaValidator,
62
+ protected readonly mappers: {
63
+ SubscriptionMapper: ResponseMapperConstructor<
64
+ SchemaValidator,
65
+ Dto['SubscriptionMapper'],
66
+ Entities['SubscriptionMapper']
67
+ >;
68
+ CreateSubscriptionMapper: RequestMapperConstructor<
69
+ SchemaValidator,
70
+ Dto['CreateSubscriptionMapper'],
71
+ Entities['CreateSubscriptionMapper'],
72
+ (
73
+ dto: Dto['CreateSubscriptionMapper'],
74
+ em: EntityManager,
75
+ subscription: Stripe.Subscription
76
+ ) => Promise<Entities['CreateSubscriptionMapper']>
77
+ >;
78
+ UpdateSubscriptionMapper: RequestMapperConstructor<
79
+ SchemaValidator,
80
+ Dto['UpdateSubscriptionMapper'],
81
+ Entities['UpdateSubscriptionMapper'],
82
+ (
83
+ dto: Dto['UpdateSubscriptionMapper'],
84
+ em: EntityManager,
85
+ subscription: Stripe.Subscription
86
+ ) => Promise<Entities['UpdateSubscriptionMapper']>
87
+ >;
88
+ },
89
+ readonly options?: {
90
+ telemetry?: TelemetryOptions;
91
+ databaseTableName?: string;
92
+ }
93
+ ) {
94
+ this._mappers = transformIntoInternalMapper(mappers, schemaValidator);
95
+ this.baseSubscriptionService = new BaseSubscriptionService(
96
+ em,
97
+ openTelemetryCollector,
98
+ schemaValidator,
99
+ {
100
+ SubscriptionMapper: IdentityResponseMapper<
101
+ Entities['SubscriptionMapper']
102
+ >,
103
+ CreateSubscriptionMapper: IdentityRequestMapper<
104
+ Entities['CreateSubscriptionMapper']
105
+ >,
106
+ UpdateSubscriptionMapper: IdentityRequestMapper<
107
+ Entities['UpdateSubscriptionMapper']
108
+ >
109
+ },
110
+ options
111
+ );
112
+ }
113
+
114
+ async createSubscription(
115
+ subscriptionDto: Dto['CreateSubscriptionMapper'],
116
+ em?: EntityManager
117
+ ): Promise<Dto['SubscriptionMapper']> {
118
+ const subscription = await this.stripe.subscriptions.create({
119
+ ...subscriptionDto.stripeFields,
120
+ customer: subscriptionDto.partyId,
121
+ items: [
122
+ {
123
+ plan: subscriptionDto.productId
124
+ }
125
+ ]
126
+ });
127
+
128
+ const subscriptionEntity =
129
+ await this.baseSubscriptionService.createSubscription(
130
+ await this._mappers.CreateSubscriptionMapper.deserializeDtoToEntity(
131
+ {
132
+ ...subscriptionDto,
133
+ externalId: subscription.id,
134
+ billingProvider: 'stripe'
135
+ },
136
+ em ?? this.em,
137
+ subscription
138
+ ),
139
+ em
140
+ );
141
+
142
+ return this._mappers.SubscriptionMapper.serializeEntityToDto(
143
+ subscriptionEntity
144
+ );
145
+ }
146
+
147
+ async getSubscription(
148
+ idDto: IdDto,
149
+ em?: EntityManager
150
+ ): Promise<Dto['SubscriptionMapper']> {
151
+ return {
152
+ ...(await this._mappers.SubscriptionMapper.serializeEntityToDto(
153
+ await this.baseSubscriptionService.getSubscription(idDto, em)
154
+ )),
155
+ stripeFields: await this.stripe.subscriptions.retrieve(idDto.id)
156
+ };
157
+ }
158
+
159
+ async getUserSubscription(
160
+ idDto: IdDto,
161
+ em?: EntityManager
162
+ ): Promise<Dto['SubscriptionMapper']> {
163
+ return {
164
+ ...(await this._mappers.SubscriptionMapper.serializeEntityToDto(
165
+ await this.baseSubscriptionService.getUserSubscription(idDto, em)
166
+ )),
167
+ stripeFields: await this.stripe.subscriptions.retrieve(idDto.id)
168
+ };
169
+ }
170
+
171
+ async getOrganizationSubscription(
172
+ idDto: IdDto,
173
+ em?: EntityManager
174
+ ): Promise<Dto['SubscriptionMapper']> {
175
+ const id = (
176
+ await em?.findOne<{ id: string; externalId: string }>(
177
+ this.options?.databaseTableName ?? 'subscription',
178
+ { externalId: idDto.id }
179
+ )
180
+ )?.id;
181
+ if (!id) {
182
+ throw new Error('Subscription not found');
183
+ }
184
+ return {
185
+ ...(await this._mappers.SubscriptionMapper.serializeEntityToDto(
186
+ await this.baseSubscriptionService.getOrganizationSubscription(
187
+ { id },
188
+ em
189
+ )
190
+ )),
191
+ stripeFields: await this.stripe.subscriptions.retrieve(idDto.id)
192
+ };
193
+ }
194
+
195
+ async updateSubscription(
196
+ subscriptionDto: Dto['UpdateSubscriptionMapper'],
197
+ em?: EntityManager
198
+ ): Promise<Dto['SubscriptionMapper']> {
199
+ const subscription = await this.stripe.subscriptions.update(
200
+ subscriptionDto.id,
201
+ {
202
+ ...subscriptionDto.stripeFields,
203
+ items: [
204
+ {
205
+ plan: subscriptionDto.productId
206
+ }
207
+ ]
208
+ }
209
+ );
210
+
211
+ const subscriptionEntity =
212
+ await this.baseSubscriptionService.updateSubscription(
213
+ await this._mappers.UpdateSubscriptionMapper.deserializeDtoToEntity(
214
+ {
215
+ ...subscriptionDto,
216
+ externalId: subscription.id,
217
+ billingProvider: 'stripe',
218
+ providerFields: subscription
219
+ },
220
+ em ?? this.em,
221
+ subscription
222
+ ),
223
+ em
224
+ );
225
+
226
+ return this._mappers.SubscriptionMapper.serializeEntityToDto(
227
+ subscriptionEntity
228
+ );
229
+ }
230
+
231
+ async deleteSubscription(
232
+ idDto: { id: string },
233
+ em?: EntityManager
234
+ ): Promise<void> {
235
+ await this.stripe.subscriptions.cancel(idDto.id);
236
+ await this.baseSubscriptionService.deleteSubscription(idDto, em);
237
+ }
238
+
239
+ async listSubscriptions(
240
+ idsDto: IdsDto,
241
+ em?: EntityManager
242
+ ): Promise<Dto['SubscriptionMapper'][]> {
243
+ const subscriptions = (
244
+ await this.stripe.subscriptions.list({
245
+ status: 'active'
246
+ })
247
+ ).data.filter((s) => idsDto.ids?.includes(s.id));
248
+
249
+ const ids = (
250
+ await em?.findAll<{ id: string; externalId: string }>(
251
+ this.options?.databaseTableName ?? 'subscription',
252
+ { where: { externalId: { $in: subscriptions.map((s) => s.id) } } }
253
+ )
254
+ )?.map((s) => s.id);
255
+
256
+ if (!ids) {
257
+ throw new Error('Subscriptions not found');
258
+ }
259
+
260
+ return await Promise.all(
261
+ (await this.baseSubscriptionService.listSubscriptions({ ids }, em)).map(
262
+ async (subscription) => {
263
+ return {
264
+ ...(await this._mappers.SubscriptionMapper.serializeEntityToDto(
265
+ subscription
266
+ )),
267
+ stripeFields: subscriptions.find(
268
+ (s) => s.id === subscription.externalId
269
+ )
270
+ };
271
+ }
272
+ )
273
+ );
274
+ }
275
+
276
+ async cancelSubscription(idDto: IdDto, em?: EntityManager): Promise<void> {
277
+ await this.stripe.subscriptions.cancel(idDto.id);
278
+ await this.baseSubscriptionService.cancelSubscription(idDto, em);
279
+ }
280
+
281
+ async resumeSubscription(idDto: IdDto, em?: EntityManager): Promise<void> {
282
+ await this.stripe.subscriptions.resume(idDto.id);
283
+ await this.baseSubscriptionService.resumeSubscription(idDto, em);
284
+ }
285
+ }