@revealui/services 0.0.2 → 0.1.0
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/LICENSE +22 -202
- package/LICENSE.commercial +112 -0
- package/README.md +177 -0
- package/dist/api/create-checkout-session/index.d.ts +2 -0
- package/dist/api/create-checkout-session/index.d.ts.map +1 -0
- package/dist/api/create-checkout-session/index.js +61 -0
- package/dist/api/create-checkout-session/index.js.map +1 -0
- package/dist/api/create-portal-link/index.d.ts +2 -0
- package/dist/api/create-portal-link/index.d.ts.map +1 -0
- package/dist/api/create-portal-link/index.js +37 -0
- package/dist/api/create-portal-link/index.js.map +1 -0
- package/dist/api/handlers/customer-handlers.d.ts +27 -0
- package/dist/api/handlers/customer-handlers.d.ts.map +1 -0
- package/dist/api/handlers/customer-handlers.js +86 -0
- package/dist/api/handlers/customer-handlers.js.map +1 -0
- package/dist/api/handlers/index.d.ts +18 -0
- package/dist/api/handlers/index.d.ts.map +1 -0
- package/dist/api/handlers/index.js +18 -0
- package/dist/api/handlers/index.js.map +1 -0
- package/dist/api/handlers/invoice-handlers.d.ts +9 -0
- package/dist/api/handlers/invoice-handlers.d.ts.map +1 -0
- package/dist/api/handlers/invoice-handlers.js +52 -0
- package/dist/api/handlers/invoice-handlers.js.map +1 -0
- package/dist/api/handlers/payment-handlers.d.ts +13 -0
- package/dist/api/handlers/payment-handlers.d.ts.map +1 -0
- package/dist/api/handlers/payment-handlers.js +133 -0
- package/dist/api/handlers/payment-handlers.js.map +1 -0
- package/dist/api/handlers/payment-intent.d.ts +21 -0
- package/dist/api/handlers/payment-intent.d.ts.map +1 -0
- package/dist/api/handlers/payment-intent.js +87 -0
- package/dist/api/handlers/payment-intent.js.map +1 -0
- package/dist/api/handlers/product-handlers.d.ts +11 -0
- package/dist/api/handlers/product-handlers.d.ts.map +1 -0
- package/dist/api/handlers/product-handlers.js +43 -0
- package/dist/api/handlers/product-handlers.js.map +1 -0
- package/dist/api/handlers/subscription-handlers.d.ts +13 -0
- package/dist/api/handlers/subscription-handlers.d.ts.map +1 -0
- package/dist/api/handlers/subscription-handlers.js +115 -0
- package/dist/api/handlers/subscription-handlers.js.map +1 -0
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +8 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/types/stripe.d.ts +42 -0
- package/dist/api/types/stripe.d.ts.map +1 -0
- package/dist/api/types/stripe.js +72 -0
- package/dist/api/types/stripe.js.map +1 -0
- package/dist/api/update-price/index.d.ts +42 -0
- package/dist/api/update-price/index.d.ts.map +1 -0
- package/dist/api/update-price/index.js +78 -0
- package/dist/api/update-price/index.js.map +1 -0
- package/dist/api/update-product/index.d.ts +44 -0
- package/dist/api/update-product/index.d.ts.map +1 -0
- package/dist/api/update-product/index.js +85 -0
- package/dist/api/update-product/index.js.map +1 -0
- package/dist/api/utils.d.ts +34 -0
- package/dist/api/utils.d.ts.map +1 -0
- package/dist/api/utils.js +66 -0
- package/dist/api/utils.js.map +1 -0
- package/dist/api/webhooks/index.d.ts +2 -0
- package/dist/api/webhooks/index.d.ts.map +1 -0
- package/dist/api/webhooks/index.js +270 -0
- package/dist/api/webhooks/index.js.map +1 -0
- package/dist/client/index.d.ts +11 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +12 -0
- package/dist/client/index.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/stripe/db-circuit-breaker.d.ts +47 -0
- package/dist/stripe/db-circuit-breaker.d.ts.map +1 -0
- package/dist/stripe/db-circuit-breaker.js +223 -0
- package/dist/stripe/db-circuit-breaker.js.map +1 -0
- package/dist/stripe/index.d.ts +2 -0
- package/dist/stripe/index.d.ts.map +1 -0
- package/dist/stripe/index.js +2 -0
- package/dist/stripe/index.js.map +1 -0
- package/dist/stripe/stripeClient.d.ts +126 -0
- package/dist/stripe/stripeClient.d.ts.map +1 -0
- package/dist/stripe/stripeClient.js +226 -0
- package/dist/stripe/stripeClient.js.map +1 -0
- package/dist/supabase/index.d.ts +6 -0
- package/dist/supabase/index.d.ts.map +1 -0
- package/dist/supabase/index.js +5 -0
- package/dist/supabase/index.js.map +1 -0
- package/dist/supabase/resilience.d.ts +50 -0
- package/dist/supabase/resilience.d.ts.map +1 -0
- package/dist/supabase/resilience.js +166 -0
- package/dist/supabase/resilience.js.map +1 -0
- package/dist/supabase/types.d.ts +206 -0
- package/dist/supabase/types.d.ts.map +1 -0
- package/dist/supabase/types.js +19 -0
- package/dist/supabase/types.js.map +1 -0
- package/dist/supabase/utils/client.d.ts +4 -0
- package/dist/supabase/utils/client.d.ts.map +1 -0
- package/dist/supabase/utils/client.js +12 -0
- package/dist/supabase/utils/client.js.map +1 -0
- package/dist/supabase/utils/server.d.ts +10 -0
- package/dist/supabase/utils/server.d.ts.map +1 -0
- package/dist/supabase/utils/server.js +49 -0
- package/dist/supabase/utils/server.js.map +1 -0
- package/dist/supabase/utils/web.d.ts +4 -0
- package/dist/supabase/utils/web.d.ts.map +1 -0
- package/dist/supabase/utils/web.js +37 -0
- package/dist/supabase/utils/web.js.map +1 -0
- package/package.json +70 -54
- package/dist/.vite/ssr-manifest.json +0 -209
- package/dist/assets/index-Bai9hEWd.js +0 -16
- package/dist/assets/index-Bai9hEWd.js.map +0 -1
- package/dist/index.html +0 -23
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Utilities - Re-exports from focused handler modules
|
|
3
|
+
*
|
|
4
|
+
* This file preserves backward compatibility. New code should import
|
|
5
|
+
* directly from the focused modules in ./handlers/ instead.
|
|
6
|
+
*
|
|
7
|
+
* @see ./handlers/subscription-handlers.ts
|
|
8
|
+
* @see ./handlers/invoice-handlers.ts
|
|
9
|
+
* @see ./handlers/product-handlers.ts
|
|
10
|
+
* @see ./handlers/customer-handlers.ts
|
|
11
|
+
* @see ./handlers/payment-handlers.ts
|
|
12
|
+
* @see ./handlers/payment-intent.ts
|
|
13
|
+
*/
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { createServerClient } from '../supabase/index.js';
|
|
17
|
+
export function createClient(context) {
|
|
18
|
+
return createServerClient(context);
|
|
19
|
+
}
|
|
20
|
+
export const getURL = () => {
|
|
21
|
+
const filename = fileURLToPath(import.meta.url);
|
|
22
|
+
const dirname = path.dirname(filename);
|
|
23
|
+
let url = path.resolve(dirname);
|
|
24
|
+
url = url.includes('http') ? url : `https://${url}`;
|
|
25
|
+
url = url.charAt(url.length - 1) === '/' ? url : `${url}/`;
|
|
26
|
+
return url;
|
|
27
|
+
};
|
|
28
|
+
// Re-export all handlers for backward compatibility
|
|
29
|
+
export { copyBillingDetailsToCustomer, createOrRetrieveCustomer, createPaymentIntent, handleCheckoutSessionCompleted, handleCustomerCreated, handleCustomerSubscriptionCreated, handleCustomerSubscriptionDeleted, handleCustomerSubscriptionUpdated, handleCustomerUpdated, handleInvoicePaymentFailed, handleInvoicePaymentSucceeded, handlePaymentMethodAttached, handlePaymentMethodCreated, handlePaymentMethodDetached, handlePaymentMethodUpdated, handleSetupIntentFailed, handleSetupIntentSucceeded, handleSupabaseError, manageSubscriptionStatusChange, toDateTime, upsertPriceRecord, upsertProductRecord, upsertRecord, } from './handlers/index.js';
|
|
30
|
+
import { logger } from '@revealui/core/utils/logger';
|
|
31
|
+
import { protectedStripe } from '../stripe/stripeClient.js';
|
|
32
|
+
export const createStripeCustomer = async ({ req, data, operation, }) => {
|
|
33
|
+
const revealuiInstance = req.revealui;
|
|
34
|
+
if (operation === 'create' && !data.stripeCustomerID && typeof data.email === 'string') {
|
|
35
|
+
try {
|
|
36
|
+
const existingCustomer = await protectedStripe.customers.list({
|
|
37
|
+
email: data.email,
|
|
38
|
+
limit: 1,
|
|
39
|
+
});
|
|
40
|
+
if (existingCustomer.data.length > 0 && existingCustomer.data[0]) {
|
|
41
|
+
return {
|
|
42
|
+
...data,
|
|
43
|
+
stripeCustomerID: existingCustomer.data[0].id,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const customer = await protectedStripe.customers.create({
|
|
47
|
+
email: data.email,
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
...data,
|
|
51
|
+
stripeCustomerID: customer.id,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
56
|
+
if (revealuiInstance?.logger) {
|
|
57
|
+
revealuiInstance.logger.error(`Error creating Stripe customer: ${errorMessage}`);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
logger.error('Error creating Stripe customer', { error: errorMessage });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return data;
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/api/utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/webhooks/index.ts"],"names":[],"mappings":"AA6EA,wBAAsB,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAkN9D"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { generateLicenseKey } from '@revealui/core/license';
|
|
2
|
+
import { logger } from '@revealui/core/utils/logger';
|
|
3
|
+
import { getClient } from '@revealui/db/client';
|
|
4
|
+
import { processedWebhookEvents } from '@revealui/db/schema';
|
|
5
|
+
import { eq } from 'drizzle-orm';
|
|
6
|
+
import { protectedStripe } from '../../stripe/stripeClient.js';
|
|
7
|
+
import { createServerClientFromRequest } from '../../supabase/index.js';
|
|
8
|
+
import { handleInvoicePaymentFailed, handleInvoicePaymentSucceeded, handlePaymentMethodAttached, manageSubscriptionStatusChange, upsertPriceRecord, upsertProductRecord, } from '../utils.js';
|
|
9
|
+
const relevantEvents = new Set([
|
|
10
|
+
'product.created',
|
|
11
|
+
'product.updated',
|
|
12
|
+
'price.created',
|
|
13
|
+
'price.updated',
|
|
14
|
+
'checkout.session.completed',
|
|
15
|
+
'customer.subscription.created',
|
|
16
|
+
'customer.subscription.updated',
|
|
17
|
+
'customer.subscription.deleted',
|
|
18
|
+
'invoice.payment_succeeded',
|
|
19
|
+
'invoice.payment_failed',
|
|
20
|
+
'payment_method.attached',
|
|
21
|
+
]);
|
|
22
|
+
/** Maximum webhook payload size (10 MB). Stripe payloads are tiny; this guards against DoS. */
|
|
23
|
+
const MAX_BODY_BYTES = 10 * 1024 * 1024;
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the webhook secret from environment variables.
|
|
26
|
+
* Throws at startup/request time if no secret is configured, preventing
|
|
27
|
+
* unsigned webhook acceptance.
|
|
28
|
+
*/
|
|
29
|
+
function getWebhookSecret() {
|
|
30
|
+
const secret = (typeof import.meta.env.STRIPE_WEBHOOK_SECRET_LIVE === 'string'
|
|
31
|
+
? import.meta.env.STRIPE_WEBHOOK_SECRET_LIVE
|
|
32
|
+
: undefined) ??
|
|
33
|
+
(typeof import.meta.env.STRIPE_WEBHOOK_SECRET === 'string'
|
|
34
|
+
? import.meta.env.STRIPE_WEBHOOK_SECRET
|
|
35
|
+
: undefined);
|
|
36
|
+
if (!secret) {
|
|
37
|
+
throw new Error('STRIPE_WEBHOOK_SECRET or STRIPE_WEBHOOK_SECRET_LIVE must be configured. ' +
|
|
38
|
+
'Refusing to process webhooks without signature verification.');
|
|
39
|
+
}
|
|
40
|
+
return secret;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Database-backed idempotency check. Prevents duplicate webhook processing
|
|
44
|
+
* across Vercel cold starts, multi-region deployments, and server restarts.
|
|
45
|
+
*/
|
|
46
|
+
async function isEventAlreadyProcessed(eventId) {
|
|
47
|
+
const db = getClient();
|
|
48
|
+
const [existing] = await db
|
|
49
|
+
.select({ id: processedWebhookEvents.id })
|
|
50
|
+
.from(processedWebhookEvents)
|
|
51
|
+
.where(eq(processedWebhookEvents.id, eventId))
|
|
52
|
+
.limit(1);
|
|
53
|
+
return !!existing;
|
|
54
|
+
}
|
|
55
|
+
async function markEventProcessed(eventId, eventType) {
|
|
56
|
+
const db = getClient();
|
|
57
|
+
await db.insert(processedWebhookEvents).values({ id: eventId, eventType }).onConflictDoNothing();
|
|
58
|
+
}
|
|
59
|
+
export async function POST(request) {
|
|
60
|
+
const supabase = createServerClientFromRequest(request);
|
|
61
|
+
if (!supabase) {
|
|
62
|
+
return new Response('Supabase client not available', { status: 500 });
|
|
63
|
+
}
|
|
64
|
+
let webhookSecret;
|
|
65
|
+
try {
|
|
66
|
+
webhookSecret = getWebhookSecret();
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
70
|
+
logger.error('Webhook secret not configured', { error: msg });
|
|
71
|
+
return new Response('Webhook endpoint not configured', { status: 500 });
|
|
72
|
+
}
|
|
73
|
+
// Reject oversized payloads before reading into memory (DoS protection).
|
|
74
|
+
// Content-Length is advisory; we still enforce after reading.
|
|
75
|
+
const contentLength = request.headers.get('content-length');
|
|
76
|
+
if (contentLength !== null && Number(contentLength) > MAX_BODY_BYTES) {
|
|
77
|
+
return new Response('Payload too large', { status: 413 });
|
|
78
|
+
}
|
|
79
|
+
const body = await request.text();
|
|
80
|
+
if (body.length > MAX_BODY_BYTES) {
|
|
81
|
+
return new Response('Payload too large', { status: 413 });
|
|
82
|
+
}
|
|
83
|
+
const sig = request.headers.get('Stripe-Signature');
|
|
84
|
+
if (!sig) {
|
|
85
|
+
return new Response('Missing Stripe-Signature header', { status: 400 });
|
|
86
|
+
}
|
|
87
|
+
let event;
|
|
88
|
+
try {
|
|
89
|
+
event = protectedStripe.webhooks.constructEvent(body, sig, webhookSecret);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
|
|
93
|
+
logger.error('Webhook signature verification failed', {
|
|
94
|
+
error: errorMessage,
|
|
95
|
+
});
|
|
96
|
+
return new Response(`Webhook Error: ${errorMessage}`, { status: 400 });
|
|
97
|
+
}
|
|
98
|
+
// Idempotency check: skip already-processed events
|
|
99
|
+
if (await isEventAlreadyProcessed(event.id)) {
|
|
100
|
+
logger.debug('Skipping duplicate webhook event', { eventId: event.id });
|
|
101
|
+
return new Response(JSON.stringify({ received: true, duplicate: true }), { status: 200 });
|
|
102
|
+
}
|
|
103
|
+
if (relevantEvents.has(event.type)) {
|
|
104
|
+
try {
|
|
105
|
+
// Mark event as processing BEFORE handling — prevents duplicate processing
|
|
106
|
+
// on Stripe retries even if the handler throws below.
|
|
107
|
+
await markEventProcessed(event.id, event.type);
|
|
108
|
+
switch (event.type) {
|
|
109
|
+
case 'product.created':
|
|
110
|
+
case 'product.updated': {
|
|
111
|
+
const product = event.data.object;
|
|
112
|
+
if (product.object === 'product') {
|
|
113
|
+
await upsertProductRecord(supabase, product);
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case 'price.created':
|
|
118
|
+
case 'price.updated': {
|
|
119
|
+
const price = event.data.object;
|
|
120
|
+
if (price.object === 'price') {
|
|
121
|
+
await upsertPriceRecord(supabase, price);
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case 'customer.subscription.created':
|
|
126
|
+
case 'customer.subscription.updated':
|
|
127
|
+
case 'customer.subscription.deleted': {
|
|
128
|
+
const subscription = event.data.object;
|
|
129
|
+
if (subscription.object === 'subscription') {
|
|
130
|
+
const customerId = typeof subscription.customer === 'string'
|
|
131
|
+
? subscription.customer
|
|
132
|
+
: subscription.customer?.id;
|
|
133
|
+
if (!customerId) {
|
|
134
|
+
throw new Error('Subscription missing customer');
|
|
135
|
+
}
|
|
136
|
+
await manageSubscriptionStatusChange(subscription.id, customerId, event.type === 'customer.subscription.created', supabase);
|
|
137
|
+
// Flag license for revocation on subscription cancellation
|
|
138
|
+
if (event.type === 'customer.subscription.deleted') {
|
|
139
|
+
logger.info('Subscription deleted — license should be revoked', {
|
|
140
|
+
customerId,
|
|
141
|
+
subscriptionId: subscription.id,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case 'checkout.session.completed': {
|
|
148
|
+
const checkoutSession = event.data.object;
|
|
149
|
+
if (checkoutSession.object === 'checkout.session') {
|
|
150
|
+
const session = checkoutSession;
|
|
151
|
+
if (session.mode === 'subscription' && session.subscription) {
|
|
152
|
+
const subscriptionId = typeof session.subscription === 'string'
|
|
153
|
+
? session.subscription
|
|
154
|
+
: session.subscription.id;
|
|
155
|
+
const customerId = typeof session.customer === 'string' ? session.customer : session.customer?.id;
|
|
156
|
+
if (subscriptionId && customerId) {
|
|
157
|
+
await manageSubscriptionStatusChange(subscriptionId, customerId, true, supabase);
|
|
158
|
+
// Retrieve the subscription to:
|
|
159
|
+
// (a) check if a license key was already generated (idempotency across retries)
|
|
160
|
+
// (b) get the actual price ID for authoritative tier resolution
|
|
161
|
+
const subscription = await protectedStripe.subscriptions.retrieve(subscriptionId);
|
|
162
|
+
if (subscription.metadata?.license_key) {
|
|
163
|
+
// Already generated — Stripe retried the webhook. Safe to skip.
|
|
164
|
+
logger.info('License key already present, skipping generation (idempotent)', {
|
|
165
|
+
customerId,
|
|
166
|
+
subscriptionId,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
const priceId = subscription.items.data[0]?.price?.id ?? null;
|
|
171
|
+
const tier = resolveTierFromMetadata(session.metadata, priceId);
|
|
172
|
+
const privateKey = process.env.REVEALUI_LICENSE_PRIVATE_KEY;
|
|
173
|
+
if (!privateKey) {
|
|
174
|
+
// This is a configuration error, not a customer error.
|
|
175
|
+
// The subscription is active but no license can be issued.
|
|
176
|
+
logger.error('CRITICAL: REVEALUI_LICENSE_PRIVATE_KEY not set — license not generated. ' +
|
|
177
|
+
'Manual intervention required.', { customerId, subscriptionId, tier });
|
|
178
|
+
}
|
|
179
|
+
else if (tier !== 'free') {
|
|
180
|
+
try {
|
|
181
|
+
const licenseKey = await generateLicenseKey({ tier, customerId }, privateKey);
|
|
182
|
+
await protectedStripe.subscriptions.update(subscriptionId, {
|
|
183
|
+
metadata: { license_key: licenseKey, license_tier: tier },
|
|
184
|
+
});
|
|
185
|
+
logger.info('License key generated for checkout', { tier, customerId });
|
|
186
|
+
}
|
|
187
|
+
catch (licenseErr) {
|
|
188
|
+
logger.error('CRITICAL: License generation failed after successful checkout. ' +
|
|
189
|
+
'Manual intervention required.', {
|
|
190
|
+
error: licenseErr instanceof Error ? licenseErr.message : 'Unknown error',
|
|
191
|
+
customerId,
|
|
192
|
+
subscriptionId,
|
|
193
|
+
tier,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
logger.info('Subscription session completed');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
case 'invoice.payment_succeeded': {
|
|
205
|
+
const invoiceEvent = event;
|
|
206
|
+
await handleInvoicePaymentSucceeded(invoiceEvent, supabase);
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case 'invoice.payment_failed': {
|
|
210
|
+
const invoiceEvent = event;
|
|
211
|
+
await handleInvoicePaymentFailed(invoiceEvent, supabase);
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
case 'payment_method.attached': {
|
|
215
|
+
const pmEvent = event;
|
|
216
|
+
await handlePaymentMethodAttached(pmEvent, supabase);
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
default:
|
|
220
|
+
throw new Error('Unhandled relevant event!');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
225
|
+
logger.error('Webhook handler error', { error: errorMessage });
|
|
226
|
+
return new Response(`Webhook error: "Webhook handler failed. View logs." Error: ${errorMessage}`, {
|
|
227
|
+
status: 400,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return new Response(JSON.stringify({ received: true }), { status: 200 });
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Resolve license tier from the subscription's actual price ID and checkout metadata.
|
|
235
|
+
*
|
|
236
|
+
* Price ID takes precedence — it cannot be spoofed because it comes from
|
|
237
|
+
* Stripe's own subscription record, not from client-supplied data.
|
|
238
|
+
* Metadata is a fallback for products that predate the env-var tier mapping.
|
|
239
|
+
*/
|
|
240
|
+
function resolveTierFromMetadata(metadata, priceId) {
|
|
241
|
+
// Primary: validate against known price IDs from environment
|
|
242
|
+
if (priceId) {
|
|
243
|
+
const proPriceId = process.env.STRIPE_PRO_PRICE_ID;
|
|
244
|
+
const maxPriceId = process.env.STRIPE_MAX_PRICE_ID;
|
|
245
|
+
const enterprisePriceId = process.env.STRIPE_ENTERPRISE_PRICE_ID;
|
|
246
|
+
if (proPriceId && priceId === proPriceId)
|
|
247
|
+
return 'pro';
|
|
248
|
+
if (maxPriceId && priceId === maxPriceId)
|
|
249
|
+
return 'max';
|
|
250
|
+
if (enterprisePriceId && priceId === enterprisePriceId)
|
|
251
|
+
return 'enterprise';
|
|
252
|
+
}
|
|
253
|
+
// Fallback: metadata set server-side during checkout session creation (not customer-supplied)
|
|
254
|
+
const tier = metadata?.tier;
|
|
255
|
+
if (tier === 'enterprise')
|
|
256
|
+
return 'enterprise';
|
|
257
|
+
if (tier === 'max')
|
|
258
|
+
return 'max';
|
|
259
|
+
if (tier === 'pro')
|
|
260
|
+
return 'pro';
|
|
261
|
+
// No tier could be resolved — this is a configuration error.
|
|
262
|
+
// Throw so Stripe retries (500) and the team can investigate.
|
|
263
|
+
logger.error('Cannot resolve license tier — unknown price ID and no metadata', {
|
|
264
|
+
priceId,
|
|
265
|
+
metadata,
|
|
266
|
+
});
|
|
267
|
+
throw new Error(`Cannot resolve license tier: price ID "${priceId}" does not match any configured tier. ` +
|
|
268
|
+
'Check STRIPE_PRO_PRICE_ID, STRIPE_MAX_PRICE_ID, and STRIPE_ENTERPRISE_PRICE_ID env vars.');
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/api/webhooks/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* services/client - Client-side Services
|
|
3
|
+
*
|
|
4
|
+
* Client-side service integrations for browser environments:
|
|
5
|
+
* - Browser clients
|
|
6
|
+
*
|
|
7
|
+
* These are client-side implementations for use in React
|
|
8
|
+
* components and browser environments.
|
|
9
|
+
*/
|
|
10
|
+
export { createBrowserClient } from '../supabase/index.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* services/client - Client-side Services
|
|
3
|
+
*
|
|
4
|
+
* Client-side service integrations for browser environments:
|
|
5
|
+
* - Browser clients
|
|
6
|
+
*
|
|
7
|
+
* These are client-side implementations for use in React
|
|
8
|
+
* components and browser environments.
|
|
9
|
+
*/
|
|
10
|
+
// Export browser client creation functions
|
|
11
|
+
export { createBrowserClient } from '../supabase/index.js';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,2CAA2C;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* services - Shared Services Package
|
|
3
|
+
*
|
|
4
|
+
* Provides server-side and client-side service integrations:
|
|
5
|
+
* - Stripe payment processing
|
|
6
|
+
* - Supabase database and auth
|
|
7
|
+
* - API routes
|
|
8
|
+
*
|
|
9
|
+
* ## Usage
|
|
10
|
+
*
|
|
11
|
+
* ### Full Package (Recommended)
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { protectedStripe, createServerClient, createBrowserClient } from 'services'
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* ### Core (Server-side)
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { createServerClient, protectedStripe } from 'services/server'
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* ### Client (Browser)
|
|
22
|
+
* ```typescript
|
|
23
|
+
* import { createBrowserClient } from 'services/client'
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Check if payment services are licensed for use.
|
|
28
|
+
* Returns false with a warning log if no Pro/Enterprise license is active.
|
|
29
|
+
*/
|
|
30
|
+
export declare function checkServicesLicense(): boolean;
|
|
31
|
+
export * from './api/index.js';
|
|
32
|
+
export * from './client/index.js';
|
|
33
|
+
export * from './stripe/index.js';
|
|
34
|
+
export * from './supabase/index.js';
|
|
35
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAKH;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAS9C;AAGD,cAAc,gBAAgB,CAAA;AAE9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA;AACjC,cAAc,qBAAqB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* services - Shared Services Package
|
|
3
|
+
*
|
|
4
|
+
* Provides server-side and client-side service integrations:
|
|
5
|
+
* - Stripe payment processing
|
|
6
|
+
* - Supabase database and auth
|
|
7
|
+
* - API routes
|
|
8
|
+
*
|
|
9
|
+
* ## Usage
|
|
10
|
+
*
|
|
11
|
+
* ### Full Package (Recommended)
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { protectedStripe, createServerClient, createBrowserClient } from 'services'
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* ### Core (Server-side)
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { createServerClient, protectedStripe } from 'services/server'
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* ### Client (Browser)
|
|
22
|
+
* ```typescript
|
|
23
|
+
* import { createBrowserClient } from 'services/client'
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
import { isFeatureEnabled } from '@revealui/core/features';
|
|
27
|
+
import { logger } from '@revealui/core/observability/logger';
|
|
28
|
+
/**
|
|
29
|
+
* Check if payment services are licensed for use.
|
|
30
|
+
* Returns false with a warning log if no Pro/Enterprise license is active.
|
|
31
|
+
*/
|
|
32
|
+
export function checkServicesLicense() {
|
|
33
|
+
if (!isFeatureEnabled('payments')) {
|
|
34
|
+
logger.warn('[@revealui/services] Payment and service integrations require a Pro or Enterprise license. ' +
|
|
35
|
+
'Visit https://revealui.com/pricing for details.');
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
// Re-export core (server-side) exports
|
|
41
|
+
export * from './api/index.js';
|
|
42
|
+
// Re-export client (client-side) exports
|
|
43
|
+
export * from './client/index.js';
|
|
44
|
+
export * from './stripe/index.js';
|
|
45
|
+
export * from './supabase/index.js';
|
|
46
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAA;AAE5D;;;GAGG;AACH,MAAM,UAAU,oBAAoB;IAClC,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,EAAE,CAAC;QAClC,MAAM,CAAC,IAAI,CACT,6FAA6F;YAC3F,iDAAiD,CACpD,CAAA;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,uCAAuC;AACvC,cAAc,gBAAgB,CAAA;AAC9B,yCAAyC;AACzC,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA;AACjC,cAAc,qBAAqB,CAAA"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DB-Backed Circuit Breaker
|
|
3
|
+
*
|
|
4
|
+
* Stores circuit state in NeonDB so all API instances share the same view.
|
|
5
|
+
*
|
|
6
|
+
* Architecture:
|
|
7
|
+
* - Local in-memory cache (5s TTL) — fast read path, no DB hit per request
|
|
8
|
+
* - DB write only on state transitions (open/closed/half-open changes)
|
|
9
|
+
* - Fail-open on DB errors: if we can't read state, we let the call through
|
|
10
|
+
* rather than blocking all traffic because the circuit state store is down
|
|
11
|
+
*/
|
|
12
|
+
export interface DbCircuitBreakerConfig {
|
|
13
|
+
/** Number of consecutive failures before tripping. Default: 5 */
|
|
14
|
+
failureThreshold: number;
|
|
15
|
+
/** Consecutive successes in half-open to close the circuit. Default: 2 */
|
|
16
|
+
successThreshold: number;
|
|
17
|
+
/** Milliseconds to wait in open state before probing. Default: 30_000 */
|
|
18
|
+
resetTimeout: number;
|
|
19
|
+
/** Local cache TTL in milliseconds. Default: 5_000 */
|
|
20
|
+
cacheTtlMs: number;
|
|
21
|
+
}
|
|
22
|
+
export declare class DbCircuitBreaker {
|
|
23
|
+
private readonly serviceName;
|
|
24
|
+
private readonly config;
|
|
25
|
+
constructor(serviceName: string, config?: Partial<DbCircuitBreakerConfig>);
|
|
26
|
+
/**
|
|
27
|
+
* Returns true if the circuit is open (requests should be blocked).
|
|
28
|
+
* Automatically transitions open→half-open when resetTimeout elapses.
|
|
29
|
+
*/
|
|
30
|
+
isOpen(): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* Record a successful call. Closes the circuit if enough successes in half-open.
|
|
33
|
+
*/
|
|
34
|
+
recordSuccess(): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Record a failed call. Trips the circuit when the failure threshold is reached.
|
|
37
|
+
*/
|
|
38
|
+
recordFailure(): Promise<void>;
|
|
39
|
+
/** Force-reset state and clear local cache. Primarily for testing. */
|
|
40
|
+
reset(): Promise<void>;
|
|
41
|
+
/** Clear only the local cache (forces next read to hit DB). For testing. */
|
|
42
|
+
clearLocalCache(): void;
|
|
43
|
+
private readState;
|
|
44
|
+
private readFromDb;
|
|
45
|
+
private writeState;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=db-circuit-breaker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-circuit-breaker.d.ts","sourceRoot":"","sources":["../../src/stripe/db-circuit-breaker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAoBH,MAAM,WAAW,sBAAsB;IACrC,iEAAiE;IACjE,gBAAgB,EAAE,MAAM,CAAC;IACzB,0EAA0E;IAC1E,gBAAgB,EAAE,MAAM,CAAC;IACzB,yEAAyE;IACzE,YAAY,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,UAAU,EAAE,MAAM,CAAC;CACpB;AAYD,qBAAa,gBAAgB;IAIzB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAH9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;gBAG7B,WAAW,EAAE,MAAM,EACpC,MAAM,GAAE,OAAO,CAAC,sBAAsB,CAAM;IAK9C;;;OAGG;IACG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAsBhC;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IA8BpC;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAyBpC,sEAAsE;IAChE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B,4EAA4E;IAC5E,eAAe,IAAI,IAAI;YAMT,SAAS;YAQT,UAAU;YA+CV,UAAU;CA2CzB"}
|