GPT-Image2 付费交流群上线手册:基于 Supabase 与支付宝网站支付的长期资格闭环
【免费下载链接】awesome-gpt-image-2Prompt as Code | GPT-Image2 工业级提示词引擎与模板库,530+ 个案例逆向工程,20+ 套工业级模板,并提炼出Skills,持续更新中项目地址: https://gitcode.com/GitHub_Trending/awe/awesome-gpt-image-2
本文是 GPT-Image2 开源仓库中付费交流群模块的完整上线与运维指南。该模块将付费群资格与积分包、会员支付完全分离:用户通过支付宝一次性支付¥9.90,资格绑定 Supabase 用户账号,只有服务端确认订单进入PAID状态后才能读取受保护的群二维码。读完本文,你将掌握该功能的页面与 API 拓扑、数据库状态机与 RPC 事务边界、环境变量配置、首次部署顺序、生产验收清单,以及群码轮换和退款等日常运维方法。
一、系统定位:与积分包、会员支付完全隔离的独立子域
付费交流群是独立于现有积分包(credit pack)与会员(membership)体系之外的一笔一次性交易,设计目标是"资格绑定账号,而非绑定设备或订单页面"。核心决策体现在三点:
- 独立订单表:创建的是独立的
community_orders订单,不会触发积分包履约,也不会影响会员有效期。 - 资格与账号绑定:付款资格绑定 Supabase 用户账号(
auth.users.id),换设备登录后仍可重新查看入群二维码。 - 服务端权威确认:同步回跳只触发服务端查单,不信任 URL 参数;只有支付宝主动查单(
alipay.trade.query)或异步通知(alipay.trade.page.pay的通知回调)验签通过并确认PAID后,二维码才可见。
前端文案在 src/community.jsx 中明确说明:"支付宝一次性支付 ¥9.90。付款资格绑定当前账号,换设备登录后仍可重新查看入群二维码";服务边界也写得很清楚:不承诺一对一服务、固定答疑次数、独家资料或收益结果,违法违规或破坏交流秩序的账号可被撤销资格。
二、本地能力速览
| 能力项 | 值 |
|---|---|
| 页面 | /community(购买与资格展示) |
| 支付回跳 | /community/result(仅触发服务端查单) |
| 支付方式 | 支付宝网站支付alipay.trade.page.pay(页面跳转收银台) |
| 固定金额 | 990分(¥9.90),币种CNY |
| 订单状态 | PENDING、PAID、CLOSED、REFUNDED、REVOKED |
| 退款处理状态 | NONE、PROCESSING、SUCCEEDED、FAILED |
| 资格规则 | 仅PAID有效;退款处理中保持PAID;支付宝确认退款成功后改为REFUNDED |
| 群码 | 数据库bytea资源,限 PNG/JPEG/WebP、最大 2 MB,管理员通过事务 RPC 原子替换 |
这些常量在 api/_lib/community.js 中有同源定义:COMMUNITY_PRICE_CENTS = 990、COMMUNITY_CURRENCY = 'CNY'、COMMUNITY_TERMS_VERSION = '2026-07-22'、COMMUNITY_PENDING_MINUTES = 30、COMMUNITY_QR_MAX_BYTES = 2 * 1024 * 1024。订单创建时默认 30 分钟过期(expires_at),超时未付的订单在再次发起支付前会先被关闭并做对账(详见下文"过期订单的关闭与对账")。
状态机语义
PENDING:订单已创建、尚未支付。同一用户最多保留一笔PENDING或PAID订单(由数据库部分唯一索引保证)。PAID:服务端已确认收款。这是唯一允许读取群码的状态,判断函数为canAccessCommunityQr(status),见 api/_lib/community.js。CLOSED:待支付订单被关闭(用户主动关闭、超时关闭或支付宝TRADE_CLOSED通知)。REFUNDED:支付宝确认退款成功后写入,此时群码访问立即失效。REVOKED:管理员人工撤销资格,不会自动退款。
退款处理状态refund_status独立于订单状态运行:NONE(从未发起退款)→PROCESSING(已发起退款、等待支付宝确认)→SUCCEEDED(退款成功,同时订单进入REFUNDED)或FAILED(退款请求被支付宝拒绝)。关键细节是:退款处理中(PROCESSING)订单仍保持PAID,群码资格依旧有效,直到REFUND_SUCCESS被确认。
三、数据模型:三张表与事务 RPC
付费群的核心数据全部由迁移脚本 supabase/migrations/20260722090000_paid_community.sql 创建,包含三张业务表:
1.community_orders:订单主表
关键字段与约束:
status:PENDING / PAID / CLOSED / REFUNDED / REVOKED,默认PENDING。amount_cents:check (amount_cents = 990),金额在数据库层被锁死为 990 分,前端无法提交或覆盖金额。currency:check (currency = 'CNY'),币种同样被数据库约束锁死。subject:默认"GPT-Image2 付费交流群长期资格"。terms_version:必填,记录下单时接受的条款版本(当前为2026-07-22),与前端 src/community.jsx 的TERMS_VERSION一致。- 支付宝侧字段:
alipay_app_id、alipay_seller_id、alipay_trade_no(支付宝交易号)。 - 退款侧字段:
refund_request_no(稳定退款请求号)、refund_status、refund_failure_code。 - 时间线字段:
expires_at(30 分钟)、paid_at、refund_requested_at、refund_checked_at、refunded_at、revoked_at。 metadata jsonb:记录notifyEnabled、returnUrl、termsAcceptedAt等支付上下文。
索引设计(防止并发下的重复下单/重复入账):
community_orders_one_active_per_user_idx:(user_id)的部分唯一索引,仅对PENDING、PAID生效——同一用户同时只能有一笔有效订单。community_orders_alipay_trade_no_idx:alipay_trade_no非空时唯一——同一支付宝交易号不能入账到多笔订单。community_orders_refund_request_no_idx:refund_request_no非空时唯一——退款请求号幂等。- 另有两个普通索引:
created_at desc与(status, created_at desc),支撑管理端订单列表按时间倒序与状态过滤。
2.community_alipay_notify_events:异步通知事件表
支付宝异步通知的原始事件落库(notify_id唯一、payload jsonb存原文),配合唯一索引(order_id, trade_no, trade_status, notify_id)保证重复通知不产生副作用,是通知幂等性的第一道防线。
3.community_group_qr_assets:群二维码资产表
qr_bytes bytea:二维码二进制本体,直接存数据库而非公开对象存储。media_type:仅允许image/png、image/jpeg、image/webp。size_bytes:> 0且<= 2097152(2 MB)。is_current:当前生效标记;部分唯一索引community_group_qr_one_current_idx保证全局同一时刻只有一张"当前"群码。
三张表均启用行级安全(RLS)且对public / anon / authenticated全部revoke,只向service_role授权,任何客户端都不能绕过 API 直接读写订单或群码。
RPC 函数:把状态迁移收敛到数据库事务
所有状态变更都以security definer的 PL/pgSQL 函数形式提供(同样只授权service_role),服务端只负责验签、鉴权与调用。从源码看,共 8 个函数:
| 函数 | 作用 | 幂等行为 |
|---|---|---|
create_or_reuse_community_order | 创建或复用PENDING/PAID订单,pg_advisory_xact_lock串行化同一用户的并发下单 | 已有有效订单直接返回 |
mark_community_order_paid | 入账:校验金额 990 分、币种 CNY、交易号一致性,写通知事件,PENDING → PAID | 已PAID/REFUNDED/REVOKED直接返回当前状态 |
close_community_order | PENDING → CLOSED | 非PENDING返回当前状态 |
prepare_community_refund | 发起退款前置:校验订单为PAID,写入refund_request_no,refund_status → PROCESSING | 已REFUNDED复用原请求号 |
set_community_refund_state | 更新退款状态为PROCESSING或FAILED(含失败码),仅对PAID订单生效 | — |
finalize_community_refund | 终态确认:三方信息完全匹配后PAID → REFUNDED、refund_status → SUCCEEDED | 已REFUNDED幂等返回 |
revoke_community_access | PAID → REVOKED;退款处理中禁止撤销 | 已REVOKED幂等返回 |
replace_community_group_qr_asset | 群码原子替换:pg_advisory_xact_lock串行化,同一事务内停用旧图并启用新图 | — |
以mark_community_order_paid为例(迁移脚本 L151-L229):函数先用SELECT ... FOR UPDATE锁定订单行,逐一校验订单存在、交易号非空、p_paid_amount_cents = 990、币种为CNY、alipay_trade_no未被其他交易占用;再把通知事件以on conflict (notify_id) do nothing写入事件表;最后仅在PENDING状态下更新为PAID。这把"先查询、再校验、后入账"的整个流程原子化,主动查单与异步通知两条路径无论谁先到达都不会产生重复入账或金额篡改。
四、API 拓扑:三组端点
1. 公开与当前用户(无需管理员)
| 方法 | 路径 | 职责 |
|---|---|---|
GET | /api/community/config | 公开配置:价格¥9.90、币种、paymentEnabled(由环境变量 + Supabase + 支付宝配置三条件叠加,见 api/community/config.js)、人工支持文案、条款版本 |
GET | /api/community/status | 当前登录用户资格状态:authenticated、eligible、qrReady、paymentEnabled、order;未登录用户返回eligible: false(api/community/status.js) |
GET | /api/community/qr | 受保护群码:仅PAID订单可读,未付款返回403 COMMUNITY_PAYMENT_REQUIRED,管理员未上传群码返回409 COMMUNITY_QR_NOT_READY,成功时以原始Content-Type回传图片字节(api/community/qr.js) |
三个响应都带Cache-Control: private, no-store(noStore工具,见 api/_lib/community.js),禁止任何中间层缓存敏感资格状态。
2. 支付宝交互(当前用户 + 支付宝服务端)
| 方法 | 路径 | 职责 |
|---|---|---|
POST | /api/community/alipay/checkout | 创建收银台:校验同源(validateSameOrigin)、条款勾选与版本、30 分钟过期订单先对账关闭;金额、币种、out_trade_no(= 订单 UUID)由服务端固定,生成alipay.trade.page.pay的POST表单 HTML 返回前端自动提交(api/community/alipay/checkout.js) |
GET | /api/community/alipay/query | 主动查单:PENDING/PAID订单调用alipay.trade.query,验签 + 校验金额币种后由 RPC 幂等入账;PAID订单在支付宝侧非PAID时直接抛错,防止状态倒挂(api/community/alipay/query.js) |
POST | /api/community/alipay/close | 关闭待支付订单:先查单对账(若实际已付款则入账并返回409),再调alipay.trade.close并本地置CLOSED |
POST | /api/community/alipay/notify | 支付宝异步通知:bodyParser: false读原始表单体,checkNotifySignV2验签(强制RSA2),校验app_id、seller_id、notify_id与订单归属;付款事件落community_alipay_notify_events并幂等入账;TRADE_CLOSED且订单PENDING时本地关闭;成功返回纯文本success(api/community/alipay/notify.js) |
对账逻辑集中在 api/_lib/community-alipay.js:查单结果code = 10000后先复校金额与币种,再按trade_status分支处理——TRADE_SUCCESS / TRADE_FINISHED入账、TRADE_CLOSED关闭、WAIT_BUYER_PAY保持待支付、ACQ.TRADE_NOT_EXIST视为未创建。由此,"支付宝主动查单和异步通知都能幂等写入PAID"的验收项得到了数据库唯一索引与 RPC 双重保障。
3. 超级管理员(super_admin)
| 方法 | 路径 | 职责 |
|---|---|---|
GET | /api/admin/community/orders | 订单列表:支持status过滤与limit(1~100,默认 50),联查profiles返回邮箱与昵称(api/admin/community/orders.js) |
GET / POST | /api/admin/community/qr | GET查看当前群码(?metadata=1只返回元数据);POST上传新群码:魔数检测真实类型(PNG/JPEG/WebP 文件头,见 api/_lib/community.js)、校验声明类型与魔数一致、2 MB 上限,然后调replace_community_group_qr_asset事务替换(api/admin/community/qr.js) |
POST | /api/admin/community/refund | 发起退款:生成稳定退款请求号cg_<订单UUID去横线>(api/_lib/community.js),调alipay.trade.refund;fund_change = Y直接终态化,否则置PROCESSING等待查询(api/admin/community/refund.js) |
GET | /api/admin/community/refund-query | 退款查询:请求发起 10 秒内拒绝(COMMUNITY_REFUND_QUERY_TOO_EARLY),调alipay.trade.fastpay.refund.query,三方信息全匹配且refund_status = REFUND_SUCCESS时执行finalize_community_refund(api/admin/community/refund-query.js) |
POST | /api/admin/community/revoke | 人工撤销:仅PAID且未在退款处理中的订单可撤销,调revoke_community_access(api/admin/community/revoke.js) |
所有管理员端点都要求isCommunityAdmin(auth)(profile.isSuperAdmin或role === 'super_admin',见 api/_lib/community.js),写操作额外校验同源。限流方面,社区端采用内存滑动窗口:checkout 每分钟 10 次、query 每分钟 30 次、管理员订单列表默认每分钟 30 次,超限返回429并带Retry-After。
五、环境变量与支付宝生产配置
生产部署必须先保持如下配置(取自 docs/paid-community.md 原文):
COMMUNITY_PAYMENT_ENABLED=false COMMUNITY_ALIPAY_NOTIFY_URL=https://gpt-image2.canghe.ai/api/community/alipay/notify COMMUNITY_SUPPORT_TEXT=微信搜索苍何各变量语义(结合 api/_lib/community.js 与 api/_lib/alipay.js):
COMMUNITY_PAYMENT_ENABLED:总开关。false时/api/community/config返回paymentEnabled: false,checkout 返回503 COMMUNITY_PAYMENT_PAUSED,已付款用户的群码查看与退款操作不受影响。这是线上"一键熔断"的开关。COMMUNITY_ALIPAY_NOTIFY_URL:公网 HTTPS 通知地址,必须无重定向;未配置时按APP_URL推导为{origin}/api/community/alipay/notify,本地地址(localhost/127.0.0.1/::1)则禁用通知。COMMUNITY_SUPPORT_TEXT:页面展示的人工支持渠道文案,默认"微信搜索苍何"。
支付宝生产变量沿用现有体系:ALIPAY_APP_ID、ALIPAY_PRIVATE_KEY、ALIPAY_PUBLIC_KEY、ALIPAY_SELLER_ID和生产网关https://openapi.alipay.com/gateway.do(见 api/_lib/alipay.js 的loadProductionConfig)。
两个必须遵守的约束:
- 生产私钥只在 Vercel Sensitive Environment Variables 中配置,不写入仓库、文档或对话;
requiredPrivateKey甚至拒绝包含-----BEGIN/-----END或空白的多行 PEM,要求传入 PKCS#1 私钥原文(api/_lib/alipay.js)。 - Node.js 使用 PKCS#1 私钥原文,SDK 以
keyType: 'PKCS1'、signType: 'RSA2'、charset: 'utf-8'初始化(api/_lib/alipay.js)。
此外还有两个内置开关可做辅助控制:ALIPAY_NOTIFY_ENABLED=false可整体禁用通知(此时依赖主动查单确认付款),APP_URL参与同源校验与通知 URL 推导(api/_lib/community.js)。
六、首次部署顺序:8 步上线流程
按 docs/paid-community.md 的部署顺序,结合源码说明每步目的:
- 保持
COMMUNITY_PAYMENT_ENABLED=false。此时页面可浏览、状态接口可用,但 checkout 会被503拒绝,避免未配置完成就产生真实订单。 - 在目标 Supabase 项目应用迁移supabase/migrations/20260722090000_paid_community.sql。该脚本一次性创建三张表、全部索引、8 个 RPC 函数与
service_role授权,可重复执行(create table if not exists/create or replace function)。 - 部署同一版本代码,确认
/community、/community/result和只读状态接口正常。前端会拉取/api/community/config与/api/community/status,若paymentEnabled为假则展示"当前暂停新订单"卡片。 - 由
super_admin在管理面板上传一张从未公开过的新群二维码。注意:受保护群码不应出现在 GitHub、README、公开对象存储或前端静态目录中,上传走POST /api/admin/community/qr的魔数校验与事务替换。 - 在支付宝开放平台完成网站支付签约、应用配置与发布,并确认公网 HTTPS 通知地址无重定向(任何跳转都会导致通知丢失)。
- 配置属于同一生产应用的 App ID、应用公钥、应用私钥和支付宝公钥;Node.js 使用 PKCS#1 私钥原文,密钥只放 Sensitive Environment Variables。
- 开启
COMMUNITY_PAYMENT_ENABLED=true。此后 checkout 正常创建收银台订单。 - 用同一生产版本完成一笔真实
¥9.90付款和原路退款验收,全部通过后再正式对外推广。
七、生产验收:真实付款必须逐项确认
上线前用真实支付跑通以下验收清单(源自 docs/paid-community.md 并对应到源码实现):
- 前端不能提交或覆盖金额:金额由服务端常量
COMMUNITY_PRICE_CENTS固定,数据库check (amount_cents = 990)二次兜底,前端只展示"支付宝支付 ¥9.90"。 - 创建的是独立
community_orders,不会触发积分包履约:订单写入community_orders表,与complete_alipay_credit_pack_order(积分包入账 RPC,见 api/_lib/alipay.js)完全走不同的表与函数。 - 支付宝主动查单和异步通知都能幂等写入
PAID:查单路径(queryCommunityOrderAtAlipay)与通知路径(markCommunityOrderPaid)最终都落到同一个mark_community_order_paidRPC,配合交易号唯一索引与FOR UPDATE行锁。 - 同步回跳只触发服务端查单,不信任 URL 参数:
/community/result页面仅解析order_id参数,随后调用/api/community/alipay/query,由服务端查单确认;页面文案明确"本页不会信任网址参数判定付款成功"(src/community.jsx)。 - 付款账号可以读取群码,未付款账号得到拒绝响应:
/api/community/qr依据canAccessCommunityQr(status)判定,非PAID一律403 COMMUNITY_PAYMENT_REQUIRED。 - 管理员退款使用稳定退款请求号;处理中资格仍有效:请求号
cg_<uuid>唯一且复用,PROCESSING期间订单保持PAID,群码继续可读。 - 退款查询得到
REFUND_SUCCESS后写入REFUNDED,随后群码访问失效:finalize_community_refund校验交易号、请求号、退款金额三方一致后置终态。 - 任何关键项失败时立即将
COMMUNITY_PAYMENT_ENABLED改回false:熔断只影响新订单创建,不回收已付款资格,也不阻断退款与管理员操作。
八、群码轮换与日常运维
- 群码过期轮换:群二维码过期时,管理员在管理面板上传新图片。
replace_community_group_qr_asset通过pg_advisory_xact_lock(hashtext('community_group_qr_current'))串行化,并在同一事务中先把旧图is_current = false、写入retired_at,再插入新图并置is_current = true——轮换过程原子完成,任何时刻客户端只能读到一张有效群码。前端对"管理员尚未上传群码或刚完成换码"的场景有专门提示:"你的付款资格不会丢失"(src/community.jsx)。 - 群码保密:不要把受保护群码上传到 GitHub、README、公开对象存储或前端静态目录。二维码只以
bytea形式存在于数据库,经/api/community/qr按需、鉴权、no-store地回传。 - 退款审核:退款由"微信搜索苍何"人工审核,管理员操作前需核对账号、订单号和退款状态;
/api/admin/community/orders返回的邮箱、昵称、refund_request_no、refund_failure_code等字段即为核对依据。退款发起后若未立即成功(fund_change != 'Y'),须在 10 秒后轮询/api/admin/community/refund-query直到REFUND_SUCCESS。 - 撤销与退款不可混用:人工撤销资格(
revoke)不会自动退款;需要退款时必须走退款流程,不能用撤销代替退款——这与数据库函数中"退款处理中禁止撤销"(revoke_community_access对refund_status = 'PROCESSING'抛COMMUNITY_ORDER_NOT_REVOKABLE)的设计互为表里。
九、扩展阅读
- 上线手册原文:docs/paid-community.md
- 数据库迁移与全部 RPC:supabase/migrations/20260722090000_paid_community.sql
- 社区公共库(常量、校验、状态机、限流):api/_lib/community.js、api/_lib/community-alipay.js
- 支付宝 SDK 封装与生产/沙箱配置:api/_lib/alipay.js
- 用户侧 API:api/community/config.js、api/community/status.js、api/community/qr.js
- 支付宝交互 API:api/community/alipay/checkout.js、api/community/alipay/query.js、api/community/alipay/notify.js、api/community/alipay/close.js
- 管理端 API:api/admin/community/orders.js、api/admin/community/qr.js、api/admin/community/refund.js、api/admin/community/refund-query.js、api/admin/community/revoke.js
- 前端页面:src/community.jsx(含中英文文案、状态卡片、支付表单提交与回跳轮询)
【免费下载链接】awesome-gpt-image-2Prompt as Code | GPT-Image2 工业级提示词引擎与模板库,530+ 个案例逆向工程,20+ 套工业级模板,并提炼出Skills,持续更新中项目地址: https://gitcode.com/GitHub_Trending/awe/awesome-gpt-image-2
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考