Wise Wally
Wise Wally
Paywall Studio
Wise Wally|Standardization Guide0/27 completed

Paywall Standardization Guide

Framework T1–T4 · Petewall / Wally

T1Compliance

Apple + Google policies, dark patterns, pre-launch checklist

T2Design

5 mandatory zones, block specs, experimentation dimensions

T3Technical

Block↔component mapping, gap registry, implementation patterns

T4AI Prompt

26 rules for AI generation, unified source of truth

This framework establishes the design, compliance and technical architecture rules for all paywalls generated in Wally (both AI-generated and manual). Every paywall must pass the runPolicyChecks() checks and validate against paywall.schema.json before publishing on Adapty.

Apple App Store Policies

T1 · Section A — Subscription and paywall UI rules

A1criticalNo hardcoded prices

No text string may contain a price or currency symbol. Prices must be dynamically resolved from the store.

policyChecks: PRICE_REGEX
A2criticalExplicit billing period

The billing period (weekly, monthly, annual, lifetime) must be visible in the pricing block. Showing the price alone is not sufficient.

A3criticalNo misleading claims

Prohibited: "guaranteed", "risk-free", "100% safe", "free forever", "last chance" and their equivalents.

policyChecks: BLOCKED_CLAIMS
A4highAnnual plan: monthly equivalent

If the annual plan is advertised with a broken-down monthly price, the total annual price must also be visible.

A5highTrial clearly labeled

The trial period duration must be stated precisely (e.g. "7 days free"). Cannot simply say "free trial".

A6criticalCancellation referenced in legal

The legal block must include a reference to the cancellation policy.

A7criticalRestore Purchases visible

The Restore Purchases button/link must be visible. show_restore cannot be false.

policyChecks: show_restore === false
A8criticalPrivacy Policy linked

The legal block must include privacy_url pointing to the real privacy policy.

policyChecks: legal.privacy_url
A9criticalTerms of Use linked

The legal block must include terms_url pointing to the real terms of use.

policyChecks: legal.terms_url
A10highNo dark patterns

4 testable sub-rules: (1) no plan may be visually pre-selected as the primary action without user choice; (2) the real price cannot be hidden with tiny typography or contrast lower than the broken-down price; (3) the dismiss button must be visually distinguishable from the CTA (cannot have the same color, size or deceptive position); (4) timers cannot be used without a real deadline_utc (fake timers = dark pattern).

A11mediumCTA text ≥ 14pt

The main CTA text must be readable. style_tokens.typography.cta_size ≥ 14.

A12highCTA contrast ≥ 4.5:1 (WCAG AA)

The contrast between cta_background and cta_text must exceed the 4.5:1 ratio required by WCAG AA.

policyChecks: contrastRatio() < 4.5
A13highTimer with real deadline

If there is a timer block, it must use deadline_utc reflecting a real deadline. Infinite or fake timers are prohibited (T1 · X7).

A14mediumHero required

The paywall must include a hero block with a clear product/benefit title.

policyChecks: hero block required

Google Play Policies

T1 · Section G — Billing Policy and UI requirements

G1criticalNo "risk-free" claims

Google explicitly rejects any claim implying a total absence of financial risk.

policyChecks: BLOCKED_CLAIMS
G2criticalNo "free forever"

The phrase "free forever" and equivalents are prohibited by Google Play Billing Policy.

policyChecks: BLOCKED_CLAIMS
G3highTotal annual plan price visible

For annual plans, the total amount charged in the period must be visible, not just the monthly breakdown.

G4highPlay Billing integrated

Payments must be processed exclusively through Google Play Billing. Prices from other payment gateways cannot be shown.

G5highExplicit renewal consent

The user must understand that the subscription renews automatically. This must be in the legal block.

G6highExplicit trial duration

If there is a trial period, the duration must be stated in number of days/weeks. Ambiguous text is not allowed.

G7mediumExplicit billing frequency

The billing frequency must be stated: weekly, monthly, annual, etc. It cannot be inferred from the price alone.

G8criticalFunctional legal links

Privacy Policy and Terms of Use links must point to real, active URLs — not placeholders.

schema: format: uri
G9mediumNo misleading pre-selection

If there are multiple plans, no logic may automatically select the most expensive one without user choice.

G10mediumClear cancellation policy

The user must be able to understand how to cancel before purchasing. This must be referenced in the legal block.

The 5 Mandatory Zones

T2 · Every paywall must include these zones in this order

ZONE 1
Hero
Main title and value proposition. Position 0, or position 1 if a timer precedes it.
ZONE 2
Benefits
List of product features or benefits. Always before pricing.
ZONE 3
Pricing
One card per product_ref. Shows price, period and optional badge.
ZONE 4
CTA
Primary action button. Immediately before the legal block.
ZONE 5
Legal
Legal text, privacy_url, terms_url and Restore Purchases. Always the last block.
Zone 1Hero

Main title and value proposition. Position 0, or position 1 if a timer precedes it.

Only 1 hero allowed. heroIdx ≤ 1.
Zone 2Benefits

List of product features or benefits. Always before pricing.

benefitsIdx < pricingIdx
Zone 3Pricing

One card per product_ref. Shows price, period and optional badge.

1 pricing per product_ref. product_ref_id required.
Zone 4CTA

Primary action button. Immediately before the legal block.

ctaIdx + 1 === legalIdx. text.cta cannot be "continue", "next", "ok", "accept".
Zone 5Legal

Legal text, privacy_url, terms_url and Restore Purchases. Always the last block.

Always last position. privacy_url and terms_url required.
OPTIONAL BLOCKS
TimerCountdown. Can only appear at position 0. Must use deadline_utc (not duration_seconds).
Social Proof(Optional zone 6) Reviews, ratings or testimonials. No unverifiable claims.

Block Specifications

T2 · Required fields, optional fields and constraints

Hero Block
REQUIRED
id
type
text.title
OPTIONAL
text.subtitle
text.body

The hero is usually complemented by assets.hero_image_url at the payload level.

Benefits Block
REQUIRED
id
type
items
OPTIONAL
text.title

items must be an array of strings. Recommended: 3–6 items.

Pricing Block
REQUIRED
id
type
product_ref_id
OPTIONAL
text.title
text.body
badge
style_ref

product_ref_id must match an id in payload.product_refs.

CTA Block
REQUIRED
id
type
text.cta
OPTIONAL
product_ref_id
style_ref
position: "sticky"

text.cta cannot be: continue, next, ok, accept. position: "sticky" anchors the CTA to the footer.

Legal Block
REQUIRED
id
type
privacy_url
terms_url
OPTIONAL
text.body
show_restore

show_restore defaults to true. Cannot be set to false.

Timer Block
REQUIRED
id
type
timer.deadline_utc
OPTIONAL
timer.duration_seconds
text.title

deadline_utc is required by policy (X7). duration_seconds is discouraged.

Experimentation

T2 · Allowed variation dimensions, rollout limits

✅ Can vary
text.cta
text.title / subtitle
style_tokens.colors
style_tokens.typography
Benefits item order
Badge in pricing
assets.hero_image_url
Timer present/absent
🚫 Cannot vary
Zone structure (Hero → Legal)
Presence of legal, privacy_url, terms_url
show_restore
product_refs (IDs and types)
Tracking events and pw_ prefix
rollout_percent > 25%
Experiment TTL
EXPERIMENT OBJECT LIMITS
{
  "rollout_percent": 10,        // ≤ 25. Schema: maximum: 25
  "ttl_utc": "2026-06-01T00:00:00Z",  // Requerido
  "rollback_variant_id": "v0"   // Requerido — ID de la variante control
}

Design Tokens & Layout Reference

Values extracted from production paywalls — FET D and Popcorn D

Popcorn DDark + Gold
#0D0D0DbackgroundApp background
#1A1A1AsurfacePlan cards
#FFFFFFtext_primaryTitles & prices
#AAAAAAtext_secondaryPrice/week, subtitles
#8B5CF6accentBadge background (purple)
#FFBF1Ecard_borderPlan card border · 2px
#FFBF1Ecta_backgroundSubscribe CTA
#000000cta_textCTA label · 10.7:1 ✅
#777777legal_textDisclaimer copy
#BBBBBBfooter_linksTerms / Privacy / Restore
FET DDark + Lime + Magenta
#0A0A0AbackgroundApp background (dark teal texture)
#1C1C1CsurfacePlan cards
#FFFFFFtext_primaryTitles & prices
#CCCCCCtext_secondaryPrice/week, subtitles
#C8FF00accentBadge bg (lime) · text #000000
#D946EFcard_borderFeatured card border (magenta) · 2px
#C8FF00cta_backgroundSubscribe CTA (lime)
#000000cta_textCTA label · 17.7:1 ✅
#666666legal_textDisclaimer copy
#D946EFfooter_linksTerms / Privacy / Restore (magenta)
Spacing — 4-point grid
4pxxs

Icon–text gap, badge vertical padding, dot spacing

8pxsm

Carousel dot gap, inline element spacing

16pxmd

Card inner padding, gap between plan cards, hero→title gap

24pxlg

Screen horizontal margin, major section separation

Element Layout (390 × 844)
Canvas390 × 844 px
Horizontal padding24 px (lg token)
Dismiss button36 px circle · top 50 px · right 20 px
Hero sticker zone~200 px height · image only, no text
Title32–34 px · weight 700 · #FFFFFF · centered
Subtitle16 px · weight 400 · muted · centered · max 2 lines
Carousel dots8 px dot diameter · 8 px gap · 32 px total zone
"BEST OPTION" badgepill 999 px radius · 32 px h · overlaps card top
Featured plan card~90 px h · 2 px border · 18–20 px radius
— Period label20 px · weight 400
— Plan price34 px · weight 700
— Price/week sub13 px · muted color
Secondary plan row2–3 equal cards · ~70 px h · 2 px border · 16 px radius
CTA buttonfull-width −48 px · 56 px h · 999 px radius · 18–20 px bold
Legal disclaimer10–11 px · 4 lines · muted · centered
Footer links row3 links (Terms · Privacy · Restore) · 14 px · ~40 px h

Technical Validation

T3 · Gap registry, block↔component mapping

GAP REGISTRY (G1–G10)
G1resolved
TimerBlock.tsx missing
TimerBlock.tsx implemented in components/blocks/.
G2resolved
Legal links not schema-enforced
schema.json includes privacy_url and terms_url as format:uri. policyChecks validates them.
G3resolved
Restore not renderer-enforced
PaywallRenderer.tsx reads show_restore from the legal block. Default: true.
G4resolved
No CTA contrast validation
policyChecks.ts implements contrastRatio() with WCAG AA 4.5:1 threshold.
G5resolved
body_size minimum too low
schema.json: typography.body_size minimum: 10.
G6resolved
"Best Value" badge not implemented
PricingBlock.tsx renders badge if present. Spec: pill 999px radius · 32px h · accent color bg · #000 text · overlaps card top.
G7resolved
Sticky CTA not implemented
PaywallRenderer.tsx: position: "sticky" anchors the CTA to the footer.
G8resolved
Dismiss button unverified
DismissButton component in PaywallRenderer.tsx. Requires onDismiss prop.
G9oos
Plan toggle (multi-period)
Out of scope for v1. No estimate.
G10minor
Icon style unspecified
Low impact. Does not block compliance or generation.
BLOCK → COMPONENT MAPPING
hero
components/blocks/HeroBlock.tsx
benefits
components/blocks/BenefitsBlock.tsx
pricing
components/blocks/PricingBlock.tsx
cta
components/blocks/CtaBlock.tsx
legal
components/blocks/LegalBlock.tsx
timer
components/blocks/TimerBlock.tsx

AI System — Generation Rules

T4 · 26 rules for app/api/generate/route.ts

Structure
R1Always generate the 5 mandatory blocks: hero, benefits, pricing, cta, legal.
R2The invariable order is: [timer?] → hero → benefits → pricing... → cta → legal.
R3One pricing block per product_ref. The product_ref_id must match exactly.
Content
R4No prices in free text. Only dynamic tokens: {{price}}, {{trial}}, {{period}}.
R5text.cta cannot contain: "continue", "next", "ok", "accept".
R6No prohibited claims in any text block.
Legal
R7The legal block MUST have valid privacy_url and terms_url (URI format).
R8Never generate show_restore: false.
Tracking
R9All tracking events must use the pw_ prefix.
R10The 5 required events are mandatory: impression, cta_click, purchase_start, purchase_success, restore.
Experiment
R11If experiment is generated, rollout_percent ≤ 25. ttl_utc and rollback_variant_id always present.
Schema
R12Output MUST validate against paywall.schema.json v1.x.x. No additional properties outside the schema.
Variaciones
R13Allowed variation axes between variants: (1) audience — social, fitness, productivity, generic; (2) urgency — none / timer with real deadline_utc; (3) price framing — monthly price vs. total price vs. percentage savings; (4) social proof — present or absent (if present: no unverifiable claims). Everything else is invariable between variants of the same paywall_id.

Production Examples

12 active paywalls on Adapty — FET D (6) + Popcorn D (6)

FET D (6)
No linked design
8bc647fd…FET D
adapty_id: 8bc647fd-7afc-46d5-98ea-6292d6c3e047
Set ADAPTY_SECRET_KEY_FET_D in .env.local
No linked design
2223d899…FET D
adapty_id: 2223d899-e2f1-431f-99b4-9fdfa4a7caf8
Set ADAPTY_SECRET_KEY_FET_D in .env.local
No linked design
a3ab6b4b…FET D
adapty_id: a3ab6b4b-84d6-46e5-867f-e1a51afd9323
Set ADAPTY_SECRET_KEY_FET_D in .env.local
No linked design
5f7cad81…FET D
adapty_id: 5f7cad81-4722-4a68-8465-e76df91174cf
Set ADAPTY_SECRET_KEY_FET_D in .env.local
No linked design
611818ce…FET D
adapty_id: 611818ce-761b-4b3a-996a-0c651d1e798c
Set ADAPTY_SECRET_KEY_FET_D in .env.local
No linked design
0c21690f…FET D
adapty_id: 0c21690f-6b9c-48b6-aef2-3e2f6ef502a2
Set ADAPTY_SECRET_KEY_FET_D in .env.local
Popcorn D (6)
No linked design
59255621…Popcorn D
adapty_id: 59255621-2ffa-45c8-afc2-1c2612d06e20
Set ADAPTY_SECRET_KEY_POPCORN_D in .env.local
No linked design
d4bcaa4d…Popcorn D
adapty_id: d4bcaa4d-8bd1-48e0-9e59-97bfb1b06995
Set ADAPTY_SECRET_KEY_POPCORN_D in .env.local
No linked design
41fed499…Popcorn D
adapty_id: 41fed499-d7ff-4852-9771-0fa228092329
Set ADAPTY_SECRET_KEY_POPCORN_D in .env.local
No linked design
d8105797…Popcorn D
adapty_id: d8105797-6e17-477d-9d04-f3bf270fb279
Set ADAPTY_SECRET_KEY_POPCORN_D in .env.local
No linked design
2dab8347…Popcorn D
adapty_id: 2dab8347-da17-4aab-93f0-9e7ce572b999
Set ADAPTY_SECRET_KEY_POPCORN_D in .env.local
No linked design
2c8ed4cd…Popcorn D
adapty_id: 2c8ed4cd-b39b-46d6-9490-d74d44710a5e
Set ADAPTY_SECRET_KEY_POPCORN_D in .env.local

Pre-launch Checklist

0/27 completed · 0/11 critical

Content & Copy
Legal Block
Block Structure
Technical Validation
Timer (if applicable)
Final QA
11 critical items pending
Complete all critical items before publishing.