codebuff 1.0.142 → 1.0.143
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/README.md +1 -1
- package/dist/chat-storage.js +7 -17
- package/dist/chat-storage.js.map +1 -1
- package/dist/cli.js +7 -17
- package/dist/cli.js.map +1 -1
- package/dist/client.js +7 -17
- package/dist/client.js.map +1 -1
- package/dist/code-map/languages.js +7 -17
- package/dist/code-map/languages.js.map +1 -1
- package/dist/code-map/parse.js +7 -17
- package/dist/code-map/parse.js.map +1 -1
- package/dist/code-map/tsconfig.tsbuildinfo +1 -1
- package/dist/common/actions.d.ts +3 -3
- package/dist/common/billing/payment-guards.d.ts +11 -0
- package/dist/common/billing/payment-guards.js +136 -0
- package/dist/common/billing/payment-guards.js.map +1 -0
- package/dist/common/billing/subscription-state.d.ts +13 -0
- package/dist/common/billing/subscription-state.js +117 -0
- package/dist/common/billing/subscription-state.js.map +1 -0
- package/dist/common/billing/webhook-processor.d.ts +21 -0
- package/dist/common/billing/webhook-processor.js +315 -0
- package/dist/common/billing/webhook-processor.js.map +1 -0
- package/dist/common/constants.d.ts +1 -1
- package/dist/common/constants.js +3 -3
- package/dist/common/constants.js.map +1 -1
- package/dist/common/scripts/get-invoices.d.ts +1 -0
- package/dist/common/scripts/get-invoices.js +5 -0
- package/dist/common/scripts/get-invoices.js.map +1 -0
- package/dist/common/scripts/get-month-overage.d.ts +1 -0
- package/dist/common/scripts/get-month-overage.js +5 -0
- package/dist/common/scripts/get-month-overage.js.map +1 -0
- package/dist/common/scripts/get-upcoming-invoices.d.ts +1 -0
- package/dist/common/scripts/get-upcoming-invoices.js +5 -0
- package/dist/common/scripts/get-upcoming-invoices.js.map +1 -0
- package/dist/common/scripts/update-subscriptions.d.ts +1 -0
- package/dist/common/scripts/update-subscriptions.js +92 -0
- package/dist/common/scripts/update-subscriptions.js.map +1 -0
- package/dist/common/types/usage.d.ts +2 -2
- package/dist/common/util/credentials.d.ts +2 -2
- package/dist/common/util/get-customer-invoices.d.ts +11 -0
- package/dist/common/util/get-customer-invoices.js +117 -0
- package/dist/common/util/get-customer-invoices.js.map +1 -0
- package/dist/common/util/get-month-overage.d.ts +11 -0
- package/dist/common/util/get-month-overage.js +143 -0
- package/dist/common/util/get-month-overage.js.map +1 -0
- package/dist/common/util/get-upcoming-invoices.d.ts +11 -0
- package/dist/common/util/get-upcoming-invoices.js +141 -0
- package/dist/common/util/get-upcoming-invoices.js.map +1 -0
- package/dist/common/websockets/logger-interface.d.ts +6 -0
- package/dist/common/websockets/logger-interface.js +9 -0
- package/dist/common/websockets/logger-interface.js.map +1 -0
- package/dist/common/websockets/websocket-schema.d.ts +10 -10
- package/dist/index.js +17 -6
- package/dist/index.js.map +1 -1
- package/dist/menu.js +8 -18
- package/dist/menu.js.map +1 -1
- package/dist/project-files.js +7 -17
- package/dist/project-files.js.map +1 -1
- package/dist/utils/terminal.js +7 -17
- package/dist/utils/terminal.js.map +1 -1
- package/package.json +1 -1
package/dist/common/actions.d.ts
CHANGED
|
@@ -497,7 +497,7 @@ export declare const CLIENT_ACTION_SCHEMA: z.ZodDiscriminatedUnion<"type", [z.Zo
|
|
|
497
497
|
content: string;
|
|
498
498
|
filePath: string;
|
|
499
499
|
}>, "many">;
|
|
500
|
-
costMode: z.ZodDefault<z.ZodOptional<z.ZodEnum<["lite", "normal", "
|
|
500
|
+
costMode: z.ZodDefault<z.ZodOptional<z.ZodEnum<["lite", "normal", "max"]>>>;
|
|
501
501
|
}, "strip", z.ZodTypeAny, {
|
|
502
502
|
type: "user-input";
|
|
503
503
|
fingerprintId: string;
|
|
@@ -558,7 +558,7 @@ export declare const CLIENT_ACTION_SCHEMA: z.ZodDiscriminatedUnion<"type", [z.Zo
|
|
|
558
558
|
content: string;
|
|
559
559
|
filePath: string;
|
|
560
560
|
}[];
|
|
561
|
-
costMode: "lite" | "normal" | "
|
|
561
|
+
costMode: "lite" | "normal" | "max";
|
|
562
562
|
authToken?: string | undefined;
|
|
563
563
|
}, {
|
|
564
564
|
type: "user-input";
|
|
@@ -621,7 +621,7 @@ export declare const CLIENT_ACTION_SCHEMA: z.ZodDiscriminatedUnion<"type", [z.Zo
|
|
|
621
621
|
filePath: string;
|
|
622
622
|
}[];
|
|
623
623
|
authToken?: string | undefined;
|
|
624
|
-
costMode?: "lite" | "normal" | "
|
|
624
|
+
costMode?: "lite" | "normal" | "max" | undefined;
|
|
625
625
|
}>, z.ZodObject<{
|
|
626
626
|
type: z.ZodLiteral<"read-files-response">;
|
|
627
627
|
files: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNull]>>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class PaymentGuards {
|
|
2
|
+
private subscriptionId;
|
|
3
|
+
constructor(subscriptionId: string);
|
|
4
|
+
validatePaymentMethod(): Promise<{
|
|
5
|
+
valid: boolean;
|
|
6
|
+
error?: string;
|
|
7
|
+
}>;
|
|
8
|
+
handleFailedPayment(): Promise<void>;
|
|
9
|
+
processPartialPayment(amountPaid: number): Promise<void>;
|
|
10
|
+
handleRefund(amount: number): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.PaymentGuards = void 0;
|
|
30
|
+
const stripe_1 = require("../util/stripe");
|
|
31
|
+
const db_1 = __importDefault(require("../db"));
|
|
32
|
+
const schema = __importStar(require("../db/schema"));
|
|
33
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
34
|
+
class PaymentGuards {
|
|
35
|
+
subscriptionId;
|
|
36
|
+
constructor(subscriptionId) {
|
|
37
|
+
this.subscriptionId = subscriptionId;
|
|
38
|
+
}
|
|
39
|
+
async validatePaymentMethod() {
|
|
40
|
+
try {
|
|
41
|
+
const subscription = await stripe_1.stripeServer.subscriptions.retrieve(this.subscriptionId, {
|
|
42
|
+
expand: ['latest_invoice.payment_intent'],
|
|
43
|
+
});
|
|
44
|
+
const invoice = subscription.latest_invoice;
|
|
45
|
+
if (!invoice?.payment_intent) {
|
|
46
|
+
return { valid: true }; // No payment needed
|
|
47
|
+
}
|
|
48
|
+
const paymentIntent = invoice.payment_intent;
|
|
49
|
+
if (paymentIntent.status === 'succeeded') {
|
|
50
|
+
return { valid: true };
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
valid: false,
|
|
54
|
+
error: `Payment failed: ${paymentIntent.last_payment_error?.message || 'Unknown error'}`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error('Error validating payment method:', error);
|
|
59
|
+
return {
|
|
60
|
+
valid: false,
|
|
61
|
+
error: 'Failed to validate payment method',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async handleFailedPayment() {
|
|
66
|
+
// Record the failed attempt
|
|
67
|
+
await db_1.default.insert(schema.payment_attempts).values({
|
|
68
|
+
id: crypto.randomUUID(),
|
|
69
|
+
subscription_id: this.subscriptionId,
|
|
70
|
+
status: 'failed',
|
|
71
|
+
created_at: new Date(),
|
|
72
|
+
});
|
|
73
|
+
// Get the subscription to check retry count
|
|
74
|
+
const attempts = await db_1.default
|
|
75
|
+
.select({
|
|
76
|
+
count: (0, drizzle_orm_1.sql) `count(*)::int`,
|
|
77
|
+
})
|
|
78
|
+
.from(schema.payment_attempts)
|
|
79
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema.payment_attempts.subscription_id, this.subscriptionId), (0, drizzle_orm_1.eq)(schema.payment_attempts.status, 'failed'), (0, drizzle_orm_1.sql) `created_at > now() - interval '24 hours'`));
|
|
80
|
+
const retryCount = attempts[0].count;
|
|
81
|
+
if (retryCount >= 3) {
|
|
82
|
+
// Too many failures, pause the subscription
|
|
83
|
+
await stripe_1.stripeServer.subscriptions.update(this.subscriptionId, {
|
|
84
|
+
pause_collection: {
|
|
85
|
+
behavior: 'void',
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async processPartialPayment(amountPaid) {
|
|
91
|
+
// Record the partial payment
|
|
92
|
+
await db_1.default.insert(schema.payment_attempts).values({
|
|
93
|
+
id: crypto.randomUUID(),
|
|
94
|
+
subscription_id: this.subscriptionId,
|
|
95
|
+
amount: amountPaid.toString(), // Convert to string for decimal type
|
|
96
|
+
status: 'partial',
|
|
97
|
+
created_at: new Date(),
|
|
98
|
+
});
|
|
99
|
+
// Update the subscription with remaining balance
|
|
100
|
+
const subscription = await stripe_1.stripeServer.subscriptions.retrieve(this.subscriptionId, {
|
|
101
|
+
expand: ['latest_invoice'],
|
|
102
|
+
});
|
|
103
|
+
const invoice = subscription.latest_invoice;
|
|
104
|
+
if (invoice) {
|
|
105
|
+
const remainingAmount = invoice.amount_due - amountPaid;
|
|
106
|
+
if (remainingAmount > 0) {
|
|
107
|
+
await stripe_1.stripeServer.invoices.update(invoice.id, {
|
|
108
|
+
description: `Partial payment received: ${amountPaid}. Remaining: ${remainingAmount}`,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async handleRefund(amount) {
|
|
114
|
+
const subscription = await stripe_1.stripeServer.subscriptions.retrieve(this.subscriptionId, {
|
|
115
|
+
expand: ['latest_invoice.payment_intent'],
|
|
116
|
+
});
|
|
117
|
+
const invoice = subscription.latest_invoice;
|
|
118
|
+
if (!invoice?.payment_intent) {
|
|
119
|
+
throw new Error('No payment found to refund');
|
|
120
|
+
}
|
|
121
|
+
await stripe_1.stripeServer.refunds.create({
|
|
122
|
+
payment_intent: invoice.payment_intent.id,
|
|
123
|
+
amount,
|
|
124
|
+
});
|
|
125
|
+
// Record the refund
|
|
126
|
+
await db_1.default.insert(schema.payment_attempts).values({
|
|
127
|
+
id: crypto.randomUUID(),
|
|
128
|
+
subscription_id: this.subscriptionId,
|
|
129
|
+
amount: (-amount).toString(), // Convert to string for decimal type
|
|
130
|
+
status: 'refunded',
|
|
131
|
+
created_at: new Date(),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
exports.PaymentGuards = PaymentGuards;
|
|
136
|
+
//# sourceMappingURL=payment-guards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment-guards.js","sourceRoot":"","sources":["../../src/billing/payment-guards.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export type SubscriptionState = 'active' | 'past_due' | 'incomplete' | 'canceled' | 'trialing' | 'paused' | 'transitioning';
|
|
3
|
+
export declare const subscriptionStateSchema: z.ZodEnum<["active", "past_due", "incomplete", "canceled", "trialing", "paused", "transitioning"]>;
|
|
4
|
+
export declare class SubscriptionStateManager {
|
|
5
|
+
private subscriptionId;
|
|
6
|
+
private transitionLock;
|
|
7
|
+
constructor(subscriptionId: string);
|
|
8
|
+
getCurrentState(): Promise<SubscriptionState>;
|
|
9
|
+
beginStateTransition(): Promise<boolean>;
|
|
10
|
+
endStateTransition(): Promise<void>;
|
|
11
|
+
addStateHistoryEntry(fromState: SubscriptionState, toState: SubscriptionState, reason?: string): Promise<void>;
|
|
12
|
+
private mapStripeState;
|
|
13
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.SubscriptionStateManager = exports.subscriptionStateSchema = void 0;
|
|
30
|
+
const zod_1 = require("zod");
|
|
31
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
32
|
+
const db_1 = __importDefault(require("../db"));
|
|
33
|
+
const schema = __importStar(require("../db/schema"));
|
|
34
|
+
const stripe_1 = require("../util/stripe");
|
|
35
|
+
exports.subscriptionStateSchema = zod_1.z.enum([
|
|
36
|
+
'active',
|
|
37
|
+
'past_due',
|
|
38
|
+
'incomplete',
|
|
39
|
+
'canceled',
|
|
40
|
+
'trialing',
|
|
41
|
+
'paused',
|
|
42
|
+
'transitioning',
|
|
43
|
+
]);
|
|
44
|
+
class SubscriptionStateManager {
|
|
45
|
+
subscriptionId;
|
|
46
|
+
transitionLock = null;
|
|
47
|
+
constructor(subscriptionId) {
|
|
48
|
+
this.subscriptionId = subscriptionId;
|
|
49
|
+
}
|
|
50
|
+
async getCurrentState() {
|
|
51
|
+
const subscription = await stripe_1.stripeServer.subscriptions.retrieve(this.subscriptionId);
|
|
52
|
+
return this.mapStripeState(subscription.status);
|
|
53
|
+
}
|
|
54
|
+
async beginStateTransition() {
|
|
55
|
+
// Generate a new lock UUID
|
|
56
|
+
const newLock = crypto.randomUUID();
|
|
57
|
+
// Try to acquire lock
|
|
58
|
+
const result = await db_1.default
|
|
59
|
+
.update(schema.user)
|
|
60
|
+
.set({
|
|
61
|
+
transition_lock: newLock,
|
|
62
|
+
})
|
|
63
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema.user.stripe_price_id, this.subscriptionId), (0, drizzle_orm_1.sql) `transition_lock IS NULL`))
|
|
64
|
+
.returning({ transitionLock: schema.user.transition_lock });
|
|
65
|
+
if (result.length === 0) {
|
|
66
|
+
return false; // Lock acquisition failed
|
|
67
|
+
}
|
|
68
|
+
this.transitionLock = newLock;
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
async endStateTransition() {
|
|
72
|
+
if (!this.transitionLock) {
|
|
73
|
+
throw new Error('No active transition');
|
|
74
|
+
}
|
|
75
|
+
await db_1.default
|
|
76
|
+
.update(schema.user)
|
|
77
|
+
.set({
|
|
78
|
+
transition_lock: null,
|
|
79
|
+
})
|
|
80
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema.user.stripe_price_id, this.subscriptionId), (0, drizzle_orm_1.eq)(schema.user.transition_lock, this.transitionLock)));
|
|
81
|
+
this.transitionLock = null;
|
|
82
|
+
}
|
|
83
|
+
async addStateHistoryEntry(fromState, toState, reason) {
|
|
84
|
+
const entry = {
|
|
85
|
+
fromState,
|
|
86
|
+
toState,
|
|
87
|
+
reason,
|
|
88
|
+
timestamp: new Date().toISOString(),
|
|
89
|
+
};
|
|
90
|
+
await db_1.default
|
|
91
|
+
.update(schema.user)
|
|
92
|
+
.set({
|
|
93
|
+
state_history: (0, drizzle_orm_1.sql) `COALESCE(state_history, '[]'::jsonb) || ${JSON.stringify(entry)}::jsonb`,
|
|
94
|
+
})
|
|
95
|
+
.where((0, drizzle_orm_1.eq)(schema.user.stripe_price_id, this.subscriptionId));
|
|
96
|
+
}
|
|
97
|
+
mapStripeState(stripeStatus) {
|
|
98
|
+
switch (stripeStatus) {
|
|
99
|
+
case 'active':
|
|
100
|
+
return 'active';
|
|
101
|
+
case 'past_due':
|
|
102
|
+
return 'past_due';
|
|
103
|
+
case 'incomplete':
|
|
104
|
+
return 'incomplete';
|
|
105
|
+
case 'canceled':
|
|
106
|
+
return 'canceled';
|
|
107
|
+
case 'trialing':
|
|
108
|
+
return 'trialing';
|
|
109
|
+
case 'paused':
|
|
110
|
+
return 'paused';
|
|
111
|
+
default:
|
|
112
|
+
throw new Error(`Unknown Stripe status: ${stripeStatus}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.SubscriptionStateManager = SubscriptionStateManager;
|
|
117
|
+
//# sourceMappingURL=subscription-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscription-state.js","sourceRoot":"","sources":["../../src/billing/subscription-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type Stripe from 'stripe';
|
|
2
|
+
export declare class WebhookProcessor {
|
|
3
|
+
private stripeSignature;
|
|
4
|
+
private rawBody;
|
|
5
|
+
constructor(stripeSignature: string, rawBody: string);
|
|
6
|
+
validateWebhook(): Promise<{
|
|
7
|
+
valid: boolean;
|
|
8
|
+
event?: Stripe.Event;
|
|
9
|
+
error?: string;
|
|
10
|
+
}>;
|
|
11
|
+
private checkDuplicateEvent;
|
|
12
|
+
processEvent(event: Stripe.Event): Promise<void>;
|
|
13
|
+
private handleEvent;
|
|
14
|
+
private handleSubscriptionChange;
|
|
15
|
+
private handleSubscriptionDeletion;
|
|
16
|
+
private handleInvoiceCreation;
|
|
17
|
+
private handleInvoicePayment;
|
|
18
|
+
private handlePaymentFailure;
|
|
19
|
+
private getTotalReferralCredits;
|
|
20
|
+
private mapStripeStatus;
|
|
21
|
+
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.WebhookProcessor = void 0;
|
|
30
|
+
const stripe_1 = require("../util/stripe");
|
|
31
|
+
const db_1 = __importDefault(require("../db"));
|
|
32
|
+
const schema = __importStar(require("../db/schema"));
|
|
33
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
34
|
+
const zod_1 = require("zod");
|
|
35
|
+
const subscription_state_1 = require("./subscription-state");
|
|
36
|
+
const quota_manager_1 = require("./quota-manager");
|
|
37
|
+
const payment_guards_1 = require("./payment-guards");
|
|
38
|
+
const webhookEventSchema = zod_1.z.object({
|
|
39
|
+
id: zod_1.z.string(),
|
|
40
|
+
type: zod_1.z.string(),
|
|
41
|
+
created: zod_1.z.number(),
|
|
42
|
+
data: zod_1.z.object({
|
|
43
|
+
object: zod_1.z.record(zod_1.z.any()),
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
class WebhookProcessor {
|
|
47
|
+
stripeSignature;
|
|
48
|
+
rawBody;
|
|
49
|
+
constructor(stripeSignature, rawBody) {
|
|
50
|
+
this.stripeSignature = stripeSignature;
|
|
51
|
+
this.rawBody = rawBody;
|
|
52
|
+
}
|
|
53
|
+
async validateWebhook() {
|
|
54
|
+
try {
|
|
55
|
+
const event = stripe_1.stripeServer.webhooks.constructEvent(this.rawBody, this.stripeSignature, process.env.STRIPE_WEBHOOK_SECRET_KEY);
|
|
56
|
+
// Validate event structure
|
|
57
|
+
const result = webhookEventSchema.safeParse(event);
|
|
58
|
+
if (!result.success) {
|
|
59
|
+
return {
|
|
60
|
+
valid: false,
|
|
61
|
+
error: 'Invalid event structure',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// Check for duplicate events
|
|
65
|
+
const isDuplicate = await this.checkDuplicateEvent(event.id);
|
|
66
|
+
if (isDuplicate) {
|
|
67
|
+
return {
|
|
68
|
+
valid: false,
|
|
69
|
+
error: 'Duplicate event',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
valid: true,
|
|
74
|
+
event,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
return {
|
|
79
|
+
valid: false,
|
|
80
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async checkDuplicateEvent(eventId) {
|
|
85
|
+
// First try to insert the event
|
|
86
|
+
try {
|
|
87
|
+
await db_1.default.insert(schema.webhook_events).values({
|
|
88
|
+
id: eventId,
|
|
89
|
+
processed_at: new Date(),
|
|
90
|
+
});
|
|
91
|
+
return false; // Not a duplicate
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
// If insert fails due to unique constraint, it's a duplicate
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async processEvent(event) {
|
|
99
|
+
// Start transaction to ensure atomic processing
|
|
100
|
+
await db_1.default.transaction(async (tx) => {
|
|
101
|
+
// Lock the subscription record if this is a subscription-related event
|
|
102
|
+
if (event.type.startsWith('customer.subscription.')) {
|
|
103
|
+
const subscription = event.data.object;
|
|
104
|
+
await tx
|
|
105
|
+
.select()
|
|
106
|
+
.from(schema.user)
|
|
107
|
+
.where((0, drizzle_orm_1.eq)(schema.user.stripe_price_id, subscription.id))
|
|
108
|
+
.for('update');
|
|
109
|
+
}
|
|
110
|
+
// Record event processing start
|
|
111
|
+
await tx.insert(schema.webhook_processing).values({
|
|
112
|
+
id: crypto.randomUUID(),
|
|
113
|
+
event_id: event.id,
|
|
114
|
+
started_at: new Date(),
|
|
115
|
+
status: 'processing',
|
|
116
|
+
});
|
|
117
|
+
try {
|
|
118
|
+
// Process the event based on type
|
|
119
|
+
await this.handleEvent(event);
|
|
120
|
+
// Record successful processing
|
|
121
|
+
await tx
|
|
122
|
+
.update(schema.webhook_processing)
|
|
123
|
+
.set({
|
|
124
|
+
status: 'completed',
|
|
125
|
+
completed_at: new Date(),
|
|
126
|
+
})
|
|
127
|
+
.where((0, drizzle_orm_1.eq)(schema.webhook_processing.event_id, event.id));
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
// Record failed processing
|
|
131
|
+
await tx
|
|
132
|
+
.update(schema.webhook_processing)
|
|
133
|
+
.set({
|
|
134
|
+
status: 'failed',
|
|
135
|
+
error_message: error instanceof Error ? error.message : 'Unknown error',
|
|
136
|
+
completed_at: new Date(),
|
|
137
|
+
})
|
|
138
|
+
.where((0, drizzle_orm_1.eq)(schema.webhook_processing.event_id, event.id));
|
|
139
|
+
throw error; // Re-throw to rollback transaction
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
async handleEvent(event) {
|
|
144
|
+
// Check for older unprocessed events
|
|
145
|
+
const pendingEvents = await db_1.default
|
|
146
|
+
.select({
|
|
147
|
+
id: schema.webhook_events.id,
|
|
148
|
+
created: schema.webhook_events.created_at,
|
|
149
|
+
})
|
|
150
|
+
.from(schema.webhook_events)
|
|
151
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.sql) `created_at < ${new Date(event.created * 1000)}`, (0, drizzle_orm_1.eq)(schema.webhook_events.processed, false)))
|
|
152
|
+
.orderBy(schema.webhook_events.created_at);
|
|
153
|
+
// If there are older unprocessed events, delay processing this one
|
|
154
|
+
if (pendingEvents.length > 0) {
|
|
155
|
+
throw new Error(`Unprocessed events exist before ${event.id}. Process those first.`);
|
|
156
|
+
}
|
|
157
|
+
// Process based on event type
|
|
158
|
+
switch (event.type) {
|
|
159
|
+
case 'customer.subscription.created':
|
|
160
|
+
case 'customer.subscription.updated':
|
|
161
|
+
await this.handleSubscriptionChange(event);
|
|
162
|
+
break;
|
|
163
|
+
case 'customer.subscription.deleted':
|
|
164
|
+
await this.handleSubscriptionDeletion(event);
|
|
165
|
+
break;
|
|
166
|
+
case 'invoice.created':
|
|
167
|
+
await this.handleInvoiceCreation(event);
|
|
168
|
+
break;
|
|
169
|
+
case 'invoice.paid':
|
|
170
|
+
await this.handleInvoicePayment(event);
|
|
171
|
+
break;
|
|
172
|
+
case 'invoice.payment_failed':
|
|
173
|
+
await this.handlePaymentFailure(event);
|
|
174
|
+
break;
|
|
175
|
+
default:
|
|
176
|
+
console.log(`Unhandled event type ${event.type}`);
|
|
177
|
+
}
|
|
178
|
+
// Mark event as processed
|
|
179
|
+
await db_1.default
|
|
180
|
+
.update(schema.webhook_events)
|
|
181
|
+
.set({
|
|
182
|
+
processed: true,
|
|
183
|
+
processed_at: new Date(),
|
|
184
|
+
})
|
|
185
|
+
.where((0, drizzle_orm_1.eq)(schema.webhook_events.id, event.id));
|
|
186
|
+
}
|
|
187
|
+
async handleSubscriptionChange(event) {
|
|
188
|
+
const subscription = event.data.object;
|
|
189
|
+
const stateManager = new subscription_state_1.SubscriptionStateManager(subscription.id);
|
|
190
|
+
const quotaManager = new quota_manager_1.QuotaManager(subscription.id);
|
|
191
|
+
// Get the current state and update it
|
|
192
|
+
const currentState = await stateManager.getCurrentState();
|
|
193
|
+
const newState = this.mapStripeStatus(subscription.status);
|
|
194
|
+
if (currentState !== newState) {
|
|
195
|
+
// Acquire lock for state transition
|
|
196
|
+
const lockAcquired = await stateManager.beginStateTransition();
|
|
197
|
+
if (!lockAcquired) {
|
|
198
|
+
throw new Error('Failed to acquire transition lock');
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
// Record state change
|
|
202
|
+
await stateManager.addStateHistoryEntry(currentState, newState, `Webhook event: ${event.type}`);
|
|
203
|
+
// Handle quota updates if needed
|
|
204
|
+
if (newState === 'active' && currentState !== 'active') {
|
|
205
|
+
await quotaManager.resetQuota();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
await stateManager.endStateTransition();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async handleSubscriptionDeletion(event) {
|
|
214
|
+
const subscription = event.data.object;
|
|
215
|
+
const stateManager = new subscription_state_1.SubscriptionStateManager(subscription.id);
|
|
216
|
+
const lockAcquired = await stateManager.beginStateTransition();
|
|
217
|
+
if (!lockAcquired) {
|
|
218
|
+
throw new Error('Failed to acquire transition lock');
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
await stateManager.addStateHistoryEntry('active', 'canceled', 'Subscription deleted');
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
await stateManager.endStateTransition();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async handleInvoiceCreation(event) {
|
|
228
|
+
const invoice = event.data.object;
|
|
229
|
+
if (!invoice.subscription)
|
|
230
|
+
return;
|
|
231
|
+
// Handle referral credits if applicable
|
|
232
|
+
if (invoice.customer) {
|
|
233
|
+
const referralCredits = await this.getTotalReferralCredits(invoice.customer.toString());
|
|
234
|
+
if (referralCredits > 0) {
|
|
235
|
+
// Apply referral credits as credit on the invoice
|
|
236
|
+
await stripe_1.stripeServer.invoices.update(invoice.id, {
|
|
237
|
+
description: `Including referral credits: ${referralCredits}`,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async handleInvoicePayment(event) {
|
|
243
|
+
const invoice = event.data.object;
|
|
244
|
+
if (!invoice.subscription)
|
|
245
|
+
return;
|
|
246
|
+
const stateManager = new subscription_state_1.SubscriptionStateManager(invoice.subscription.toString());
|
|
247
|
+
const quotaManager = new quota_manager_1.QuotaManager(invoice.subscription.toString());
|
|
248
|
+
// Reset quota for new billing period
|
|
249
|
+
await quotaManager.resetQuota();
|
|
250
|
+
// Update subscription state if needed
|
|
251
|
+
const currentState = await stateManager.getCurrentState();
|
|
252
|
+
if (currentState !== 'active') {
|
|
253
|
+
const lockAcquired = await stateManager.beginStateTransition();
|
|
254
|
+
if (!lockAcquired) {
|
|
255
|
+
throw new Error('Failed to acquire transition lock');
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
await stateManager.addStateHistoryEntry(currentState, 'active', 'Invoice payment successful');
|
|
259
|
+
}
|
|
260
|
+
finally {
|
|
261
|
+
await stateManager.endStateTransition();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async handlePaymentFailure(event) {
|
|
266
|
+
const invoice = event.data.object;
|
|
267
|
+
if (!invoice.subscription)
|
|
268
|
+
return;
|
|
269
|
+
const paymentGuards = new payment_guards_1.PaymentGuards(invoice.subscription.toString());
|
|
270
|
+
await paymentGuards.handleFailedPayment();
|
|
271
|
+
// Update subscription state
|
|
272
|
+
const stateManager = new subscription_state_1.SubscriptionStateManager(invoice.subscription.toString());
|
|
273
|
+
const lockAcquired = await stateManager.beginStateTransition();
|
|
274
|
+
if (!lockAcquired) {
|
|
275
|
+
throw new Error('Failed to acquire transition lock');
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
await stateManager.addStateHistoryEntry('active', 'past_due', 'Payment failed');
|
|
279
|
+
}
|
|
280
|
+
finally {
|
|
281
|
+
await stateManager.endStateTransition();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async getTotalReferralCredits(customerId) {
|
|
285
|
+
const result = await db_1.default
|
|
286
|
+
.select({
|
|
287
|
+
total: (0, drizzle_orm_1.sql) `COALESCE(SUM(${schema.referral.credits}), 0)::int`,
|
|
288
|
+
})
|
|
289
|
+
.from(schema.user)
|
|
290
|
+
.leftJoin(schema.referral, (0, drizzle_orm_1.or)((0, drizzle_orm_1.eq)(schema.referral.referrer_id, schema.user.id), (0, drizzle_orm_1.eq)(schema.referral.referred_id, schema.user.id)))
|
|
291
|
+
.where((0, drizzle_orm_1.eq)(schema.user.stripe_customer_id, customerId))
|
|
292
|
+
.groupBy(schema.user.id);
|
|
293
|
+
return result[0]?.total || 0;
|
|
294
|
+
}
|
|
295
|
+
mapStripeStatus(status) {
|
|
296
|
+
switch (status) {
|
|
297
|
+
case 'active':
|
|
298
|
+
return 'active';
|
|
299
|
+
case 'past_due':
|
|
300
|
+
return 'past_due';
|
|
301
|
+
case 'incomplete':
|
|
302
|
+
return 'incomplete';
|
|
303
|
+
case 'canceled':
|
|
304
|
+
return 'canceled';
|
|
305
|
+
case 'trialing':
|
|
306
|
+
return 'trialing';
|
|
307
|
+
case 'paused':
|
|
308
|
+
return 'paused';
|
|
309
|
+
default:
|
|
310
|
+
throw new Error(`Unknown Stripe status: ${status}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
exports.WebhookProcessor = WebhookProcessor;
|
|
315
|
+
//# sourceMappingURL=webhook-processor.js.map
|