自己动手
$109 每个平台,一次性付费
WebViewGold 许可证:一个现成的 Xcode 或 Android Studio 项目。粘贴您的 URL,设置名称和图标,开启所需功能,构建并提交。永久免费更新。
在主页查看方案ChatGPT & Codex
支付是代理最可能让您遭遇拒审或真金白银损失的地方,所以请把它当作一个需要严格审阅的云端任务来处理。Codex 会找出您销售数字商品的每一个位置,在页面运行于应用内时把购买转到 RevenueCat,并编写在服务器上授予权限的 webhook。商品、密钥和合并按钮始终掌握在您手中。
每个平台一次性 $109 起,或由我们的团队全程代劳,$698 起。
目标是:在 iOS 和 Android 应用内,每一件数字商品(订阅、点数、高级功能、可解锁内容)都通过商店购买;在浏览器中,Stripe 照常工作;两条路径都在您的服务器上写入同一条权益记录。
审核人员会检查三件事。数字商品的购买按钮如果打开银行卡表单或外部结账页面,会因准则 3.1.1 被拒。订阅付费墙如果没有价格、计费周期、续订条款以及条款和隐私政策链接,会因准则 3.1.2 被拒。缺少“恢复购买”入口同样会被指出。实体商品和线下服务属于另一类,可以在应用内继续使用 Stripe(如何区分两者)。
关于链接到外部购买的规则在部分地区已有变化,例如 2025 年 Epic 案判决后的美国商店,以及 DMA 下的欧盟。如果您想使用这些选项,请先在 App Store Connect 中阅读最新的准则原文,并且不要在首次提交时使用。本页其余部分假设采用标准路径,这也是 Codex 发布工作流程所推荐的。WebViewGold 提供的所有购买方案都汇总在应用内购买专题中。
| 场景 | Web 应用的做法 | 说明 |
|---|---|---|
| 浏览器用户购买 | 沿用现有的 Stripe Checkout,不做改动 | Stripe webhook 写入权益 |
| 应用用户购买商品 | revenuecat://purchase?external_id=UID&product=PRODUCT_ID | iOS 和 Android;UID 是您稳定的用户 ID,PRODUCT_ID 与 RevenueCat 中显示的一致 |
| 应用用户打开付费墙 | iOS:revenuecat://launchPaywall?external_id=UID&offering=OFFERING(iOS 15 或更高版本)。Android:您自己的付费墙页面,调用 purchase | 文档只为 iOS 描述了 launchPaywall |
| 恢复购买 | restoreinapppurchases:// | 一个可见的入口,订阅和非消耗型商品都应提供 |
| 管理或取消 | cancelinapppurchase:// | 打开商店的订阅页面 |
| 显示购买记录 | getpurchasehistory:// 会填充 window.purchaseHistory | 按设备区分;iOS 和 Android 的字段名不同 |
| 授予权限 | 客户端不做任何事 | 发往 Express 的 RevenueCat webhook 写入权益 |

在过去5年我们尝试过的所有应用平台中,WebViewGold为我们带来了有史以来最短、最高效的上市时间。我不知道为什么没有更早发现你们,但我真的非常满意。目前我们的应用在iOS App Store和Google Play商店的下载量已超过6000次,没有任何问题!
Codex 会把从仓库根目录到当前工作文件夹的所有 AGENTS.md 合并起来,离代码最近的文件优先。在您的根目录 AGENTS.md之外,再放一个小小的 server/billing/AGENTS.md,就能让支付规则紧挨着支付代码;其中的 Review guidelines 部分会告诉 Codex 代码审查,在每个涉及计费的 pull request 中需要标记什么。GitHub 上的 Codex 审查会报告 P0 和 P1 级别的问题,所以规则也使用这两个级别。
# 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.把它作为云端任务运行,这样结果会以 diff 的形式返回,您可以把它转成 pull request,而不是直接改动您的工作副本。可以从 ChatGPT 中的 Codex 页面、IDE 扩展,或在终端中用 codex cloud 启动。环境需要包含仓库和一个安装依赖的设置脚本(例如 npm ci)。它不需要访问 RevenueCat 或 Stripe 的网络,也绝不应该持有生产环境的密钥。
请阅读 AGENTS.md 和 server/billing/AGENTS.md。目标:在原生 iOS 和 Android 应用内(isNativeApp() 为 true),所有数字商品都通过 WebViewGold 的 RevenueCat 桥接以应用内购买的方式销售。浏览器继续使用 Stripe。技术栈:React 前端、Express API、Firebase Auth、Firestore。
1. 盘点:列出所有销售订阅、点数包或高级功能,或带价格宣传它们的位置(组件、路由、链接到结账页面的邮件或横幅)。把这份清单连同每处对应的文件写进 pull request 描述。
2. 客户端:创建 src/lib/purchase.ts,包含 buy(productKey)、openPaywall()、restorePurchases() 和 manageSubscription()。在应用内,buy 跳转到 revenuecat://purchase?external_id=UID&product=PRODUCT_ID,其中 UID = Firebase uid(经过 URL 编码),PRODUCT_ID 取自 src/config/products.ts。restorePurchases 跳转到 restoreinapppurchases://,manageSubscription 跳转到 cancelinapppurchase://。在浏览器中,buy 沿用现有的 Stripe Checkout。
3. 付费墙:在 iOS 上,openPaywall 可以使用 revenuecat://launchPaywall?external_id=UID&offering=default(文档说明仅适用于 iOS 15 或更高版本)。为 Android 以及作为备用方案,保留我们自己的付费墙组件,并通过 src/config/products.ts 中的一个开关控制。我们的付费墙要显示价格、计费周期、续订条款,以及指向 /terms 和 /privacy 的链接。
4. 在应用内,移除数字商品的 Stripe 按钮、网页定价链接和“网页上更便宜”之类的文案。实体商品和服务继续使用 Stripe。
5. 服务器:在 server/billing/ 中实现 POST /webhooks/revenuecat。除非 Authorization 请求头等于 REVENUECAT_WEBHOOK_AUTH,否则返回 401。在 billingEvents 中按 event.id 去重。写入 entitlements/{uid},包含 premium、source、store、productId、expiresAt 和 sandbox。Stripe webhook 写入同一个文档,source 为 "stripe"。服务器上所有的高级权限检查都只读取这个文档。
6. 原生购买面板关闭后,客户端不授予任何权限。它最多轮询 GET /api/entitlements 20 秒,期间显示“正在处理您的购买”。
7. 设置:添加“恢复购买”和“管理订阅”两行,仅在原生应用中显示。
8. 使用测试数据事件(INITIAL_PURCHASE、RENEWAL、CANCELLATION、EXPIRATION、TEST,以及一个重复的事件 ID)编写测试,外加一个带错误 Authorization 请求头的请求。不要调用真实的 RevenueCat 或 Stripe API。
9. Pull request 描述:盘点清单、我必须设置的环境变量、RevenueCat 控制台中还需完成的步骤,以及 iOS 和 Android 的沙盒测试计划。经过简化,但保留了审阅时最重要的部分:授权校验、幂等性、从 app_user_id(也就是您的 external_id)获取用户 ID,以及完全不依赖客户端。在真实应用中,退款、转移和终身买断商品还需要额外处理。
// 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();
});先看盘点清单:每个销售点都应被列出,并且每一个最终要么走 Stripe(浏览器),要么走桥接(应用)。然后在 pull request 中评论 @codex review for payment bypasses,获得一份应用嵌套审查规则、有针对性的第二意见。只有当您能解释每一条路径时才合并。
在 RevenueCat 中添加一个指向 https://api.example.com/webhooks/revenuecat 的 webhook,并设置一个 Authorization 请求头的值;在服务器的 REVENUECAT_WEBHOOK_AUTH 中填入同样的值。发送 RevenueCat 的测试事件,确认返回 200。
iOS,Config.swift:kRevenueCatEnable 设为 true,填写 revenueCatAPIKey 和 revenueCatProjectID;除非希望每次启动都打开付费墙,否则让 kRevenueCatShowDefaultOfferingPaywall 保持 false。Android,Config.java:REVENUECAT_API_KEY 和 REVENUECAT_PROJECT_ID。Bundle ID 和包名必须与 RevenueCat 中的应用完全一致(iOS IAP 文档,Android IAP 文档)。
iOS:一个 TestFlight 构建版本和一个沙盒 Apple 账号。Android:许可测试人员和一个内部测试轨道。购买,在第二台设备上恢复购买,让一个沙盒订阅过期,并确认权益只在 webhook 到达后才发生变化。
在 App Store Connect 中把首批应用内购买项目附加到应用版本上,并告诉审核人员付费墙在哪里、使用哪个演示账号(审核备注模板)。
revenuecat:// 链接。external_id。邮箱会变,RevenueCat 中的客户就会分裂成两个。请使用 uid。get-uuid:// 提供的设备 ID,并有意识地进行合并。deletecache,Android 上的 CLEAR_CACHE_ON_STARTUP)。当购买流程依赖会话时,WebViewGold 文档建议保持关闭。window.purchaseHistory 在各处都一样:iOS 返回字符串形式的 purchaseDateMs,Android 返回 purchaseTime 和 orderId。让 Codex 写一个统一的规范化函数。不必。您 Web 应用中调用 revenuecat://purchase 的付费墙页面在两个平台上都能用,只要它显示价格、周期、续订条款和法律链接即可。在 iOS 上,您也可以用 revenuecat://launchPaywall 打开在 RevenueCat 中设计的付费墙(iOS 15 或更高版本)。
不应该。该任务针对测试数据编写代码和测试。密钥应放在您服务器的环境变量中,在合并后由您亲自设置。让生产环境的机密远离代理环境,是您成本最低的风险控制手段。
通常可以。Apple 的多平台规则(准则 3.1.3(b))允许用户在应用中使用他们在您网站上购买的内容,前提是同样的项目在应用中也可以通过应用内购买获得。由于两个 webhook 写入的是同一条权益记录,一个登录的 Stripe 订阅用户在应用中同样是高级用户。
标准佣金是 30%。Apple 的小型企业计划和 Google 针对每年前一百万美元收入的优惠档位会把它降到 15%;RevenueCat 则在超出其免费额度后按收入比例收费。
需要。WebViewGold 的应用内购买文档规定,在最终产品中使用应用内购买需要 Extended License。请在提交包含购买功能的构建版本之前完成升级。
结果相同,只是您需要投入的工作量不同。三种方式都附带审核通过保证和 7×24 小时人工支持。
$109 每个平台,一次性付费
WebViewGold 许可证:一个现成的 Xcode 或 Android Studio 项目。粘贴您的 URL,设置名称和图标,开启所需功能,构建并提交。永久免费更新。
在主页查看方案$349 每个许可证,一次性附加购买
无需 Mac、Xcode 或 Android Studio:在浏览器中完成配置,我们在云端构建两个应用,并将其交付到 App Store Connect 和 Play Console。
Cloud Builder 如何运作 试用免费演示$698 每个平台 $798
把您的 URL 和图标发给我们。我们的团队在 72 小时内完成配置、构建并交付您的应用,白标,附源码项目。两个平台共 $1,158。
Full Service 详情 服务提供方:冬季特惠价格有效期至 2026年12月31日。
App Store & Google Play 审核通过保证 – 否则 14 天内退款
想直接交给我们?Full Service:我们在 72 小时内完成配置、构建并交付,每个平台 $698 起。有疑问?通过 7×24 小时在线聊天联系我们。
退款依据我们 FAQ 中列出的条件:应用因非内容原因被 Apple 和/或 Google 拒审,并在购买后 14 天内提出申请。