In-App-Käufe für eine Codex-Web-App: RevenueCat, WebViewGold und ein Pull Request, dem du vertrauen kannst
Bei Zahlungen kann dich ein Agent eine Ablehnung oder echtes Geld kosten, also behandle diese Aufgabe als Cloud-Aufgabe mit strengem Review. Codex findet jede Stelle, an der du digitale Güter verkaufst, leitet sie über RevenueCat, wenn die Seite in der App läuft, und schreibt den Webhook, der auf deinem Server den Zugang freischaltet. Die Produkte, die Keys und den Merge-Button behältst du.
Ab $109 einmalig pro Plattform, oder unser Team erledigt alles ab $698.
Genehmigungsgarantie – oder Geld zurück innerhalb von 14 Tagen
Kein Programmieren nötig
Einmalzahlung, kostenlose Updates
24/7 Support von echten Menschen
Made in Germany seit 2014 · ISO 27001
9:41
Deine App
Deine AppJetzt auf dem Smartphone
App Store · Google Play Freigegeben
Ziel und Blick des Reviewers
Das Ziel: In den iOS- und Android-Apps wird jedes digitale Produkt (Abos, Credits, Premium-Features, freischaltbare Inhalte) über den Store gekauft; im Browser funktioniert Stripe weiter; und beide Wege schreiben denselben Entitlement-Eintrag auf deinem Server.
Der Reviewer prüft drei Dinge. Ein Kaufen-Button, der für digitale Güter ein Kartenformular oder einen externen Checkout öffnet, führt zur Ablehnung nach Richtlinie 3.1.1. Eine Abo-Paywall ohne Preis, Abrechnungszeitraum, Verlängerungsbedingungen und Links zu AGB und Datenschutz führt zur Ablehnung nach 3.1.2. Ein fehlendes "Käufe wiederherstellen" wird ebenfalls beanstandet. Physische Waren und reale Dienstleistungen sind eine andere Kategorie und dürfen in der App bei Stripe bleiben (so unterscheidest du sie).
Die Regeln für Links zu externen Käufen haben sich in einigen Regionen verschoben, etwa im US-Storefront nach dem Epic-Urteil von 2025 und in der EU unter dem DMA. Wenn du diese Optionen nutzen willst, lies zuerst den aktuellen Richtlinientext in App Store Connect und lass sie aus deiner ersten Einreichung heraus. Der Rest dieser Seite geht vom Standardweg aus, den auch der Codex-Release-Workflow empfiehlt. Alle Kaufoptionen, die WebViewGold bietet, sind im Hub zu In-App-Käufen zusammengefasst.
Pro Gerät; die Feldnamen unterscheiden sich zwischen iOS und Android
Zugang freischalten
Nichts im Client
Der RevenueCat-Webhook an Express schreibt das Entitlement
WebViewGold hat uns die kürzeste und effizienteste Time-to-Market ermöglicht, die jemals möglich war, verglichen mit allen anderen App-Plattformen, die wir in den letzten 5 Jahren ausprobiert haben. Ich weiß nicht, warum ich euch nicht schon früher entdeckt habe, aber ich bin wirklich, wirklich froh, dass ich es getan habe. Wir nähern uns derzeit fast 6000 App-Downloads im iOS App Store & Google Play Store ohne Probleme!
Eine verschachtelte AGENTS.md für den Billing-Code
Codex kombiniert AGENTS.md-Dateien vom Root des Repositorys bis hinunter zu dem Ordner, in dem es gerade arbeitet, und die Datei, die dem Code am nächsten liegt, gewinnt. Eine kleine server/billing/AGENTS.md hält die Zahlungsregeln direkt neben dem Zahlungscode, zusätzlich zu deiner AGENTS.md im Root, und ihr Abschnitt Review guidelines sagt dem Codex Code Review, was es bei jedem Pull Request melden soll, der Billing berührt. Das Codex Review auf GitHub meldet Befunde der Stufen P0 und P1, deshalb nutzen die Regeln diese Stufen.
markdown
# server/billing/AGENTS.md
## Invariants
- Entitlements live in Firestore at entitlements/{uid} and are written ONLY by
webhooks: /webhooks/stripe (source "stripe") and /webhooks/revenuecat
(source "revenuecat"). No client code, callable function or admin page sets premium.
- Every access check reads entitlements/{uid} on the server. Never trust a flag,
cookie or localStorage value from the device.
- The RevenueCat app user ID is the Firebase uid, passed as external_id in
revenuecat://purchase?external_id=...&product=... (never the email address).
- Webhooks: compare the Authorization header with REVENUECAT_WEBHOOK_AUTH, dedupe by
event.id in billingEvents/{eventId}, answer 200 quickly. Tests use fixtures in
server/billing/__fixtures__, never live RevenueCat or Stripe APIs.
- Inside the native app (isNativeApp()), digital goods use the bridge only:
revenuecat://purchase, revenuecat://launchPaywall (iOS only),
restoreinapppurchases://, cancelinapppurchase://.
No Stripe links, no card forms, no "cheaper on the web" copy.
- Product IDs come from src/config/products.ts, never hardcoded in components.
## Review guidelines
- P0: any code path that grants or extends premium outside the two webhooks.
- P0: a webhook handler that does not check the Authorization header.
- P1: a Stripe checkout, price link or external payment URL for a digital product
that is reachable when isNativeApp() is true.
- P1: a paywall without price, billing period, renewal terms, Terms and Privacy
links, or without a Restore Purchases control.
Die Cloud-Aufgabe
Führe das als Cloud-Aufgabe aus, damit das Ergebnis als Diff ankommt, aus dem du einen Pull Request machen kannst, und nicht als Änderungen in deiner Arbeitskopie. Starte sie von der Codex-Seite in ChatGPT, aus der IDE-Erweiterung oder mit codex cloud im Terminal. Die Umgebung braucht das Repository und ein Setup-Skript, das die Abhängigkeiten installiert (zum Beispiel npm ci). Sie braucht keinen Netzwerkzugriff auf RevenueCat oder Stripe und sollte niemals Produktions-Keys enthalten.
In eine Codex-Cloud-Aufgabe einfügen (oder in die CLI)
Lies AGENTS.md und server/billing/AGENTS.md. Ziel: In den nativen iOS- und Android-Apps (isNativeApp() ist true) wird jedes digitale Produkt per In-App-Kauf über die RevenueCat-Bridge von WebViewGold verkauft. Browser behalten Stripe. Stack: React-Frontend, Express-API, Firebase Auth, Firestore.
1. Bestandsaufnahme: Liste jede Stelle auf, an der ein Abo, ein Credit-Paket oder ein Premium-Feature verkauft oder mit Preis beworben wird (Komponenten, Routen, E-Mails oder Banner, die zum Checkout verlinken). Schreib die Liste mit der jeweiligen Datei in die Beschreibung des Pull Requests.
2. Client: Lege src/lib/purchase.ts mit buy(productKey), openPaywall(), restorePurchases() und manageSubscription() an. In der App navigiert buy zu revenuecat://purchase?external_id=UID&product=PRODUCT_ID, mit UID = der Firebase-uid (URL-kodiert) und PRODUCT_ID aus src/config/products.ts. restorePurchases navigiert zu restoreinapppurchases://, manageSubscription zu cancelinapppurchase://. Im Browser behält buy den bestehenden Stripe Checkout.
3. Paywall: Auf iOS darf openPaywall revenuecat://launchPaywall?external_id=UID&offering=default verwenden (nur für iOS 15 oder neuer dokumentiert). Behalte unsere eigene Paywall-Komponente für Android und als Fallback, hinter einem Flag in src/config/products.ts. Unsere Paywall zeigt Preis, Abrechnungszeitraum, Verlängerungsbedingungen und Links zu /terms und /privacy.
4. Entferne in der App Stripe-Buttons, Links zu Web-Preisen und Texte wie "im Web günstiger" für digitale Güter. Physische Waren und Dienstleistungen bleiben bei Stripe.
5. Server: POST /webhooks/revenuecat in server/billing/. Gib 401 zurück, außer der Authorization-Header entspricht REVENUECAT_WEBHOOK_AUTH. Dedupliziere nach event.id in billingEvents. Schreibe entitlements/{uid} mit premium, source, store, productId, expiresAt und sandbox. Der Stripe-Webhook schreibt dasselbe Dokument mit source "stripe". Jede Premium-Prüfung auf dem Server liest nur dieses Dokument.
6. Nachdem sich das native Kauf-Sheet geschlossen hat, schaltet der Client nichts frei. Er pollt GET /api/entitlements bis zu 20 Sekunden lang und zeigt währenddessen "Dein Kauf wird verarbeitet".
7. Einstellungen: Zeilen für "Käufe wiederherstellen" und "Abo verwalten", nur in der nativen App.
8. Tests mit Fixture-Events (INITIAL_PURCHASE, RENEWAL, CANCELLATION, EXPIRATION, TEST, eine doppelte Event-ID) und ein Request mit falschem Authorization-Header. Keine Aufrufe der echten RevenueCat- oder Stripe-APIs.
9. Beschreibung des Pull Requests: die Bestandsaufnahme, die Umgebungsvariablen, die ich setzen muss, die verbleibenden Schritte im RevenueCat-Dashboard und ein Sandbox-Testplan für iOS und Android.
Der Webhook, den du im Pull Request finden solltest
Vereinfacht, aber mit den Teilen, die im Review zählen: Autorisierung, Idempotenz, die User-ID aus app_user_id (das ist deine external_id) und keine Beteiligung des Clients. Erstattungen, Transfers und Lifetime-Produkte brauchen in einer echten App zusätzliche Behandlung.
javascript
// server/billing/revenuecat.js (Express + firebase-admin)
import express from "express";
import { getFirestore, FieldValue } from "firebase-admin/firestore";
const db = getFirestore();
export const revenuecatRouter = express.Router();
const GRANT = new Set(["INITIAL_PURCHASE", "RENEWAL", "UNCANCELLATION",
"PRODUCT_CHANGE", "NON_RENEWING_PURCHASE", "SUBSCRIPTION_EXTENDED"]);
revenuecatRouter.post("/webhooks/revenuecat", express.json(), async (req, res) => {
if (req.get("Authorization") !== process.env.REVENUECAT_WEBHOOK_AUTH) {
return res.status(401).end();
}
const event = req.body && req.body.event;
if (!event || !event.id) return res.status(400).end();
if (event.type === "TEST") return res.status(200).end();
const seen = db.collection("billingEvents").doc(event.id);
if ((await seen.get()).exists) return res.status(200).end(); // duplicate delivery
const uid = event.app_user_id; // = external_id from revenuecat://purchase
if (uid) {
const expiresAt = event.expiration_at_ms ? new Date(event.expiration_at_ms) : null;
const ref = db.collection("entitlements").doc(uid);
if (GRANT.has(event.type)) {
await ref.set({ premium: true, source: "revenuecat", store: event.store,
productId: event.product_id, expiresAt, sandbox: event.environment === "SANDBOX",
updatedAt: FieldValue.serverTimestamp() }, { merge: true });
} else if (event.type === "EXPIRATION") {
await ref.set({ premium: false, expiresAt, updatedAt: FieldValue.serverTimestamp() }, { merge: true });
}
// CANCELLATION and BILLING_ISSUE: access usually runs until EXPIRATION.
}
await seen.set({ type: event.type, uid: uid || null, at: FieldValue.serverTimestamp() });
res.status(200).end();
});
Vom Pull Request zum Sandbox-Kauf
Den Pull Request wie ein Auditor prüfen
Lies zuerst die Bestandsaufnahme: Jede Verkaufsstelle sollte aufgeführt sein, und jede sollte entweder bei Stripe (Browser) oder in der Bridge (App) enden. Kommentiere dann @codex review for payment bypasses im Pull Request, um eine fokussierte Zweitmeinung zu bekommen, die die verschachtelten Review-Richtlinien anwendet. Merge erst, wenn du jeden Pfad erklären kannst.
Den Webhook konfigurieren
Füge in RevenueCat einen Webhook hinzu, der auf https://api.example.com/webhooks/revenuecat zeigt, und setze einen Wert für den Authorization-Header; denselben Wert trägst du auf deinem Server in REVENUECAT_WEBHOOK_AUTH ein. Sende das Test-Event von RevenueCat und prüfe, dass eine 200-Antwort zurückkommt.
Die nativen Keys setzen
iOS, Config.swift: kRevenueCatEnable auf true, revenueCatAPIKey, revenueCatProjectID; lass kRevenueCatShowDefaultOfferingPaywall auf false, außer die Paywall soll bei jedem Start erscheinen. Android, Config.java: REVENUECAT_API_KEY und REVENUECAT_PROJECT_ID. Bundle-ID und Paketname müssen exakt zu den Apps in RevenueCat passen (iOS-IAP-Docs, Android-IAP-Docs).
In der Sandbox testen
iOS: ein TestFlight-Build und ein Sandbox-Apple-Account. Android: Lizenztester und ein interner Test-Track. Kaufe, stelle auf einem zweiten Gerät wieder her, lass ein Sandbox-Abo ablaufen und beobachte, dass sich das Entitlement erst ändert, wenn der Webhook ankommt.
Mit angehängten Produkten einreichen
Hänge die ersten In-App-Käufe in App Store Connect an die App-Version an und sag dem Reviewer, wo die Paywall ist und welchen Demo-Account er nutzen soll (Template für Review-Notizen).
Test-Checkliste
Desktop-Browser: Stripe funktioniert genau wie vorher, und kein revenuecat://-Link ist erreichbar.
App: Kein Screen zeigt einen Stripe-Button, ein Kartenformular oder einen Link zu Web-Preisen für digitale Güter.
Ein Kauf auf iOS und auf Android schaltet Premium innerhalb von Sekunden frei, und zwar erst, nachdem der Webhook das Entitlement geschrieben hat.
Wird die App bei offenem Kauf-Sheet beendet, bleibt kein halber Zustand zurück; der Webhook schaltet den Zugang trotzdem frei.
Wiederherstellen auf einem zweiten Gerät (gleiches Store-Konto, gleicher Login) bringt Premium zurück.
"Abo verwalten" öffnet den Store-Screen; ein gekündigtes Sandbox-Abo bleibt aktiv, bis es abläuft.
Die Paywall zeigt Preis, Zeitraum, Verlängerungsbedingungen und Links zu AGB und Datenschutz.
Ein Webhook-Request mit falschem Authorization-Header bekommt eine 401.
Stolperfallen
Die E-Mail-Adresse als external_id verwenden. E-Mail-Adressen ändern sich, und der RevenueCat-Kunde zerfällt in zwei. Nimm die uid.
Vor dem Login verkaufen. Ohne stabile ID landet der Kauf bei einem Kunden, den du später nicht zuordnen kannst. Verlange zuerst den Login oder nutze eine Geräte-ID aus get-uuid:// und führe die Konten bewusst zusammen.
Premium im Client freischalten, sobald sich das Kauf-Sheet schließt. Das ist ein Gratis-Unlock für jeden, der einen Request manipuliert; polle stattdessen deinen Server.
Auf die Reihenfolge der Events vertrauen. Webhooks können verspätet oder doppelt ankommen. Dedupliziere nach Event-ID; für einen robusteren Zustand lässt du den Handler bei jedem Event den Kunden über die REST-API von RevenueCat neu laden, statt jeden Event-Typ einzeln zu interpretieren.
Cookies bei jedem Start löschen (deletecache auf iOS, CLEAR_CACHE_ON_STARTUP auf Android). Die WebViewGold-Docs empfehlen, das ausgeschaltet zu lassen, wenn der Kaufablauf von der Session abhängt.
Annehmen, dass window.purchaseHistory überall gleich aussieht: iOS liefert purchaseDateMs als String, Android purchaseTime und orderId. Lass Codex einen einzigen Normalizer schreiben.
Häufig gestellte Fragen
Muss die Paywall nativ sein?
Nein. Eine Paywall-Seite in deiner Web-App, die revenuecat://purchase aufruft, funktioniert auf beiden Plattformen, solange sie Preis, Zeitraum, Verlängerungsbedingungen und die rechtlichen Links zeigt. Auf iOS kannst du mit revenuecat://launchPaywall (iOS 15 oder neuer) auch eine in RevenueCat gestaltete Paywall öffnen.
Soll die Cloud-Aufgabe meine RevenueCat- oder Stripe-Keys bekommen?
Nein. Die Aufgabe schreibt Code und Tests gegen Fixtures. Keys gehören in die Umgebung deines Servers und werden nach dem Merge von dir gesetzt. Produktions-Secrets aus Agenten-Umgebungen herauszuhalten ist die günstigste Risikominderung, die du hast.
Können Web-Abonnenten Premium in der App nutzen?
Meistens ja. Apples Multiplattform-Regel (Richtlinie 3.1.3(b)) erlaubt Nutzern den Zugriff auf das, was sie auf deiner Website gekauft haben, sofern dieselben Artikel in der App auch als In-App-Käufe verfügbar sind. Weil beide Webhooks dasselbe Entitlement schreiben, ist ein Stripe-Abonnent nach dem Login auch in der App Premium.
Wie viel behalten Apple und Google ein?
Die Standardprovision beträgt 30 Prozent. Apples Small Business Program und Googles reduzierte Stufe für die erste Million US-Dollar Jahreseinnahmen senken sie auf 15 Prozent, und RevenueCat berechnet oberhalb seiner kostenlosen Schwelle einen Anteil am Umsatz.
Brauche ich die WebViewGold Extended License?
Ja. Die WebViewGold-Dokumentation zu In-App-Käufen setzt die Extended License voraus, wenn du In-App-Käufe in einem Endprodukt nutzt. Mach das Upgrade, bevor du den Build mit Käufen einreichst.
Drei Wege, deine App in die Stores zu bringen
Gleiches Ergebnis, unterschiedlich viel Arbeit auf deiner Seite. Alle drei kommen mit Genehmigungsgarantie und 24/7 Support von echten Menschen.
Selbst machen
$109 pro Plattform, einmalig
Die WebViewGold-Lizenz: ein fertiges Xcode- oder Android-Studio-Projekt. URL einfügen, Name und Icon setzen, Features einschalten, bauen und einreichen. Kostenlose Updates für immer.
Kein Mac, kein Xcode, kein Android Studio: Du konfigurierst im Browser, wir bauen beide Apps in der Cloud und übergeben sie an App Store Connect und die Play Console.
Schick uns deine URL und dein Icon. Unser Team konfiguriert, baut und liefert deine App innerhalb von 72 Stunden, White-Label, inklusive Quellprojekten. Beide Plattformen für $1,158.
Erstattungen erfolgen zu den in unseren FAQ genannten Bedingungen: eine nicht inhaltsbedingte Ablehnung durch Apple und/oder Google, geltend gemacht innerhalb von 14 Tagen nach dem Kauf.