# Pro Purchase Verifier Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Validate Google Play purchases in Cloud Run and issue short-lived, backend-bound Pro certificates without retaining raw purchase tokens.

**Architecture:** A standalone Node service accepts native Android verification requests, queries Google Play through a keyless Cloud Run runtime identity, acknowledges purchased products, records only token hashes in Firestore, and signs Ed25519 certificates. Authenticated RTDN and scheduled voided-purchase reconciliation invalidate future issuance after cancellation or refund.

**Tech Stack:** Node.js 22 ESM, native HTTP/fetch, `google-auth-library` 11, `@google-cloud/firestore` 8, Google Play Android Publisher API v3, Cloud Run, Pub/Sub, Firestore, Secret Manager.

## Global Constraints

- Package and product allowlists are fixed release configuration, never request-controlled.
- Use Application Default Credentials; do not create or accept a service-account key file.
- Never log or persist purchase tokens, request bodies, authorization headers, or Google raw errors.
- Store `SHA-256(packageName + NUL + productId + NUL + purchaseToken)` as the purchase key.
- Certificate lifetime is 30 days; the verifier does not encode the separate local 7-day grace.
- No deployment or Google Cloud/Play mutation without explicit operator approval.

---

### Task 1: Workspace and hardened HTTP shell

**Files:**
- Modify: `package.json`
- Create: `apps/entitlement-verifier/package.json`
- Create: `apps/entitlement-verifier/src/config.js`
- Create: `apps/entitlement-verifier/src/http.js`
- Create: `apps/entitlement-verifier/src/server.js`
- Create: `apps/entitlement-verifier/src/server.test.js`

- [ ] Add the verifier workspace with exact runtime dependencies compatible with
  `google-auth-library@11.0.0` and `@google-cloud/firestore@8.7.0`, plus Node `>=22`.
- [ ] Write tests for `GET /healthz`, unknown routes, method rejection, JSON-only content type,
  maximum body size, timeout, safe error envelopes, and redacted structured logs.
- [ ] Run `npm.cmd test -- apps/entitlement-verifier/src/server.test.js` and confirm failure.
- [ ] Implement a dependency-injected server exporting `createServer(dependencies)` and
  `loadVerifierConfig(env)`. Bind to Cloud Run `PORT`; validate package name, product ID, public
  base URL, signing key version, and internal OIDC allowlists at startup.
- [ ] Run focused tests and confirm all pass.
- [ ] Commit with `feat(verifier): add hardened service shell`.

### Task 2: Google Play ProductPurchaseV2 validation

**Files:**
- Create: `apps/entitlement-verifier/src/google-play.js`
- Create: `apps/entitlement-verifier/src/google-play.test.js`

- [ ] Write fixtures for purchased, pending, canceled, refunded/voided, wrong product, wrong
  package, already acknowledged, and transient API failure states.
- [ ] Run the focused test and confirm the module is missing.
- [ ] Implement `createGooglePlayClient({ auth, fetch, packageName, productId })` using
  `GoogleAuth` Application Default Credentials with Android Publisher scope.
- [ ] Query `purchases.productsv2.getproductpurchasev2`; accept only a purchased, unconsumed line
  item matching `pro_multi_account` with remaining refundable quantity. Return normalized state,
  never the upstream body.
- [ ] If purchase is valid but unacknowledged, call `purchases.products.acknowledge` before issuing
  entitlement. Treat acknowledgement retries idempotently and map upstream failures to safe codes.
- [ ] Run focused tests and confirm all pass.
- [ ] Commit with `feat(verifier): validate Play purchases`.

### Task 3: Purchase state and signed certificate

**Files:**
- Create: `apps/entitlement-verifier/src/purchase-state.js`
- Create: `apps/entitlement-verifier/src/purchase-state.test.js`
- Create: `apps/entitlement-verifier/src/entitlement-signer.js`
- Create: `apps/entitlement-verifier/src/entitlement-signer.test.js`

- [ ] Write Firestore adapter tests proving document IDs use the defined SHA-256 tuple, raw tokens
  never appear in writes, active state is idempotent, invalidation blocks issuance, and invalidated
  records retain only the minimum audit fields for 180 days.
- [ ] Write signer tests using an ephemeral Ed25519 key. Require compact token header `alg: EdDSA`
  and configured `kid`, and claims `iss`, `aud: code-remote-manager-backend`, `productId`,
  `maxAccounts: 5`, `backendKeyThumbprint`, `challengeHash`, `iat`, `exp`, and random `jti`.
- [ ] Run both focused tests and confirm failures.
- [ ] Implement `createPurchaseStateStore({ firestore, now })` and
  `createEntitlementSigner({ privateKey, keyId, issuer, now, randomUUID })`. Parse the signing key
  only from a Secret Manager mounted value and reject malformed or non-Ed25519 material at startup.
- [ ] Run focused tests and confirm all pass.
- [ ] Commit with `feat(verifier): issue bound Pro certificates`.

### Task 4: Public native verification endpoint

**Files:**
- Create: `apps/entitlement-verifier/src/verify-purchase.js`
- Create: `apps/entitlement-verifier/src/verify-purchase.test.js`
- Modify: `apps/entitlement-verifier/src/server.js`
- Modify: `apps/entitlement-verifier/src/server.test.js`

- [ ] Test `POST /v1/entitlements/google-play/verify` with exact input fields `purchaseToken`,
  `backendKeyThumbprint`, and `challengeHash`. Reject additional fields, invalid encodings, wrong
  lengths, replayed invalid purchases, and all non-allowlisted package/product attempts.
- [ ] Prove responses return only `certificate`, `expiresAt`, and safe status codes; log capture and
  Firestore capture must not contain the token.
- [ ] Run focused tests and confirm failures.
- [ ] Implement the flow: validate request → hash token tuple → query Play → acknowledge → update
  purchase state transactionally → sign certificate. Add per-IP and per-token-hash rate-limit
  interfaces suitable for Cloud Armor plus application fallback limits.
- [ ] Run focused tests and confirm all pass.
- [ ] Commit with `feat(verifier): verify native Play purchases`.

### Task 5: RTDN cancellation and scheduled refund reconciliation

**Files:**
- Create: `apps/entitlement-verifier/src/google-oidc.js`
- Create: `apps/entitlement-verifier/src/google-oidc.test.js`
- Create: `apps/entitlement-verifier/src/rtdn.js`
- Create: `apps/entitlement-verifier/src/rtdn.test.js`
- Create: `apps/entitlement-verifier/src/voided-purchases.js`
- Create: `apps/entitlement-verifier/src/voided-purchases.test.js`
- Modify: `apps/entitlement-verifier/src/server.js`

- [ ] Test OIDC validation for exact audience, issuer, and allowlisted Pub/Sub or Scheduler service
  account; reject missing, expired, or mismatched identity before reading the body.
- [ ] Test Pub/Sub envelope limits, base64 decode, package allowlist, message-ID idempotency, one-time
  purchase cancellation, and safe acknowledgement of duplicate notifications.
- [ ] Test a cursor-based `POST /internal/google-play/voided-purchases/sync` that reads Voided
  Purchases and invalidates matching token hashes without storing raw tokens.
- [ ] Run focused tests and confirm failures.
- [ ] Implement authenticated internal handlers. Persist processed message IDs with bounded TTL and
  update purchase records transactionally. A canceled/refunded state prevents future certificate
  issuance; it cannot revoke an already-issued offline certificate early.
- [ ] Run focused tests and confirm all pass.
- [ ] Commit with `feat(verifier): reconcile canceled purchases`.

### Task 6: Container and pre-deployment gate

**Files:**
- Create: `apps/entitlement-verifier/Dockerfile`
- Create: `apps/entitlement-verifier/.dockerignore`
- Create: `apps/entitlement-verifier/README.md`
- Create: `tools/verifier-release.test.mjs`
- Modify: `docs/PRO_ENTITLEMENT_OPERATIONS.md`
- Modify: `docs/PRE_RELEASE_CHECKLIST.md`

- [ ] Add release tests that inspect the container context and source for private keys, service
  account JSON, purchase fixtures, unsafe logging, floating Node tags, and package/product mismatch.
- [ ] Build a non-root Node 22 image with production dependencies only and a health check. Document
  environment variable names and IAM roles but no real project IDs, URLs, or secret values.
- [ ] Run all verifier tests, then `npm.cmd test`, PWA build, and `git diff --check`; record concise
  counts and exit codes.
- [ ] Perform a local fake-Google end-to-end check: purchase → acknowledgement → certificate →
  duplicate restore → RTDN invalidation → later issuance denied.
- [ ] Update the checklist and advance only to Android Billing integration.
- [ ] Commit with `build(verifier): add Cloud Run release gate`.
- [ ] Stop before creating Cloud resources, linking Play Console, deploying Cloud Run, or uploading
  secrets; request explicit operator approval for those external changes.
