# Poki Yoki Rewards — Build Spec (cheat codes, points, free shipping)

**v1 · 2026-06-14 · companion to `rewards-program-report.html` + `rewards-launch-comms.html`**

This is the engineering-ready spec for the mechanics that need real implementation. Scope: (1) the founders **cheat-code** system, (2) the **PokiStars** earning/redemption rules, (3) the **free-shipping threshold**, (4) the **tier/status** flags. Everything here is designed to lean on Shopify-native tooling + the existing Arcade backend so the build is small.

---

## 0. What's off-the-shelf vs. custom

| Piece | Build approach | Cost |
|---|---|---|
| $-discount half of cheat codes | Shopify **bulk discount codes** (native) or "Bulk Discount Code Bot" app | $0 / free tier |
| Points + referral engine | **Smile.io Free** (≤200 orders/mo) — points, referral, fraud controls, Shopify-native | $0 until ~200 orders/mo |
| Status/Founders tiers | **Shopify customer tags** + email segments (manual) for year one | $0 |
| Free-shipping threshold | Shopify Markets / native shipping rule | $0 |
| PokiStars grant from a code | **Custom** — small table + endpoint in the existing Arcade backend | dev time only |
| In-game badge display | **Custom** — render tier badge next to leaderboard name / on Poki Pet | dev time only |

The only genuinely custom work is the PokiStars-grant half of the cheat code and the badge rendering. Everything else is configuration.

---

## 1. Cheat-code system

### 1.1 Concept

Each early supporter gets **one unique, single-use code** that does two things on redemption:
1. Credits **PokiStars** in the Arcade (the custom part).
2. Carries a matching **Shopify discount** ($10 or $5 off, single-use) for real-world redemption (native Shopify).

Three batches, distinguished by prefix:

| Batch prefix | Cohort | PokiStars grant | Shopify discount |
|---|---|---|---|
| `POKIOG-` | 700 early customers (Founding Backer) | 500 | $10 off, single-use, 90-day |
| `CHARTER-` | 250 VIP group (Charter) | 500 | $10 off, single-use, 90-day |
| `PIONEER-` | 7,000 email list (Pioneer) | 100 | $5 off, single-use, 90-day |

> Pioneer grants are smaller AND the badge/stars **only activate on first purchase** (see §4) — the code can be entered anytime, but the visible reward unlocks at first order.

### 1.2 Data model (Arcade backend)

```
table reward_codes
  code            string  PRIMARY KEY      -- e.g. "POKIOG-XR7KM2"
  batch           enum    [POKIOG, CHARTER, PIONEER]
  stars_grant     int                      -- 500 | 500 | 100
  status          enum    [unclaimed, claimed, void]  default unclaimed
  claimed_by      string  NULL             -- user_id, set at redemption (locks the code)
  claimed_at      timestamp NULL
  issued_at       timestamp                -- for the 90-day expiry
  expires_at      timestamp                -- issued_at + 90 days

table reward_redemptions          -- append-only audit log
  id              uuid PRIMARY KEY
  code            string  FK -> reward_codes.code
  user_id         string
  stars_credited  int
  redeemed_at     timestamp
  ip              string
  result          enum [ok, already_claimed, expired, rate_limited, mismatch]
```

`reward_codes.code` is also the Shopify discount-code string — same string in both systems, so the customer types one code and it works at the Arcade and at Shopify checkout. (Generate the codes once, import the discount half into Shopify, the grant half into this table.)

### 1.3 Code generation

- Format: `PREFIX-` + 6 chars from an unambiguous alphabet (`ABCDEFGHJKLMNPQRSTUVWXYZ23456789` — no `0/O/1/I`). ~1.07B combos per prefix; collision risk negligible at our volumes, but enforce uniqueness on insert.
  - **WHY: unambiguous alphabet** — these get typed by hand off a phone screen; `0/O` and `1/I` cause failed redemptions and support tickets.
- Generate N codes per batch (700 / 250 / 7,000), insert into `reward_codes` (status `unclaimed`, `issued_at = now`, `expires_at = now + 90d`).
- Export the code list with the per-person mapping (email ↔ code, plus backer number for `POKIOG-`) for the merge into Klaviyo/email + the VIP DM list.
- In Shopify: bulk-create matching single-use discount codes — `POKIOG-`/`CHARTER-` → $10 off, `PIONEER-` → $5 off; **set "can't combine with other discounts"** (non-stacking); no minimum; expiry = same 90 days.

### 1.4 Redemption flow (Arcade)

Endpoint: `POST /api/rewards/redeem { code }` (auth required — must be a logged-in user).

```
1. Normalize input: uppercase, trim, collapse internal spaces.
2. Look up code in reward_codes.
   - not found              -> 404 { result: "invalid" }
3. Rate-limit: count this user's redemptions in last 30 days.
   - >= 1 already           -> 429 { result: "rate_limited" }   (one code per account / 30 days)
4. Check status / expiry:
   - status != unclaimed     -> 409 { result: "already_claimed" }
   - now > expires_at        -> 410 { result: "expired" }
5. Claim (atomic / transaction):
   - SET status=claimed, claimed_by=user_id, claimed_at=now
     WHERE code=? AND status='unclaimed'        -- conditional update prevents races
   - if 0 rows updated -> 409 already_claimed (lost the race)
6. Credit stars: user.pokistars += stars_grant
7. If batch == PIONEER and user has 0 paid orders:
     - mark badge+stars as "pending_first_purchase" (grant recorded, shown locked)
   else:
     - activate tier badge immediately (see §4)
8. Append reward_redemptions row (result: ok).
9. Return 200 { stars_credited, badge, shopify_discount: code }
```

**Anti-abuse, concretely:**
- **Single-use:** the conditional `WHERE status='unclaimed'` update in step 5 makes claiming atomic — two simultaneous requests can't both win.
- **Account-bound:** `claimed_by` locks the code to the first user; re-entry by anyone returns `already_claimed`.
- **Rate limit:** one redemption per account per 30 days (step 3) — even if codes leak, one account can't drain many.
- **Batch isolation:** `batch` is stored; a nightly check flags any account whose claimed code `batch` doesn't match its cohort tag (e.g. a `PIONEER-` code claimed by a tagged Founding Backer, or vice-versa) for manual review.
- **Expiry:** 90-day hard stop; a cron sets `status=void` on expired unclaimed codes so they can't be revived.
- **Velocity alert:** if `reward_redemptions` shows the same `ip` across >3 distinct accounts in 24h, alert ops.

### 1.5 PokiStars value peg (decide before launch)

500 stars should feel meaningful but not let a player skip the game loop. Recommended peg: **500 stars = one Founders-exclusive Poki Pet cosmetic + a head start (name + equip without grinding)**. If stars currently earn at ~10–25/play, 500 ≈ 20–50 plays of head start. Physical redemption (stars → product) capped at **one item / account / quarter**. **Eric/Cristina to confirm the exact in-game catalog price of 500 stars.**

---

## 2. PokiStars earning & redemption (loyalty currency)

### 2.1 Earn rates

| Source | Stars | Cap |
|---|---|---|
| Purchase | 5 per $1 spent | — (tier multipliers: Stacker 6/$1, Inner Circle 7/$1) |
| Account create / verify email | 50 / 25 | one-time |
| Complete profile (child name+age) | 100 | one-time |
| Review text / photo / video | 100 / 200 / 350 | one-time per order |
| Referral (friend's order ships +14d) | 500 | per converting referral |
| Follow IG / TikTok | 50 each | one-time |
| Child birthday | 150 | annual |
| Arcade: completed session | 5 | part of 200/mo cap |
| Arcade: personal best | 10 | 1/day, within cap |
| Arcade: weekly top-100 leaderboard | 25 | within cap |
| Arcade: 7-day streak | 50 | within cap |
| **Arcade monthly total** | — | **200 stars / month** |

> **WHY: 200/mo Arcade cap** — keeps a non-buyer's cost to retain at ≤$2/mo. Optionally run **uncapped for the first 90 days** to observe real behavior, then apply the cap if exploitation appears.

### 2.2 Redemption

- **100 stars = $1.** Minimum redemption **500 stars ($5)**; maximum **1,000 stars ($10) per order**.
  - **WHY: 500 minimum** — at ~225 stars per $45 order, a customer needs 2–3 orders to redeem, so the threshold itself drives the second purchase before any value leaves.
- **Expiry: rolling 24-month inactivity** (any purchase OR Arcade session resets the clock).
  - **WHY: rolling expiry** — keeps the ASC-606 points liability tied to active customers only, and is legally cleaner than "never expires" (which you can't later revoke).
- Effective reward rate ≈ 2% on purchases; ~1.5% after ~25% breakage. Margin-safe at $45 AOV / 72–75% GM.

If using **Smile.io**: configure points = 5/$1, redemption 500=$5, cap, and the earning actions above. Smile.io handles the ledger, referral links, and fraud controls natively — only the **Arcade earning** is custom (post stars to Smile.io via its API on game events, OR keep Arcade stars in the game ledger and sync balances; simplest is one source of truth — see §5).

---

## 3. Free-shipping threshold

- Threshold: **$75** (sits between Starter $45 and Family $85).
- Implement as a Shopify shipping rate: free shipping when cart subtotal ≥ $75; flat shipping below.
- Add a **cart progress bar** ("Add $X for free shipping") — native in most Shopify themes or a free app; worth +15–25% threshold conversion.
- Review quarterly against real carrier rates (USPS Ground Advantage rose ~7.8% Jan 2026).

---

## 4. Tiers & status flags

Run as **Shopify customer tags** for year one (no paid tier software until ~500 orders/mo).

| Tag | Meaning | How set |
|---|---|---|
| `founding-backer` | Gold cohort (700) | bulk-import from early-customer/KS export, before `{{CLOSE_DATE}}` |
| `charter` | Silver cohort (250 VIP) | bulk-import from FB-group list |
| `pioneer` | Bronze cohort (7,000) | bulk-import from email list; **perks gated until first paid order** |
| `tier-stacker` | $150+ lifetime spend | automation (Shopify Flow) on lifetime-spend crossing |
| `tier-inner-circle` | $350+ lifetime spend | Shopify Flow |

- **Founders tags are frozen after `{{CLOSE_DATE}}`** — no new founder tags issued after the close. This is enforced by simply not running the import again; document the date.
- **Badge rendering (custom, Arcade):** read the user's cohort tag (synced from Shopify, or stored on the Arcade account at code redemption) and render the matching hex badge (gold/silver/bronze) next to the leaderboard name and on the Poki Pet. Pioneer badge renders **locked/greyed until `pending_first_purchase` clears**.

---

## 5. The one architectural decision: where do stars live?

Two currencies risk drift (Arcade stars vs Smile.io points). Pick one source of truth:

- **Option A (recommended): Arcade backend is the ledger.** All stars (purchase, review, referral, Arcade play) accrue in the Arcade account. Shopify purchases post stars to it via webhook (`orders/paid` → credit 5×subtotal). Redemption generates a one-off Shopify discount for the star value. Keeps the game-native currency unified and avoids paying for Smile.io's higher tiers. More custom code.
- **Option B: Smile.io is the ledger.** Smile.io handles purchase/review/referral points + redemption natively (fast to launch, less custom code); the Arcade posts game-earned stars to Smile.io via its API. Two systems to keep in sync, and VIP automation needs Smile.io Growth (~$199/mo).

**Recommendation:** launch the **status tiers + cheat codes** now (both are independent of this choice), start points on **Smile.io Free (Option B)** to validate cheaply, and migrate to **Option A** if/when the Arcade-native unified currency proves worth the custom build. Don't block the founders launch on this decision.

---

## 6. Build order (smallest shippable first)

1. **Lock `{{CLOSE_DATE}}` + the 500-star peg.** (Decisions, not code.)
2. **Cohort tags** imported to Shopify (founding-backer / charter / pioneer). → unblocks launch comms.
3. **Cheat codes generated** (§1.3): insert to `reward_codes`, bulk-import discount halves to Shopify, export the email↔code merge file. → unblocks §2 of the comms kit.
4. **Redemption endpoint + `reward_codes`/`reward_redemptions`** (§1.4). → cheat codes go live.
5. **Badge rendering** in Arcade (gold/silver/bronze) (§4).
6. **Free-shipping threshold + progress bar** (§3).
7. **Points** via Smile.io Free (§2) + Arcade earning hook.
8. **Referral** via Smile.io (single-use links, 14-day post-fulfillment hold) (referral §7 of the main report).

Steps 1–4 are the critical path to the founders launch. 5–8 can follow within the 90-day window.

---

## 7. Test cases (must pass before sending codes)

- Redeem a valid unclaimed code → stars credited, status→claimed, discount works at Shopify checkout once.
- Re-redeem the same code (same user) → `already_claimed`, no double credit.
- Redeem same code (different user) → `already_claimed`.
- Two simultaneous redeems of one code → exactly one succeeds (race test).
- Second code within 30 days (same account) → `rate_limited`.
- Expired code → `expired`; expired+void code → not revivable.
- `PIONEER-` code, user with 0 orders → stars/badge `pending_first_purchase`; after first paid order → activates.
- Cross-batch claim (Pioneer code, Founding Backer account) → succeeds but flagged in nightly review.
- Shopify discount: non-stacking verified (can't combine with another code); single-use verified.

---

*Open decisions live in §11 of `rewards-program-report.html`. The two that block this build: `{{CLOSE_DATE}}` and the 500-star in-game peg.*
