feat(server): remove hard paywall — credits gate scans, guests stay blocked, free tier gets pro model
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -203,17 +203,15 @@ const resolveIdempotencyKey = (request) => {
|
||||
return '';
|
||||
};
|
||||
|
||||
const createHardPaywallError = (requiredCredits) => {
|
||||
const error = new Error('Active Pro or trial entitlement required.');
|
||||
error.code = 'INSUFFICIENT_CREDITS';
|
||||
error.status = 402;
|
||||
error.metadata = { required: requiredCredits, available: 0 };
|
||||
return error;
|
||||
};
|
||||
|
||||
const ensureActiveProEntitlement = (accountSnapshot, requiredCredits) => {
|
||||
if (!accountSnapshot || accountSnapshot.plan !== 'pro') {
|
||||
throw createHardPaywallError(requiredCredits);
|
||||
const ensureNotGuest = (userId, requiredCredits) => {
|
||||
// Guests use the client-side demo scan; the shared 'guest' billing account
|
||||
// must never consume real credits or run free AI analyses.
|
||||
if (isGuest(userId)) {
|
||||
const error = new Error('Sign in to use scan credits.');
|
||||
error.code = 'INSUFFICIENT_CREDITS';
|
||||
error.status = 402;
|
||||
error.metadata = { required: requiredCredits, available: 0 };
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -731,7 +729,7 @@ app.post('/v1/scan', async (request, response) => {
|
||||
getAccountSnapshot(db, userId),
|
||||
getCachedCatalogEntries(db),
|
||||
]);
|
||||
ensureActiveProEntitlement(accountSnapshot, SCAN_PRIMARY_COST);
|
||||
ensureNotGuest(userId, SCAN_PRIMARY_COST);
|
||||
creditsCharged += await consumeCreditsWithIdempotency(
|
||||
db,
|
||||
userId,
|
||||
@@ -739,7 +737,8 @@ app.post('/v1/scan', async (request, response) => {
|
||||
SCAN_PRIMARY_COST,
|
||||
);
|
||||
|
||||
const scanPlan = accountSnapshot.plan === 'pro' ? 'pro' : 'free';
|
||||
// Free tier gets the same model quality; quantity (3 credits/month) is the differentiator.
|
||||
const scanPlan = 'pro';
|
||||
let result = pickCatalogFallback(catalogEntries, imageUri, false, { silent: true });
|
||||
let usedOpenAi = false;
|
||||
let rawPrimaryResult = null;
|
||||
@@ -903,7 +902,7 @@ app.post('/v1/search/semantic', async (request, response) => {
|
||||
}
|
||||
|
||||
const accountSnapshot = await getAccountSnapshot(db, userId);
|
||||
ensureActiveProEntitlement(accountSnapshot, SEMANTIC_SEARCH_COST);
|
||||
ensureNotGuest(userId, SEMANTIC_SEARCH_COST);
|
||||
|
||||
const creditsCharged = await consumeCreditsWithIdempotency(
|
||||
db,
|
||||
@@ -943,7 +942,7 @@ app.post('/v1/health-check', async (request, response) => {
|
||||
}
|
||||
|
||||
const accountSnapshot = await getAccountSnapshot(db, userId);
|
||||
ensureActiveProEntitlement(accountSnapshot, HEALTH_CHECK_COST);
|
||||
ensureNotGuest(userId, HEALTH_CHECK_COST);
|
||||
|
||||
if (!isOpenAiConfigured()) {
|
||||
const error = new Error('OpenAI health check is unavailable. Please configure OPENAI_API_KEY.');
|
||||
|
||||
Reference in New Issue
Block a user