# Manadeck Credit System This document explains how the Mana Core credit system is integrated into Manadeck. ## Overview Manadeck uses **Mana** as its credit currency to charge for operations like deck creation, card generation, and AI features. The credit system is powered by [Mana Core](https://github.com/Memo-2023/mana-core-nestjs-package), which provides: - Credit validation before operations - Credit consumption after successful operations - Real-time balance tracking - Transaction history - App-level usage tracking ## Credit Costs | Operation | Cost (Mana) | Description | |-----------|-------------|-------------| | Deck Creation | 10 | Create a new deck | | Card Creation | 2 | Add a single card to a deck | | AI Card Generation | 5 | Generate a card using AI | | Deck Export | 3 | Export a deck to various formats | These costs are defined in `backend/src/config/credit-operations.ts`. ## Architecture ``` ┌─────────────┐ │ Frontend │ │ (React Native) └─────┬───────┘ │ 1. Create Deck Request │ POST /api/decks │ { name, description } ▼ ┌─────────────┐ │ Backend │ │ (NestJS) │ ├─────────────┤ │ API │ 2. Validate Credits (10 mana) │ Controller │ ├─ Has credits? → Continue │ │ └─ No credits? → Return 400 │ │ │ │ 3. Create Deck (business logic) │ │ │ │ 4. Consume Credits (10 mana) │ │ │ │ 5. Return Success + Credits Used └─────┬───────┘ │ ▼ ┌─────────────┐ │ Mana Core │ - Validate balance │ Service │ - Deduct credits │ │ - Record transaction └─────────────┘ ``` ## Backend Integration ### 1. Service Key Configuration The backend needs a service key from Mana Core to perform credit operations. **backend/.env**: ```env SERVICE_KEY=your-service-key-from-mana-core ``` **backend/src/app.module.ts**: ```typescript ManaCoreModule.forRootAsync({ imports: [ConfigModule], useFactory: (configService: ConfigService) => ({ serviceKey: configService.get('SERVICE_KEY', ''), // ... other config }), inject: [ConfigService], }) ``` ### 2. Credit Operations **backend/src/config/credit-operations.ts**: ```typescript export enum CreditOperationType { DECK_CREATION = 'deck_creation', CARD_CREATION = 'card_creation', // Add more as needed } export const CREDIT_COSTS: Record = { [CreditOperationType.DECK_CREATION]: 10, [CreditOperationType.CARD_CREATION]: 2, }; ``` ### 3. Controller Implementation **backend/src/controllers/api.controller.ts**: ```typescript import { CreditClientService } from '@mana-core/nestjs-integration/services'; import { CreditOperationType, getCreditCost } from '../config/credit-operations'; @Controller('api') @UseGuards(AuthGuard) export class ApiController { constructor(private readonly creditClient: CreditClientService) {} @Post('decks') async createDeck(@CurrentUser() user: any, @Body() deckData: any) { const operationType = CreditOperationType.DECK_CREATION; const creditCost = getCreditCost(operationType); // 1. Validate credits BEFORE operation const validation = await this.creditClient.validateCredits( user.id, operationType, creditCost, ); if (!validation.hasCredits) { throw new BadRequestException({ error: 'insufficient_credits', message: `Insufficient mana. Required: ${creditCost}, Available: ${validation.availableCredits}`, requiredCredits: creditCost, availableCredits: validation.availableCredits, }); } // 2. Perform the operation const newDeck = await this.createDeckInDatabase(deckData); // 3. Consume credits AFTER success await this.creditClient.consumeCredits( user.id, operationType, creditCost, `Created deck: ${deckData.name}`, { deckId: newDeck.id }, ); return { success: true, deck: newDeck, creditsUsed: creditCost }; } } ``` ### 4. Credit Balance Endpoint Get user's current credit balance: ```typescript @Get('credits/balance') async getCreditBalance(@CurrentUser() user: any) { const balance = await this.creditClient.getCreditBalance(user.id); return { userId: user.id, balance: balance.balance || 0, currency: 'mana', }; } ``` ## Frontend Integration ### 1. Types **apps/mobile/types/credits.ts**: ```typescript export interface InsufficientCreditsError { error: 'insufficient_credits'; message: string; requiredCredits: number; availableCredits: number; operation?: string; } export function isInsufficientCreditsError(error: any): boolean { return error && error.error === 'insufficient_credits'; } ``` ### 2. Credit Service **apps/mobile/services/creditService.ts**: ```typescript export const creditService = { async getBalance(): Promise { const response = await get(`${API_URL}/api/credits/balance`); return response.balance || 0; }, }; ``` ### 3. Insufficient Credits Modal **apps/mobile/components/InsufficientCreditsModal.tsx**: A pre-built modal component that displays: - Required vs available credits - Shortfall amount - "Get More Mana" button (optional) - Cancel button ### 4. Hook for Easy Integration **apps/mobile/hooks/useInsufficientCredits.ts**: ```typescript export function useInsufficientCredits() { // ... state management return { visible, requiredCredits, availableCredits, operation, handleCreditError, // Automatically shows modal for credit errors hideInsufficientCredits, }; } ``` ### 5. Usage Example **In any screen that creates a deck**: ```typescript import { useInsufficientCredits } from '../hooks/useInsufficientCredits'; import { InsufficientCreditsModal } from '../components/InsufficientCreditsModal'; import { creditService } from '../services/creditService'; function DeckCreationScreen() { const [creditBalance, setCreditBalance] = useState(0); const insufficientCredits = useInsufficientCredits(); // Load balance useEffect(() => { creditService.getBalance().then(setCreditBalance); }, []); const handleCreateDeck = async () => { try { const response = await post('/api/decks', deckData); Alert.alert('Success', `Deck created! ${response.creditsUsed} mana used.`); // Refresh balance const newBalance = await creditService.getBalance(); setCreditBalance(newBalance); } catch (error) { // Automatically shows modal if it's a credit error if (!insufficientCredits.handleCreditError(error)) { // Handle other errors Alert.alert('Error', error.message); } } }; return ( {/* Show balance */} Your Mana: {creditBalance} ⚡ {/* Create deck button */}