managarten/services/mana-credits/src/lib/validation.ts
Till JS 004fc0b2fd feat(credits): add 2-phase debit (reserve/commit/refund)
Introduces credit_reservations table + three internal endpoints so services
that need to charge only after a downstream call succeeds (notably the
upcoming mana-research fan-out across paid provider APIs) can reserve
credits atomically, then commit on success or refund on failure. One-shot
/credits/use remains for synchronous operations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 14:41:41 +02:00

93 lines
2.9 KiB
TypeScript

/**
* Zod schemas for request body validation.
*/
import { z } from 'zod';
// ─── Credits ────────────────────────────────────────────────
export const useCreditsSchema = z.object({
amount: z.number().positive(),
appId: z.string().min(1),
description: z.string().min(1),
idempotencyKey: z.string().optional(),
metadata: z.record(z.unknown()).optional(),
});
export const purchaseCreditsSchema = z.object({
packageId: z.string().uuid(),
});
export const createPaymentLinkSchema = z.object({
packageId: z.string().uuid(),
quantity: z.number().int().positive().default(1),
});
// ─── Gifts ──────────────────────────────────────────────────
export const createGiftSchema = z.object({
totalCredits: z.number().int().positive().min(1).max(10000),
type: z.enum(['simple', 'personalized']).default('simple'),
targetEmail: z.string().email().optional(),
message: z.string().max(500).optional(),
expirationDays: z.number().int().positive().optional(),
});
export const redeemGiftSchema = z.object({
sourceAppId: z.string().optional(),
});
// ─── Sync ──────────────────────────────────────────────────
export const activateSyncSchema = z.object({
interval: z.enum(['monthly', 'quarterly', 'yearly']).default('monthly'),
});
export const changeSyncIntervalSchema = z.object({
interval: z.enum(['monthly', 'quarterly', 'yearly']),
});
// ─── Internal (Service-to-Service) ──────────────────────────
export const internalUseCreditsSchema = z.object({
userId: z.string().min(1),
amount: z.number().positive(),
appId: z.string().min(1),
description: z.string().min(1),
idempotencyKey: z.string().optional(),
metadata: z.record(z.unknown()).optional(),
});
export const internalRefundSchema = z.object({
userId: z.string().min(1),
amount: z.number().positive(),
description: z.string().min(1),
appId: z.string().default('system'),
metadata: z.record(z.unknown()).optional(),
});
export const internalInitSchema = z.object({
userId: z.string().min(1),
});
export const internalRedeemPendingSchema = z.object({
userId: z.string().min(1),
email: z.string().email(),
});
// ─── Reservations (2-phase debit) ──────────────────────────
export const internalReserveSchema = z.object({
userId: z.string().min(1),
amount: z.number().int().positive(),
reason: z.string().min(1).max(200),
});
export const internalCommitSchema = z.object({
reservationId: z.string().uuid(),
description: z.string().max(500).optional(),
});
export const internalRefundReservationSchema = z.object({
reservationId: z.string().uuid(),
});