@forklaunch/implementation-billing-stripe 1.1.30 → 1.1.32
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 +41 -7
- package/lib/persistence/entities/index.js +107 -0
- package/lib/persistence/entities/index.mjs +77 -0
- package/lib/services/index.js +11 -75
- package/lib/services/index.mjs +11 -75
- package/lib/services/webhook.service.d.ts +24 -2
- package/lib/services/webhook.service.d.ts.map +1 -1
- package/package.json +14 -3
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
OpenTelemetryCollector
|
|
4
4
|
} from '@forklaunch/core/http';
|
|
5
5
|
import { AnySchemaValidator } from '@forklaunch/validator';
|
|
6
|
-
import { EntityManager } from '@mikro-orm/core';
|
|
6
|
+
import { EntityManager, EntityName, InferEntity } from '@mikro-orm/core';
|
|
7
7
|
import Stripe from 'stripe';
|
|
8
8
|
import { BillingProviderEnum } from '../domain/enum/billingProvider.enum';
|
|
9
9
|
import { CurrencyEnum } from '../domain/enum/currency.enum';
|
|
@@ -18,11 +18,30 @@ import {
|
|
|
18
18
|
} from '../domain/types/stripe.entity.types';
|
|
19
19
|
import { StripeWebhookEvent } from '../persistence/entities';
|
|
20
20
|
import { StripeBillingPortalService } from './billingPortal.service';
|
|
21
|
+
|
|
21
22
|
import { StripeCheckoutSessionService } from './checkoutSession.service';
|
|
22
23
|
import { StripePaymentLinkService } from './paymentLink.service';
|
|
23
24
|
import { StripePlanService } from './plan.service';
|
|
24
25
|
import { StripeSubscriptionService } from './subscription.service';
|
|
25
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Webhook idempotency records are looked up and written BY NAME so they
|
|
29
|
+
* resolve against the consuming application's discovered metadata. Passing
|
|
30
|
+
* this package's internal `StripeWebhookEvent` object to the EntityManager
|
|
31
|
+
* queries an entity the app's ORM never discovered, which mikro-orm 7.1.x
|
|
32
|
+
* rejects deep in EntityLoader (`meta.relations` is undefined for
|
|
33
|
+
* undiscovered entities). Applications must discover an entity named
|
|
34
|
+
* `StripeWebhookEvent` — either their own definition (the blueprint app
|
|
35
|
+
* ships one) or this package's, importable from
|
|
36
|
+
* `@forklaunch/implementation-billing-stripe/persistence`.
|
|
37
|
+
*/
|
|
38
|
+
type StripeWebhookEventEntity = InferEntity<typeof StripeWebhookEvent>;
|
|
39
|
+
// mikro-orm 7 narrowed EntityName's type to exclude strings, but the runtime
|
|
40
|
+
// resolves registered entity names as it always has — and name resolution is
|
|
41
|
+
// exactly what we need by default (see above).
|
|
42
|
+
const DEFAULT_STRIPE_WEBHOOK_EVENT_ENTITY =
|
|
43
|
+
'StripeWebhookEvent' as unknown as EntityName<StripeWebhookEventEntity>;
|
|
44
|
+
|
|
26
45
|
export class StripeWebhookService<
|
|
27
46
|
SchemaValidator extends AnySchemaValidator,
|
|
28
47
|
StatusEnum,
|
|
@@ -38,6 +57,7 @@ export class StripeWebhookService<
|
|
|
38
57
|
StripeSubscriptionEntities<PartyEnum> = StripeSubscriptionEntities<PartyEnum>
|
|
39
58
|
> {
|
|
40
59
|
protected readonly partyEnum: PartyEnum;
|
|
60
|
+
protected readonly webhookEventEntity: EntityName<StripeWebhookEventEntity>;
|
|
41
61
|
protected readonly stripeClient: Stripe;
|
|
42
62
|
protected readonly em: EntityManager;
|
|
43
63
|
protected readonly schemaValidator: SchemaValidator;
|
|
@@ -91,8 +111,16 @@ export class StripeWebhookService<
|
|
|
91
111
|
PartyEnum,
|
|
92
112
|
SubscriptionEntities
|
|
93
113
|
>,
|
|
94
|
-
partyEnum: PartyEnum
|
|
114
|
+
partyEnum: PartyEnum,
|
|
115
|
+
/**
|
|
116
|
+
* The entity used for webhook idempotency records. Defaults to
|
|
117
|
+
* resolving the application's discovered entity named
|
|
118
|
+
* 'StripeWebhookEvent'; inject your own entity (mapper-style) to use a
|
|
119
|
+
* different one.
|
|
120
|
+
*/
|
|
121
|
+
webhookEventEntity: EntityName<StripeWebhookEventEntity> = DEFAULT_STRIPE_WEBHOOK_EVENT_ENTITY
|
|
95
122
|
) {
|
|
123
|
+
this.webhookEventEntity = webhookEventEntity;
|
|
96
124
|
this.partyEnum = partyEnum;
|
|
97
125
|
this.stripeClient = stripeClient;
|
|
98
126
|
this.em = em;
|
|
@@ -154,9 +182,12 @@ export class StripeWebhookService<
|
|
|
154
182
|
}
|
|
155
183
|
|
|
156
184
|
if (
|
|
157
|
-
await this.em.findOne(
|
|
158
|
-
|
|
159
|
-
|
|
185
|
+
await this.em.findOne<StripeWebhookEventEntity>(
|
|
186
|
+
this.webhookEventEntity,
|
|
187
|
+
{
|
|
188
|
+
idempotencyKey: event.request?.idempotency_key
|
|
189
|
+
}
|
|
190
|
+
)
|
|
160
191
|
) {
|
|
161
192
|
this.openTelemetryCollector.info(
|
|
162
193
|
'Webhook event already processed',
|
|
@@ -472,12 +503,15 @@ export class StripeWebhookService<
|
|
|
472
503
|
break;
|
|
473
504
|
}
|
|
474
505
|
|
|
475
|
-
|
|
506
|
+
// em.create (not native em.insert): the app's entity generates its id and
|
|
507
|
+
// timestamps via onCreate hooks, which native inserts bypass — a native
|
|
508
|
+
// insert fails NOT NULL on id for sqlBaseProperties-style entities.
|
|
509
|
+
this.em.create<StripeWebhookEventEntity>(this.webhookEventEntity, {
|
|
476
510
|
stripeId: event.id,
|
|
477
511
|
idempotencyKey: event.request?.idempotency_key,
|
|
478
512
|
eventType: event.type,
|
|
479
513
|
eventData: event.data
|
|
480
|
-
});
|
|
514
|
+
} as never);
|
|
481
515
|
await this.em.flush();
|
|
482
516
|
}
|
|
483
517
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// persistence/entities/index.ts
|
|
21
|
+
var entities_exports = {};
|
|
22
|
+
__export(entities_exports, {
|
|
23
|
+
BillingPortal: () => BillingPortal,
|
|
24
|
+
CheckoutSession: () => CheckoutSession,
|
|
25
|
+
PaymentLink: () => PaymentLink,
|
|
26
|
+
Plan: () => Plan,
|
|
27
|
+
StripeWebhookEvent: () => StripeWebhookEvent,
|
|
28
|
+
Subscription: () => Subscription
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(entities_exports);
|
|
31
|
+
var import_persistence = require("@forklaunch/core/persistence");
|
|
32
|
+
var BillingPortal = (0, import_persistence.defineComplianceEntity)({
|
|
33
|
+
name: "BillingPortal",
|
|
34
|
+
properties: {
|
|
35
|
+
id: import_persistence.fp.string().primary().compliance("none"),
|
|
36
|
+
customerId: import_persistence.fp.string().compliance("none"),
|
|
37
|
+
providerFields: import_persistence.fp.json().compliance("pci")
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
var CheckoutSession = (0, import_persistence.defineComplianceEntity)({
|
|
41
|
+
name: "CheckoutSession",
|
|
42
|
+
properties: {
|
|
43
|
+
id: import_persistence.fp.string().primary().compliance("none"),
|
|
44
|
+
customerId: import_persistence.fp.string().compliance("none"),
|
|
45
|
+
paymentMethods: import_persistence.fp.enum().array().compliance("none"),
|
|
46
|
+
currency: import_persistence.fp.enum().compliance("none"),
|
|
47
|
+
status: import_persistence.fp.enum().compliance("none"),
|
|
48
|
+
providerFields: import_persistence.fp.json().compliance("pci")
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
var PaymentLink = (0, import_persistence.defineComplianceEntity)({
|
|
52
|
+
name: "PaymentLink",
|
|
53
|
+
properties: {
|
|
54
|
+
id: import_persistence.fp.string().primary().compliance("none"),
|
|
55
|
+
amount: import_persistence.fp.double().compliance("none"),
|
|
56
|
+
paymentMethods: import_persistence.fp.enum().array().compliance("none"),
|
|
57
|
+
currency: import_persistence.fp.enum().compliance("none"),
|
|
58
|
+
status: import_persistence.fp.enum().compliance("none"),
|
|
59
|
+
providerFields: import_persistence.fp.json().compliance("pci")
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
var Plan = (0, import_persistence.defineComplianceEntity)({
|
|
63
|
+
name: "Plan",
|
|
64
|
+
properties: {
|
|
65
|
+
id: import_persistence.fp.string().primary().compliance("none"),
|
|
66
|
+
name: import_persistence.fp.string().compliance("none"),
|
|
67
|
+
price: import_persistence.fp.double().compliance("none"),
|
|
68
|
+
externalId: import_persistence.fp.string().compliance("none"),
|
|
69
|
+
cadence: import_persistence.fp.enum().compliance("none"),
|
|
70
|
+
currency: import_persistence.fp.enum().compliance("none"),
|
|
71
|
+
billingProvider: import_persistence.fp.enum().nullable().compliance("none"),
|
|
72
|
+
providerFields: import_persistence.fp.json().compliance("pci")
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
var Subscription = (0, import_persistence.defineComplianceEntity)({
|
|
76
|
+
name: "Subscription",
|
|
77
|
+
properties: {
|
|
78
|
+
id: import_persistence.fp.string().primary().compliance("none"),
|
|
79
|
+
partyId: import_persistence.fp.string().compliance("none"),
|
|
80
|
+
externalId: import_persistence.fp.string().compliance("none"),
|
|
81
|
+
partyType: import_persistence.fp.enum().compliance("none"),
|
|
82
|
+
billingProvider: import_persistence.fp.enum().nullable().compliance("none"),
|
|
83
|
+
active: import_persistence.fp.boolean().compliance("none"),
|
|
84
|
+
providerFields: import_persistence.fp.json().compliance("pci")
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
var StripeWebhookEvent = (0, import_persistence.defineComplianceEntity)({
|
|
88
|
+
name: "StripeWebhookEvent",
|
|
89
|
+
properties: {
|
|
90
|
+
id: import_persistence.fp.uuid().primary().compliance("none"),
|
|
91
|
+
createdAt: import_persistence.fp.datetime().onCreate(() => /* @__PURE__ */ new Date()).compliance("none"),
|
|
92
|
+
updatedAt: import_persistence.fp.datetime().onCreate(() => /* @__PURE__ */ new Date()).onUpdate(() => /* @__PURE__ */ new Date()).compliance("none"),
|
|
93
|
+
stripeId: import_persistence.fp.string().compliance("none"),
|
|
94
|
+
idempotencyKey: import_persistence.fp.string().nullable().compliance("none"),
|
|
95
|
+
eventType: import_persistence.fp.string().compliance("none"),
|
|
96
|
+
eventData: import_persistence.fp.json().compliance("pci")
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
100
|
+
0 && (module.exports = {
|
|
101
|
+
BillingPortal,
|
|
102
|
+
CheckoutSession,
|
|
103
|
+
PaymentLink,
|
|
104
|
+
Plan,
|
|
105
|
+
StripeWebhookEvent,
|
|
106
|
+
Subscription
|
|
107
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// persistence/entities/index.ts
|
|
2
|
+
import { defineComplianceEntity, fp } from "@forklaunch/core/persistence";
|
|
3
|
+
var BillingPortal = defineComplianceEntity({
|
|
4
|
+
name: "BillingPortal",
|
|
5
|
+
properties: {
|
|
6
|
+
id: fp.string().primary().compliance("none"),
|
|
7
|
+
customerId: fp.string().compliance("none"),
|
|
8
|
+
providerFields: fp.json().compliance("pci")
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
var CheckoutSession = defineComplianceEntity({
|
|
12
|
+
name: "CheckoutSession",
|
|
13
|
+
properties: {
|
|
14
|
+
id: fp.string().primary().compliance("none"),
|
|
15
|
+
customerId: fp.string().compliance("none"),
|
|
16
|
+
paymentMethods: fp.enum().array().compliance("none"),
|
|
17
|
+
currency: fp.enum().compliance("none"),
|
|
18
|
+
status: fp.enum().compliance("none"),
|
|
19
|
+
providerFields: fp.json().compliance("pci")
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
var PaymentLink = defineComplianceEntity({
|
|
23
|
+
name: "PaymentLink",
|
|
24
|
+
properties: {
|
|
25
|
+
id: fp.string().primary().compliance("none"),
|
|
26
|
+
amount: fp.double().compliance("none"),
|
|
27
|
+
paymentMethods: fp.enum().array().compliance("none"),
|
|
28
|
+
currency: fp.enum().compliance("none"),
|
|
29
|
+
status: fp.enum().compliance("none"),
|
|
30
|
+
providerFields: fp.json().compliance("pci")
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
var Plan = defineComplianceEntity({
|
|
34
|
+
name: "Plan",
|
|
35
|
+
properties: {
|
|
36
|
+
id: fp.string().primary().compliance("none"),
|
|
37
|
+
name: fp.string().compliance("none"),
|
|
38
|
+
price: fp.double().compliance("none"),
|
|
39
|
+
externalId: fp.string().compliance("none"),
|
|
40
|
+
cadence: fp.enum().compliance("none"),
|
|
41
|
+
currency: fp.enum().compliance("none"),
|
|
42
|
+
billingProvider: fp.enum().nullable().compliance("none"),
|
|
43
|
+
providerFields: fp.json().compliance("pci")
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
var Subscription = defineComplianceEntity({
|
|
47
|
+
name: "Subscription",
|
|
48
|
+
properties: {
|
|
49
|
+
id: fp.string().primary().compliance("none"),
|
|
50
|
+
partyId: fp.string().compliance("none"),
|
|
51
|
+
externalId: fp.string().compliance("none"),
|
|
52
|
+
partyType: fp.enum().compliance("none"),
|
|
53
|
+
billingProvider: fp.enum().nullable().compliance("none"),
|
|
54
|
+
active: fp.boolean().compliance("none"),
|
|
55
|
+
providerFields: fp.json().compliance("pci")
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
var StripeWebhookEvent = defineComplianceEntity({
|
|
59
|
+
name: "StripeWebhookEvent",
|
|
60
|
+
properties: {
|
|
61
|
+
id: fp.uuid().primary().compliance("none"),
|
|
62
|
+
createdAt: fp.datetime().onCreate(() => /* @__PURE__ */ new Date()).compliance("none"),
|
|
63
|
+
updatedAt: fp.datetime().onCreate(() => /* @__PURE__ */ new Date()).onUpdate(() => /* @__PURE__ */ new Date()).compliance("none"),
|
|
64
|
+
stripeId: fp.string().compliance("none"),
|
|
65
|
+
idempotencyKey: fp.string().nullable().compliance("none"),
|
|
66
|
+
eventType: fp.string().compliance("none"),
|
|
67
|
+
eventData: fp.json().compliance("pci")
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
export {
|
|
71
|
+
BillingPortal,
|
|
72
|
+
CheckoutSession,
|
|
73
|
+
PaymentLink,
|
|
74
|
+
Plan,
|
|
75
|
+
StripeWebhookEvent,
|
|
76
|
+
Subscription
|
|
77
|
+
};
|
package/lib/services/index.js
CHANGED
|
@@ -626,79 +626,11 @@ var BillingProviderEnum = {
|
|
|
626
626
|
STRIPE: "stripe"
|
|
627
627
|
};
|
|
628
628
|
|
|
629
|
-
// persistence/entities/index.ts
|
|
630
|
-
var import_persistence = require("@forklaunch/core/persistence");
|
|
631
|
-
var BillingPortal = (0, import_persistence.defineComplianceEntity)({
|
|
632
|
-
name: "BillingPortal",
|
|
633
|
-
properties: {
|
|
634
|
-
id: import_persistence.fp.string().primary().compliance("none"),
|
|
635
|
-
customerId: import_persistence.fp.string().compliance("none"),
|
|
636
|
-
providerFields: import_persistence.fp.json().compliance("pci")
|
|
637
|
-
}
|
|
638
|
-
});
|
|
639
|
-
var CheckoutSession = (0, import_persistence.defineComplianceEntity)({
|
|
640
|
-
name: "CheckoutSession",
|
|
641
|
-
properties: {
|
|
642
|
-
id: import_persistence.fp.string().primary().compliance("none"),
|
|
643
|
-
customerId: import_persistence.fp.string().compliance("none"),
|
|
644
|
-
paymentMethods: import_persistence.fp.enum().array().compliance("none"),
|
|
645
|
-
currency: import_persistence.fp.enum().compliance("none"),
|
|
646
|
-
status: import_persistence.fp.enum().compliance("none"),
|
|
647
|
-
providerFields: import_persistence.fp.json().compliance("pci")
|
|
648
|
-
}
|
|
649
|
-
});
|
|
650
|
-
var PaymentLink = (0, import_persistence.defineComplianceEntity)({
|
|
651
|
-
name: "PaymentLink",
|
|
652
|
-
properties: {
|
|
653
|
-
id: import_persistence.fp.string().primary().compliance("none"),
|
|
654
|
-
amount: import_persistence.fp.double().compliance("none"),
|
|
655
|
-
paymentMethods: import_persistence.fp.enum().array().compliance("none"),
|
|
656
|
-
currency: import_persistence.fp.enum().compliance("none"),
|
|
657
|
-
status: import_persistence.fp.enum().compliance("none"),
|
|
658
|
-
providerFields: import_persistence.fp.json().compliance("pci")
|
|
659
|
-
}
|
|
660
|
-
});
|
|
661
|
-
var Plan = (0, import_persistence.defineComplianceEntity)({
|
|
662
|
-
name: "Plan",
|
|
663
|
-
properties: {
|
|
664
|
-
id: import_persistence.fp.string().primary().compliance("none"),
|
|
665
|
-
name: import_persistence.fp.string().compliance("none"),
|
|
666
|
-
price: import_persistence.fp.double().compliance("none"),
|
|
667
|
-
externalId: import_persistence.fp.string().compliance("none"),
|
|
668
|
-
cadence: import_persistence.fp.enum().compliance("none"),
|
|
669
|
-
currency: import_persistence.fp.enum().compliance("none"),
|
|
670
|
-
billingProvider: import_persistence.fp.enum().nullable().compliance("none"),
|
|
671
|
-
providerFields: import_persistence.fp.json().compliance("pci")
|
|
672
|
-
}
|
|
673
|
-
});
|
|
674
|
-
var Subscription = (0, import_persistence.defineComplianceEntity)({
|
|
675
|
-
name: "Subscription",
|
|
676
|
-
properties: {
|
|
677
|
-
id: import_persistence.fp.string().primary().compliance("none"),
|
|
678
|
-
partyId: import_persistence.fp.string().compliance("none"),
|
|
679
|
-
externalId: import_persistence.fp.string().compliance("none"),
|
|
680
|
-
partyType: import_persistence.fp.enum().compliance("none"),
|
|
681
|
-
billingProvider: import_persistence.fp.enum().nullable().compliance("none"),
|
|
682
|
-
active: import_persistence.fp.boolean().compliance("none"),
|
|
683
|
-
providerFields: import_persistence.fp.json().compliance("pci")
|
|
684
|
-
}
|
|
685
|
-
});
|
|
686
|
-
var StripeWebhookEvent = (0, import_persistence.defineComplianceEntity)({
|
|
687
|
-
name: "StripeWebhookEvent",
|
|
688
|
-
properties: {
|
|
689
|
-
id: import_persistence.fp.uuid().primary().compliance("none"),
|
|
690
|
-
createdAt: import_persistence.fp.datetime().onCreate(() => /* @__PURE__ */ new Date()).compliance("none"),
|
|
691
|
-
updatedAt: import_persistence.fp.datetime().onCreate(() => /* @__PURE__ */ new Date()).onUpdate(() => /* @__PURE__ */ new Date()).compliance("none"),
|
|
692
|
-
stripeId: import_persistence.fp.string().compliance("none"),
|
|
693
|
-
idempotencyKey: import_persistence.fp.string().nullable().compliance("none"),
|
|
694
|
-
eventType: import_persistence.fp.string().compliance("none"),
|
|
695
|
-
eventData: import_persistence.fp.json().compliance("pci")
|
|
696
|
-
}
|
|
697
|
-
});
|
|
698
|
-
|
|
699
629
|
// services/webhook.service.ts
|
|
630
|
+
var DEFAULT_STRIPE_WEBHOOK_EVENT_ENTITY = "StripeWebhookEvent";
|
|
700
631
|
var StripeWebhookService = class {
|
|
701
632
|
partyEnum;
|
|
633
|
+
webhookEventEntity;
|
|
702
634
|
stripeClient;
|
|
703
635
|
em;
|
|
704
636
|
schemaValidator;
|
|
@@ -708,7 +640,8 @@ var StripeWebhookService = class {
|
|
|
708
640
|
paymentLinkService;
|
|
709
641
|
planService;
|
|
710
642
|
subscriptionService;
|
|
711
|
-
constructor(stripeClient, em, schemaValidator, openTelemetryCollector, billingPortalService, checkoutSessionService, paymentLinkService, planService, subscriptionService, partyEnum) {
|
|
643
|
+
constructor(stripeClient, em, schemaValidator, openTelemetryCollector, billingPortalService, checkoutSessionService, paymentLinkService, planService, subscriptionService, partyEnum, webhookEventEntity = DEFAULT_STRIPE_WEBHOOK_EVENT_ENTITY) {
|
|
644
|
+
this.webhookEventEntity = webhookEventEntity;
|
|
712
645
|
this.partyEnum = partyEnum;
|
|
713
646
|
this.stripeClient = stripeClient;
|
|
714
647
|
this.em = em;
|
|
@@ -754,9 +687,12 @@ var StripeWebhookService = class {
|
|
|
754
687
|
if (this.openTelemetryCollector) {
|
|
755
688
|
this.openTelemetryCollector.info("Handling webhook event", event);
|
|
756
689
|
}
|
|
757
|
-
if (await this.em.findOne(
|
|
758
|
-
|
|
759
|
-
|
|
690
|
+
if (await this.em.findOne(
|
|
691
|
+
this.webhookEventEntity,
|
|
692
|
+
{
|
|
693
|
+
idempotencyKey: event.request?.idempotency_key
|
|
694
|
+
}
|
|
695
|
+
)) {
|
|
760
696
|
this.openTelemetryCollector.info(
|
|
761
697
|
"Webhook event already processed",
|
|
762
698
|
event
|
|
@@ -999,7 +935,7 @@ var StripeWebhookService = class {
|
|
|
999
935
|
);
|
|
1000
936
|
break;
|
|
1001
937
|
}
|
|
1002
|
-
|
|
938
|
+
this.em.create(this.webhookEventEntity, {
|
|
1003
939
|
stripeId: event.id,
|
|
1004
940
|
idempotencyKey: event.request?.idempotency_key,
|
|
1005
941
|
eventType: event.type,
|
package/lib/services/index.mjs
CHANGED
|
@@ -594,79 +594,11 @@ var BillingProviderEnum = {
|
|
|
594
594
|
STRIPE: "stripe"
|
|
595
595
|
};
|
|
596
596
|
|
|
597
|
-
// persistence/entities/index.ts
|
|
598
|
-
import { defineComplianceEntity, fp } from "@forklaunch/core/persistence";
|
|
599
|
-
var BillingPortal = defineComplianceEntity({
|
|
600
|
-
name: "BillingPortal",
|
|
601
|
-
properties: {
|
|
602
|
-
id: fp.string().primary().compliance("none"),
|
|
603
|
-
customerId: fp.string().compliance("none"),
|
|
604
|
-
providerFields: fp.json().compliance("pci")
|
|
605
|
-
}
|
|
606
|
-
});
|
|
607
|
-
var CheckoutSession = defineComplianceEntity({
|
|
608
|
-
name: "CheckoutSession",
|
|
609
|
-
properties: {
|
|
610
|
-
id: fp.string().primary().compliance("none"),
|
|
611
|
-
customerId: fp.string().compliance("none"),
|
|
612
|
-
paymentMethods: fp.enum().array().compliance("none"),
|
|
613
|
-
currency: fp.enum().compliance("none"),
|
|
614
|
-
status: fp.enum().compliance("none"),
|
|
615
|
-
providerFields: fp.json().compliance("pci")
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
var PaymentLink = defineComplianceEntity({
|
|
619
|
-
name: "PaymentLink",
|
|
620
|
-
properties: {
|
|
621
|
-
id: fp.string().primary().compliance("none"),
|
|
622
|
-
amount: fp.double().compliance("none"),
|
|
623
|
-
paymentMethods: fp.enum().array().compliance("none"),
|
|
624
|
-
currency: fp.enum().compliance("none"),
|
|
625
|
-
status: fp.enum().compliance("none"),
|
|
626
|
-
providerFields: fp.json().compliance("pci")
|
|
627
|
-
}
|
|
628
|
-
});
|
|
629
|
-
var Plan = defineComplianceEntity({
|
|
630
|
-
name: "Plan",
|
|
631
|
-
properties: {
|
|
632
|
-
id: fp.string().primary().compliance("none"),
|
|
633
|
-
name: fp.string().compliance("none"),
|
|
634
|
-
price: fp.double().compliance("none"),
|
|
635
|
-
externalId: fp.string().compliance("none"),
|
|
636
|
-
cadence: fp.enum().compliance("none"),
|
|
637
|
-
currency: fp.enum().compliance("none"),
|
|
638
|
-
billingProvider: fp.enum().nullable().compliance("none"),
|
|
639
|
-
providerFields: fp.json().compliance("pci")
|
|
640
|
-
}
|
|
641
|
-
});
|
|
642
|
-
var Subscription = defineComplianceEntity({
|
|
643
|
-
name: "Subscription",
|
|
644
|
-
properties: {
|
|
645
|
-
id: fp.string().primary().compliance("none"),
|
|
646
|
-
partyId: fp.string().compliance("none"),
|
|
647
|
-
externalId: fp.string().compliance("none"),
|
|
648
|
-
partyType: fp.enum().compliance("none"),
|
|
649
|
-
billingProvider: fp.enum().nullable().compliance("none"),
|
|
650
|
-
active: fp.boolean().compliance("none"),
|
|
651
|
-
providerFields: fp.json().compliance("pci")
|
|
652
|
-
}
|
|
653
|
-
});
|
|
654
|
-
var StripeWebhookEvent = defineComplianceEntity({
|
|
655
|
-
name: "StripeWebhookEvent",
|
|
656
|
-
properties: {
|
|
657
|
-
id: fp.uuid().primary().compliance("none"),
|
|
658
|
-
createdAt: fp.datetime().onCreate(() => /* @__PURE__ */ new Date()).compliance("none"),
|
|
659
|
-
updatedAt: fp.datetime().onCreate(() => /* @__PURE__ */ new Date()).onUpdate(() => /* @__PURE__ */ new Date()).compliance("none"),
|
|
660
|
-
stripeId: fp.string().compliance("none"),
|
|
661
|
-
idempotencyKey: fp.string().nullable().compliance("none"),
|
|
662
|
-
eventType: fp.string().compliance("none"),
|
|
663
|
-
eventData: fp.json().compliance("pci")
|
|
664
|
-
}
|
|
665
|
-
});
|
|
666
|
-
|
|
667
597
|
// services/webhook.service.ts
|
|
598
|
+
var DEFAULT_STRIPE_WEBHOOK_EVENT_ENTITY = "StripeWebhookEvent";
|
|
668
599
|
var StripeWebhookService = class {
|
|
669
600
|
partyEnum;
|
|
601
|
+
webhookEventEntity;
|
|
670
602
|
stripeClient;
|
|
671
603
|
em;
|
|
672
604
|
schemaValidator;
|
|
@@ -676,7 +608,8 @@ var StripeWebhookService = class {
|
|
|
676
608
|
paymentLinkService;
|
|
677
609
|
planService;
|
|
678
610
|
subscriptionService;
|
|
679
|
-
constructor(stripeClient, em, schemaValidator, openTelemetryCollector, billingPortalService, checkoutSessionService, paymentLinkService, planService, subscriptionService, partyEnum) {
|
|
611
|
+
constructor(stripeClient, em, schemaValidator, openTelemetryCollector, billingPortalService, checkoutSessionService, paymentLinkService, planService, subscriptionService, partyEnum, webhookEventEntity = DEFAULT_STRIPE_WEBHOOK_EVENT_ENTITY) {
|
|
612
|
+
this.webhookEventEntity = webhookEventEntity;
|
|
680
613
|
this.partyEnum = partyEnum;
|
|
681
614
|
this.stripeClient = stripeClient;
|
|
682
615
|
this.em = em;
|
|
@@ -722,9 +655,12 @@ var StripeWebhookService = class {
|
|
|
722
655
|
if (this.openTelemetryCollector) {
|
|
723
656
|
this.openTelemetryCollector.info("Handling webhook event", event);
|
|
724
657
|
}
|
|
725
|
-
if (await this.em.findOne(
|
|
726
|
-
|
|
727
|
-
|
|
658
|
+
if (await this.em.findOne(
|
|
659
|
+
this.webhookEventEntity,
|
|
660
|
+
{
|
|
661
|
+
idempotencyKey: event.request?.idempotency_key
|
|
662
|
+
}
|
|
663
|
+
)) {
|
|
728
664
|
this.openTelemetryCollector.info(
|
|
729
665
|
"Webhook event already processed",
|
|
730
666
|
event
|
|
@@ -967,7 +903,7 @@ var StripeWebhookService = class {
|
|
|
967
903
|
);
|
|
968
904
|
break;
|
|
969
905
|
}
|
|
970
|
-
|
|
906
|
+
this.em.create(this.webhookEventEntity, {
|
|
971
907
|
stripeId: event.id,
|
|
972
908
|
idempotencyKey: event.request?.idempotency_key,
|
|
973
909
|
eventType: event.type,
|
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
import { MetricsDefinition, OpenTelemetryCollector } from '@forklaunch/core/http';
|
|
2
2
|
import { AnySchemaValidator } from '@forklaunch/validator';
|
|
3
|
-
import { EntityManager } from '@mikro-orm/core';
|
|
3
|
+
import { EntityManager, EntityName, InferEntity } from '@mikro-orm/core';
|
|
4
4
|
import Stripe from 'stripe';
|
|
5
5
|
import { StripeBillingPortalEntities, StripeCheckoutSessionEntities, StripePaymentLinkEntities, StripePlanEntities, StripeSubscriptionEntities } from '../domain/types/stripe.entity.types';
|
|
6
|
+
import { StripeWebhookEvent } from '../persistence/entities';
|
|
6
7
|
import { StripeBillingPortalService } from './billingPortal.service';
|
|
7
8
|
import { StripeCheckoutSessionService } from './checkoutSession.service';
|
|
8
9
|
import { StripePaymentLinkService } from './paymentLink.service';
|
|
9
10
|
import { StripePlanService } from './plan.service';
|
|
10
11
|
import { StripeSubscriptionService } from './subscription.service';
|
|
12
|
+
/**
|
|
13
|
+
* Webhook idempotency records are looked up and written BY NAME so they
|
|
14
|
+
* resolve against the consuming application's discovered metadata. Passing
|
|
15
|
+
* this package's internal `StripeWebhookEvent` object to the EntityManager
|
|
16
|
+
* queries an entity the app's ORM never discovered, which mikro-orm 7.1.x
|
|
17
|
+
* rejects deep in EntityLoader (`meta.relations` is undefined for
|
|
18
|
+
* undiscovered entities). Applications must discover an entity named
|
|
19
|
+
* `StripeWebhookEvent` — either their own definition (the blueprint app
|
|
20
|
+
* ships one) or this package's, importable from
|
|
21
|
+
* `@forklaunch/implementation-billing-stripe/persistence`.
|
|
22
|
+
*/
|
|
23
|
+
type StripeWebhookEventEntity = InferEntity<typeof StripeWebhookEvent>;
|
|
11
24
|
export declare class StripeWebhookService<SchemaValidator extends AnySchemaValidator, StatusEnum, PartyEnum, BillingPortalEntities extends StripeBillingPortalEntities = StripeBillingPortalEntities, CheckoutSessionEntities extends StripeCheckoutSessionEntities<StatusEnum> = StripeCheckoutSessionEntities<StatusEnum>, PaymentLinkEntities extends StripePaymentLinkEntities<StatusEnum> = StripePaymentLinkEntities<StatusEnum>, PlanEntities extends StripePlanEntities = StripePlanEntities, SubscriptionEntities extends StripeSubscriptionEntities<PartyEnum> = StripeSubscriptionEntities<PartyEnum>> {
|
|
12
25
|
protected readonly partyEnum: PartyEnum;
|
|
26
|
+
protected readonly webhookEventEntity: EntityName<StripeWebhookEventEntity>;
|
|
13
27
|
protected readonly stripeClient: Stripe;
|
|
14
28
|
protected readonly em: EntityManager;
|
|
15
29
|
protected readonly schemaValidator: SchemaValidator;
|
|
@@ -19,7 +33,14 @@ export declare class StripeWebhookService<SchemaValidator extends AnySchemaValid
|
|
|
19
33
|
protected readonly paymentLinkService: StripePaymentLinkService<SchemaValidator, StatusEnum, PaymentLinkEntities>;
|
|
20
34
|
protected readonly planService: StripePlanService<SchemaValidator, PlanEntities>;
|
|
21
35
|
protected readonly subscriptionService: StripeSubscriptionService<SchemaValidator, PartyEnum, SubscriptionEntities>;
|
|
22
|
-
constructor(stripeClient: Stripe, 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>, partyEnum: PartyEnum
|
|
36
|
+
constructor(stripeClient: Stripe, 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>, partyEnum: PartyEnum,
|
|
37
|
+
/**
|
|
38
|
+
* The entity used for webhook idempotency records. Defaults to
|
|
39
|
+
* resolving the application's discovered entity named
|
|
40
|
+
* 'StripeWebhookEvent'; inject your own entity (mapper-style) to use a
|
|
41
|
+
* different one.
|
|
42
|
+
*/
|
|
43
|
+
webhookEventEntity?: EntityName<StripeWebhookEventEntity>);
|
|
23
44
|
/**
|
|
24
45
|
* Resolve the party type for a subscription event.
|
|
25
46
|
* Stripe subscriptions are customer-scoped — customers map to users
|
|
@@ -36,4 +57,5 @@ export declare class StripeWebhookService<SchemaValidator extends AnySchemaValid
|
|
|
36
57
|
private extractFeaturesFromProduct;
|
|
37
58
|
handleWebhookEvent(event: Stripe.Event): Promise<void>;
|
|
38
59
|
}
|
|
60
|
+
export {};
|
|
39
61
|
//# sourceMappingURL=webhook.service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook.service.d.ts","sourceRoot":"","sources":["../../services/webhook.service.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"webhook.service.d.ts","sourceRoot":"","sources":["../../services/webhook.service.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forklaunch/implementation-billing-stripe",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.32",
|
|
4
4
|
"description": "Stripe implementation for forklaunch billing",
|
|
5
5
|
"homepage": "https://github.com/forklaunch/forklaunch-js#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -48,6 +48,15 @@
|
|
|
48
48
|
},
|
|
49
49
|
"./types/*": {
|
|
50
50
|
"types": "./lib/domain/types/*.d.ts"
|
|
51
|
+
},
|
|
52
|
+
"./persistence": {
|
|
53
|
+
"types": "./lib/persistence/entities/index.d.ts",
|
|
54
|
+
"import": "./lib/persistence/entities/index.mjs",
|
|
55
|
+
"require": "./lib/persistence/entities/index.js",
|
|
56
|
+
"default": "./lib/persistence/entities/index.js"
|
|
57
|
+
},
|
|
58
|
+
"./persistence/*": {
|
|
59
|
+
"types": "./lib/persistence/entities/*.d.ts"
|
|
51
60
|
}
|
|
52
61
|
},
|
|
53
62
|
"files": [
|
|
@@ -70,13 +79,15 @@
|
|
|
70
79
|
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
71
80
|
"depcheck": "^1.4.7",
|
|
72
81
|
"prettier": "^3.9.6",
|
|
73
|
-
"typedoc": "^0.28.20"
|
|
82
|
+
"typedoc": "^0.28.20",
|
|
83
|
+
"@mikro-orm/sqlite": "7.1.11",
|
|
84
|
+
"uuid": "^14.0.1"
|
|
74
85
|
},
|
|
75
86
|
"publishConfig": {
|
|
76
87
|
"access": "public"
|
|
77
88
|
},
|
|
78
89
|
"scripts": {
|
|
79
|
-
"build": "tsgo --noEmit && tsup domain/schemas/index.ts services/index.ts domain/enum/index.ts domain/types/index.ts --format cjs,esm --no-splitting --tsconfig tsconfig.json --out-dir lib --clean && tsgo -p tsconfig.build.json --emitDeclarationOnly && if [ -f eject-package.bash ]; then pnpm package:eject; fi",
|
|
90
|
+
"build": "tsgo --noEmit && tsup persistence/entities/index.ts domain/schemas/index.ts services/index.ts domain/enum/index.ts domain/types/index.ts --format cjs,esm --no-splitting --tsconfig tsconfig.json --out-dir lib --clean && tsgo -p tsconfig.build.json --emitDeclarationOnly && if [ -f eject-package.bash ]; then pnpm package:eject; fi",
|
|
80
91
|
"clean": "rm -rf lib pnpm.lock.yaml node_modules",
|
|
81
92
|
"docs": "typedoc --out docs *",
|
|
82
93
|
"format": "prettier --ignore-path=.prettierignore --config .prettierrc '**/*.{ts,tsx,json}' --write",
|