/** * ComplianceManager.js * Manages underage protection, probability disclosure, and anti-cheat measures. * Ensures compliance with Chinese gaming regulations and WeChat platform rules. */ class ComplianceManager { constructor() { this._isMinor = false; this._monthlySpending = 0; this._dailyAdCount = 0; this._trackingDate = ''; this._load(); this._checkMinorStatus(); } // ============================================================ // Persistence // ============================================================ /** @private */ _getTodayKey() { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; } /** @private */ _getMonthKey() { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`; } /** @private */ _load() { try { if (GameGlobal && GameGlobal.storageManager) { const data = GameGlobal.storageManager.get('compliance', null); if (data) { this._isMinor = data.isMinor || false; // Monthly spending const currentMonth = this._getMonthKey(); if (data.spendingMonth === currentMonth) { this._monthlySpending = data.monthlySpending || 0; } // Daily ad count const today = this._getTodayKey(); if (data.adDate === today) { this._dailyAdCount = data.dailyAdCount || 0; } this._trackingDate = today; } } } catch (e) { console.warn('[ComplianceManager] Failed to load:', e); } } /** @private */ _save() { try { if (GameGlobal && GameGlobal.storageManager) { GameGlobal.storageManager.set('compliance', { isMinor: this._isMinor, spendingMonth: this._getMonthKey(), monthlySpending: this._monthlySpending, adDate: this._getTodayKey(), dailyAdCount: this._dailyAdCount, }); } } catch (e) {} } // ============================================================ // Minor Status Detection // ============================================================ /** * Check if the user is a minor via WeChat platform API. * @private */ _checkMinorStatus() { // WeChat provides user age info through specific APIs // In production, this would call wx.getUserInfo or a server-side check // For now, default to non-minor try { if (typeof wx !== 'undefined' && typeof wx.getSetting === 'function') { // Placeholder: in production, check real-name verification status console.log('[ComplianceManager] Minor status check: defaulting to adult'); } } catch (e) {} } // ============================================================ // Public API // ============================================================ /** * Whether the current user is identified as a minor. * @returns {boolean} */ isMinor() { return this._isMinor; } /** * Set minor status (called after real-name verification). * @param {boolean} isMinor */ setMinorStatus(isMinor) { this._isMinor = isMinor; this._save(); } /** * Check if a purchase is allowed for the current user. * Minors: monthly limit ¥400, single purchase > ¥50 requires confirmation. * @param {number} amountFen - Purchase amount in fen (分). * @returns {{ allowed: boolean, needsConfirmation: boolean, reason?: string }} */ checkPurchaseRestriction(amountFen) { if (!this._isMinor) { return { allowed: true, needsConfirmation: false }; } const amountYuan = amountFen / 100; // Monthly limit: ¥400 const newTotal = this._monthlySpending + amountFen; if (newTotal > 40000) { // 400 yuan in fen return { allowed: false, needsConfirmation: false, reason: 'monthly_limit', }; } // Single purchase > ¥50 needs confirmation if (amountYuan > 50) { return { allowed: true, needsConfirmation: true, reason: 'large_purchase', }; } return { allowed: true, needsConfirmation: false }; } /** * Record a successful purchase amount. * @param {number} amountFen */ recordPurchase(amountFen) { this._monthlySpending += amountFen; this._save(); } /** * Check if an ad can be shown to the current user. * Minors: max 5 ads per day. * @returns {boolean} */ canShowAd() { if (!this._isMinor) return true; const today = this._getTodayKey(); if (this._trackingDate !== today) { this._trackingDate = today; this._dailyAdCount = 0; } return this._dailyAdCount < 5; } /** * Record an ad shown to the user. */ recordAdShown() { if (!this._isMinor) return; const today = this._getTodayKey(); if (this._trackingDate !== today) { this._trackingDate = today; this._dailyAdCount = 0; } this._dailyAdCount++; this._save(); } /** * Validate game session data for anti-cheat. * Checks for impossible stats (e.g., too many kills in too short time). * @param {object} stats - { kills, timeElapsed, score } * @returns {{ valid: boolean, flags: string[] }} */ validateGameSession(stats) { const flags = []; if (!stats) return { valid: true, flags }; // Check impossible kill rate (>10 kills per minute) if (stats.kills && stats.timeElapsed) { const killsPerMinute = stats.kills / (stats.timeElapsed / 60); if (killsPerMinute > 10) { flags.push('suspicious_kill_rate'); } } // Check impossible score if (stats.score && stats.score > 100000) { flags.push('suspicious_score'); } // Check suspicious ad reward frequency // (anti-cheat for ad reward manipulation) return { valid: flags.length === 0, flags, }; } } module.exports = ComplianceManager;