Share funktion
This commit is contained in:
@@ -46,6 +46,7 @@ const {
|
||||
const {
|
||||
chargeKey,
|
||||
consumeCreditsWithIdempotency,
|
||||
refundCreditsWithIdempotency,
|
||||
endpointKey,
|
||||
ensureBillingSchema,
|
||||
getAccountSnapshot,
|
||||
@@ -757,9 +758,11 @@ app.post('/v1/billing/sync-revenuecat', async (request, response) => {
|
||||
|
||||
app.post('/v1/scan', async (request, response) => {
|
||||
let userId = 'unknown';
|
||||
let idempotencyKey = null;
|
||||
let creditsCharged = 0;
|
||||
try {
|
||||
userId = ensureRequestAuth(request);
|
||||
const idempotencyKey = ensureNonEmptyString(resolveIdempotencyKey(request), 'Idempotency-Key header');
|
||||
idempotencyKey = ensureNonEmptyString(resolveIdempotencyKey(request), 'Idempotency-Key header');
|
||||
const imageUri = ensureNonEmptyString(request.body?.imageUri, 'imageUri');
|
||||
const language = normalizeLanguage(request.body?.language);
|
||||
const endpointId = endpointKey('scan', userId, idempotencyKey);
|
||||
@@ -770,7 +773,6 @@ app.post('/v1/scan', async (request, response) => {
|
||||
return;
|
||||
}
|
||||
|
||||
let creditsCharged = 0;
|
||||
const modelPath = [];
|
||||
let modelUsed = null;
|
||||
let modelFallbackCount = 0;
|
||||
@@ -936,6 +938,19 @@ app.post('/v1/scan', async (request, response) => {
|
||||
response.status(200).json(payload);
|
||||
} catch (error) {
|
||||
console.error(`Scan error for user ${userId}:`, error);
|
||||
if (error?.code === 'NOT_A_PLANT' && creditsCharged > 0 && idempotencyKey) {
|
||||
try {
|
||||
const refunded = await refundCreditsWithIdempotency(
|
||||
db,
|
||||
userId,
|
||||
chargeKey('scan-refund', userId, idempotencyKey),
|
||||
creditsCharged,
|
||||
);
|
||||
console.log(`Refunded ${refunded} credit(s) for user ${userId} (NOT_A_PLANT)`);
|
||||
} catch (refundError) {
|
||||
console.error(`Failed to refund credits for user ${userId} (NOT_A_PLANT):`, refundError);
|
||||
}
|
||||
}
|
||||
const payload = toApiErrorPayload(error);
|
||||
response.status(payload.status).json(payload.body);
|
||||
}
|
||||
|
||||
@@ -623,6 +623,37 @@ const consumeCreditsWithIdempotency = async (db, userId, key, cost) => {
|
||||
});
|
||||
};
|
||||
|
||||
const refundCredits = (account, amount) => {
|
||||
if (amount <= 0) return 0;
|
||||
|
||||
// Spiegelbildlich zu consumeCredits: zuerst das Monatskontingent entlasten,
|
||||
// ein etwaiger Rest wandert auf das Topup-Guthaben zurück.
|
||||
let remaining = amount;
|
||||
const monthlyRefund = Math.min(account.usedThisCycle, remaining);
|
||||
account.usedThisCycle -= monthlyRefund;
|
||||
remaining -= monthlyRefund;
|
||||
|
||||
if (remaining > 0) {
|
||||
account.topupBalance += remaining;
|
||||
}
|
||||
|
||||
return amount;
|
||||
};
|
||||
|
||||
const refundCreditsWithIdempotency = async (db, userId, key, amount) => {
|
||||
return runInTransaction(db, async (tx) => {
|
||||
const existing = await readIdempotentValue(tx, key);
|
||||
if (existing && typeof existing.refunded === 'number') return existing.refunded;
|
||||
|
||||
const account = await getOrCreateAccount(tx, userId);
|
||||
const refunded = refundCredits(account, amount);
|
||||
account.updatedAt = nowIso();
|
||||
await upsertAccount(tx, account);
|
||||
await writeIdempotentValue(tx, key, { refunded });
|
||||
return refunded;
|
||||
});
|
||||
};
|
||||
|
||||
const getBillingSummary = async (db, userId) => {
|
||||
if (userId === 'guest') {
|
||||
return {
|
||||
@@ -820,6 +851,7 @@ module.exports = {
|
||||
chargeKey,
|
||||
claimNotificationOnce,
|
||||
consumeCreditsWithIdempotency,
|
||||
refundCreditsWithIdempotency,
|
||||
endpointKey,
|
||||
ensureBillingSchema,
|
||||
getAccountSnapshot,
|
||||
|
||||
Reference in New Issue
Block a user