From b13c9dd914fd1cfc19b25ea8e5d5c4b403ed6192 Mon Sep 17 00:00:00 2001 From: Till JS Date: Tue, 19 May 2026 00:11:05 +0200 Subject: [PATCH] fix(marketplace): credits-client camelCase + grant() + referenceId MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validierung gegen mana-credits internalReserveSchema / internalGrantSchema: - snake_case → camelCase (userId, appId, reservationId, referenceId) Vorher hätte jeder reserve-Call 400 geworfen — fiel nicht auf, weil Paid-Decks dormant sind. - grant()-Methode neu: für Author-Payouts beim Paid-Deck-Live-Schalten. Braucht referenceId (Pflicht, max 128 chars) + reason (max 64). - appId 'wordeck' statt 'cards' (Brand-Konsistenz mit ev.mana.wordeck- Rebrand 2026-05-17). - commit() akzeptiert optionalen description-Parameter. - Response-Type: reservationId/grantId statt snake_case. Bereitet den Live-Bezahl-Flow vor (immer noch dormant). Synchron mit Comicello-Fix vom 2026-05-19. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/api/src/services/credits-client.ts | 45 +++++++++++++++++++++---- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/apps/api/src/services/credits-client.ts b/apps/api/src/services/credits-client.ts index 530bbd5..db743ef 100644 --- a/apps/api/src/services/credits-client.ts +++ b/apps/api/src/services/credits-client.ts @@ -39,27 +39,32 @@ export class CreditsClient { /** * Reserviert Credits — `commit` oder `refund-reservation` muss * folgen. Pattern für teure asynchrone Operationen. + * + * Verifiziert gegen mana-credits internalReserveSchema (2026-05-19): + * camelCase-Felder, appId Pflicht seit Verein-Konsolidierung + * 2026-05-05. Korrektur vom 2026-05-19 (vorher snake_case — wäre + * 400 geworden, fiel nur nicht auf weil Paid-Decks dormant sind). */ async reserve(input: { userId: string; amount: number; reason: string }) { const r = await fetch(`${CREDITS_URL}/api/v1/internal/credits/reserve`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...authHeader() }, body: JSON.stringify({ - user_id: input.userId, + userId: input.userId, amount: input.amount, reason: input.reason, - app_id: 'cards', + appId: 'wordeck', }), }); if (!r.ok) throw new Error(`mana-credits reserve ${r.status}`); - return r.json() as Promise<{ reservation_id: string }>; + return r.json() as Promise<{ reservationId: string; balance: number }>; } - async commit(reservationId: string) { + async commit(reservationId: string, description?: string) { const r = await fetch(`${CREDITS_URL}/api/v1/internal/credits/commit`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...authHeader() }, - body: JSON.stringify({ reservation_id: reservationId }), + body: JSON.stringify({ reservationId, description }), }); if (!r.ok) throw new Error(`mana-credits commit ${r.status}`); return r.json(); @@ -69,11 +74,39 @@ export class CreditsClient { const r = await fetch(`${CREDITS_URL}/api/v1/internal/credits/refund-reservation`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...authHeader() }, - body: JSON.stringify({ reservation_id: reservationId }), + body: JSON.stringify({ reservationId }), }); if (!r.ok) throw new Error(`mana-credits refund ${r.status}`); return r.json(); } + + /** + * Gewährt Credits an einen User (Author-Payout). `referenceId` ist + * Pflicht (Reconcile-Anker), `reason` max 64 chars. + * + * Reason-Konvention: `wordeck.payout.`. + */ + async grant(input: { + userId: string; + amount: number; + reason: string; + referenceId: string; + description?: string; + }) { + const r = await fetch(`${CREDITS_URL}/api/v1/internal/credits/grant`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', ...authHeader() }, + body: JSON.stringify({ + userId: input.userId, + amount: input.amount, + reason: input.reason, + referenceId: input.referenceId, + description: input.description, + }), + }); + if (!r.ok) throw new Error(`mana-credits grant ${r.status}`); + return r.json() as Promise<{ grantId: string }>; + } } let cached: CreditsClient | null = null;