# Fan Passport Virtual Album & Item-Exchange Platform Design **Milestone:** #7 Partner Integration **Artifact status:** Complete standalone design **Scope:** Virtual albums, inventory synchronization, duplicate handling, trade proposals, exchange confirmation, moderation, fraud prevention, and user safety for Fan Passport collector experiences. --- ## 1. Executive Summary Fan Passport should support a virtual collector experience where fans build digital sticker/card albums, showcase their collections, identify duplicates, maintain wishlists, and exchange eligible items with other users. The platform is designed to work with Fan Passport-native collectible items first, while allowing proposed integrations with licensed sticker/card providers such as Panini and other card/sticker brands when commercial agreements, technical credentials, and approved usage rights are in place. The exchange system must be safe, auditable, consent-based, and reversible where possible. Trades should never rely on informal direct messages or off-platform promises. Instead, items are locked during proposals, both parties explicitly confirm the exchange, the system performs an atomic transfer, and both users receive a receipt. This document defines a production-ready design for the virtual album and exchange platform, including service responsibilities, data models, inventory sync flows, duplicate logic, trade state machines, moderation operations, anti-fraud controls, and user safety rules. --- ## 2. Design Principles 1. **Licensed and consent-based** - Provider-supplied stickers/cards are used only when partner rights permit it. - Users must explicitly connect any external provider account and consent to import or sync collectible data. 2. **Collection-first, exchange-second** - The primary motivation is completing albums, teams, stadium sets, and World Cup memories. - Trading exists to help fans complete collections, not to create an unregulated marketplace. 3. **No implied ownership transfer without rights** - A Fan Passport virtual item may represent: - A Fan Passport-native digital collectible. - A synced view of a partner-owned collectible. - A promotional reward entitlement. - Transfer rules depend on the item’s license and provider capabilities. 4. **Safe by default** - Public visibility, trade eligibility, profile sharing, and direct interaction are opt-in. - Minors and vulnerable users receive stricter protections. 5. **Atomic, auditable exchanges** - No item leaves a user’s inventory unless the exchange passes validation, both users confirm, and the inventory ledger records the mutation. 6. **Fraud-resistant** - All inventory changes are source-attributed. - External syncs are idempotent. - Trade activity is risk-scored. - Suspicious patterns trigger holds, review, or limits. 7. **Partner-flexible** - The architecture supports different partner capabilities: - Read-only inventory sync. - Webhook-driven sync. - Transfer/write-back APIs. - Redemption code imports. - Batch catalog feeds. - No partner-specific logic is hard-coded into core trade rules. --- ## 3. Product Experience Overview ### 3.1 Virtual Albums Users can browse and complete virtual albums such as: - FIFA World Cup 2026 Teams Album - Stadiums Album - Match Memories Album - Player Stars Album - Daily Trivia Rewards Album - Partner Sticker Album, if licensed by a provider - Future Premier League or Champions League Passport albums Each album contains slots. A slot may accept one specific item or a set of equivalent variants. Example: | Album | Slot | Accepted Items | |---|---:|---| | World Cup Teams | England Team Crest | `team_crest_england_2026` | | Stadiums | MetLife Stadium | `stadium_metlife_base`, `stadium_metlife_gold` | | Player Stars | Argentina Star Player | Any approved Argentina star-player item from the 2026 catalog | ### 3.2 Inventory A user’s inventory contains item holdings. Holdings can come from: - Passport challenge rewards - Daily trivia rewards - Prediction achievements - Promotional campaigns - Partner imports - Partner pack openings, if integrated - Exchange with another user - Admin grants for support corrections or live-ops events Inventory distinguishes between: - **Item definition:** The catalog identity, such as “England Crest 2026”. - **Item instance:** A specific owned copy, if uniqueness is required. - **Stacked holding:** A count of interchangeable copies, if uniqueness is not required. ### 3.3 Duplicates A duplicate is any eligible copy beyond what the user needs to complete relevant album slots, subject to item rules. Examples: - User owns three identical England Crest stickers. - One copy is reserved for the album. - Two copies are duplicates and may be tradeable if allowed by item policy. Not all duplicates are tradeable. Some may be: - Account-bound. - Partner-read-only. - Event-locked. - Under fraud review. - Required for multiple album slots. - Temporarily locked in an active trade proposal. ### 3.4 Wishlist Users can mark missing items as wanted. Wishlists power: - Trade suggestions. - Friend matching. - Global exchange board matches. - Push notifications when a compatible offer appears. ### 3.5 Item Exchange The exchange platform supports controlled swaps between users: - One-to-one item trades. - Many-for-one item trades. - Many-for-many item trades. - Optional “help complete my album” recommendations. - No cash settlement in the core MVP. - No off-platform payment prompts. - No uncontrolled direct marketplace listings in child-safe modes. A proposed trade is completed only after validation, item locking, explicit acceptance, confirmation, and atomic inventory transfer. --- ## 4. Architecture ### 4.1 Logical Components ```text Client Apps ├─ Passport Web App ├─ Mobile App / PWA └─ Admin & Moderation Console API Gateway ├─ Authentication / Session Validation ├─ Rate Limiting ├─ Abuse Controls └─ Consent Enforcement Core Services ├─ Catalog Service ├─ Album Service ├─ Inventory Service ├─ Duplicate & Wishlist Service ├─ Trade Service ├─ Matching Service ├─ Notification Service ├─ Moderation & Safety Service ├─ Fraud/Risk Service └─ Audit Ledger Partner Integration Layer ├─ Provider Adapter Interface ├─ Panini Adapter, proposed and disabled until authorized ├─ Other Sticker/Card Provider Adapters, proposed and disabled until authorized ├─ Catalog Feed Ingestion ├─ Inventory Sync Jobs └─ Partner Webhook Receivers Storage ├─ Relational Transaction Store ├─ Immutable Inventory Ledger ├─ Search/Matching Index ├─ Moderation Case Store ├─ Analytics Warehouse └─ Object Storage for licensed media assets ``` ### 4.2 Service Responsibilities | Service | Responsibilities | |---|---| | Catalog Service | Stores canonical item definitions, partner item mappings, rarity, album slot compatibility, media references, and tradeability rules. | | Album Service | Tracks album definitions, slot completion, collection progress, and completion rewards. | | Inventory Service | Maintains user item holdings, item locks, imports, transfers, sync status, and ledger entries. | | Duplicate & Wishlist Service | Computes duplicate availability, maintains wanted items, and powers matching. | | Trade Service | Creates proposals, validates eligibility, manages trade state, locks items, confirms exchanges, and issues receipts. | | Matching Service | Suggests compatible trades using duplicate and wishlist data. | | Moderation & Safety Service | Handles reports, blocks, content review, enforcement, child-safety rules, and dangerous behavior detection. | | Fraud/Risk Service | Scores accounts, items, partner syncs, and trades for manipulation or abuse. | | Partner Adapter Layer | Normalizes partner catalog and inventory data into Fan Passport canonical models. | | Audit Ledger | Immutable record of inventory changes, trade events, admin actions, moderation decisions, and partner sync batches. | --- ## 5. Core Domain Model ### 5.1 Catalog Item Represents a collectible type. | Field | Type | Description | |---|---|---| | `item_id` | string | Fan Passport canonical item ID. | | `provider_id` | string/null | Source provider, such as `fan_passport`, `panini_proposed`, or another authorized provider. | | `provider_item_id` | string/null | Provider’s item identifier, if applicable. | | `collection_id` | string | Collection or album series ID. | | `name` | string | User-facing item name. | | `description` | string | Short description. | | `item_type` | enum | `sticker`, `card`, `badge`, `memory`, `team`, `stadium`, `player`, `achievement`. | | `rarity` | enum | `common`, `uncommon`, `rare`, `epic`, `legendary`, `promo`, `limited`. | | `season` | string | Example: `world_cup_2026`. | | `country_code` | string/null | FIFA country code where relevant. | | `team_id` | string/null | Team reference where relevant. | | `player_id` | string/null | Player reference where licensed. | | `stadium_id` | string/null | Stadium reference where relevant. | | `media_asset_id` | string/null | Approved image/media reference. | | `trade_policy` | object | Eligibility rules for exchange. | | `license_policy` | object | Display, transfer, expiration, and attribution rules. | | `created_at` | timestamp | Creation timestamp. | | `updated_at` | timestamp | Last update timestamp. | ### 5.2 Trade Policy ```json { "tradeable": true, "requires_min_account_age_days": 3, "requires_verified_email": true, "requires_partner_writeback": false, "allow_cross_provider_trade": true, "max_trade_quantity_per_transaction": 10, "cooldown_after_acquisition_minutes": 30, "bind_on_receive": false, "age_restricted": false, "regions_allowed": ["global"], "expires_at": null } ``` Policy meanings: - `tradeable`: Whether the item can ever be exchanged. - `requires_partner_writeback`: If true, the item can only trade when the provider confirms transfer support. - `allow_cross_provider_trade`: Whether it may be swapped for items from other providers or only same-provider items. - `cooldown_after_acquisition_minutes`: Prevents rapid laundering of newly acquired items. - `bind_on_receive`: Item becomes account-bound after exchange. - `age_restricted`: Requires age-appropriate access controls. - `regions_allowed`: Supports licensing restrictions by market. ### 5.3 User Inventory Item Represents a user’s holding of a catalog item. | Field | Type | Description | |---|---|---| | `inventory_item_id` | string | Internal unique holding ID. | | `user_id` | string | Owner. | | `item_id` | string | Catalog item. | | `quantity` | integer | Count for stackable items. | | `instance_id` | string/null | Unique instance ID when non-stackable. | | `source_type` | enum | `challenge`, `trivia`, `prediction`, `partner_sync`, `exchange`, `promo`, `admin_adjustment`. | | `source_ref` | string | Event, sync batch, trade, or grant reference. | | `provider_account_id` | string/null | Connected external account if imported. | | `ownership_status` | enum | `owned`, `synced_view`, `pending_partner_confirmation`, `revoked`, `locked`, `under_review`. | | `trade_status` | enum | `eligible`, `ineligible`, `locked_in_trade`, `cooldown`, `held_for_review`. | | `acquired_at` | timestamp | Acquisition timestamp. | | `last_synced_at` | timestamp/null | Last external sync timestamp. | | `metadata` | object | Partner or campaign metadata after normalization. | ### 5.4 Album Slot | Field | Type | Description | |---|---|---| | `slot_id` | string | Album slot ID. | | `album_id` | string | Parent album. | | `slot_number` | integer | Display order. | | `name` | string | Slot name. | | `accepted_item_ids` | array | Exact accepted item IDs. | | `accepted_tags` | array | Optional matching rules such as `team:england`. | | `required_quantity` | integer | Usually `1`; can support multi-copy challenges. | | `completion_weight` | integer | Progress scoring weight. | | `is_required_for_album_completion` | boolean | Whether this slot counts toward completion. | ### 5.5 Wishlist Entry | Field | Type | Description | |---|---|---| | `wishlist_entry_id` | string | Unique ID. | | `user_id` | string | Owner. | | `item_id` | string | Wanted item. | | `album_id` | string/null | Album context. | | `priority` | enum | `low`, `normal`, `high`. | | `visibility` | enum | `private`, `friends`, `public_exchange`. | | `created_at` | timestamp | Creation timestamp. | | `expires_at` | timestamp/null | Optional expiry. | ### 5.6 Duplicate Stack A computed or materialized view showing tradeable extras. | Field | Type | Description | |---|---|---| | `user_id` | string | Owner. | | `item_id` | string | Item. | | `total_quantity` | integer | Total owned/synced count. | | `reserved_for_albums` | integer | Quantity needed to preserve album completion. | | `locked_quantity` | integer | Quantity locked in active trades or reviews. | | `tradeable_duplicate_quantity` | integer | Quantity available for exchange. | | `reasons_unavailable` | array | Explanations for non-tradeable copies. | | `computed_at` | timestamp | Calculation timestamp. | ### 5.7 Trade Proposal | Field | Type | Description | |---|---|---| | `trade_id` | string | Unique trade ID. | | `initiator_user_id` | string | Creator. | | `recipient_user_id` | string | Target user. | | `status` | enum | Current trade state. | | `initiator_offer` | array | Items/quantities offered by initiator. | | `recipient_offer` | array | Items/quantities requested from recipient. | | `message_template_id` | string/null | Safe preset message. | | `custom_message` | string/null | Allowed only for eligible users and moderated. | | `expires_at` | timestamp | Expiry timestamp. | | `risk_score` | number | Fraud/risk score. | | `created_at` | timestamp | Creation timestamp. | | `updated_at` | timestamp | Last update timestamp. | ### 5.8 Trade Item Lock | Field | Type | Description | |---|---|---| | `lock_id` | string | Unique lock ID. | | `trade_id` | string | Trade reference. | | `user_id` | string | Owner whose item is locked. | | `item_id` | string | Locked item. | | `inventory_item_id` | string/null | Specific holding if non-stackable. | | `quantity` | integer | Locked quantity. | | `lock_status` | enum | `active`, `released`, `consumed`, `expired`. | | `expires_at` | timestamp | Lock expiry. | | `created_at` | timestamp | Creation timestamp. | ### 5.9 Exchange Receipt | Field | Type | Description | |---|---|---| | `receipt_id` | string | Unique receipt ID. | | `trade_id` | string | Trade reference. | | `participants` | array | User IDs involved. | | `items_transferred` | array | Item movements. | | `partner_writeback_status` | enum | `not_required`, `pending`, `confirmed`, `failed`, `manual_review`. | | `ledger_entry_ids` | array | Inventory ledger references. | | `completed_at` | timestamp | Completion timestamp. | | `reversal_policy` | object | Applicable reversal window and reason codes. | --- ## 6. Inventory Synchronization Design ### 6.1 Supported Sync Sources | Source | Example | Sync Direction | Notes | |---|---|---|---| | Fan Passport-native rewards | Challenges, trivia, predictions | Internal write | Fully controlled by Fan Passport. | | Partner account inventory | Proposed Panini or other provider account | Read-only or read/write depending on agreement | Requires OAuth or equivalent consent. | | Partner pack/campaign feed | Promotional pack drops | Partner to Fan Passport | Requires signed webhook or batch feed. | | Redemption code | Code from physical pack or campaign | User submits code; verified by provider or Fan Passport | Must prevent replay. | | Admin adjustment | Support correction | Internal write | Requires audit reason and privileged access. | ### 6.2 Provider Adapter Interface Each provider adapter normalizes partner-specific data into the canonical inventory model. Required adapter capabilities: ```text getCatalogFeed(providerCollectionId) mapProviderItem(providerItem) getUserInventory(providerAccountConnection) getInventoryDelta(providerAccountConnection, cursor) verifyRedemptionCode(code, userContext) transferItem(sourceAccount, destinationAccount, itemRef, quantity) [optional] receiveWebhook(eventPayload, signatureHeaders) [optional] ``` Adapters must declare their capability profile: ```json { "provider_id": "panini_proposed", "display_name": "Panini Proposed Integration", "catalog_sync": "batch_feed", "inventory_sync": "read_only_delta", "supports_webhooks": true, "supports_item_transfer": false, "supports_redemption_codes": true, "requires_user_oauth": true, "requires_partner_contract": true } ``` If `supports_item_transfer` is false, partner-origin items may still appear in a user’s album as a synced collection view, but cannot be transferred as true provider-owned items. The platform may allow Fan Passport-native “tradeable representations” only if the license explicitly permits that distinction. ### 6.3 User Consent Flow Before importing partner inventory: 1. User selects “Connect sticker/card account”. 2. Platform displays: - Provider name. - Data requested. - Usage purpose. - Visibility defaults. - Whether imported items can be traded. - Whether trades write back to the partner account. 3. User authenticates with provider. 4. Provider returns authorization token or signed grant. 5. Fan Passport stores: - Connection ID. - Provider account pseudonymous ID. - Granted scopes. - Token reference in secure secret storage. - Consent timestamp. - Expiration timestamp. 6. First sync runs immediately. 7. User can disconnect at any time. On disconnect: - Stop future sync. - Revoke token if provider supports revocation. - Preserve Fan Passport-native items. - Hide or remove synced-only partner views according to the original consent and license policy. - Maintain audit ledger entries for integrity, using non-public internal identifiers. ### 6.4 Sync Modes #### 6.4.1 Full Import Used on first connection or when no cursor is available. Process: 1. Create sync batch with status `started`. 2. Fetch provider inventory. 3. Validate provider response signature or authenticated channel. 4. Normalize items through provider adapter. 5. Map partner item IDs to canonical catalog item IDs. 6. Mark unknown items as `unmapped_provider_item`. 7. Upsert holdings idempotently. 8. Recompute album progress and duplicate stacks. 9. Write ledger entries for changed quantities. 10. Store sync cursor, if provided. 11. Mark sync batch `completed`, `partial`, or `failed`. #### 6.4.2 Delta Sync Used after initial import. Process: 1. Load last successful provider cursor. 2. Fetch changed holdings since cursor. 3. Apply additions, removals, revocations, and metadata changes. 4. Preserve local trade locks until reconciliation. 5. Detect conflicts between partner state and local exchange state. 6. Advance cursor only after successful persistence. #### 6.4.3 Webhook Sync Used when partner sends events. Security requirements: - Verify webhook signature. - Validate timestamp and replay nonce. - Enforce provider IP allowlist where contractually available. - Store raw event hash for audit. - Process webhook asynchronously through an idempotent queue. - Never trust webhook item IDs without catalog mapping. #### 6.4.4 Manual Refresh Users can request a manual sync, subject to rate limits. Recommended limits: - Connected provider inventory refresh: 5 per hour per user. - Failed sync retry button: 3 per hour per provider connection. - Support-triggered sync: privileged and audited. ### 6.5 Idempotency and Reconciliation Every sync batch must include: | Field | Purpose | |---|---| | `sync_batch_id` | Internal unique processing ID. | | `provider_id` | Source provider. | | `provider_account_id` | External account reference. | | `provider_event_id` | Partner event ID, if supplied. | | `cursor_before` | Previous cursor. | | `cursor_after` | New cursor. | | `payload_hash` | Detects replay and tampering. | | `started_at` / `completed_at` | Operational tracing. | | `status` | `started`, `completed`, `partial`, `failed`, `quarantined`. | Idempotency keys: - For full sync: `provider_id + provider_account_id + payload_hash`. - For delta event: `provider_id + provider_event_id`. - For redemption code: `provider_id + redemption_code_hash`. - For transfer write-back: `provider_id + partner_transfer_id`. Reconciliation outcomes: | Condition | Action | |---|---| | Partner shows more copies than local view | Add or update holdings. | | Partner shows fewer copies than local view, no local trade involved | Reduce synced quantity and update albums. | | Partner revokes item locked in active trade | Freeze trade and send to review. | | Partner item ID is unknown | Store as unmapped, hide from trade, surface as “unsupported item” if allowed. | | Partner feed changes item metadata | Update catalog mapping after validation and moderation review for visible assets. | | Same item appears from multiple sources | Preserve source attribution and stack only when policies allow equivalence. | ### 6.6 Partner-Origin Transfer Rules Partner-origin item transfer has three possible modes: 1. **Read-only display** - User can show item in album. - User cannot trade it. - Duplicate count may show “extra synced copies” but not “available to trade”. 2. **Fan Passport representation trade** - User can trade a Fan Passport-native entitlement inspired by or linked to the partner item. - The actual partner account inventory does not change. - UI must clearly disclose this distinction. 3. **Provider write-back transfer** - Trade executes in Fan Passport and calls partner transfer API. - Completion is final only after partner confirmation, unless contract allows eventual consistency. - Failures create a `manual_review` exchange receipt and freeze affected holdings. No item should be described as an official transferable partner collectible unless the provider agreement and adapter capability support it. --- ## 7. Duplicate Handling ### 7.1 Duplicate Calculation For each user and item: ```text tradeable_duplicate_quantity = total_eligible_quantity - quantity_reserved_for_required_album_slots - quantity_locked_in_active_trades - quantity_under_review - quantity_in_cooldown - quantity_bound_or_untradeable ``` A duplicate is tradeable only when: - User owns or controls the item under a tradeable source. - Item catalog `trade_policy.tradeable` is true. - License permits exchange. - Item is not locked, revoked, expired, or under review. - User passes account eligibility requirements. - The item is not required to preserve album completion, unless user explicitly permits trading away album-filled items. ### 7.2 Album Reservation Rules Default reservation policy: 1. Reserve enough copies to keep completed album slots filled. 2. Reserve rare variant if the slot can accept common or rare and the user explicitly pinned the rare variant. 3. Prefer reserving non-tradeable copies for album completion before tradeable copies. 4. If multiple albums use the same item, reserve the maximum required quantity across active album commitments unless the album requires distinct copies. User controls: - “Protect completed albums” enabled by default. - “Allow offers for album-filled items” disabled by default. - “Auto-add duplicates to exchange board” disabled by default for new users. ### 7.3 Duplicate Visibility Visibility levels: | Level | Meaning | |---|---| | Private | Only user sees duplicate count. | | Friends | Friends can see tradeable duplicates. | | Public Exchange | Eligible users can discover duplicates through matching/search. | | Hidden by Safety | Platform hides duplicates due to age, moderation, risk, or licensing rules. | For minors, public duplicate exposure should default to off. Matching should use privacy-preserving recommendations rather than exposing full inventories. ### 7.4 Duplicate Stack Examples #### Example A: Tradeable Fan Passport Item - User owns 4 `england_crest_2026`. - 1 copy reserved for Teams Album. - 1 copy locked in pending trade. - 2 copies available. Result: ```json { "item_id": "england_crest_2026", "total_quantity": 4, "reserved_for_albums": 1, "locked_quantity": 1, "tradeable_duplicate_quantity": 2 } ``` #### Example B: Partner Read-Only Item - User syncs 3 provider stickers. - Provider integration is read-only. - 1 copy fills album slot. - 2 copies are extra but not tradeable. Result: ```json { "item_id": "provider_sticker_123", "total_quantity": 3, "reserved_for_albums": 1, "locked_quantity": 0, "tradeable_duplicate_quantity": 0, "reasons_unavailable": ["provider_read_only"] } ``` #### Example C: User Chooses to Trade Album Item - User owns 1 rare stadium card. - It fills a stadium album slot. - User disables protection for that item and offers it. Allowed only if: - User receives clear warning. - Item is tradeable. - Album progress impact is shown. - User is not a minor in protected mode. - Fraud/risk service does not block the action. --- ## 8. Exchange Platform Design ### 8.1 Exchange Modes #### 8.1.1 Direct Proposal A user proposes a trade to another known user. Best for: - Friends. - Leaderboard peers. - Users discovered through album matching. #### 8.1.2 Match-Assisted Proposal System recommends users with compatible duplicates and wishlists. Example: - Alice has duplicate England Crest. - Bob wants England Crest and has duplicate Brazil Crest. - Alice wants Brazil Crest. - System suggests a swap but does not disclose unnecessary inventory details. #### 8.1.3 Exchange Board Listing A user lists tradeable duplicates and desired items. Recommended for adult/general audience accounts only, with stronger rate limits and moderation. Listing fields: - Offered item. - Quantity. - Wanted item IDs or album categories. - Expiry. - Visibility. - Preset message. - Region/license availability. #### 8.1.4 Guided Completion Trade Album page offers “Find trades to complete this page”. The system: 1. Identifies missing album slots. 2. Checks user duplicates. 3. Finds compatible users. 4. Suggests low-risk balanced trades. 5. Allows user to send a proposal. ### 8.2 Trade Proposal State Machine ```text draft └─ submitted ├─ declined ├─ cancelled ├─ expired ├─ countered │ └─ submitted ├─ accepted_pending_locks │ ├─ failed_validation │ └─ locked_pending_confirmation │ ├─ cancelled │ ├─ expired │ ├─ disputed │ └─ confirmed_by_both │ ├─ executing │ │ ├─ completed │ │ ├─ partner_pending │ │ ├─ failed_released │ │ └─ manual_review │ └─ cancelled_before_execution └─ blocked_by_safety ``` ### 8.3 Status Definitions | Status | Meaning | |---|---| | `draft` | Proposal being composed by initiator. No recipient visibility. | | `submitted` | Sent to recipient; no items locked yet or soft validation only. | | `countered` | Recipient proposed changes; initiator must accept or decline. | | `declined` | Recipient rejected proposal. | | `cancelled` | Initiator or eligible party cancelled before execution. | | `expired` | Proposal exceeded expiry window. | | `accepted_pending_locks` | Recipient accepted; system is validating and locking items. | | `failed_validation` | Items/user no longer eligible. | | `locked_pending_confirmation` | Items locked; both users must confirm final exchange. | | `confirmed_by_both` | Both parties confirmed; ready for execution. | | `executing` | Inventory transfer is in progress. | | `partner_pending` | Waiting for partner write-back confirmation. | | `completed` | Exchange succeeded and receipt issued. | | `failed_released` | Exchange failed safely and locks were released. | | `manual_review` | Requires support/moderation/fraud review. | | `disputed` | User reported a problem before or after execution. | | `blocked_by_safety` | Safety or moderation policy blocked the proposal. | ### 8.4 Proposal Creation Flow 1. Initiator selects duplicate item(s) to offer. 2. Initiator selects wanted item(s) from recipient’s visible duplicates, wishlist match, or exchange board listing. 3. Client requests proposal preview. 4. Server validates: - Authentication. - User eligibility. - Recipient can receive proposals. - Block relationship does not exist. - Items are visible and tradeable. - License regions are compatible. - Age and safety rules allow interaction. - Rate limits are not exceeded. - Proposal is not obviously unfair beyond allowed thresholds for protected users. 5. Server returns preview: - Album impact for both sides, when visible. - Items offered/requested. - Warnings. - Expiry time. - Confirmation requirements. 6. Initiator submits proposal. 7. Recipient receives notification if permitted. ### 8.5 Trade Validation Rules A trade is valid only if all checks pass: #### User Eligibility - Both users are active and not suspended. - Both satisfy age-mode rules. - Both have verified email or equivalent trust signal if required. - Neither has blocked the other. - Neither is under a trade freeze. - Both are in regions where the relevant item licenses permit exchange. #### Item Eligibility - Offered quantities are available. - Items are not locked in another trade. - Items are not under fraud or moderation hold. - Items are not expired or revoked. - Items satisfy provider transfer policy. - Items satisfy cooldown rules. - Items do not exceed transaction quantity limits. - Album protection settings are respected. #### Trade Shape Eligibility - Maximum number of unique item types per side is respected. - Maximum total quantity per trade is respected. - Cross-provider trade is allowed by all involved items. - Risk score is below the block threshold. - If risk score is elevated but not blocked, additional confirmation or review is required. Recommended MVP limits: | Limit | Value | |---|---:| | Max active outgoing proposals per user | 25 | | Max active incoming proposals per user | 100 | | Max unique item types per side | 10 | | Max total item quantity per side | 50 | | Default proposal expiry | 48 hours | | Locked confirmation window | 15 minutes | | New account trade unlock | 72 hours or verified campaign trust signal | | Max completed trades per day for new users | 10 | | Max completed trades per day for established users | 100, adjustable by risk tier | ### 8.6 Item Locking Items are locked only after the recipient accepts the proposal or both parties accept the latest counteroffer. Locking too early would let users grief others by freezing inventories. Locking process: 1. Begin transaction. 2. Re-read current inventory rows with write locks. 3. Confirm quantities are still eligible. 4. Create `trade_item_lock` rows for both parties. 5. Update inventory `trade_status` or reserved quantities. 6. Write audit ledger entry `items_locked_for_trade`. 7. Commit transaction. 8. Notify both users to confirm. Lock expiry: - If both users do not confirm within the confirmation window, release locks. - If execution fails safely, release locks. - If fraud/manual review triggers, keep locks until decision or release according to support policy. ### 8.7 Confirmation Experience Before final confirmation, both users see: - Items they will give. - Items they will receive. - Album progress impact. - Whether any item is partner-origin. - Whether transfer writes back to partner. - Whether items become account-bound. - Whether the exchange can be reversed. - Safety reminder: no off-platform payment or contact is required. - Report/cancel controls. Confirmation requirements: | User Type | Requirement | |---|---| | Standard adult/general user | Tap/click final confirm. | | New or elevated-risk account | Final confirm plus email/session re-authentication if risk warrants. | | Minor/protected account | Restricted trading; guardian-approved or preset safe exchange modes depending on jurisdiction and product policy. | | High-value/limited item trade | Additional warning and possible cooling-off period. | --- ## 9. Exchange Execution and Atomicity ### 9.1 Two-Phase Exchange Model The platform should use a two-phase model: #### Phase 1: Internal Lock and Confirmation - Validate trade. - Lock items. - Obtain final confirmation from both users. - Compute final risk score. #### Phase 2: Transfer and Receipt - Execute atomic inventory mutation. - Call partner write-back if required. - Update album progress. - Release/consume locks. - Create exchange receipt. - Notify users. ### 9.2 Internal-Only Transfer For Fan Passport-native items: 1. Begin database transaction. 2. Verify active locks for both users. 3. Decrement or remove items from source holdings. 4. Increment or create items in destination holdings. 5. Mark locks as `consumed`. 6. Write ledger entries: - `trade_debit` - `trade_credit` - `trade_completed` 7. Recompute album progress and duplicate stacks. 8. Commit. 9. Send receipt. This must be atomic: either all debits and credits succeed, or no inventory changes persist. ### 9.3 Partner Write-Back Transfer For partner-origin transferable items: 1. Complete internal preflight validation. 2. Create partner transfer intent. 3. Lock local synced representations. 4. Call provider transfer API using adapter. 5. Receive partner confirmation ID. 6. Apply local inventory changes. 7. Mark receipt `partner_writeback_status = confirmed`. 8. Store partner transfer reference. If partner supports asynchronous transfer: 1. Mark receipt `partner_writeback_status = pending`. 2. Keep items in `pending_partner_confirmation`. 3. Poll or wait for webhook. 4. On confirmation, complete local transfer. 5. On failure, release locks and mark trade `failed_released` or `manual_review`. If partner confirmation conflicts with local state, freeze affected items and escalate to support review. ### 9.4 Failure Handling | Failure | Platform Response | |---|---| | User loses item before lock | Proposal fails validation; no lock created. | | User disconnects provider after lock | Freeze trade; verify policy; release or review. | | Partner transfer API timeout | Retry with idempotency key; keep pending state. | | Partner transfer rejected | Release locks; mark failed; explain to users. | | Internal inventory transaction fails | Roll back all changes; release locks if safe. | | Fraud service blocks after confirmation | Move to manual review; do not execute. | | User reports scam before execution | Pause trade; send to moderation review. | | Item revoked after completed trade | Apply provider/license policy; may reverse, compensate, or review. | ### 9.5 Reversal Policy Reversals should be rare and policy-driven. Allowed reversal reasons: - Technical duplicate execution. - Confirmed account compromise. - Partner transfer failure after local completion. - Moderation determines coercion, scam, or prohibited conduct. - Item was counterfeit/spoofed due to sync error. - Legal/licensing takedown. Reversal process: 1. Freeze involved items. 2. Create moderation/fraud case. 3. Preserve receipts and ledger history. 4. If reversal approved: - Execute compensating ledger entries, not destructive edits. - Restore items where possible. - If item moved onward, freeze downstream chain and apply support policy. 5. Notify affected users. 6. Record final decision. Ledger entries are immutable. Corrections must be additive. --- ## 10. Exchange APIs The collector profile sharing API is specified separately. The exchange platform should expose additional authenticated APIs for albums, inventory, duplicates, wishlists, and trades. ### 10.1 Authentication All write endpoints require authenticated user sessions. Sensitive actions require CSRF protection for browser clients and idempotency keys for mutation endpoints. Recommended headers: ```http Authorization: Bearer Idempotency-Key: X-Client-Version: web-1.0.0 ``` ### 10.2 Common Error Format ```json { "error": { "code": "item_not_tradeable", "message": "This item cannot be traded.", "details": { "item_id": "stadium_metlife_gold", "reason": "provider_read_only" }, "request_id": "req_01HY..." } } ``` ### 10.3 Album Endpoints #### `GET /v1/albums` Returns albums available to the authenticated user. Response: ```json { "albums": [ { "album_id": "world_cup_2026_teams", "name": "World Cup 2026 Teams", "season": "world_cup_2026", "completion": { "completed_slots": 18, "total_slots": 48, "percent": 37.5 } } ] } ``` #### `GET /v1/albums/{album_id}` Returns album slots and completion state. Response: ```json { "album_id": "world_cup_2026_teams", "name": "World Cup 2026 Teams", "slots": [ { "slot_id": "slot_england_crest", "slot_number": 12, "name": "England Crest", "completed": true, "filled_by_item_id": "england_crest_2026", "protected": true } ] } ``` ### 10.4 Inventory Endpoints #### `GET /v1/inventory` Query parameters: | Parameter | Description | |---|---| | `collection_id` | Filter by collection. | | `item_type` | Filter by item type. | | `tradeable_only` | Return only tradeable holdings. | | `include_synced` | Include partner synced items. | | `page_cursor` | Pagination cursor. | Response: ```json { "items": [ { "inventory_item_id": "inv_123", "item_id": "england_crest_2026", "name": "England Crest", "quantity": 4, "source_types": ["challenge", "exchange"], "trade_status": "eligible", "album_usage": [ { "album_id": "world_cup_2026_teams", "slot_id": "slot_england_crest", "reserved_quantity": 1 } ] } ], "next_page_cursor": null } ``` #### `POST /v1/inventory/sync` Triggers a manual sync for a connected provider account. Request: ```json { "provider_connection_id": "conn_123" } ``` Response: ```json { "sync_batch_id": "sync_123", "status": "started", "estimated_completion_seconds": 30 } ``` ### 10.5 Duplicate Endpoints #### `GET /v1/duplicates` Returns computed duplicate availability. Response: ```json { "duplicates": [ { "item_id": "england_crest_2026", "name": "England Crest", "total_quantity": 4, "reserved_for_albums": 1, "locked_quantity": 0, "tradeable_duplicate_quantity": 3, "visibility": "friends" } ] } ``` #### `PATCH /v1/duplicates/{item_id}/visibility` Request: ```json { "visibility": "public_exchange" } ``` Response: ```json { "item_id": "england_crest_2026", "visibility": "public_exchange", "updated_at": "2026-06-18T12:00:00Z" } ``` ### 10.6 Wishlist Endpoints #### `GET /v1/wishlist` Response: ```json { "wishlist": [ { "wishlist_entry_id": "wish_123", "item_id": "brazil_crest_2026", "priority": "high", "visibility": "public_exchange", "album_id": "world_cup_2026_teams" } ] } ``` #### `POST /v1/wishlist` Request: ```json { "item_id": "brazil_crest_2026", "album_id": "world_cup_2026_teams", "priority": "high", "visibility": "public_exchange" } ``` Response: ```json { "wishlist_entry_id": "wish_123", "created_at": "2026-06-18T12:00:00Z" } ``` #### `DELETE /v1/wishlist/{wishlist_entry_id}` Response: ```json { "deleted": true } ``` ### 10.7 Matching Endpoints #### `GET /v1/exchange/matches` Returns privacy-safe suggested trades. Response: ```json { "matches": [ { "match_id": "match_123", "counterparty_profile": { "user_id": "usr_456", "display_name": "Collector92", "trust_badges": ["verified_email", "fair_trader"] }, "you_give": [ { "item_id": "england_crest_2026", "quantity": 1 } ], "you_receive": [ { "item_id": "brazil_crest_2026", "quantity": 1 } ], "album_impact": { "slots_completed_for_you": 1, "slots_lost_for_you": 0 }, "expires_at": "2026-06-20T12:00:00Z" } ] } ``` ### 10.8 Trade Endpoints #### `POST /v1/trades/preview` Validates a proposed trade before submission. Request: ```json { "recipient_user_id": "usr_456", "initiator_offer": [ { "item_id": "england_crest_2026", "quantity": 1 } ], "recipient_offer": [ { "item_id": "brazil_crest_2026", "quantity": 1 } ] } ``` Response: ```json { "valid": true, "warnings": [], "album_impact": { "initiator": { "slots_completed": 1, "slots_lost": 0 }, "recipient": { "visible": false } }, "expires_at": "2026-06-20T12:00:00Z" } ``` #### `POST /v1/trades` Creates a proposal. Request: ```json { "recipient_user_id": "usr_456", "initiator_offer": [ { "item_id": "england_crest_2026", "quantity": 1 } ], "recipient_offer": [ { "item_id": "brazil_crest_2026", "quantity": 1 } ], "message_template_id": "complete_album_swap" } ``` Response: ```json { "trade_id": "trade_123", "status": "submitted", "expires_at": "2026-06-20T12:00:00Z" } ``` #### `GET /v1/trades` Query parameters: | Parameter | Description | |---|---| | `status` | Filter by trade status. | | `role` | `initiator`, `recipient`, or `any`. | | `page_cursor` | Pagination cursor. | Response: ```json { "trades": [ { "trade_id": "trade_123", "status": "submitted", "counterparty": { "user_id": "usr_456", "display_name": "Collector92" }, "you_give": [ { "item_id": "england_crest_2026", "quantity": 1 } ], "you_receive": [ { "item_id": "brazil_crest_2026", "quantity": 1 } ], "expires_at": "2026-06-20T12:00:00Z" } ], "next_page_cursor": null } ``` #### `GET /v1/trades/{trade_id}` Returns full trade detail for a participant or privileged moderator. Response: ```json { "trade_id": "trade_123", "status": "locked_pending_confirmation", "initiator_user_id": "usr_123", "recipient_user_id": "usr_456", "initiator_offer": [ { "item_id": "england_crest_2026", "quantity": 1, "lock_status": "active" } ], "recipient_offer": [ { "item_id": "brazil_crest_2026", "quantity": 1, "lock_status": "active" } ], "confirmation": { "initiator_confirmed": true, "recipient_confirmed": false, "confirmation_expires_at": "2026-06-18T12:15:00Z" } } ``` #### `POST /v1/trades/{trade_id}/accept` Recipient accepts the current proposal and starts item locking. Response: ```json { "trade_id": "trade_123", "status": "locked_pending_confirmation", "confirmation_expires_at": "2026-06-18T12:15:00Z" } ``` #### `POST /v1/trades/{trade_id}/counter` Creates a counteroffer. Request: ```json { "initiator_offer": [ { "item_id": "england_crest_2026", "quantity": 1 } ], "recipient_offer": [ { "item_id": "brazil_crest_2026", "quantity": 1 }, { "item_id": "mexico_stadium_memory_2026", "quantity": 1 } ] } ``` Response: ```json { "trade_id": "trade_124", "status": "countered", "supersedes_trade_id": "trade_123" } ``` #### `POST /v1/trades/{trade_id}/confirm` Confirms a locked trade. Request: ```json { "confirmation_acknowledged": true } ``` Response: ```json { "trade_id": "trade_123", "status": "completed", "receipt_id": "receipt_123" } ``` If the other party has not yet confirmed: ```json { "trade_id": "trade_123", "status": "locked_pending_confirmation", "waiting_for": "recipient" } ``` #### `POST /v1/trades/{trade_id}/cancel` Cancels an eligible trade. Request: ```json { "reason": "changed_my_mind" } ``` Response: ```json { "trade_id": "trade_123", "status": "cancelled", "locks_released": true } ``` #### `POST /v1/trades/{trade_id}/report` Reports a trade for safety or fraud review. Request: ```json { "reason": "suspected_scam", "details": "The user asked me to pay outside the app." } ``` Response: ```json { "case_id": "case_123", "trade_id": "trade_123", "status": "under_review" } ``` ### 10.9 Rate Limits Recommended starting limits: | Endpoint group | Limit | |---|---:| | Inventory reads | 120 requests/minute/user | | Album reads | 120 requests/minute/user | | Duplicate reads | 60 requests/minute/user | | Manual provider sync | 5 requests/hour/user/provider | | Trade previews | 30 requests/minute/user | | Trade creation | 20 requests/hour/user, lower for new accounts | | Trade accept/confirm/cancel | 60 requests/hour/user | | Reports | 10 requests/hour/user | | Matching search | 30 requests/minute/user | The Fraud/Risk Service can dynamically reduce limits for suspicious accounts. --- ## 11. Matching and Recommendations ### 11.1 Matching Inputs The Matching Service uses: - User wishlist entries. - Tradeable duplicate stacks. - Album completion gaps. - Visibility settings. - Block lists. - Age/safety constraints. - Region/license constraints. - Trust and risk scores. - Historical trade fairness. - Existing active proposals. ### 11.2 Matching Algorithm Recommended MVP algorithm: 1. For the current user, collect wanted item IDs from active albums and wishlist. 2. Collect user’s tradeable duplicate item IDs. 3. Search for counterparties where: - Counterparty has wanted items as duplicates. - Counterparty wants items the current user has as duplicates. - Both users’ visibility settings allow matching. - Safety and block rules pass. 4. Score matches: - Higher score for mutual wishlist match. - Higher score for album slot completion. - Higher score for same rarity class. - Lower score for high risk. - Lower score for repeated proposals to same user. 5. Return the top N suggestions with minimal necessary information. ### 11.3 Fairness Guardrails For protected accounts and new users: - Warn when trading rare items for common items. - Block extremely imbalanced trades in child-safe mode. - Require additional confirmation for limited or legendary items. - Use plain-language explanations: “You are giving a rare item and receiving a common item.” For general users: - Allow unequal trades but disclose relative rarity and album impact. - Detect repeated exploitative patterns by the same counterparty. --- ## 12. Moderation Design ### 12.1 Moderated Surfaces The platform must moderate: - Display names. - Profile bios. - Profile images. - Album showcase captions. - Trade listing text. - Custom trade messages. - User reports. - Public exchange board content. - Uploaded proof images, if support flows allow them. ### 12.2 Message Safety Recommended MVP messaging model: - Preset message templates are available to all eligible users. - Free-text trade messages are disabled for minors and new accounts. - Free-text messages for trusted users are scanned before delivery. - Contact details, payment terms, abusive language, and external links are blocked or quarantined. - Repeated unsafe message attempts reduce trust score. Allowed preset examples: - “Would this help complete your album?” - “I have a duplicate you want.” - “Counteroffer sent.” - “Thanks for the fair trade.” - “I’m only looking for stadium items.” Blocked content examples: - Phone numbers. - External payment requests. - Social media handles when used to move off-platform. - Harassment or hate speech. - Gambling or betting prompts. - Attempts to buy/sell outside the exchange flow. ### 12.3 Reporting Flow Users can report: - A profile. - A trade proposal. - A message. - A listing. - A completed exchange. - Suspicious inventory or item provenance. Report categories: | Category | Examples | |---|---| | Scam or fraud | Off-platform payment, fake promises, manipulation. | | Harassment | Abuse, threats, repeated unwanted proposals. | | Inappropriate content | Sexual content, hate, graphic content. | | Child safety | Adult contacting minor, grooming indicators. | | Counterfeit/spoofed item | Suspicious partner import or impossible item. | | Technical issue | Items not received, duplicate execution, sync conflict. | | Licensing/IP concern | Unauthorized image/name/use. | ### 12.4 Moderation Queue Moderation cases should include: - Case ID. - Reporter ID. - Reported user ID. - Trade/listing/message references. - Evidence snapshot. - Automated risk signals. - User history. - Current item lock status. - Recommended action. - Reviewer notes. - Decision and enforcement action. Priority levels: | Priority | SLA Target | Examples | |---|---:|---| | Critical | Immediate / under 1 hour | Child safety, credible threats, large fraud ring. | | High | Same day | Scam reports, account compromise, hate harassment. | | Medium | 2 business days | Disputed trade, repeated spam. | | Low | 5 business days | Minor profile/content concerns. | ### 12.5 Enforcement Actions Possible actions: - No action. - Content removal. - Warning. - Trade proposal deletion. - Temporary trade freeze. - Visibility reduction. - Rate-limit reduction. - Temporary suspension. - Permanent ban. - Item quarantine. - Trade reversal or compensation. - Escalation to partner provider. - Escalation to legal/safety team. All enforcement actions must be audit-logged. ### 12.6 Appeals Users should be able to appeal: - Suspensions. - Trade freezes. - Item quarantines. - Reversal decisions. - Moderation removals. Appeal review should be performed by a different reviewer or an elevated review queue when feasible. --- ## 13. Fraud Prevention ### 13.1 Threat Model | Threat | Risk | Controls | |---|---|---| | Fake partner inventory import | Users claim items they do not own. | Signed provider APIs, OAuth, redemption verification, idempotent sync, no manual trust of screenshots. | | Redemption code replay | Same code used multiple times. | Code hashing, single-use redemption ledger, provider verification. | | Bot-created accounts farming rewards | Inflated duplicate supply and unfair trades. | Account age limits, CAPTCHA/risk checks, device fingerprinting where lawful, rate limits, reward cooldowns. | | Trade laundering | Moving suspicious items through chains. | Provenance graph, cooldowns, high-risk item holds, downstream freeze capability. | | Exploitative trades | Experienced users trick new users. | Rarity warnings, protected-mode blocks, trust scoring, report flow. | | Off-platform scams | Payment/contact outside app. | Message filtering, no free-text for protected users, warnings, enforcement. | | Collusion/manipulated scarcity | Groups create artificial item value signals. | Analytics monitoring, listing limits, no cash prices in MVP. | | Account takeover | Stolen accounts trade away items. | Re-auth for risky trades, login anomaly detection, reversal policy. | | Duplicate execution bug | Same locked item transferred twice. | Transactional locks, idempotency keys, immutable ledger reconciliation. | | Partner API spoofing | Fake webhooks or tampered payloads. | Signature verification, nonce/timestamp replay checks, provider allowlists. | ### 13.2 Risk Scoring Risk score inputs: - Account age. - Email/phone/session verification. - Login anomalies. - Device reputation, where lawful and disclosed. - Trade frequency. - Proposal decline/report rate. - Imbalanced trade patterns. - High-rarity item movement. - New item cooldown status. - Partner sync anomalies. - Block/report history. - Velocity of inventory acquisition. - Shared identifiers across banned accounts. - User age/safety mode. Risk actions: | Score Band | Action | |---|---| | Low | Allow normal trading. | | Medium | Add warnings, lower rate limits, require re-confirmation. | | High | Hold trades for review or restrict high-rarity items. | | Critical | Freeze trading, quarantine items, require investigation. | ### 13.3 Item Provenance Every item movement must be traceable. Provenance chain: ```text source event -> inventory ledger credit -> duplicate stack availability -> trade lock -> trade debit -> trade credit -> receipt ``` For each item or stack mutation, store: - Source type. - Source reference. - Actor. - Previous quantity. - New quantity. - Trade ID, if any. - Partner sync batch ID, if any. - Admin action ID, if any. - Timestamp. - Request ID. - Risk decision ID. ### 13.4 Inventory Ledger Integrity Ledger rules: - Append-only. - No destructive edits. - Every inventory balance can be recomputed from ledger entries. - Every correction uses compensating entries. - Ledger writes occur in the same transaction as balance updates where possible. - Ledger entries include idempotency keys for mutation endpoints. Example ledger entry: ```json { "ledger_entry_id": "led_123", "user_id": "usr_123", "item_id": "england_crest_2026", "event_type": "trade_debit", "quantity_delta": -1, "balance_after": 3, "source_ref": "trade_123", "idempotency_key": "idem_abc", "created_at": "2026-06-18T12:00:00Z" } ``` ### 13.5 Quarantine Items can be quarantined when: - Partner revokes or disputes them. - Sync data appears spoofed. - Account compromise is suspected. - A trade involving the item is disputed. - Legal/licensing takedown is received. - Item originated from a banned fraud cluster. Quarantined items: - Cannot be traded. - May be hidden from public profile. - May still appear privately with a “under review” explanation. - Are released, revoked, or compensated after review. ### 13.6 Anti-Spam Controls Controls: - Proposal rate limits. - Duplicate proposal suppression. - Cooldown after declines. - Recipient-level “only friends can send trades”. - Automatic muting after repeated ignored proposals. - Blocking and reporting. - Risk-based friction. - Exchange board listing caps. Recommended spam-specific rules: - If a user sends five proposals to the same recipient with no response, pause further proposals to that recipient for 24 hours. - If a user has more than 50% of proposals reported or blocked in a rolling window, freeze outbound proposals pending review. - If a new account sends proposals immediately after creation, require trust verification. --- ## 14. User Safety and Privacy ### 14.1 Visibility Defaults Default settings for general users: | Feature | Default | |---|---| | Public collector profile | Off | | Show completed albums | Friends only | | Show full inventory | Off | | Show duplicate availability | Private | | Allow friend trade proposals | On | | Allow public exchange proposals | Off | | Allow free-text trade messages | Off for new users; on only after trust threshold | | Appear in match suggestions | Opt-in | Default settings for protected/minor users: | Feature | Default | |---|---| | Public collector profile | Off | | Show completed albums | Guardian/user-approved limited mode | | Show full inventory | Off | | Show duplicate availability | Private | | Allow friend trade proposals | Restricted | | Allow public exchange proposals | Off | | Allow free-text trade messages | Off | | Appear in match suggestions | Off unless safe closed group mode is enabled | ### 14.2 Age-Appropriate Design Protected account requirements: - No public exchange board access by default. - No free-text messaging. - No sharing precise location. - No off-platform contact fields. - No adult-to-minor unrestricted trading. - Guardian controls where required by jurisdiction/product policy. - Plain-language warnings. - Easy report/block controls. - Limits on high-rarity or irreversible trades. ### 14.3 Blocking When User A blocks User B: - B cannot view A’s public collector profile while authenticated. - B cannot send trade proposals to A. - B cannot message A. - Matching excludes the pair. - Existing active proposals are cancelled or hidden according to safety policy. - Reports remain available. Blocking must not reveal sensitive status details to the blocked user beyond generic unavailability. ### 14.4 Privacy-Preserving Matching The system should not expose full inventories for matching. Instead, return minimal match data: - Items relevant to the proposed exchange. - Public trust badges. - Public display name. - No email, phone, precise location, birth date, or provider account ID. - No complete partner inventory unless explicitly shared. ### 14.5 Consent for Profile Sharing If a user shares a collector profile externally: - They choose which albums and stats are visible. - They choose whether duplicate availability is visible. - Shared links can expire. - Users can revoke shared links. - Partner-origin items respect partner display license. - Minor/protected profiles cannot be broadly public by default. ### 14.6 Data Retention Recommended retention: | Data | Retention | |---|---| | Active inventory | Life of account unless deletion/revocation applies. | | Inventory ledger | Retained as required for integrity, fraud, legal, and accounting obligations. | | Partner tokens | Deleted on disconnect or expiration. | | Raw partner payloads | Minimized; retain hashes and normalized records where possible. | | Trade proposals | Retain for user history and safety review. | | Moderation reports | Retain per safety/legal policy. | | Public shared profile snapshots | Deleted or disabled when user revokes link. | Privacy requests must distinguish between deleting public/profile data and preserving minimal anti-fraud ledger records where legally permitted. --- ## 15. Partner and Licensing Considerations ### 15.1 Partner Capability Matrix Before enabling a provider, confirm: | Capability | Required for Display | Required for Trading | Required for Write-Back | |---|---:|---:|---:| | Licensed catalog metadata | Yes | Yes | Yes | | Approved media assets | Yes | Yes | Yes | | User inventory API | Optional | Yes for partner-origin inventory | Yes | | User auth/consent mechanism | Optional for generic catalog; yes for inventory | Yes | Yes | | Transfer API | No | Only if trading true provider items | Yes | | Webhooks or polling | Recommended | Recommended | Strongly recommended | | Revocation handling | Yes | Yes | Yes | | Legal approval of terminology | Yes | Yes | Yes | ### 15.2 Terminology Rules Use precise wording: | Situation | Safe Wording | |---|---| | Read-only imported partner item | “Synced from your connected provider account.” | | Fan Passport-native collectible | “Fan Passport digital collectible.” | | Partner-approved transferable item | “Transferable partner collectible,” only if contract permits. | | Representation not changing partner account | “Fan Passport exchange item; your provider account inventory will not change.” | | Unlicensed/non-partner concept | Avoid provider marks and official imagery. Use generic placeholders or Fan Passport-owned creative. | ### 15.3 Media Handling - Store only approved assets. - Track asset license source. - Enforce region restrictions. - Support takedown and asset revocation. - Do not allow user-uploaded scans of copyrighted cards/stickers as public item art unless rights permit. - Use provider attribution exactly as contract specifies. --- ## 16. Notifications ### 16.1 Notification Events | Event | Recipient | Channel | |---|---|---| | Trade proposal received | Recipient | In-app, push/email if opted in | | Counteroffer received | Other party | In-app, push/email if opted in | | Trade accepted; confirmation needed | Both | In-app, push | | Trade confirmed by one party | Other party | In-app | | Trade completed | Both | In-app, optional email | | Trade expired | Both | In-app | | Trade failed | Both | In-app with reason | | Item sync completed | User | In-app | | Partner sync issue | User | In-app with reconnect prompt | | Moderation action | Affected user | In-app/email depending severity | | Report received | Reporter | In-app acknowledgement | | Appeal decision | Appealing user | In-app/email | ### 16.2 Notification Safety Notifications should not reveal sensitive inventory details on locked screens by default. Example safe push: > “You have a new Fan Passport trade proposal.” Instead of: > “Collector92 wants your rare Lionel Messi card.” Users can opt into richer notifications, subject to age and privacy settings. --- ## 17. Admin and Operations ### 17.1 Admin Console Capabilities Admin/moderation tooling should support: - Search users by internal ID, not public personal data by default. - View inventory ledger. - View trade history and state. - Inspect item provenance. - Freeze user trading. - Quarantine items. - Release locks. - Trigger provider resync. - Resolve sync conflicts. - Reverse trades with compensating ledger entries. - Review reports. - Apply moderation actions. - Export partner incident reports. - View fraud cluster signals. - Audit all admin actions. ### 17.2 Operational Metrics Product metrics: - Album completion rate. - Daily active collectors. - Wishlist creation rate. - Duplicate-to-trade conversion rate. - Proposal acceptance rate. - Trade completion rate. - Average time to complete trade. - Items exchanged per active collector. - Partner account connection rate. - Partner sync success rate. Safety metrics: - Reports per 1,000 trades. - Confirmed scam rate. - Trade reversal rate. - Block rate. - Message rejection rate. - Suspicious account clusters. - Minor/protected account interaction attempts blocked. - Moderation SLA compliance. Reliability metrics: - Inventory sync latency. - Webhook processing lag. - Trade execution error rate. - Lock expiry cleanup count. - Ledger reconciliation mismatch count. - Partner API timeout/error rate. - Queue backlog. ### 17.3 SLO Recommendations | Capability | Target | |---|---:| | Album/inventory read availability | 99.9% | | Trade creation availability | 99.5% | | Internal trade execution success after lock | 99.9% | | Inventory ledger consistency | 100% reconciled or alerted | | Partner sync completion for normal batches | 95% within 5 minutes | | Critical moderation response | Under 1 hour | | Lock cleanup job | Every 1 minute | ### 17.4 Scheduled Jobs | Job | Frequency | Purpose | |---|---:|---| | Duplicate recomputation | Event-driven plus hourly sweep | Keep availability current. | | Trade expiry cleanup | Every minute | Expire proposals and release locks. | | Partner sync polling | Provider-specific | Keep external inventory current. | | Ledger reconciliation | Hourly and daily deep run | Detect balance mismatches. | | Risk model refresh | Near-real-time plus daily | Update risk tiers. | | Moderation queue escalation | Every 15 minutes | Promote overdue/high-risk cases. | | Asset license validation | Daily | Ensure expired/revoked assets are hidden. | --- ## 18. Rollout Plan ### 18.1 Phase 1: Fan Passport-Native Albums and Trading Capabilities: - Fan Passport-owned catalog. - Albums and slots. - Inventory rewards. - Duplicate calculation. - Wishlists. - Direct proposals. - Atomic internal exchanges. - Basic moderation/report/block. - Immutable ledger. - No partner write-back. Rationale: - Delivers user value immediately. - Tests exchange mechanics safely. - Avoids dependency on partner contracts. ### 18.2 Phase 2: Read-Only Partner Inventory Sync Capabilities: - Authorized provider catalog import. - User account connection. - Read-only inventory display. - Album slot filling with synced partner items. - Duplicate display as non-tradeable unless license permits. - Partner sync reconciliation. Rationale: - Lets fans showcase official collections. - Avoids unauthorized transfer claims. - Builds partner confidence. ### 18.3 Phase 3: Partner-Approved Exchange Representations Capabilities: - Contract-approved Fan Passport tradeable representations. - Clear UI distinction from provider account ownership. - Partner-branded campaigns. - Promotional drops and redemption codes. Rationale: - Enables trading experiences without requiring provider account item transfer APIs. ### 18.4 Phase 4: Provider Write-Back Transfers Capabilities: - True partner item transfer where supported. - Partner transfer intent and confirmation. - Webhook reconciliation. - Joint support operations. - Partner incident reporting. Rationale: - Highest-value integration, requiring mature trust, safety, and legal alignment. ### 18.5 Phase 5: Football Fan Rewards Ecosystem Capabilities: - Premier League Passport albums. - Champions League Passport albums. - Cross-season collector identity. - Partner reward tiers. - Sponsor campaigns. - Verified collector reputation. Rationale: - Extends World Cup engagement into a year-round football passport ecosystem. --- ## 19. Key Edge Cases ### 19.1 User Trades Away Album-Completing Item Handling: - Default protection prevents this. - If user explicitly allows it, confirmation shows lost album slots. - Album progress updates immediately after exchange. - Completion badges may remain historical, but current album completion reflects missing slot. ### 19.2 Same Item Required by Multiple Albums Handling: - Reserve according to album policy. - If one copy can satisfy multiple display slots, mark shared usage. - If distinct copies are required, reserve multiple quantities. - UI must explain why fewer duplicates are available than expected. ### 19.3 Partner Disconnect During Active Trade Handling: - If partner item is read-only display, cancel trade if item was somehow selected. - If partner write-back item is locked, freeze trade and check token validity. - User may reconnect to proceed. - If reconnect does not happen before expiry, release locks. ### 19.4 Recipient No Longer Has Requested Item Handling: - Acceptance validation fails. - Proposal status becomes `failed_validation`. - Initiator is notified item is unavailable. - No private reason such as “user was banned” is disclosed unless appropriate. ### 19.5 Trade Completed, Then User Reports Scam Handling: - Create moderation case. - Do not automatically reverse. - Freeze disputed items only if risk threshold is met. - Review evidence, message logs, trade warnings, and provenance. - Apply enforcement and compensating ledger entries if warranted. ### 19.6 User Receives Item Already Owned Handling: - Inventory quantity increases. - Album progress unchanged if slot already filled. - Duplicate stack recomputes. - User may now have a new tradeable duplicate. ### 19.7 Partner Revokes Asset Art Handling: - Hide or replace media asset according to license policy. - Preserve inventory identity if permitted. - Use generic placeholder only if allowed. - Notify users if album visuals change materially. ### 19.8 Fraud Ring Moves Items Through Many Accounts Handling: - Provenance graph identifies downstream holders. - Quarantine suspicious item chain. - Freeze high-risk accounts. - Preserve legitimate user experience where possible through compensation policy. - Escalate partner-origin incidents to provider. --- ## 20. Security Requirements ### 20.1 API Security - All mutations require authentication. - Use idempotency keys for trade and inventory mutation endpoints. - Enforce authorization on every trade, inventory, and album resource. - Validate all item IDs server-side. - Never trust client-supplied rarity, ownership, quantity, or album impact. - Use CSRF protection for browser sessions. - Apply rate limiting by user, IP, device/session, and risk tier. - Log request IDs for support traceability. ### 20.2 Partner Security - Store tokens encrypted in managed secret storage. - Use least-privilege scopes. - Verify webhook signatures. - Use nonce/timestamp replay protection. - Rotate provider credentials. - Segregate partner adapters from core inventory mutation logic. - Keep partner raw payloads minimized. - Alert on provider sync anomalies. ### 20.3 Data Access - Internal staff access must be role-based. - Moderators see only data needed for safety decisions. - Partner support exports must exclude unrelated user data. - Admin actions require reason codes and audit logs. - Sensitive identifiers should be pseudonymized in analytics. --- ## 21. Compliance and Policy Notes The platform should be reviewed against: - GDPR and UK GDPR for EU/UK users. - CCPA/CPRA for California users. - COPPA and other child privacy rules where children may use the product. - Online safety obligations in supported markets. - FIFA, team, player, stadium, and partner IP licensing. - Promotional campaign and sweepstakes regulations. - Consumer protection rules around digital goods, scarcity claims, and reversals. - App store policies for user-generated content and trading mechanics. No official Panini or other provider integration should be launched until the relevant commercial, legal, technical, and brand approvals are complete. --- ## 22. Acceptance Criteria A competent delivery team can consider the virtual album and exchange design ready for implementation planning when all of the following are satisfied: 1. **Virtual album model defined** - Albums, slots, item definitions, item instances/holdings, and completion rules are specified. 2. **Inventory sync covered** - Full import, delta sync, webhook sync, manual refresh, consent, idempotency, and reconciliation are specified. 3. **Duplicate handling covered** - Duplicate calculations, album reservation, visibility, and partner read-only cases are defined. 4. **Trade lifecycle covered** - Proposal creation, validation, counteroffers, locking, confirmation, execution, expiry, failure, and receipts are specified. 5. **Exchange confirmation covered** - Two-party confirmation, album impact warnings, partner write-back disclosure, and atomic transfer rules are specified. 6. **Moderation covered** - Moderated surfaces, reporting, queues, enforcement, appeals, and SLAs are specified. 7. **Fraud prevention covered** - Threat model, risk scoring, provenance, ledger integrity, quarantine, and anti-spam controls are specified. 8. **User safety covered** - Visibility defaults, minor/protected account rules, blocking, privacy-preserving matching, and data retention are specified. 9. **Partner constraints covered** - Read-only display, representation trading, true provider transfer, licensing, media handling, and terminology rules are specified. 10. **Operational readiness covered** - Metrics, SLOs, scheduled jobs, admin tooling, edge cases, and rollout phases are specified. --- ## 23. Implementation Readiness Checklist Before engineering begins, confirm: - Product has selected MVP exchange mode: direct proposals, match-assisted proposals, or both. - Legal has approved item terminology and partner-origin disclosure language. - Trust and Safety has approved minor/protected account restrictions. - Data Protection has approved consent, retention, and public profile visibility defaults. - Engineering has selected storage architecture for transactional balances and immutable ledger. - Security has approved partner token storage and webhook verification approach. - Support has approved reversal, quarantine, and dispute workflows. - Partner Management has confirmed which proposed providers are authorized for catalog display, inventory sync, or transfer. - Analytics has defined dashboards for trades, syncs, fraud, and moderation SLAs. The design is intentionally compatible with a Fan Passport-native MVP that can launch without partner credentials, while preserving a clean path to authorized Panini or other sticker/card provider integrations later.