mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-20 21:46:41 +02:00
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>
93 lines
2.9 KiB
TypeScript
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(),
|
|
});
|