Skip to main content
InternalMarkdown

Loyalty

The private player owns loyalty and analytical facts. External aliases are access handles, not separate wallets.

Long-form: spec §18, §33, §38. Decisions: ADR 0007, 0008, 0009, 0026.

Authority​

PlayerPal calculates points. Providers report factual activity; they do not calculate the PlayerPal reward. The append-only points ledger is authoritative. Cached balances and projections are derived views.

The live ledger may reference the internal player UUID (players.id) in memory. Partner-facing analytics do not: they are Audience-scoped numbers, never UUID, pp_plr_..., another Audience’s alias, or the analytics subject.

Phase 1 demo earn path​

  • Partners (or POST /demo/activity-batches when DEMO_PARTNER_HELPER=1) submit Activity facts; Nest awards Points.
  • Fixed campaign version pp_cmp_BET_EUR_V1: 1 Point per €1 bet, EUR only, round_settled events for linked pp_plr_prv_… aliases.
  • Machine ingest requires x-api-key and x-integration-id (no default audience). ACK is 202.
  • StarBucks is owned by the NetEnt provider iframe (:5175), which receives only the NetEnt×Bucks (pp_int_NETENT_BUCKS) alias and batches settled rounds through a retrying demo outbox. Big Bass → Pragmatic×Bucks remains distinct.

Eligibility​

  • Earning starts only after explicit registration/activation. No pre-registration shadow profiles. No historical points by default (spec §33). Spec §18's "pre-enrolment accrual" option is not current product policy.
  • An activity event is eligible only when occurred_at >= connection.effective_from and before Unlink revocation (unlinked_at; spec §36.6 effective_at).
  • Unlink stops qualifying later Activity for that Audience; Points already on the ledger stay. Delete Account erases the identity graph and does not confiscate retained gameplay facts (no identity keys).
  • A cookie expiring does not stop asynchronous earning once the server-side link exists.

Campaigns​

Campaign configuration is versioned and snapshotted (ADR 0007). reward_event references campaign_version_id (or rule version), never a mutable campaign row. Past decisions stay explainable after edits.

The domain model is generic (campaign → eligibility → trigger → reward → limits → schedule → audience), not casino-only.

Ledger​

Raw activity is immutable. Corrections are adjustments, reversals or compensating events, not mutations. Merge of two player accounts does not rewrite history; it applies programme-specific reconciliation and compensating entries (ADR 0019).

Idempotency on the reward side: UNIQUE (campaign_rule_version_id, activity_event_id, ledger_action) (spec §38.1).

Analytics​

Processing Activity upserts daily serving rows. Partner studio HTTP (GET /v1/analytics/studio, GET /v1/analytics/studio/aliases/:alias) is provider-Party scoped. The provider comes from the partner principal: an OAuth client or a PP-HMAC-SHA256 key with scope studio.read (api/hmac-signing.md). The legacy shared machine key with x-party-id (pp_prv_...) is still accepted while STUDIO_LEGACY_PARTY_HEADER is not false, and must be turned off in production. Ingest stays integration-scoped (x-integration-id). Scope never comes from a payload operator_id.

Per-alias lookup uses only a pp_plr_prv_... that provider was issued. Two aliases for the same Account at that provider return the same Account-level numbers. A foreign alias is the same unknown as a missing alias. After Delete Account, live per-alias GET is unknown.

“On you” is all of that provider’s integrations for the Account that period, not one casino. Response fields (numbers only): from, to, bets, wins, bet_eur, share_of_their_slot_turnover, rounds_on_you, rounds_elsewhere. Studio rollup adds accounts (distinct Accounts, not a sum of per-alias rows) and suppresses windows below a configurable Account floor (production default 50).

A provider-global cohort is allowed; a provider-global alias is not (ADR 0014, ADR 0026).

Each Activity event may carry a licensed market (ISO 3166-1 alpha-2). It is checked against the integration's declared markets, and events outside them are rejected with market_not_licensed. Market feeds partner KPIs and anonymous comparisons; see kpis.md and ADR 0033.