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.py — subscription_tier, subscription_status, paypal_subscription_id columns on User.
- services/gameserver/src/models/region.py — subscription_tier, paypal_subscription_id on Region (for Region Owner billing).
- services/gameserver/src/services/regional_auth_service.py — RegionalRole.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:
- Signature verification against PayPal's webhook key (
validate_webhook_signature). - Timestamp window: the webhook's
event_timemust be within 5 minutes of servernow(). Older or far-future timestamps are rejected — closes the replay-attack window. - Idempotency: the webhook's
event_idis checked against aprocessed_webhook_eventstable (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-catalogentry 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=Trueso 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¶
- multi-regional.md — what region ownership unlocks at the gameplay level.
- aria.md — ARIA tier benefits for paid subscribers.
- FEATURES/economy/lifecycle.md — subscription perks as economy faucets.
- FEATURES/gameplay/player-journey.md — when in the player lifecycle each tier becomes attractive.