This system solves the critical issue where RevenueCat creates multiple IDs/aliases for the same user, causing purchased credits to not appear correctly in the app. The solution uses a "Duplicate-and-Cleanup" approach that ensures users always see their purchased credits regardless of which ID RevenueCat uses.
RevenueCat's ID management creates complexity:
app_user_id, sometimes as an aliasapp_user_id: Could be canonical OR RevenueCat-generatedoriginal_app_user_id: Another potential IDaliases[]: Array of additional IDsCritical Issue: Credit grants from webhooks may use non-canonical IDs, causing users to not see their purchased credits.
CREATE TABLE IF NOT EXISTS canonical_ids_v1 (
canonical_id TEXT PRIMARY KEY,
first_seen_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
aliases_processed BOOLEAN DEFAULT FALSE
);
CREATE TABLE IF NOT EXISTS credit_grants_v1 (
-- ... existing columns ...
is_consolidated BOOLEAN DEFAULT FALSE
);
Webhook Arrives
↓
Extract ALL IDs (app_user_id, original_app_user_id, aliases[])
↓
For Each ID:
Create Grant (idempotent via transaction_id)
↓
Result: Multiple grants with same tx_id (safe)
User Authenticates (canonical_id in header)
↓
Is this a new canonical_id?
├─ No → Continue normally
└─ Yes →
├─ Register canonical_id
├─ Fetch aliases from RevenueCat
├─ Consolidate existing grants
└─ Mark as processed
GET /api/credits/balance
{ "customer_id": "canonical_123", "balance": 125000, "total_granted": 125000, "total_consumed": 0 }
GET /admin/aliases/status
{ "unconsolidated_grants": 42, "users_with_multiple_grants": 5, "unprocessed_canonical_ids": 2, "details": { "users_with_multiple_grants": [...], "unprocessed_canonical_ids": [...] } }
GET /admin/credits/:customerId
Webhook-First Purchase: User purchases before opening app
Multiple Aliases: RevenueCat provides various IDs
Overlapping Aliases: Same alias in multiple purchases
Refunds: Negative grants for refunded purchases
Run the test suite to verify the implementation:
deno run --allow-net --allow-env test/test-aliases.ts
Tests cover:
-- Check for unconsolidated grants
SELECT COUNT(*) as unconsolidated_count
FROM credit_grants_v1
WHERE is_consolidated = FALSE;
-- Find users with multiple grant records
SELECT customer_id, COUNT(*) as grant_count
FROM credit_grants_v1
GROUP BY customer_id
HAVING COUNT(*) > 5;
-- Check for unprocessed canonical IDs
SELECT canonical_id, first_seen_at
FROM canonical_ids_v1
WHERE aliases_processed = FALSE
AND first_seen_at < datetime('now', '-1 hour');
If issues arise:
The system is designed to be fail-safe:
This aliasing system ensures that:
The key insight: duplicate grants with the same transaction ID are harmless, and one-time consolidation when we first see a canonical ID keeps the system clean and performant.