"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.PREMIUM_REVOKED_TYPES = exports.PREMIUM_ACTIVE_TYPES = void 0; exports.isPremiumEntitlement = isPremiumEntitlement; exports.applyEntitlementEvent = applyEntitlementEvent; exports.applyEntitlementSync = applyEntitlementSync; const admin = __importStar(require("firebase-admin")); // Events that should grant or keep premium access active. exports.PREMIUM_ACTIVE_TYPES = new Set([ 'INITIAL_PURCHASE', 'RENEWAL', 'PRODUCT_CHANGE', 'TRANSFER', 'UNCANCELLATION', ]); // Events that remove premium access. exports.PREMIUM_REVOKED_TYPES = new Set([ 'EXPIRATION', 'CANCELLATION', 'BILLING_ISSUE', 'SUBSCRIBER_ALIAS', ]); // Premium entitlement identifier used by the app. const PREMIUM_ENTITLEMENT_ID = 'closer_premium'; function getDb() { return admin.firestore(); } function entitlementsRef(userId) { return getDb().collection('users').doc(userId).collection('entitlements').doc('premium'); } function now() { return admin.firestore.Timestamp.now(); } function isPremiumEntitlement(event) { var _a; const entitlementId = event.entitlement_id; const entitlementIds = (_a = event.entitlement_ids) !== null && _a !== void 0 ? _a : []; if (entitlementId === PREMIUM_ENTITLEMENT_ID) return true; if (entitlementIds.includes(PREMIUM_ENTITLEMENT_ID)) return true; return false; } /** * Apply a RevenueCat entitlement event to Firestore. * Writes state to users/{userId}/entitlements. */ async function applyEntitlementEvent(event) { var _a; const { type, app_user_id: userId, id: eventId, product_id: productId } = event; // Idempotency: create a processed event marker; if it exists, skip. const eventRef = getDb().collection('entitlement_events').doc(eventId); try { await eventRef.create({ processedAt: now() }); } catch (err) { if ((err === null || err === void 0 ? void 0 : err.code) === 6 || ((_a = err === null || err === void 0 ? void 0 : err.message) === null || _a === void 0 ? void 0 : _a.includes('ALREADY_EXISTS'))) { console.log(`[entitlement] skipping duplicate event: ${eventId}`); return; } throw err; } if (!isPremiumEntitlement(event)) { console.log(`[entitlement] ignored event for non-premium entitlement: ${eventId}`); return; } const ref = entitlementsRef(userId); if (exports.PREMIUM_ACTIVE_TYPES.has(type)) { const expiresAt = event.expiration_at_ms ? admin.firestore.Timestamp.fromMillis(event.expiration_at_ms) : null; await ref.set({ premium: true, expiresAt, updatedAt: now(), productId, eventType: type, }); console.log(`[entitlement] premium=true for ${userId} (${type})`); return; } if (exports.PREMIUM_REVOKED_TYPES.has(type)) { await ref.set({ premium: false, expiresAt: null, updatedAt: now(), productId, eventType: type, }); console.log(`[entitlement] premium=false for ${userId} (${type})`); return; } console.log(`[entitlement] ignored event type: ${type}`); } /** * Recompute and rewrite the entitlement document for a user. * * In production this should query RevenueCat; for now it ensures the * Firestore document is consistent and reflects the latest stored values. */ async function applyEntitlementSync(userId) { var _a; const ref = entitlementsRef(userId); const snap = await ref.get(); const data = snap.data(); const updatedAt = now(); let premium = false; let expiresAt = null; if ((data === null || data === void 0 ? void 0 : data.premium) === true) { const storedExpiresAt = (_a = data.expiresAt) !== null && _a !== void 0 ? _a : null; if (storedExpiresAt instanceof admin.firestore.Timestamp) { premium = storedExpiresAt.toMillis() > Date.now(); expiresAt = storedExpiresAt; } else { // No expiration means a currently active, non-expiring entitlement. premium = true; } } const state = { premium, expiresAt: premium ? expiresAt : null, updatedAt, }; await ref.set(state, { merge: true }); return state; } //# sourceMappingURL=entitlementLogic.js.map