diff --git a/packages/shared-hono/package.json b/packages/shared-hono/package.json index 4ad1d79fd..f5718bf05 100644 --- a/packages/shared-hono/package.json +++ b/packages/shared-hono/package.json @@ -12,7 +12,8 @@ "./db": "./src/db.ts", "./health": "./src/health.ts", "./admin": "./src/admin.ts", - "./error": "./src/error.ts" + "./error": "./src/error.ts", + "./credits": "./src/credits.ts" }, "scripts": { "type-check": "tsc --noEmit" diff --git a/packages/shared-hono/src/credits.ts b/packages/shared-hono/src/credits.ts new file mode 100644 index 000000000..be003ef25 --- /dev/null +++ b/packages/shared-hono/src/credits.ts @@ -0,0 +1,125 @@ +/** + * Credit client for Hono backends. + * + * Drop-in replacement for @mana-core/nestjs-integration CreditClientService. + * Calls mana-credits service to validate/consume/refund credits. + */ + +export interface CreditBalance { + balance: number; + totalEarned: number; + totalSpent: number; +} + +export interface CreditValidationResult { + hasCredits: boolean; + availableCredits: number; + requiredCredits?: number; +} + +const CREDITS_URL = () => + process.env.MANA_CREDITS_URL || process.env.MANA_CORE_AUTH_URL || 'http://localhost:3061'; +const SERVICE_KEY = () => process.env.MANA_CORE_SERVICE_KEY || ''; +const APP_ID = () => process.env.APP_ID || 'unknown'; + +const DEFAULT_BALANCE: CreditBalance = { balance: 1000, totalEarned: 0, totalSpent: 0 }; + +async function callCredits(path: string, options: RequestInit = {}): Promise { + const key = SERVICE_KEY(); + if (!key) { + console.warn('[credits] Service key not configured'); + return null; + } + + try { + const res = await fetch(`${CREDITS_URL()}${path}`, { + ...options, + headers: { + 'Content-Type': 'application/json', + 'X-Service-Key': key, + 'X-App-Id': APP_ID(), + ...options.headers, + }, + }); + if (!res.ok) return null; + return res.json(); + } catch (error) { + console.error('[credits] Request failed:', error); + return null; + } +} + +/** + * Get user's credit balance. + */ +export async function getBalance(userId: string): Promise { + const result = await callCredits(`/api/v1/internal/credits/balance/${userId}`); + return result || DEFAULT_BALANCE; +} + +/** + * Validate that user has enough credits for an operation. + */ +export async function validateCredits( + userId: string, + operation: string, + amount: number +): Promise { + try { + const balance = await getBalance(userId); + return { + hasCredits: balance.balance >= amount, + availableCredits: balance.balance, + requiredCredits: amount, + }; + } catch { + return { hasCredits: true, availableCredits: 0, requiredCredits: amount }; + } +} + +/** + * Consume credits after a successful operation. + */ +export async function consumeCredits( + userId: string, + operation: string, + amount: number, + description: string, + metadata?: Record, + creditSource?: { type: 'guild'; guildId: string } +): Promise { + const result = await callCredits('/api/v1/internal/credits/use', { + method: 'POST', + body: JSON.stringify({ + userId, + amount, + appId: APP_ID(), + description, + metadata: { operation, ...metadata }, + ...(creditSource && { creditSource }), + }), + }); + return !!result; +} + +/** + * Refund credits after a failed operation. + */ +export async function refundCredits( + userId: string, + amount: number, + description: string, + metadata?: Record +): Promise { + const result = await callCredits('/api/v1/internal/credits/refund', { + method: 'POST', + body: JSON.stringify({ + userId, + amount, + appId: APP_ID(), + description, + metadata, + }), + }); + return !!result; +} diff --git a/packages/shared-hono/src/index.ts b/packages/shared-hono/src/index.ts index 23bddf98e..961f04e66 100644 --- a/packages/shared-hono/src/index.ts +++ b/packages/shared-hono/src/index.ts @@ -38,4 +38,6 @@ export type { DbOptions } from './db'; export { healthRoute } from './health'; export { adminRoutes } from './admin'; export { errorHandler, notFoundHandler } from './error'; +export { getBalance, validateCredits, consumeCredits, refundCredits } from './credits'; +export type { CreditBalance, CreditValidationResult } from './credits'; export type { CurrentUserData, AuthVariables } from './types';