Onboarding flow
This commit is contained in:
@@ -204,11 +204,11 @@ const resolveIdempotencyKey = (request) => {
|
||||
return '';
|
||||
};
|
||||
|
||||
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.');
|
||||
const ensureNotGuest = (userId, requiredCredits) => {
|
||||
// Guests may use the limited pre-auth demo identification scan. Other
|
||||
// endpoints must not consume credits from the shared 'guest' account.
|
||||
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 };
|
||||
@@ -726,17 +726,20 @@ app.post('/v1/scan', async (request, response) => {
|
||||
let modelUsed = null;
|
||||
let modelFallbackCount = 0;
|
||||
|
||||
const [accountSnapshot, catalogEntries] = await Promise.all([
|
||||
getAccountSnapshot(db, userId),
|
||||
getCachedCatalogEntries(db),
|
||||
]);
|
||||
ensureNotGuest(userId, SCAN_PRIMARY_COST);
|
||||
creditsCharged += await consumeCreditsWithIdempotency(
|
||||
db,
|
||||
userId,
|
||||
chargeKey('scan-primary', userId, idempotencyKey),
|
||||
SCAN_PRIMARY_COST,
|
||||
);
|
||||
const [accountSnapshot, catalogEntries] = await Promise.all([
|
||||
getAccountSnapshot(db, userId),
|
||||
getCachedCatalogEntries(db),
|
||||
]);
|
||||
if (isGuest(userId)) {
|
||||
modelPath.push('guest-demo-no-credit');
|
||||
} else {
|
||||
creditsCharged += await consumeCreditsWithIdempotency(
|
||||
db,
|
||||
userId,
|
||||
chargeKey('scan-primary', userId, idempotencyKey),
|
||||
SCAN_PRIMARY_COST,
|
||||
);
|
||||
}
|
||||
|
||||
// Free tier gets the same model quality; quantity (3 credits/month) is the differentiator.
|
||||
const scanPlan = 'pro';
|
||||
@@ -764,8 +767,14 @@ app.post('/v1/scan', async (request, response) => {
|
||||
usedOpenAi = true;
|
||||
modelUsed = openAiPrimary.modelUsed || modelUsed;
|
||||
modelPath.push('openai-primary');
|
||||
if (grounded.grounded) modelPath.push('catalog-grounded-primary');
|
||||
} else {
|
||||
if (grounded.grounded) modelPath.push('catalog-grounded-primary');
|
||||
} else {
|
||||
if (isGuest(userId)) {
|
||||
const error = new Error('AI demo scan failed. Please try again with a clearer plant photo.');
|
||||
error.code = 'PROVIDER_ERROR';
|
||||
error.status = 502;
|
||||
throw error;
|
||||
}
|
||||
console.warn(`OpenAI primary identification returned null for user ${userId} — using catalog fallback.`, {
|
||||
attemptedModels: openAiPrimary?.attemptedModels,
|
||||
plant: result?.name,
|
||||
@@ -773,8 +782,14 @@ app.post('/v1/scan', async (request, response) => {
|
||||
modelPath.push('openai-primary-failed');
|
||||
modelPath.push('catalog-primary-fallback');
|
||||
}
|
||||
} else {
|
||||
console.log(`OpenAI not configured, using catalog fallback for user ${userId}`);
|
||||
} else {
|
||||
if (isGuest(userId)) {
|
||||
const error = new Error('AI demo scan is unavailable. Please configure OpenAI before enabling guest demo scans.');
|
||||
error.code = 'PROVIDER_ERROR';
|
||||
error.status = 502;
|
||||
throw error;
|
||||
}
|
||||
console.log(`OpenAI not configured, using catalog fallback for user ${userId}`);
|
||||
modelPath.push('openai-not-configured');
|
||||
modelPath.push('catalog-primary-fallback');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user