自己动手
$109 每个平台,一次性付费
WebViewGold 许可证:一个现成的 Xcode 或 Android Studio 项目。粘贴您的 URL,设置名称和图标,开启所需功能,构建并提交。永久免费更新。
在主页查看方案ChatGPT & Codex
两大商店都会拒绝那些能注册却不能注销的应用。这个功能看起来很小,但一次正确的删除会涉及身份验证、每一个集合或数据表、文件存储、订阅以及 Sign in with Apple 的 token,这正是 Codex 在拿到一份数据清单后最擅长的跨模块改动。下面是一个分两轮的工作流程:Codex 先在只读会话中梳理数据,然后带着测试实现清除逻辑。
每个平台一次性 $109 起,或由我们的团队全程代劳,$698 起。
Apple(准则 5.1.1(v)):如果应用支持创建账号,用户就必须能在应用内发起账号删除,而删除指的是删除账号及其数据,而不是打上一个停用标记。加一个确认步骤是可以的。如果用户可以用 Apple 登录,Apple 期望您在用户删除账号时通过 Sign in with Apple REST API 撤销他们的 token。Google Play:除了应用内路径,还需要一个网页,让用户无需重新安装应用就能申请删除,并在 Play Console 的数据安全部分填写该网页的 URL。
审核人员实际会做的是:注册一个账号或使用您的演示账号,打开设置,寻找“删除账号”,有时还会真的走完整个流程。在 Google Play 上,网页链接也会被打开;如果它需要登录或返回 404,就会被拒(Play 拒审详解)。
为什么要和 Codex 分两轮进行:删除会悄无声息地失败。被遗忘的集合或存储文件夹不会报错,它们只是继续留在那里。在写代码之前先梳理数据,才能让结果完整。示例使用 React、Express、Firebase Auth、Firestore 和 Cloud Storage;Supabase 的对应做法见下表。账号删除要求指南为不使用编程代理的开发者讲解了同样的规则,isNativeApp() 辅助函数来自 AGENTS.md 模板。
read-only 沙盒允许 Codex 读取文件并运行 rg 之类的无害命令,但不能修改任何内容。这让第一轮保持纯粹:它只能看。
# Pass 1: inventory only. Codex can read, search and answer, but not edit.
cd my-app
codex --sandbox read-only --ask-for-approval on-request
# Paste this into the session:
# Read AGENTS.md. Find every place this codebase stores or sends data about a
# user: auth providers, Firestore collections and subcollections (and the field
# that links each document to the user), Cloud Storage paths, server logs,
# third parties that receive user data (RevenueCat, Stripe, OneSignal, email,
# analytics, error tracking) and what the web app keeps on the device
# (cookies, localStorage, IndexedDB, values written with writevalue://).
# Output one Markdown table: location | link to the user | delete or retain | reason.
# Mark anything you are unsure about. Do not propose code yet.
# When the table is right, switch to write access inside the session with
# /permissions and ask Codex to save it as docs/user-data-map.md. Commit it.| 位置 | Firebase(Express API) | Supabase |
|---|---|---|
| Auth 用户 | 使用 firebase-admin 调用 getAuth().deleteUser(uid),作为最后一步 | 在 Edge Function 中使用 service role key 调用 supabase.auth.admin.deleteUser(id),作为最后一步 |
| 数据库 | 对用户文档及其子集合使用 recursiveDelete(),再加上按所有者字段的查询 | 指向 auth.users 的外键设置 on delete cascade,或在调用 auth 之前显式删除 |
| 文件 | getStorage().bucket().deleteFiles({ prefix: "users/UID/" }) | 列出该用户的文件夹,然后调用 storage.from(bucket).remove(paths) |
| Sign in with Apple | 在用户通过 Apple 重新验证身份后调用 revokeAccessToken(auth, token);Firebase 不保存 Apple token | 不会自动处理。Apple 的撤销接口需要一个来自登录过程的 token;让 Codex 检查您的流程能拿到什么,并标出缺口 |
| 商店订阅 | 您无法替用户取消订阅。在对话框中说明这一点,并提供 cancelinapppurchase://;也可以在服务器端删除 RevenueCat 客户 | 相同 |
| 推送、邮件、分析 | 删除推送设备记录,从您的邮件工具中移除该联系人,使用各服务商的删除 API | 相同 |
| 设备本身 | reset:// 让 WebViewGold 清除 Cookie 和缓存;用 writevalue:// 保存的内容在清除缓存后依然存在,需要覆盖掉 | 相同 |

在过去5年我们尝试过的所有应用平台中,WebViewGold为我们带来了有史以来最短、最高效的上市时间。我不知道为什么没有更早发现你们,但我真的非常满意。目前我们的应用在iOS App Store和Google Play商店的下载量已超过6000次,没有任何问题!
用 codex --sandbox workspace-write --ask-for-approval on-request 开启一个全新的会话,让数据地图而不是之前的对话成为唯一依据。如果使用 Supabase,请把第 3 步替换为一个 Edge Function:验证 JWT、删除存储对象、依靠级联外键,并调用 auth.admin.deleteUser。
请阅读 AGENTS.md 和 docs/user-data-map.md。实现符合 App Store 准则 5.1.1(v) 和 Google Play 账号删除政策的账号删除功能。技术栈:React 前端、Express API、Firebase Auth、Firestore、Cloud Storage。这个 Web 应用也运行在用 WebViewGold 构建的原生 iOS 和 Android 应用中;仅限应用内的行为使用 isNativeApp() 判断。
1. 设置 > 账号 > 删除账号。确认面板说明哪些内容会被删除、哪些内容我们会保留以及保留多久(依据数据地图),并说明商店订阅在取消之前会继续由 Apple 或 Google 扣费。如果用户有有效的商店订阅且 isNativeApp() 为 true,显示一个跳转到 cancelinapppurchase:// 的“管理订阅”按钮。
2. 近期登录:如果服务器返回 recent-login-required,就重新验证身份。对于使用 apple.com 提供方的用户,一律用 OAuthProvider("apple.com") 重新验证身份,用 OAuthProvider.credentialFromResult 读取 access token,并在调用服务器之前调用 revokeAccessToken(auth, token)。
3. Express 中的 DELETE /api/account:用 checkRevoked 验证 ID token,要求 auth_time 在最近 5 分钟内,然后按以下顺序删除:users/{uid}/ 下的 Storage 文件、数据地图中的每一个 Firestore 位置(子集合使用 recursiveDelete,包括 pushDevices)、Stripe 客户,以及(如果数据地图要求)RevenueCat 客户(放在功能开关之后,密钥从环境变量读取),最后删除 Auth 用户。如果请求中途失败,每一步都必须可以安全地重新执行。
4. 成功之后:退出登录;当 isNativeApp() 为 true 时跳转到 reset://,让 WebViewGold 清除 Cookie 和缓存;在浏览器中则清除 localStorage。然后显示告别页面。
5. 一个无需登录即可访问的公开 /delete-account 页面:说明应用内的操作步骤,并为已经没有该应用的用户提供申请表单(电子邮件地址)。申请写入 deletionRequests 集合,并触发一封发往[支持邮箱]的邮件。
6. 在 /privacy 中添加一小节关于删除和数据保留的说明。
7. 测试:如果项目已经在使用 Firebase 模拟器就用模拟器,否则使用 mock。删除操作会清除数据地图中的每一个位置,第二次调用不产生任何效果,过期的 token 会被拒绝。
最后报告修改过的文件、需要填入 Play Console 的 URL,以及我必须配置的内容。逻辑集中在两个文件中。服务器端的顺序很重要:先删数据,最后删 Auth 用户,这样失败的请求可以直接重试。Apple 撤销由客户端处理,因为 Firebase 不保存 Apple token,而用户反正也需要重新验证身份。
// server/routes/account.js (Express + firebase-admin)
import express from "express";
import { getAuth } from "firebase-admin/auth";
import { getFirestore } from "firebase-admin/firestore";
import { getStorage } from "firebase-admin/storage";
export const accountRouter = express.Router();
const OWNED = [["projects", "ownerId"], ["comments", "authorId"], ["pushDevices", "uid"]]; // from docs/user-data-map.md
accountRouter.delete("/api/account", async (req, res) => {
const idToken = (req.get("Authorization") || "").replace(/^Bearer /, "");
let user;
try {
user = await getAuth().verifyIdToken(idToken, true); // true = reject revoked tokens
} catch {
return res.status(401).json({ error: "unauthenticated" });
}
if (Date.now() / 1000 - user.auth_time > 300) {
return res.status(403).json({ error: "recent-login-required" });
}
const uid = user.uid;
const db = getFirestore();
await getStorage().bucket().deleteFiles({ prefix: "users/" + uid + "/" });
for (const [name, field] of OWNED) {
const owned = await db.collection(name).where(field, "==", uid).get();
for (const doc of owned.docs) await db.recursiveDelete(doc.ref);
}
await db.recursiveDelete(db.collection("users").doc(uid));
await getAuth().deleteUser(uid); // last: everything above is safe to re-run
res.status(204).end();
});
// src/lib/deleteAccount.js (client, Firebase JS SDK)
import { OAuthProvider, reauthenticateWithPopup, revokeAccessToken } from "firebase/auth";
import { isNativeApp } from "./platform";
export async function deleteAccount(auth) {
const user = auth.currentUser;
if (user.providerData.some((p) => p.providerId === "apple.com")) {
const result = await reauthenticateWithPopup(user, new OAuthProvider("apple.com"));
const credential = OAuthProvider.credentialFromResult(result);
await revokeAccessToken(auth, credential.accessToken); // Apple 5.1.1(v)
}
const res = await fetch("/api/account", {
method: "DELETE",
headers: { Authorization: "Bearer " + (await user.getIdToken(true)) }
});
if (res.status === 403) throw new Error("recent-login-required");
if (!res.ok) throw new Error("delete-failed");
await auth.signOut();
if (isNativeApp()) window.location.href = "reset://"; // WebViewGold clears cookies and cache
}在只读沙盒中运行第一轮,亲自修正表格(Codex 无法知道哪些数据出于税务或法律原因必须保留),然后提交 docs/user-data-map.md。
在一个全新的会话中运行第二轮。接受改动之前先执行 /review,并对照数据地图逐行阅读服务器路由。
在 Play Console 的数据安全部分填写公开的 /delete-account URL。在隐私政策中说明删除和数据保留(隐私政策需要包含什么)。
用一个临时账号,在 TestFlight 构建版本和内部测试构建版本上逐项检查下面的清单。
审核人员可能会把它删掉。每次提交前都重新创建它,并在审核备注中说明欢迎他们新建一个账号来测试删除功能。发布流程的更多内容见 Codex 工作流程指南。
/delete-account 页面在浏览器无痕窗口中无需登录即可打开,申请表单能够送达。deleted: true 标记不算删除。reauthenticateWithPopup 在 WebView 中表现异常,请使用 reauthenticateWithRedirect,并用 getRedirectResult 读取结果。/delete-account 页面需要登录,或者在 Supabase 的客户端代码中放了 service role key。这两种情况都会很快被发现。允许。只要您在确认步骤和隐私政策中说明,就可以把永久删除安排在稍后的一段短时间之后(例如 14 天),并允许用户通过登录来取消。但删除本身必须真正执行。
它是一个不错的基础:在 Auth 用户被删除时,它会删除配置好的 Firestore 路径和 Storage 文件。您仍然需要应用内按钮、Apple token 撤销、公开的申请页面,并检查它的配置是否与您的数据地图一致。
不必。Google Play 要求的是一种在应用之外申请删除的方式。用一个表单生成申请、再由您处理是可以接受的,前提是您确实在承诺的时间内处理。
删除或匿名化您不需要保留的数据。付款记录可能有法定保留期限;请在隐私政策中列出您保留了什么以及原因。取消商店订阅仍然需要用户自己操作。
不应该。Codex 针对模拟器或 mock 编写代码和测试;您通过已部署的路由在生产环境中执行删除。让生产环境的凭据远离代理会话和云端环境。
结果相同,只是您需要投入的工作量不同。三种方式都附带审核通过保证和 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 天内提出申请。