@skillsmith/core 0.6.3 → 0.7.2
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/CHANGELOG.md +22 -1
- package/README.md +18 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/api/client.d.ts.map +1 -1
- package/dist/src/api/client.events.d.ts.map +1 -1
- package/dist/src/api/client.events.js +10 -1
- package/dist/src/api/client.events.js.map +1 -1
- package/dist/src/api/client.health.d.ts.map +1 -1
- package/dist/src/api/client.health.js +7 -1
- package/dist/src/api/client.health.js.map +1 -1
- package/dist/src/api/client.js +11 -1
- package/dist/src/api/client.js.map +1 -1
- package/dist/src/api/utils.d.ts +14 -3
- package/dist/src/api/utils.d.ts.map +1 -1
- package/dist/src/api/utils.js +14 -4
- package/dist/src/api/utils.js.map +1 -1
- package/dist/src/exports/services.d.ts +0 -1
- package/dist/src/exports/services.d.ts.map +1 -1
- package/dist/src/exports/services.js +14 -3
- package/dist/src/exports/services.js.map +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/types.d.ts +12 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/tests/api/client.auth.test.js +50 -0
- package/dist/tests/api/client.auth.test.js.map +1 -1
- package/dist/tests/api/utils.test.js +7 -3
- package/dist/tests/api/utils.test.js.map +1 -1
- package/dist/tests/{billing/stripe-validators.test.d.ts → security/sanitization-stripe-ids.test.d.ts} +1 -1
- package/dist/tests/security/sanitization-stripe-ids.test.d.ts.map +1 -0
- package/dist/tests/{billing/stripe-validators.test.js → security/sanitization-stripe-ids.test.js} +1 -1
- package/dist/tests/security/sanitization-stripe-ids.test.js.map +1 -0
- package/dist/tests/shared.test.js.map +1 -1
- package/package.json +2 -8
- package/dist/src/billing/BillingService.d.ts +0 -101
- package/dist/src/billing/BillingService.d.ts.map +0 -1
- package/dist/src/billing/BillingService.helpers.d.ts +0 -15
- package/dist/src/billing/BillingService.helpers.d.ts.map +0 -1
- package/dist/src/billing/BillingService.helpers.js +0 -45
- package/dist/src/billing/BillingService.helpers.js.map +0 -1
- package/dist/src/billing/BillingService.js +0 -263
- package/dist/src/billing/BillingService.js.map +0 -1
- package/dist/src/billing/BillingService.types.d.ts +0 -52
- package/dist/src/billing/BillingService.types.d.ts.map +0 -1
- package/dist/src/billing/BillingService.types.js +0 -6
- package/dist/src/billing/BillingService.types.js.map +0 -1
- package/dist/src/billing/GDPRComplianceService.d.ts +0 -81
- package/dist/src/billing/GDPRComplianceService.d.ts.map +0 -1
- package/dist/src/billing/GDPRComplianceService.js +0 -361
- package/dist/src/billing/GDPRComplianceService.js.map +0 -1
- package/dist/src/billing/StripeClient.d.ts +0 -119
- package/dist/src/billing/StripeClient.d.ts.map +0 -1
- package/dist/src/billing/StripeClient.js +0 -405
- package/dist/src/billing/StripeClient.js.map +0 -1
- package/dist/src/billing/StripeReconciliationJob.d.ts +0 -50
- package/dist/src/billing/StripeReconciliationJob.d.ts.map +0 -1
- package/dist/src/billing/StripeReconciliationJob.js +0 -365
- package/dist/src/billing/StripeReconciliationJob.js.map +0 -1
- package/dist/src/billing/StripeWebhookHandler.d.ts +0 -49
- package/dist/src/billing/StripeWebhookHandler.d.ts.map +0 -1
- package/dist/src/billing/StripeWebhookHandler.js +0 -162
- package/dist/src/billing/StripeWebhookHandler.js.map +0 -1
- package/dist/src/billing/gdpr-types.d.ts +0 -103
- package/dist/src/billing/gdpr-types.d.ts.map +0 -1
- package/dist/src/billing/gdpr-types.js +0 -7
- package/dist/src/billing/gdpr-types.js.map +0 -1
- package/dist/src/billing/index.d.ts +0 -18
- package/dist/src/billing/index.d.ts.map +0 -1
- package/dist/src/billing/index.js +0 -19
- package/dist/src/billing/index.js.map +0 -1
- package/dist/src/billing/reconciliation-helpers.d.ts +0 -16
- package/dist/src/billing/reconciliation-helpers.d.ts.map +0 -1
- package/dist/src/billing/reconciliation-helpers.js +0 -53
- package/dist/src/billing/reconciliation-helpers.js.map +0 -1
- package/dist/src/billing/reconciliation-types.d.ts +0 -71
- package/dist/src/billing/reconciliation-types.d.ts.map +0 -1
- package/dist/src/billing/reconciliation-types.js +0 -7
- package/dist/src/billing/reconciliation-types.js.map +0 -1
- package/dist/src/billing/stripe-client-types.d.ts +0 -45
- package/dist/src/billing/stripe-client-types.d.ts.map +0 -1
- package/dist/src/billing/stripe-client-types.js +0 -7
- package/dist/src/billing/stripe-client-types.js.map +0 -1
- package/dist/src/billing/stripe-helpers.d.ts +0 -17
- package/dist/src/billing/stripe-helpers.d.ts.map +0 -1
- package/dist/src/billing/stripe-helpers.js +0 -50
- package/dist/src/billing/stripe-helpers.js.map +0 -1
- package/dist/src/billing/types.d.ts +0 -266
- package/dist/src/billing/types.d.ts.map +0 -1
- package/dist/src/billing/types.js +0 -23
- package/dist/src/billing/types.js.map +0 -1
- package/dist/src/billing/webhook-handlers.d.ts +0 -56
- package/dist/src/billing/webhook-handlers.d.ts.map +0 -1
- package/dist/src/billing/webhook-handlers.js +0 -303
- package/dist/src/billing/webhook-handlers.js.map +0 -1
- package/dist/src/billing/webhook-types.d.ts +0 -42
- package/dist/src/billing/webhook-types.d.ts.map +0 -1
- package/dist/src/billing/webhook-types.js +0 -7
- package/dist/src/billing/webhook-types.js.map +0 -1
- package/dist/tests/billing/BillingService.test.d.ts +0 -7
- package/dist/tests/billing/BillingService.test.d.ts.map +0 -1
- package/dist/tests/billing/BillingService.test.js +0 -168
- package/dist/tests/billing/BillingService.test.js.map +0 -1
- package/dist/tests/billing/GDPRCompliance.test.d.ts +0 -7
- package/dist/tests/billing/GDPRCompliance.test.d.ts.map +0 -1
- package/dist/tests/billing/GDPRCompliance.test.js +0 -380
- package/dist/tests/billing/GDPRCompliance.test.js.map +0 -1
- package/dist/tests/billing/StripeClient.test.d.ts +0 -18
- package/dist/tests/billing/StripeClient.test.d.ts.map +0 -1
- package/dist/tests/billing/StripeClient.test.js +0 -566
- package/dist/tests/billing/StripeClient.test.js.map +0 -1
- package/dist/tests/billing/StripeReconciliation.test.d.ts +0 -7
- package/dist/tests/billing/StripeReconciliation.test.d.ts.map +0 -1
- package/dist/tests/billing/StripeReconciliation.test.js +0 -266
- package/dist/tests/billing/StripeReconciliation.test.js.map +0 -1
- package/dist/tests/billing/StripeWebhookHandler.test.d.ts +0 -16
- package/dist/tests/billing/StripeWebhookHandler.test.d.ts.map +0 -1
- package/dist/tests/billing/StripeWebhookHandler.test.js +0 -240
- package/dist/tests/billing/StripeWebhookHandler.test.js.map +0 -1
- package/dist/tests/billing/stripe-helpers.test.d.ts +0 -7
- package/dist/tests/billing/stripe-helpers.test.d.ts.map +0 -1
- package/dist/tests/billing/stripe-helpers.test.js +0 -91
- package/dist/tests/billing/stripe-helpers.test.js.map +0 -1
- package/dist/tests/billing/stripe-validators.test.d.ts.map +0 -1
- package/dist/tests/billing/stripe-validators.test.js.map +0 -1
- package/dist/tests/billing/webhook-handlers.test.d.ts +0 -16
- package/dist/tests/billing/webhook-handlers.test.d.ts.map +0 -1
- package/dist/tests/billing/webhook-handlers.test.js +0 -519
- package/dist/tests/billing/webhook-handlers.test.js.map +0 -1
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SMI-1062: Billing Types and Interfaces
|
|
3
|
-
*
|
|
4
|
-
* Type definitions for Stripe billing integration including:
|
|
5
|
-
* - Subscription management
|
|
6
|
-
* - Customer management
|
|
7
|
-
* - Invoice handling
|
|
8
|
-
* - Webhook events
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
|
-
* Available license tiers
|
|
12
|
-
* - community: Free tier (1,000 API calls/month)
|
|
13
|
-
* - individual: Solo developers ($9.99/mo, 10,000 API calls/month)
|
|
14
|
-
* - team: Teams ($25/user/mo, 100,000 API calls/month)
|
|
15
|
-
* - enterprise: Full enterprise ($55/user/mo, unlimited)
|
|
16
|
-
*/
|
|
17
|
-
export type LicenseTier = 'community' | 'individual' | 'team' | 'enterprise';
|
|
18
|
-
/**
|
|
19
|
-
* Stripe Customer ID (cus_xxx)
|
|
20
|
-
*/
|
|
21
|
-
export type StripeCustomerId = string & {
|
|
22
|
-
readonly __brand: 'StripeCustomerId';
|
|
23
|
-
};
|
|
24
|
-
/**
|
|
25
|
-
* Stripe Subscription ID (sub_xxx)
|
|
26
|
-
*/
|
|
27
|
-
export type StripeSubscriptionId = string & {
|
|
28
|
-
readonly __brand: 'StripeSubscriptionId';
|
|
29
|
-
};
|
|
30
|
-
/**
|
|
31
|
-
* Stripe Price ID (price_xxx)
|
|
32
|
-
*/
|
|
33
|
-
export type StripePriceId = string & {
|
|
34
|
-
readonly __brand: 'StripePriceId';
|
|
35
|
-
};
|
|
36
|
-
/**
|
|
37
|
-
* Stripe Invoice ID (in_xxx)
|
|
38
|
-
*/
|
|
39
|
-
export type StripeInvoiceId = string & {
|
|
40
|
-
readonly __brand: 'StripeInvoiceId';
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* Stripe Checkout Session ID (cs_xxx)
|
|
44
|
-
*/
|
|
45
|
-
export type StripeCheckoutSessionId = string & {
|
|
46
|
-
readonly __brand: 'StripeCheckoutSessionId';
|
|
47
|
-
};
|
|
48
|
-
/**
|
|
49
|
-
* Stripe Event ID (evt_xxx)
|
|
50
|
-
*/
|
|
51
|
-
export type StripeEventId = string & {
|
|
52
|
-
readonly __brand: 'StripeEventId';
|
|
53
|
-
};
|
|
54
|
-
/**
|
|
55
|
-
* Subscription status values
|
|
56
|
-
*/
|
|
57
|
-
export type SubscriptionStatus = 'active' | 'past_due' | 'canceled' | 'trialing' | 'paused' | 'incomplete' | 'incomplete_expired' | 'unpaid';
|
|
58
|
-
/**
|
|
59
|
-
* Customer record in our database
|
|
60
|
-
*/
|
|
61
|
-
export interface Customer {
|
|
62
|
-
id: string;
|
|
63
|
-
customerId: string;
|
|
64
|
-
email: string;
|
|
65
|
-
stripeCustomerId: StripeCustomerId | null;
|
|
66
|
-
tier: LicenseTier;
|
|
67
|
-
status: SubscriptionStatus;
|
|
68
|
-
createdAt: Date;
|
|
69
|
-
updatedAt: Date;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Create customer request
|
|
73
|
-
*/
|
|
74
|
-
export interface CreateCustomerRequest {
|
|
75
|
-
email: string;
|
|
76
|
-
name?: string;
|
|
77
|
-
metadata?: Record<string, string>;
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Create customer response
|
|
81
|
-
*/
|
|
82
|
-
export interface CreateCustomerResponse {
|
|
83
|
-
customerId: string;
|
|
84
|
-
stripeCustomerId: StripeCustomerId;
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Subscription record in our database
|
|
88
|
-
*/
|
|
89
|
-
export interface Subscription {
|
|
90
|
-
id: string;
|
|
91
|
-
customerId: string;
|
|
92
|
-
stripeSubscriptionId: StripeSubscriptionId | null;
|
|
93
|
-
stripePriceId: StripePriceId | null;
|
|
94
|
-
tier: LicenseTier;
|
|
95
|
-
status: SubscriptionStatus;
|
|
96
|
-
seatCount: number;
|
|
97
|
-
currentPeriodStart: Date | null;
|
|
98
|
-
currentPeriodEnd: Date | null;
|
|
99
|
-
canceledAt: Date | null;
|
|
100
|
-
createdAt: Date;
|
|
101
|
-
updatedAt: Date;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Billing period (monthly or annual)
|
|
105
|
-
*/
|
|
106
|
-
export type BillingPeriod = 'monthly' | 'annual';
|
|
107
|
-
/**
|
|
108
|
-
* Create checkout session request
|
|
109
|
-
*/
|
|
110
|
-
export interface CreateCheckoutSessionRequest {
|
|
111
|
-
tier: LicenseTier;
|
|
112
|
-
billingPeriod: BillingPeriod;
|
|
113
|
-
seatCount?: number;
|
|
114
|
-
successUrl: string;
|
|
115
|
-
cancelUrl: string;
|
|
116
|
-
customerId?: string;
|
|
117
|
-
email?: string;
|
|
118
|
-
metadata?: Record<string, string>;
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Create checkout session response
|
|
122
|
-
*/
|
|
123
|
-
export interface CreateCheckoutSessionResponse {
|
|
124
|
-
sessionId: StripeCheckoutSessionId;
|
|
125
|
-
url: string;
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Update subscription request
|
|
129
|
-
*/
|
|
130
|
-
export interface UpdateSubscriptionRequest {
|
|
131
|
-
subscriptionId: StripeSubscriptionId;
|
|
132
|
-
tier?: LicenseTier;
|
|
133
|
-
seatCount?: number;
|
|
134
|
-
prorate?: boolean;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Cancel subscription request
|
|
138
|
-
*/
|
|
139
|
-
export interface CancelSubscriptionRequest {
|
|
140
|
-
subscriptionId: StripeSubscriptionId;
|
|
141
|
-
immediately?: boolean;
|
|
142
|
-
feedback?: string;
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Invoice status values
|
|
146
|
-
*/
|
|
147
|
-
export type InvoiceStatus = 'draft' | 'open' | 'paid' | 'void' | 'uncollectible';
|
|
148
|
-
/**
|
|
149
|
-
* Invoice record in our database
|
|
150
|
-
*/
|
|
151
|
-
export interface Invoice {
|
|
152
|
-
id: string;
|
|
153
|
-
customerId: string;
|
|
154
|
-
stripeInvoiceId: StripeInvoiceId;
|
|
155
|
-
subscriptionId: string | null;
|
|
156
|
-
amountCents: number;
|
|
157
|
-
currency: string;
|
|
158
|
-
status: InvoiceStatus;
|
|
159
|
-
pdfUrl: string | null;
|
|
160
|
-
hostedInvoiceUrl: string | null;
|
|
161
|
-
invoiceNumber: string | null;
|
|
162
|
-
paidAt: Date | null;
|
|
163
|
-
periodStart: Date | null;
|
|
164
|
-
periodEnd: Date | null;
|
|
165
|
-
createdAt: Date;
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Invoice list request
|
|
169
|
-
*/
|
|
170
|
-
export interface ListInvoicesRequest {
|
|
171
|
-
customerId: string;
|
|
172
|
-
limit?: number;
|
|
173
|
-
startingAfter?: string;
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Invoice list response
|
|
177
|
-
*/
|
|
178
|
-
export interface ListInvoicesResponse {
|
|
179
|
-
invoices: Invoice[];
|
|
180
|
-
hasMore: boolean;
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* Stripe webhook event types we handle
|
|
184
|
-
*/
|
|
185
|
-
export type StripeWebhookEventType = 'customer.subscription.created' | 'customer.subscription.updated' | 'customer.subscription.deleted' | 'invoice.payment_succeeded' | 'invoice.payment_failed' | 'checkout.session.completed' | 'customer.created' | 'customer.updated';
|
|
186
|
-
/**
|
|
187
|
-
* Webhook event record in our database
|
|
188
|
-
*/
|
|
189
|
-
export interface WebhookEvent {
|
|
190
|
-
id: string;
|
|
191
|
-
stripeEventId: StripeEventId;
|
|
192
|
-
eventType: StripeWebhookEventType;
|
|
193
|
-
processedAt: Date;
|
|
194
|
-
payload: string;
|
|
195
|
-
success: boolean;
|
|
196
|
-
errorMessage: string | null;
|
|
197
|
-
createdAt: Date;
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Webhook processing result
|
|
201
|
-
*/
|
|
202
|
-
export interface WebhookProcessResult {
|
|
203
|
-
success: boolean;
|
|
204
|
-
message: string;
|
|
205
|
-
eventId: StripeEventId;
|
|
206
|
-
processed: boolean;
|
|
207
|
-
error?: string;
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Create portal session request
|
|
211
|
-
*/
|
|
212
|
-
export interface CreatePortalSessionRequest {
|
|
213
|
-
customerId: string;
|
|
214
|
-
returnUrl: string;
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Create portal session response
|
|
218
|
-
*/
|
|
219
|
-
export interface CreatePortalSessionResponse {
|
|
220
|
-
url: string;
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Seat update request
|
|
224
|
-
*/
|
|
225
|
-
export interface UpdateSeatsRequest {
|
|
226
|
-
subscriptionId: StripeSubscriptionId;
|
|
227
|
-
seatCount: number;
|
|
228
|
-
prorate?: boolean;
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Seat update response
|
|
232
|
-
*/
|
|
233
|
-
export interface UpdateSeatsResponse {
|
|
234
|
-
success: boolean;
|
|
235
|
-
seatCount: number;
|
|
236
|
-
proratedAmount?: number;
|
|
237
|
-
message: string;
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* Price configuration for a tier
|
|
241
|
-
*/
|
|
242
|
-
export interface TierPriceConfig {
|
|
243
|
-
tier: LicenseTier;
|
|
244
|
-
monthlyPriceId: StripePriceId;
|
|
245
|
-
annualPriceId: StripePriceId;
|
|
246
|
-
monthlyPrice: number;
|
|
247
|
-
annualPrice: number;
|
|
248
|
-
perUser: boolean;
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* All price configurations
|
|
252
|
-
*/
|
|
253
|
-
export type PriceConfigs = Record<LicenseTier, TierPriceConfig>;
|
|
254
|
-
/**
|
|
255
|
-
* Billing-specific error codes
|
|
256
|
-
*/
|
|
257
|
-
export type BillingErrorCode = 'CUSTOMER_NOT_FOUND' | 'SUBSCRIPTION_NOT_FOUND' | 'INVALID_TIER' | 'PAYMENT_FAILED' | 'WEBHOOK_SIGNATURE_INVALID' | 'WEBHOOK_DUPLICATE_EVENT' | 'STRIPE_API_ERROR' | 'SEAT_LIMIT_EXCEEDED' | 'DOWNGRADE_NOT_ALLOWED';
|
|
258
|
-
/**
|
|
259
|
-
* Billing error class
|
|
260
|
-
*/
|
|
261
|
-
export declare class BillingError extends Error {
|
|
262
|
-
readonly code: BillingErrorCode;
|
|
263
|
-
readonly details?: Record<string, unknown> | undefined;
|
|
264
|
-
constructor(message: string, code: BillingErrorCode, details?: Record<string, unknown> | undefined);
|
|
265
|
-
}
|
|
266
|
-
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/billing/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SMI-1062: Billing Types and Interfaces
|
|
3
|
-
*
|
|
4
|
-
* Type definitions for Stripe billing integration including:
|
|
5
|
-
* - Subscription management
|
|
6
|
-
* - Customer management
|
|
7
|
-
* - Invoice handling
|
|
8
|
-
* - Webhook events
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
|
-
* Billing error class
|
|
12
|
-
*/
|
|
13
|
-
export class BillingError extends Error {
|
|
14
|
-
code;
|
|
15
|
-
details;
|
|
16
|
-
constructor(message, code, details) {
|
|
17
|
-
super(message);
|
|
18
|
-
this.code = code;
|
|
19
|
-
this.details = details;
|
|
20
|
-
this.name = 'BillingError';
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
//# sourceMappingURL=types.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/billing/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAkVH;;GAEG;AACH,MAAM,OAAO,YAAa,SAAQ,KAAK;IAGnB;IACA;IAHlB,YACE,OAAe,EACC,IAAsB,EACtB,OAAiC;QAEjD,KAAK,CAAC,OAAO,CAAC,CAAA;QAHE,SAAI,GAAJ,IAAI,CAAkB;QACtB,YAAO,GAAP,OAAO,CAA0B;QAGjD,IAAI,CAAC,IAAI,GAAG,cAAc,CAAA;IAC5B,CAAC;CACF"}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SMI-1070: Stripe Webhook Event Handlers
|
|
3
|
-
*
|
|
4
|
-
* Individual event handler functions for Stripe webhooks.
|
|
5
|
-
* These are extracted to reduce the size of the main handler file.
|
|
6
|
-
*/
|
|
7
|
-
import type Stripe from 'stripe';
|
|
8
|
-
import type { Database as DatabaseType } from '../db/database-interface.js';
|
|
9
|
-
import { StripeClient } from './StripeClient.js';
|
|
10
|
-
import type { BillingService } from './BillingService.js';
|
|
11
|
-
import type { LicenseTier } from './types.js';
|
|
12
|
-
export interface WebhookHandlerContext {
|
|
13
|
-
stripe: StripeClient;
|
|
14
|
-
billing: BillingService;
|
|
15
|
-
db: DatabaseType;
|
|
16
|
-
onLicenseKeyNeeded?: (params: {
|
|
17
|
-
customerId: string;
|
|
18
|
-
tier: LicenseTier;
|
|
19
|
-
expiresAt: Date;
|
|
20
|
-
subscriptionId: string;
|
|
21
|
-
}) => Promise<string>;
|
|
22
|
-
onEmailNeeded?: (params: {
|
|
23
|
-
type: 'license_key' | 'payment_failed' | 'subscription_canceled';
|
|
24
|
-
email: string;
|
|
25
|
-
data: Record<string, unknown>;
|
|
26
|
-
}) => Promise<void>;
|
|
27
|
-
}
|
|
28
|
-
export declare function handleSubscriptionCreated(ctx: WebhookHandlerContext, subscription: Stripe.Subscription): Promise<void>;
|
|
29
|
-
export declare function handleSubscriptionUpdated(ctx: WebhookHandlerContext, subscription: Stripe.Subscription): Promise<void>;
|
|
30
|
-
export declare function handleSubscriptionDeleted(ctx: WebhookHandlerContext, subscription: Stripe.Subscription): Promise<void>;
|
|
31
|
-
export declare function handleInvoicePaymentSucceeded(ctx: WebhookHandlerContext, invoice: Stripe.Invoice): Promise<void>;
|
|
32
|
-
export declare function handleInvoicePaymentFailed(ctx: WebhookHandlerContext, invoice: Stripe.Invoice): Promise<void>;
|
|
33
|
-
export declare function handleCheckoutSessionCompleted(session: Stripe.Checkout.Session): void;
|
|
34
|
-
export declare function storeLicenseKey(db: DatabaseType, params: {
|
|
35
|
-
subscriptionId: string;
|
|
36
|
-
organizationId: string;
|
|
37
|
-
keyJwt: string;
|
|
38
|
-
keyExpiry: Date;
|
|
39
|
-
}): void;
|
|
40
|
-
export declare function revokeLicenseKey(db: DatabaseType, subscriptionId: string, reason: string): void;
|
|
41
|
-
export declare function extractTier(subscription: Stripe.Subscription): LicenseTier;
|
|
42
|
-
export declare function extractSeatCount(subscription: Stripe.Subscription): number;
|
|
43
|
-
/**
|
|
44
|
-
* Get current period end from subscription (Stripe v20+ moved this to items)
|
|
45
|
-
*/
|
|
46
|
-
export declare function getCurrentPeriodEnd(subscription: Stripe.Subscription): number;
|
|
47
|
-
/**
|
|
48
|
-
* Get current period start from subscription (Stripe v20+ moved this to items)
|
|
49
|
-
*/
|
|
50
|
-
export declare function getCurrentPeriodStart(subscription: Stripe.Subscription): number;
|
|
51
|
-
/**
|
|
52
|
-
* Extract subscription ID from invoice (Stripe v20+ structure)
|
|
53
|
-
* The subscription is now at invoice.parent.subscription_details.subscription
|
|
54
|
-
*/
|
|
55
|
-
export declare function extractSubscriptionIdFromInvoice(invoice: Stripe.Invoice): string | undefined;
|
|
56
|
-
//# sourceMappingURL=webhook-handlers.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-handlers.d.ts","sourceRoot":"","sources":["../../../src/billing/webhook-handlers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAA;AAEhC,OAAO,KAAK,EAAE,QAAQ,IAAI,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAE3E,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,KAAK,EAAE,WAAW,EAA0C,MAAM,YAAY,CAAA;AASrF,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,YAAY,CAAA;IACpB,OAAO,EAAE,cAAc,CAAA;IACvB,EAAE,EAAE,YAAY,CAAA;IAChB,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE;QAC5B,UAAU,EAAE,MAAM,CAAA;QAClB,IAAI,EAAE,WAAW,CAAA;QACjB,SAAS,EAAE,IAAI,CAAA;QACf,cAAc,EAAE,MAAM,CAAA;KACvB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IACrB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE;QACvB,IAAI,EAAE,aAAa,GAAG,gBAAgB,GAAG,uBAAuB,CAAA;QAChE,KAAK,EAAE,MAAM,CAAA;QACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAC9B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACpB;AAMD,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,qBAAqB,EAC1B,YAAY,EAAE,MAAM,CAAC,YAAY,GAChC,OAAO,CAAC,IAAI,CAAC,CA4Df;AAED,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,qBAAqB,EAC1B,YAAY,EAAE,MAAM,CAAC,YAAY,GAChC,OAAO,CAAC,IAAI,CAAC,CAiDf;AAED,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,qBAAqB,EAC1B,YAAY,EAAE,MAAM,CAAC,YAAY,GAChC,OAAO,CAAC,IAAI,CAAC,CAiCf;AAMD,wBAAsB,6BAA6B,CACjD,GAAG,EAAE,qBAAqB,EAC1B,OAAO,EAAE,MAAM,CAAC,OAAO,GACtB,OAAO,CAAC,IAAI,CAAC,CA4Bf;AAED,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,qBAAqB,EAC1B,OAAO,EAAE,MAAM,CAAC,OAAO,GACtB,OAAO,CAAC,IAAI,CAAC,CA2Cf;AAMD,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,GAAG,IAAI,CASrF;AAMD,wBAAgB,eAAe,CAC7B,EAAE,EAAE,YAAY,EAChB,MAAM,EAAE;IACN,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,IAAI,CAAA;CAChB,GACA,IAAI,CAwBN;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAU/F;AAMD,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,CAAC,YAAY,GAAG,WAAW,CAS1E;AAED,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,CAAC,YAAY,GAAG,MAAM,CAY1E;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,CAAC,YAAY,GAAG,MAAM,CAU7E;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,CAAC,YAAY,GAAG,MAAM,CAU/E;AAED;;;GAGG;AACH,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,GAAG,MAAM,GAAG,SAAS,CAM5F"}
|
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SMI-1070: Stripe Webhook Event Handlers
|
|
3
|
-
*
|
|
4
|
-
* Individual event handler functions for Stripe webhooks.
|
|
5
|
-
* These are extracted to reduce the size of the main handler file.
|
|
6
|
-
*/
|
|
7
|
-
import { createHash, randomUUID } from 'crypto';
|
|
8
|
-
import { createLogger } from '../utils/logger.js';
|
|
9
|
-
import { StripeClient } from './StripeClient.js';
|
|
10
|
-
import { BillingError } from './types.js';
|
|
11
|
-
const logger = createLogger('WebhookHandlers');
|
|
12
|
-
// ============================================================================
|
|
13
|
-
// Subscription Event Handlers
|
|
14
|
-
// ============================================================================
|
|
15
|
-
export async function handleSubscriptionCreated(ctx, subscription) {
|
|
16
|
-
logger.info('Processing subscription.created', {
|
|
17
|
-
subscriptionId: subscription.id,
|
|
18
|
-
customerId: subscription.customer,
|
|
19
|
-
status: subscription.status,
|
|
20
|
-
});
|
|
21
|
-
const customer = await ctx.stripe.getCustomer(subscription.customer);
|
|
22
|
-
if (!customer) {
|
|
23
|
-
throw new BillingError('Customer not found', 'CUSTOMER_NOT_FOUND');
|
|
24
|
-
}
|
|
25
|
-
const tier = extractTier(subscription);
|
|
26
|
-
const seatCount = extractSeatCount(subscription);
|
|
27
|
-
// Create/update subscription record
|
|
28
|
-
const sub = ctx.billing.upsertSubscription({
|
|
29
|
-
customerId: customer.id,
|
|
30
|
-
email: customer.email,
|
|
31
|
-
stripeCustomerId: customer.id,
|
|
32
|
-
stripeSubscriptionId: subscription.id,
|
|
33
|
-
stripePriceId: subscription.items.data[0]?.price.id ?? '',
|
|
34
|
-
tier,
|
|
35
|
-
status: StripeClient.mapSubscriptionStatus(subscription.status),
|
|
36
|
-
seatCount,
|
|
37
|
-
currentPeriodStart: new Date(getCurrentPeriodStart(subscription) * 1000),
|
|
38
|
-
currentPeriodEnd: new Date(getCurrentPeriodEnd(subscription) * 1000),
|
|
39
|
-
});
|
|
40
|
-
// Generate license key if subscription is active
|
|
41
|
-
if (subscription.status === 'active' && ctx.onLicenseKeyNeeded) {
|
|
42
|
-
const periodEnd = getCurrentPeriodEnd(subscription);
|
|
43
|
-
const licenseKey = await ctx.onLicenseKeyNeeded({
|
|
44
|
-
customerId: customer.id,
|
|
45
|
-
tier,
|
|
46
|
-
expiresAt: new Date(periodEnd * 1000),
|
|
47
|
-
subscriptionId: sub.id,
|
|
48
|
-
});
|
|
49
|
-
// Store license key
|
|
50
|
-
storeLicenseKey(ctx.db, {
|
|
51
|
-
subscriptionId: sub.id,
|
|
52
|
-
organizationId: customer.id,
|
|
53
|
-
keyJwt: licenseKey,
|
|
54
|
-
keyExpiry: new Date(periodEnd * 1000),
|
|
55
|
-
});
|
|
56
|
-
// Send license key email
|
|
57
|
-
if (ctx.onEmailNeeded) {
|
|
58
|
-
await ctx.onEmailNeeded({
|
|
59
|
-
type: 'license_key',
|
|
60
|
-
email: customer.email,
|
|
61
|
-
data: {
|
|
62
|
-
licenseKey,
|
|
63
|
-
tier,
|
|
64
|
-
expiresAt: new Date(periodEnd * 1000).toISOString(),
|
|
65
|
-
},
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
export async function handleSubscriptionUpdated(ctx, subscription) {
|
|
71
|
-
logger.info('Processing subscription.updated', {
|
|
72
|
-
subscriptionId: subscription.id,
|
|
73
|
-
status: subscription.status,
|
|
74
|
-
});
|
|
75
|
-
const existingSub = ctx.billing.getSubscriptionByStripeId(subscription.id);
|
|
76
|
-
if (!existingSub) {
|
|
77
|
-
// Subscription doesn't exist locally, create it
|
|
78
|
-
await handleSubscriptionCreated(ctx, subscription);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
// Update status
|
|
82
|
-
const newStatus = StripeClient.mapSubscriptionStatus(subscription.status);
|
|
83
|
-
const canceledAt = subscription.canceled_at ? new Date(subscription.canceled_at * 1000) : null;
|
|
84
|
-
ctx.billing.updateSubscriptionStatus(subscription.id, newStatus, canceledAt);
|
|
85
|
-
// Check if tier changed (regenerate license key)
|
|
86
|
-
const newTier = extractTier(subscription);
|
|
87
|
-
if (existingSub.tier !== newTier && ctx.onLicenseKeyNeeded) {
|
|
88
|
-
const customer = await ctx.stripe.getCustomer(subscription.customer);
|
|
89
|
-
if (customer) {
|
|
90
|
-
// Revoke old license key
|
|
91
|
-
revokeLicenseKey(ctx.db, existingSub.id, 'tier_change');
|
|
92
|
-
// Generate new license key
|
|
93
|
-
const periodEnd = getCurrentPeriodEnd(subscription);
|
|
94
|
-
const licenseKey = await ctx.onLicenseKeyNeeded({
|
|
95
|
-
customerId: customer.id,
|
|
96
|
-
tier: newTier,
|
|
97
|
-
expiresAt: new Date(periodEnd * 1000),
|
|
98
|
-
subscriptionId: existingSub.id,
|
|
99
|
-
});
|
|
100
|
-
storeLicenseKey(ctx.db, {
|
|
101
|
-
subscriptionId: existingSub.id,
|
|
102
|
-
organizationId: customer.id,
|
|
103
|
-
keyJwt: licenseKey,
|
|
104
|
-
keyExpiry: new Date(periodEnd * 1000),
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
export async function handleSubscriptionDeleted(ctx, subscription) {
|
|
110
|
-
logger.info('Processing subscription.deleted', {
|
|
111
|
-
subscriptionId: subscription.id,
|
|
112
|
-
});
|
|
113
|
-
const existingSub = ctx.billing.getSubscriptionByStripeId(subscription.id);
|
|
114
|
-
if (existingSub) {
|
|
115
|
-
// Update status to canceled
|
|
116
|
-
ctx.billing.updateSubscriptionStatus(subscription.id, 'canceled', new Date());
|
|
117
|
-
// Revoke license key
|
|
118
|
-
revokeLicenseKey(ctx.db, existingSub.id, 'subscription_canceled');
|
|
119
|
-
// Send cancellation email
|
|
120
|
-
if (ctx.onEmailNeeded) {
|
|
121
|
-
const customer = await ctx.stripe.getCustomer(subscription.customer);
|
|
122
|
-
if (customer?.email) {
|
|
123
|
-
await ctx.onEmailNeeded({
|
|
124
|
-
type: 'subscription_canceled',
|
|
125
|
-
email: customer.email,
|
|
126
|
-
data: {
|
|
127
|
-
subscriptionId: subscription.id,
|
|
128
|
-
canceledAt: new Date().toISOString(),
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
// ============================================================================
|
|
136
|
-
// Invoice Event Handlers
|
|
137
|
-
// ============================================================================
|
|
138
|
-
export async function handleInvoicePaymentSucceeded(ctx, invoice) {
|
|
139
|
-
logger.info('Processing invoice.payment_succeeded', {
|
|
140
|
-
invoiceId: invoice.id,
|
|
141
|
-
customerId: invoice.customer,
|
|
142
|
-
amount: invoice.amount_paid,
|
|
143
|
-
});
|
|
144
|
-
const customerId = typeof invoice.customer === 'string' ? invoice.customer : invoice.customer?.id;
|
|
145
|
-
if (!customerId)
|
|
146
|
-
return;
|
|
147
|
-
// Store invoice
|
|
148
|
-
ctx.billing.storeInvoice({
|
|
149
|
-
customerId,
|
|
150
|
-
stripeInvoiceId: invoice.id,
|
|
151
|
-
subscriptionId: extractSubscriptionIdFromInvoice(invoice),
|
|
152
|
-
amountCents: invoice.amount_paid,
|
|
153
|
-
currency: invoice.currency,
|
|
154
|
-
status: 'paid',
|
|
155
|
-
pdfUrl: invoice.invoice_pdf ?? undefined,
|
|
156
|
-
hostedInvoiceUrl: invoice.hosted_invoice_url ?? undefined,
|
|
157
|
-
invoiceNumber: invoice.number ?? undefined,
|
|
158
|
-
paidAt: invoice.status_transitions?.paid_at
|
|
159
|
-
? new Date(invoice.status_transitions.paid_at * 1000)
|
|
160
|
-
: new Date(),
|
|
161
|
-
periodStart: invoice.period_start ? new Date(invoice.period_start * 1000) : undefined,
|
|
162
|
-
periodEnd: invoice.period_end ? new Date(invoice.period_end * 1000) : undefined,
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
export async function handleInvoicePaymentFailed(ctx, invoice) {
|
|
166
|
-
logger.warn('Processing invoice.payment_failed', {
|
|
167
|
-
invoiceId: invoice.id,
|
|
168
|
-
customerId: invoice.customer,
|
|
169
|
-
amount: invoice.amount_due,
|
|
170
|
-
});
|
|
171
|
-
const customerId = typeof invoice.customer === 'string' ? invoice.customer : invoice.customer?.id;
|
|
172
|
-
if (!customerId)
|
|
173
|
-
return;
|
|
174
|
-
// Get subscription ID from parent.subscription_details (Stripe v20+ structure)
|
|
175
|
-
const subscriptionId = extractSubscriptionIdFromInvoice(invoice);
|
|
176
|
-
// Store invoice with failed status
|
|
177
|
-
ctx.billing.storeInvoice({
|
|
178
|
-
customerId,
|
|
179
|
-
stripeInvoiceId: invoice.id,
|
|
180
|
-
subscriptionId,
|
|
181
|
-
amountCents: invoice.amount_due,
|
|
182
|
-
currency: invoice.currency,
|
|
183
|
-
status: 'open',
|
|
184
|
-
pdfUrl: invoice.invoice_pdf ?? undefined,
|
|
185
|
-
hostedInvoiceUrl: invoice.hosted_invoice_url ?? undefined,
|
|
186
|
-
invoiceNumber: invoice.number ?? undefined,
|
|
187
|
-
});
|
|
188
|
-
// Send payment failed email
|
|
189
|
-
if (ctx.onEmailNeeded) {
|
|
190
|
-
const customer = await ctx.stripe.getCustomer(customerId);
|
|
191
|
-
if (customer?.email) {
|
|
192
|
-
await ctx.onEmailNeeded({
|
|
193
|
-
type: 'payment_failed',
|
|
194
|
-
email: customer.email,
|
|
195
|
-
data: {
|
|
196
|
-
invoiceId: invoice.id,
|
|
197
|
-
amount: invoice.amount_due,
|
|
198
|
-
currency: invoice.currency,
|
|
199
|
-
hostedInvoiceUrl: invoice.hosted_invoice_url,
|
|
200
|
-
},
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
// ============================================================================
|
|
206
|
-
// Checkout Event Handlers
|
|
207
|
-
// ============================================================================
|
|
208
|
-
export function handleCheckoutSessionCompleted(session) {
|
|
209
|
-
logger.info('Processing checkout.session.completed', {
|
|
210
|
-
sessionId: session.id,
|
|
211
|
-
customerId: session.customer,
|
|
212
|
-
subscriptionId: session.subscription,
|
|
213
|
-
});
|
|
214
|
-
// The subscription.created event will handle the actual subscription setup
|
|
215
|
-
// This handler is useful for tracking successful checkouts and analytics
|
|
216
|
-
}
|
|
217
|
-
// ============================================================================
|
|
218
|
-
// License Key Management
|
|
219
|
-
// ============================================================================
|
|
220
|
-
export function storeLicenseKey(db, params) {
|
|
221
|
-
const id = randomUUID();
|
|
222
|
-
const keyHash = createHash('sha256').update(params.keyJwt).digest('hex');
|
|
223
|
-
const now = new Date().toISOString();
|
|
224
|
-
db.prepare(`INSERT INTO license_keys (
|
|
225
|
-
id, subscription_id, organization_id, key_jwt, key_hash,
|
|
226
|
-
key_expiry, is_active, generated_at
|
|
227
|
-
) VALUES (?, ?, ?, ?, ?, ?, 1, ?)`).run(id, params.subscriptionId, params.organizationId, params.keyJwt, keyHash, params.keyExpiry.toISOString(), now);
|
|
228
|
-
logger.info('License key stored', {
|
|
229
|
-
subscriptionId: params.subscriptionId,
|
|
230
|
-
keyHash: keyHash.slice(0, 16) + '...',
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
export function revokeLicenseKey(db, subscriptionId, reason) {
|
|
234
|
-
const now = new Date().toISOString();
|
|
235
|
-
db.prepare(`UPDATE license_keys
|
|
236
|
-
SET is_active = 0, revoked_at = ?, revocation_reason = ?
|
|
237
|
-
WHERE subscription_id = ? AND is_active = 1`).run(now, reason, subscriptionId);
|
|
238
|
-
logger.info('License key revoked', { subscriptionId, reason });
|
|
239
|
-
}
|
|
240
|
-
// ============================================================================
|
|
241
|
-
// Helper Functions
|
|
242
|
-
// ============================================================================
|
|
243
|
-
export function extractTier(subscription) {
|
|
244
|
-
// Try to get tier from metadata first
|
|
245
|
-
const metadataTier = subscription.metadata?.tier;
|
|
246
|
-
if (metadataTier && ['community', 'individual', 'team', 'enterprise'].includes(metadataTier)) {
|
|
247
|
-
return metadataTier;
|
|
248
|
-
}
|
|
249
|
-
// Fallback: infer from price ID or default to individual
|
|
250
|
-
return 'individual';
|
|
251
|
-
}
|
|
252
|
-
export function extractSeatCount(subscription) {
|
|
253
|
-
// Try metadata first
|
|
254
|
-
const metadataSeats = subscription.metadata?.seatCount;
|
|
255
|
-
if (metadataSeats) {
|
|
256
|
-
const count = parseInt(metadataSeats, 10);
|
|
257
|
-
if (!isNaN(count) && count > 0) {
|
|
258
|
-
return count;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
// Fallback to quantity from first item
|
|
262
|
-
return subscription.items.data[0]?.quantity ?? 1;
|
|
263
|
-
}
|
|
264
|
-
/**
|
|
265
|
-
* Get current period end from subscription (Stripe v20+ moved this to items)
|
|
266
|
-
*/
|
|
267
|
-
export function getCurrentPeriodEnd(subscription) {
|
|
268
|
-
const periodEnd = subscription.items.data[0]?.current_period_end;
|
|
269
|
-
if (!periodEnd) {
|
|
270
|
-
logger.warn('No subscription items found for period end', {
|
|
271
|
-
subscriptionId: subscription.id,
|
|
272
|
-
itemCount: subscription.items.data.length,
|
|
273
|
-
});
|
|
274
|
-
return Math.floor(Date.now() / 1000);
|
|
275
|
-
}
|
|
276
|
-
return periodEnd;
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
|
-
* Get current period start from subscription (Stripe v20+ moved this to items)
|
|
280
|
-
*/
|
|
281
|
-
export function getCurrentPeriodStart(subscription) {
|
|
282
|
-
const periodStart = subscription.items.data[0]?.current_period_start;
|
|
283
|
-
if (!periodStart) {
|
|
284
|
-
logger.warn('No subscription items found for period start', {
|
|
285
|
-
subscriptionId: subscription.id,
|
|
286
|
-
itemCount: subscription.items.data.length,
|
|
287
|
-
});
|
|
288
|
-
return Math.floor(Date.now() / 1000);
|
|
289
|
-
}
|
|
290
|
-
return periodStart;
|
|
291
|
-
}
|
|
292
|
-
/**
|
|
293
|
-
* Extract subscription ID from invoice (Stripe v20+ structure)
|
|
294
|
-
* The subscription is now at invoice.parent.subscription_details.subscription
|
|
295
|
-
*/
|
|
296
|
-
export function extractSubscriptionIdFromInvoice(invoice) {
|
|
297
|
-
const subscription = invoice.parent?.subscription_details?.subscription;
|
|
298
|
-
if (!subscription) {
|
|
299
|
-
return undefined;
|
|
300
|
-
}
|
|
301
|
-
return typeof subscription === 'string' ? subscription : subscription.id;
|
|
302
|
-
}
|
|
303
|
-
//# sourceMappingURL=webhook-handlers.js.map
|