fix(marketplace): credits-client camelCase + grant() + referenceId
Some checks are pending
CI / validate (push) Waiting to run

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) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-05-19 00:11:05 +02:00
parent 070cb44de8
commit b13c9dd914

View file

@ -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.<purchase_id>`.
*/
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;