Skip to content

Monetization

Status: 🚧 Partial — PayPal create/activate/cancel/suspend webhook flow is live; the three security invariants (timestamp window, idempotency table, production bypass guard) are now implemented + proven live (2026-06-16, dev e123ab5). Remaining Partial: container-provisioning gap on the webhook activation path + region-lifecycle states. (impl audit 2026-06-16)

SectorWars 2102 is free-to-play with optional subscriptions. The base game (sign-up, single-region play, all core gameplay) is free forever. Real money buys cross-region travel, region ownership, and (future) cosmetic / convenience perks — never a competitive ceiling.

Pay-to-win firewall (ratified 2026-06-21). Paid buys SHAPE, CONVENIENCE, and EXPRESSION; earned-and-free buys POWER. A Galactic-Citizen ship or module may differ from its free counterpart only in grid shape (best-case adjacency ≤ the free anchor's), non-income utility breadth (under the best-3 stacking cap), QoL reachable by free credits+time, and cosmetics. Its competitive ceiling — combat and income — is no higher than a free player reaches. No paid slot or effect carries an income axis (passive_income, mining_efficiency, cargo_bonus_percent, or any income class). A Citizen supercharged slot is permitted but class-locked to a non-combat/non-income utility class, so its multiplier raises utility/QoL, never the combat or income ceiling. Higher paid tiers may grant broader shape / convenience / cosmetic, never a higher competitive ceiling. Enforced in code by a CI income-fence test.

Source files: - services/gameserver/src/services/paypal_service.py — PayPal API integration, plan IDs, webhook handling. - services/gameserver/src/models/user.pysubscription_tier, subscription_status, paypal_subscription_id columns on User. - services/gameserver/src/models/region.pysubscription_tier, paypal_subscription_id on Region (for Region Owner billing). - services/gameserver/src/services/regional_auth_service.pyRegionalRole.GALACTIC_CITIZEN, role permissions.


1. Subscription tiers

Three tiers are defined in paypal_service.py:SubscriptionTier. The first two are live; the third is reserved for future expansion.

1.1 Free

Price: $0.

Identity: Default for every new account. User.subscription_tier = NULL.

Includes: - Full character creation and play within a single home region. - All core gameplay: trading, combat, planet ownership, colonization, emergent faction reputation, team membership, ranking progression, NPC contracts. - ARIA companion at base consciousness tier. - Access to leaderboards and cross-team chat within the home region. - Travel to Central Nexus as a visitor membership. The Nexus is the universal hub and is not gated by Galactic Citizenship — free-tier players can warp from their home region's gate to Gateway Plaza, trade and socialize in the Nexus, and warp back. This does not grant access to other player-owned regions.

Excludes: - Travel to other player-owned regions. The Player.can_travel_between_regions gate (which permits inbound to any region from the Nexus warp gate network) requires Galactic Citizenship. - Region ownership. - (Future) cosmetic ship skins and ARIA voice packs.

1.2 Galactic Citizen — $5 / month

Identity: User.subscription_tier = "galactic_citizen". PayPal plan ID configured via PAYPAL_GALACTIC_CITIZEN_PLAN_ID env var (services/gameserver/src/core/config.py:73).

When a Galactic Citizenship subscription activates, Player.is_galactic_citizen is set to true (paypal_service.py:_activate_galactic_citizenship, line 412).

Unlocks: - Travel to other player-owned regions via Central Nexus warp gates (the Player.can_travel_between_regions property gates this — models/player.py:147). Free-tier players can already reach the Nexus itself; Galactic Citizenship is what permits onward warp from the Nexus into another region. - Auto-elevation to RegionalRole.GALACTIC_CITIZEN in any visited region, granting GALACTIC_CITIZEN_BENEFITS permission (regional_auth_service.py:179). - A small weekly in-game credit stipend (target: 2,500 cr/week — see FEATURES/economy/lifecycle.md). - Faster ARIA relationship score growth and elevated initial consciousness tier. - Cosmetic citizenship badge on player profile.

1.3 Region Owner — $25 / month

Identity: Region.subscription_tier = "regional_owner", Region.paypal_subscription_id set, Region.owner_id linked to the User. PayPal plan ID via PAYPAL_REGIONAL_OWNER_PLAN_ID.

Activation: paypal_service.py:_activate_regional_ownership (line 426). On subscription approval, the system either creates a new Region row or transfers ownership of an existing one.

Unlocks: - Ability to create and own a player-owned region (Standard tier ≈1,000 sectors with ±20% variability, range 800–1,200; higher subscription tiers 📐 Design-only widen the upper bound). Schema CHECK constraint widens to 100–1,500 to accommodate the Standard range plus tier headroom. - Configure governance_type (autocracy / democracy / council). - Set tax_rate (5% to 25%, constraint in models/region.py:134). - Set starting_credits for new players spawning in the region. - Customize language_pack, aesthetic_theme, traditions (cultural identity). - Receive a share of regional tax revenue (📐 Design-only — payout share %). - Issue regional bounties from treasury. - Includes Galactic Citizen benefits for the owning user.

1.4 Nexus Premium — $50 / month (future)

Identity: User.subscription_tier = "nexus_premium". Plan ID PAYPAL_NEXUS_PREMIUM_PLAN_ID.

Reserved tier (paypal_service.py:30 annotates "future expansion"). Intended for power users who want Galactic Citizen + a permanent presence in the Central Nexus + enhanced ARIA features. Specifics to be defined.


2. PayPal billing flow

End-to-end topology of a subscription purchase. Source: paypal_service.py:create_galactic_citizen_subscription (line 166) and create_regional_ownership_subscription (line 206).

2.1 Subscription create

Player-client                 gameserver                      PayPal
     │                             │                             │
     │ POST /api/v1/subscriptions/ │                             │
     │   galactic-citizen          │                             │
     │ ─────────────────────────►  │                             │
     │                             │ POST /v1/billing/           │
     │                             │   subscriptions             │
     │                             │ ──────────────────────────► │
     │                             │ ◄────── subscription.id +   │
     │                             │         approval_url        │
     │ ◄─── { approval_url } ─────  │                             │
     │                             │                             │
     │ user redirected to PayPal approval page                   │
     │ ─────────────────────────────────────────────────────────►│
     │ user completes PayPal flow                                │
     │ ◄──── return_url with subscription_id ─────────────────── │

2.2 Webhook activation

PayPal calls back to the gameserver webhook endpoint when the subscription state changes. Source: paypal_service.py:handle_subscription_webhook (line 294).

PayPal                          gameserver                   database
   │                                │                            │
   │ POST /webhooks/paypal          │                            │
   │   { event_type:                │                            │
   │     BILLING.SUBSCRIPTION       │                            │
   │     .ACTIVATED }               │                            │
   │ ─────────────────────────────► │                            │
   │                                │ verify signature           │
   │                                │ (validate_webhook_signature)│
   │                                │ ─────────────────────────► │
   │                                │ parse custom_id            │
   │                                │ → "galactic_citizen_<uid>" │
   │                                │ or "regional_owner_<uid>_<region>"
   │                                │                            │
   │                                │ User.paypal_subscription_id│
   │                                │ User.subscription_tier     │
   │                                │ Player.is_galactic_citizen │
   │                                │   = true                   │
   │                                │ ─────────────────────────► │
   │ ◄── 200 OK ────────────────────│                            │

2.3 Feature flag flip

After the webhook commits, gated features become available immediately: - Player.can_travel_between_regions returns true (gates inter-region travel API). - Player.is_galactic_citizen = true (gates Galactic-Citizen-only UI elements). - For Region Owner: Region.status = "active", Region.owner_id set — region governance UI unlocks.

No client-side cache flush is required; the player-client checks these flags on each session and on key UI transitions.


3. Webhook event handling

Source: paypal_service.py:handle_subscription_webhook (line 294). Mapping of PayPal event → action:

PayPal event Handler Effect
BILLING.SUBSCRIPTION.ACTIVATED _handle_subscription_activated Set is_galactic_citizen = true or activate region
BILLING.SUBSCRIPTION.CANCELLED _handle_subscription_cancelled Clear is_galactic_citizen; suspend region (Region.status = "suspended")
BILLING.SUBSCRIPTION.SUSPENDED _handle_subscription_suspended Suspend region access
BILLING.SUBSCRIPTION.PAYMENT.FAILED _handle_payment_failed Log warning; failure-counter for eventual suspension
PAYMENT.SALE.COMPLETED _handle_payment_completed Confirm renewal; reactivate suspended region if applicable

Webhook signature validation is mandatory and binding in production (per ADR-0058 A-D3). Three checks run on every webhook; all must pass before the mutation commits:

  1. Signature verification against PayPal's webhook key (validate_webhook_signature).
  2. Timestamp window: the webhook's event_time must be within 5 minutes of server now(). Older or far-future timestamps are rejected — closes the replay-attack window.
  3. Idempotency: the webhook's event_id is checked against a processed_webhook_events table (UNIQUE INDEX (event_id)). Duplicate delivery (PayPal's at-least-once semantics) returns HTTP 200 without re-applying the mutation.

Validation failure → HTTP 401 (not log-and-allow). The processed_webhook_events insert is in the same transaction as the subscription mutation, so a successful insert means the mutation committed.

Bypass code path. Production binary contains zero bypass code reachable at runtime. The dev/test bypass lives behind a separate config-gated module imported only when settings.environment != 'production'. If the bypass flag is set while environment == 'production', the gameserver fails to start with a BypassFlagInProductionError — fail-fast at import time, not runtime. (code-wins note 2026-06-16: the implemented env var is PAYPAL_SKIP_WEBHOOK_VALIDATION, not PAYPAL_WEBHOOK_BYPASS.)


4. Refund, suspension, termination

4.1 Suspension

Triggered by: - Repeated payment failures (PayPal sends BILLING.SUBSCRIPTION.SUSPENDED after configured retry attempts). - Operator-initiated suspension via admin UI. - Direct PayPal API call: paypal_service.suspend_subscription(subscription_id, reason) (line 266).

Effect: - Region.status = "suspended" for region subscriptions — the region remains in the database, citizens cannot enter/leave, owner cannot configure. - Player.is_galactic_citizen unset for citizenship subscriptions — cross-region travel blocked, but in-game character data is untouched.

A suspended subscription auto-reactivates when payment resumes (_handle_payment_completed, line 393, sets region.status = "active").

4.2 Cancellation

User-initiated: - Player cancels via PayPal account or via in-game UI (which calls paypal_service.cancel_subscription, line 252).

Effect: - Subscription becomes CANCELLED at PayPal. - Webhook fires BILLING.SUBSCRIPTION.CANCELLED. - _handle_subscription_cancelled (line 340) clears Region.paypal_subscription_id, sets region.status = "suspended", or clears player.is_galactic_citizen.

4.3 Refunds

Refund handling is operator-driven — there is no in-app "request refund" button. An operator reviews the case and: 1. Issues the PayPal refund out-of-band via the PayPal merchant dashboard. 2. Cancels the subscription via paypal_service.cancel_subscription. 3. Logs an audit entry via services/audit_service.py.

Target policy: pro-rata refunds within 7 days of charge for Galactic Citizen; pro-rata only on first month for Region Owner; no refund after the first month of a region subscription (since the universe state has been altered by the player's ownership actions).

4.4 Termination — region lifecycle cascade

Per ADR-0050, region subscriptions follow a 5-state cascade with multiple opportunities for recovery before content is deleted. Galactic Citizen cancellation, by contrast, is simple — it returns the user to free tier without any cascade.

Region lifecycle states (Region.status):

State Trigger What works What's blocked Takeover available?
active Subscription paid Everything Nothing n/a
suspended Payment failure event All gameplay continues New residents from outside cannot join. Owner UI shows payment-recovery prompt. Yes — opens immediately
grace 7 days suspended, payment unrecovered All gameplay continues New construction blocked (no new station builds, no new gate beacons, no region-funded TradeDocks). Visible warning banner. Yes — still open
terminated 30 days from initial suspension, payment unrecovered Region marked for cleanup; 7-day final notice fires All region-content writes blocked No — takeover window closed
generation_corrupt Phase 13 rollback failure n/a All access blocked; ops alert n/a (handled out-of-band)

After terminated, the cleanup cascade runs (asset preservation rules below). 7 days after termination, the Region row + dependent content hard-deletes via CASCADE; audit rows persist via region_id_snapshot.

Total grace from first payment failure to content deletion: ~44 days.

Region-owner takeover

While a region is suspended or grace, any Galactic Citizen subscriber (galaxy-wide, not just current residents) can offer to assume ownership:

POST /api/v1/regions/{id}/takeover

The PayPal flow runs at standard $25/mo Region Owner rate. On payment success, ownership flips atomically (Region.owner_id, Region.paypal_subscription_id, Region.status = active). The old owner, if still GC-subscribed, retains all their assets inside the region as a regular resident.

Concurrent claim serialization (per ADR-0058 A-F3). Multiple offers can race. The takeover handler serializes claims via SELECT FOR UPDATE on the Region row at claim time. First commit wins; losing claims reject with ERR_REGION_TAKEOVER_LOST and refund escrow inside the same transaction — no temporal window where money is held without a corresponding ownership state. TakeoverIntent state machine:

pending  ──claim_accepted──▶  won         (region transfer follows)
pending  ──claim_rejected──▶  lost        (escrow refunded in same tx)
won      ──transfer_done──▶   transferred
won      ──transfer_failed──▶ failed      (escrow refunded; region stays with old owner)

Every transition is atomic.

Asset preservation cascade

When termination fires and no takeover happens, the cleanup orchestrator processes each player with assets in the region. Paying players' belongings are preserved as much as possible. The full table is in ADR-0050; summary:

  • Ships (piloted, parked, hangared): evacuate to Central Nexus with player. Drifting/parked routed to Abandoned Hangar at Starport Prime for free claim.
  • Player-owned stations: relocate as a unit to a destination region. Structure + upgrades + treasury + cargo intact; security tier resets. Cost: 30% of (acquisition + upgrade capital) — Path A debits station treasury (with player-wallet fallback then upgrade-strip then loss-with-credit-comp); Path B prepay during grace preserves treasury intact.
  • Player-owned planets (immovable): lost. Compensation: Genesis devices + credits scaled to citadel level (L1 1B+50k → L5 5A+25M cr). Plus: planet's safe vault contents → Central Nexus Bank with 20% transport loss (Path A) or 100% transfer (Path B prepay) or no loss (Path C manual evacuation during grace).
  • Captured pirate holdings: stations relocate, planets lost per planet rule.
  • Player credits: survive (region-independent).
  • Active contracts: cancelled with refunds.
  • Bounties placed: refunded pro-rata.
  • Ship-construction-in-progress at TradeDock: cancelled per AU2-6 + ADR-0039.
  • Player-built warp gates anchored to/from region: per ADR-0052 SK38 — gate's both endpoints destroyed atomically; gate owner receives 50% of construction cost as a credit refund (deposited to wallet if online, Central Bank if offline). Atomic delete; no orphan half-gates.

Central Nexus Bank

A new player-facing surface at any Starport Prime dock in the Central Nexus, operated by the Galactic Concord. Holds credits + commodities indefinitely on behalf of players. Region-independent. Receives planet-safe transfers and station credit-compensation fallbacks during cascades. Players withdraw at any Starport Prime dock — credits free instantly, commodities at 1 turn per 100 units to ship cargo.

Schema in ../DATA_MODELS/player.md#playercentralbankaccount. Runtime in ../SYSTEMS/region-lifecycle.md.

Galactic Citizen termination — 7-day asset-liquidation window

Per ADR-0054, a GC subscription entering payment-failure state opens a 7-day asset-liquidation window for assets the player owns in regions outside their home region.

  • Home region is unaffected. A free-tier player can continue to play normally in their home region indefinitely. Ships, planets, stations, credits in their home region all stay accessible.
  • Foreign-region assets (planets / stations / captured pirate holdings / warp gate endpoints in any region other than the player's home) enter the 7-day liquidation window.
  • ARIA proactively notifies on next login: "Your Galactic Citizen subscription is lapsed. You have 7 days to withdraw assets from regions outside your home region. After that, captured holdings, foreign-region planet safes, and station ownerships enter the standard 30-day abandonment cascade." Per the catalog at ../FEATURES/gameplay/aria-companion.md#aria-narration-hooks-event-catalog entry P-F6. Subscription-upgrade-screen narration (P-F3) surfaces tier benefits at the moment a player views the upgrade UI.
  • GC-bypass transport — the player gets a one-time, free, system-action transport (POST /api/v1/players/me/gc-emergency-relocation) to one of their foreign-region holdings. Consumed once per lapse cycle (renewed on next GC re-subscription).
  • On-site liquidation — once teleported, the player can withdraw planet safes physically (no transport fee), sell stations to NPCs at the standard 50%-acquisition-cost depreciated buyback, or voluntarily surrender holdings (triggers cascade now).
  • After 7 days post-lapse with no action: foreign-region assets enter the standard 30-day abandonment cascade per ADR-0047 and ADR-0050. Cascade compensation deposits to Bank carry access_override=True so the player can withdraw at any port (not just Starport Prime).
  • Re-subscription before day 7 clears the lapse and restores foreign-region access. The GC-bypass transport's "consumed" flag resets on re-subscription.

Runtime details in ../SYSTEMS/region-lifecycle.md § "GC-lapse foreign-asset liquidation window."


5. Pricing rationale

Tier Price Marginal cost Target audience Conversion goal
Free $0 Hosting share Everyone Onboard, retain ≥ 10 hours
Galactic Citizen $5 Trivial Hour-50+ players 5–10% of active players
Region Owner $25 Operator cost dominant (region creation, persistence, governance UI) Hour-300+ committed players 0.5–1% of active players
Nexus Premium $50 (future) Power users, content creators <0.1% (luxury tier)

The price ladder is deliberately steep — Region Owner is 5x Galactic Citizen — because region ownership is a creator role, not a consumer role, and pricing it close to Galactic Citizen would devalue regional politics.


6. Identity-state lookup

A user's combined subscription state can be queried via paypal_service.get_user_subscriptions(user_id) (line 459), which returns all active subscriptions across both Galactic Citizen and any owned regions. The function consults both User.paypal_subscription_id and the joined Region.paypal_subscription_id rows.

For internal feature-gating, services should consult: - User.subscription_tier (string column) — fast in-process check. - User.subscription_status — must equal "active" (or PayPal's "ACTIVE"). - Player.is_galactic_citizen — derived flag for cross-region permissions. - Region.owner_id == user.id AND Region.status == "active" — region ownership check.


7. Audit and compliance

All subscription state changes log to services/audit_service.py. Webhooks include: - Inbound webhook payload (full body) with redacted PayPal access tokens. - Resulting database mutations. - Operator who initiated any direct cancellation/suspension.

Retention: 7 years for billing-related audit entries (financial-services standard).


8. Cross-references