fraim-hub 2.0.352 → 2.0.353
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/dist/src/api/payment/dashboard-link.js +42 -68
- package/dist/src/api/payment/webhook.js +63 -389
- package/dist/src/cli/mcp/fraim-mcp-latest-launcher.js +86 -9
- package/dist/src/cli/setup/user-level-sync.js +51 -7
- package/dist/src/core/handoff-contracts.js +8 -2
- package/dist/src/core/proposal-set-contract.js +105 -21
- package/dist/src/fraim/current-license.js +51 -0
- package/dist/src/fraim/db-service.js +253 -30
- package/dist/src/fraim/license-presentation.js +41 -0
- package/dist/src/services/email-service.js +46 -32
- package/dist/src/services/persona-entitlement-service.js +89 -31
- package/dist/src/services/subscription-payment.js +171 -0
- package/dist/src/services/subscription-provisioning.js +68 -0
- package/dist/src/services/subscription-reconciliation.js +147 -0
- package/package.json +7 -2
- package/public/ai-hub/script.js +18 -4
|
@@ -6,6 +6,7 @@ const stripe_1 = require("../../config/stripe");
|
|
|
6
6
|
const feature_flags_1 = require("../../config/feature-flags");
|
|
7
7
|
const dashboard_access_1 = require("../../services/dashboard-access");
|
|
8
8
|
const persona_entitlement_service_1 = require("../../services/persona-entitlement-service");
|
|
9
|
+
const subscription_provisioning_1 = require("../../services/subscription-provisioning");
|
|
9
10
|
function isCheckoutSessionReadyForDashboard(session) {
|
|
10
11
|
const status = session.status;
|
|
11
12
|
const paymentStatus = session.payment_status;
|
|
@@ -24,15 +25,14 @@ function getSessionSubscriptionId(session) {
|
|
|
24
25
|
return typeof session.subscription === 'string' ? session.subscription : session.subscription.id;
|
|
25
26
|
}
|
|
26
27
|
async function resolveSessionEmail(session, customerId, customerRetriever) {
|
|
27
|
-
const customerDetailsEmail = session.customer_details?.email;
|
|
28
|
-
if (customerDetailsEmail)
|
|
29
|
-
return customerDetailsEmail.toLowerCase();
|
|
28
|
+
const customerDetailsEmail = (session.customer_details?.email || session.customer_email)?.trim().toLowerCase();
|
|
30
29
|
if (!customerId)
|
|
31
30
|
return null;
|
|
32
31
|
try {
|
|
33
32
|
const customer = await customerRetriever(customerId);
|
|
34
|
-
if (!('deleted' in customer) && customer.email) {
|
|
35
|
-
|
|
33
|
+
if (!('deleted' in customer) && customer.id === customerId && customer.email) {
|
|
34
|
+
const email = customer.email.trim().toLowerCase();
|
|
35
|
+
return !customerDetailsEmail || customerDetailsEmail === email ? email : null;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
catch {
|
|
@@ -40,21 +40,6 @@ async function resolveSessionEmail(session, customerId, customerRetriever) {
|
|
|
40
40
|
}
|
|
41
41
|
return null;
|
|
42
42
|
}
|
|
43
|
-
function extractSubscriptionPeriodEnd(subscription) {
|
|
44
|
-
if (!subscription)
|
|
45
|
-
return null;
|
|
46
|
-
const rootPeriodEnd = subscription.current_period_end;
|
|
47
|
-
if (typeof rootPeriodEnd === 'number' && rootPeriodEnd > 0) {
|
|
48
|
-
return new Date(rootPeriodEnd * 1000);
|
|
49
|
-
}
|
|
50
|
-
const itemEnds = (subscription.items?.data || [])
|
|
51
|
-
.map((item) => item?.current_period_end)
|
|
52
|
-
.filter((value) => typeof value === 'number' && value > 0);
|
|
53
|
-
if (itemEnds.length > 0) {
|
|
54
|
-
return new Date(Math.min(...itemEnds) * 1000);
|
|
55
|
-
}
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
43
|
async function resolveDashboardLinkForCheckoutSession(sessionId, dbService, deps = {}) {
|
|
59
44
|
if (!sessionId || !sessionId.startsWith('cs_')) {
|
|
60
45
|
return { ok: false, status: 400, error: 'Invalid session_id' };
|
|
@@ -75,11 +60,8 @@ async function resolveDashboardLinkForCheckoutSession(sessionId, dbService, deps
|
|
|
75
60
|
const customerId = getSessionCustomerId(session);
|
|
76
61
|
const subscriptionId = getSessionSubscriptionId(session);
|
|
77
62
|
const email = await resolveSessionEmail(session, customerId, customerRetriever);
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
...session,
|
|
81
|
-
customer_details: session.customer_details || (email ? { email } : undefined)
|
|
82
|
-
}, 'dashboard-link');
|
|
63
|
+
if (!customerId || !email) {
|
|
64
|
+
return { ok: false, status: 409, error: 'Could not verify checkout customer identity' };
|
|
83
65
|
}
|
|
84
66
|
let subscription = null;
|
|
85
67
|
if (subscriptionId) {
|
|
@@ -87,54 +69,46 @@ async function resolveDashboardLinkForCheckoutSession(sessionId, dbService, deps
|
|
|
87
69
|
subscription = await subscriptionRetriever(subscriptionId);
|
|
88
70
|
}
|
|
89
71
|
catch (error) {
|
|
90
|
-
|
|
72
|
+
return { ok: false, status: 503, error: 'Could not verify current subscription status' };
|
|
73
|
+
}
|
|
74
|
+
if (subscription.id !== subscriptionId || (0, subscription_provisioning_1.subscriptionCustomerId)(subscription) !== customerId) {
|
|
75
|
+
return { ok: false, status: 409, error: 'Checkout subscription customer does not match' };
|
|
76
|
+
}
|
|
77
|
+
if (['canceled', 'incomplete_expired'].includes(subscription.status)) {
|
|
78
|
+
return { ok: false, status: 409, error: 'Subscription is not active' };
|
|
91
79
|
}
|
|
92
80
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
81
|
+
if (!subscription && session.metadata?.plan === 'persona-hire') {
|
|
82
|
+
// A persona purchase does not grant general subscription access.
|
|
83
|
+
const apiKey = await dbService.getApiKeyByUserId(email, false);
|
|
84
|
+
if (!apiKey) {
|
|
85
|
+
return { ok: false, status: 404, error: 'Could not resolve account for persona purchase' };
|
|
86
|
+
}
|
|
87
|
+
if (apiKey.isActive === false || apiKey.revokedAt) {
|
|
88
|
+
return { ok: false, status: 403, error: 'Account credential has been revoked' };
|
|
89
|
+
}
|
|
90
|
+
if ((0, feature_flags_1.isPersonaEntitlementsEnabled)()) {
|
|
91
|
+
await (0, persona_entitlement_service_1.syncPersonaEntitlementFromCheckoutSession)(dbService, {
|
|
92
|
+
...session, customer_details: { ...session.customer_details, email }
|
|
93
|
+
}, 'dashboard-link');
|
|
94
|
+
}
|
|
95
|
+
return { ok: true, dashboardUrl: await (0, dashboard_access_1.createDashboardAccessUrl)(dbService, email, apiKey.key) };
|
|
97
96
|
}
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
tier: 'paid-subscription',
|
|
101
|
-
status: 'active',
|
|
102
|
-
expiresAt: null,
|
|
103
|
-
};
|
|
104
|
-
if (customerId)
|
|
105
|
-
updatePatch.stripeCustomerId = customerId;
|
|
106
|
-
if (subscriptionId)
|
|
107
|
-
updatePatch.stripeSubscriptionId = subscriptionId;
|
|
108
|
-
if (nextBillingDate)
|
|
109
|
-
updatePatch.currentPeriodEnd = nextBillingDate;
|
|
110
|
-
await dbService.updateApiKey(apiKey.key, updatePatch);
|
|
97
|
+
if (!subscription) {
|
|
98
|
+
return { ok: false, status: 409, error: 'Checkout session has no subscription' };
|
|
111
99
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
tier: 'paid-subscription',
|
|
122
|
-
status: 'active',
|
|
123
|
-
expiresAt: null,
|
|
124
|
-
stripeCustomerId: customerId,
|
|
125
|
-
stripeSubscriptionId: subscriptionId,
|
|
126
|
-
currentPeriodEnd: nextBillingDate,
|
|
127
|
-
cancelAt: null,
|
|
128
|
-
suspendedAt: null,
|
|
129
|
-
suspensionReason: null,
|
|
130
|
-
lastUsedAt: null,
|
|
131
|
-
apiCallCount: 0,
|
|
132
|
-
personaSystemActive: (0, feature_flags_1.isPersonaEntitlementsEnabled)() || undefined
|
|
133
|
-
});
|
|
134
|
-
apiKey = { key: newKey, userId: email };
|
|
100
|
+
let apiKey;
|
|
101
|
+
try {
|
|
102
|
+
apiKey = await (0, subscription_provisioning_1.provisionSubscriptionApiKey)(dbService, email, subscription, (0, subscription_provisioning_1.subscriptionPeriodEnd)(subscription));
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return { ok: false, status: 503, error: 'Could not verify subscription payment' };
|
|
106
|
+
}
|
|
107
|
+
if (apiKey.isActive === false || apiKey.revokedAt) {
|
|
108
|
+
return { ok: false, status: 403, error: 'Account credential has been revoked' };
|
|
135
109
|
}
|
|
136
|
-
if (
|
|
137
|
-
return { ok: false, status:
|
|
110
|
+
if (apiKey.status !== 'active' || apiKey.stripeSubscriptionEndedAt) {
|
|
111
|
+
return { ok: false, status: 409, error: 'Subscription is no longer active' };
|
|
138
112
|
}
|
|
139
113
|
const dashboardUrl = await (0, dashboard_access_1.createDashboardAccessUrl)(dbService, apiKey.userId, apiKey.key);
|
|
140
114
|
return { ok: true, dashboardUrl };
|