Logo花火漫画开发文档
函数详情

订单与支付

create-order、payment-notify、query-order 三个函数的完整支付流程、Hypay 集成与 confirm_payment 业务逻辑

本页面描述 VIP 购买的完整支付链路。三个 Edge Function 分别负责下单、回调确认和状态查询,共享 _shared/hypay.ts 签名模块与 Hypay 支付平台对接。

支付流程总览

┌──────────┐     ①创建订单      ┌──────────────┐    ②统一下单     ┌───────────┐
│  客户端   │ ───────────────→  │ create-order │ ─────────────→ │   Hypay   │
│          │ ←─────────────── │              │ ←───────────── │  支付平台  │
└──────────┘   返回 pay_url    └──────────────┘  返回支付链接    └───────────┘
     │                                                              │
     │  ③用户跳转支付                                                │
     │ ──────────────────────────────────────────────────────────→  │
     │                                                              │
     │                         ④异步回调                             │
     │                    ┌────────────────┐                        │
     │                    │ payment-notify │ ←──────────────────── │
     │                    └────────────────┘                        │
     │                           │                                  │
     │                    ⑤confirm_payment RPC                      │
     │                           │                                  │
     │                    ┌──────┴──────┐                           │
     │                    │  更新订单    │                           │
     │                    │  续期 VIP   │                           │
     │                    │  发放徽章    │                           │
     │                    └─────────────┘                           │
     │                                                              │
     │  ⑥轮询订单状态    ┌──────────────┐                           │
     │ ───────────────→ │ query-order  │                           │
     │ ←─────────────── │              │ ─── sync_hypay ──────→   │
     │   返回最新状态    └──────────────┘   (可选:兜底同步)        │

Hypay 集成(共享模块)

三个函数通过 _shared/hypay.ts 共享支付平台的签名与通信逻辑。

签名算法

Hypay V2 使用 RSA + SHA-256 签名方案:

构建待签名字符串:将所有参数(排除 signsign_type,排除空值)按 ASCII 字典序排序,以 key=value&key=value 格式拼接

请求签名:使用商户私钥(HYPAY_PRIVATE_KEY)对待签名字符串进行 RSASSA-PKCS1-v1_5 + SHA-256 签名,结果 Base64 编码

回调验签:使用平台公钥(HYPAY_PUBLIC_KEY)验证回调参数的签名,确认请求来自 Hypay

订单号生成

格式为 YYYYMMDDHHmmss + 6 位随机数,例如 20250615143052028491。由 generateTradeNo() 生成,作为 out_trade_no 传给 Hypay。

共享接口

Prop

Type


create-order

创建 VIP 购买订单并获取支付链接。

属性
方法POST
JWT 校验
CORS支持(Access-Control-Allow-Origin: *

请求参数

Prop

Type

处理流程

身份验证:从 Authorization 头获取 JWT,调用 supabase.auth.getUser() 确认用户身份

查询商品:从 products 表读取商品信息,验证 is_active = true

库存检查:如果 stock_limit 不为 null,检查 sales_count 是否已达上限

计算价格finalPrice × quantity,当前直接使用 product.price(预留促销逻辑)

创建商品快照:将下单时的商品信息(名称、价格、天数、类型)存入 product_snapshot(JSONB),确保后续即使商品信息变动,订单仍有据可查

生成订单号:调用 generateTradeNo() 生成唯一 trade_no

写入订单:向 orders 表插入一条 status = 'pending' 的记录,依赖 RLS 策略(用户只能创建自己的订单)

调用 Hypay 下单:将订单信息发送到 Hypay /api/pay/create,传入 notify_url(指向 payment-notify)和 return_url

处理 Hypay 响应:如果 code !== 0,将订单状态更新为 failed,返回 502;成功则将 hypay_trade_nopay_url 写回订单

返回支付信息:将 pay_info(支付跳转 URL)返回给客户端

成功响应

{
  "success": true,
  "data": {
    "order_id": "uuid",
    "trade_no": "20250615143052028491",
    "hypay_trade_no": "HP2025061500001",
    "amount": "30.00",
    "pay_type": "wxpay",
    "pay_info": "https://hyzf.yuyuanai.top/pay/..."
  }
}

pay_info 的含义取决于请求时的 method 参数。当前固定为 jump(跳转模式),返回一个支付页面 URL,客户端直接跳转即可。


payment-notify

Hypay 支付成功后的异步回调端点。

属性
方法GET(Hypay 以 Query String 传参)
JWT 校验否(verify_jwt: false
调用方Hypay 支付平台服务端

此函数不校验 JWT,通过 RSA 签名验证请求来源。PAYMENT_TEST_MODE 环境变量可跳过签名验证,生产环境务必设为 "false"

回调参数

Hypay 以 URL Query String 方式传递以下参数:

Prop

Type

处理流程

参数校验:检查 trade_noout_trade_notrade_status 是否存在

签名验证:使用 Hypay 平台公钥验证回调签名(测试模式跳过)

状态检查:如果 trade_status !== 'TRADE_SUCCESS',直接返回 success(忽略非成功通知)

查询订单:使用 SERVICE_ROLE_KEYorders 表查找对应订单

幂等性检查:如果订单 status 已经是 paid,直接返回 success,不重复处理

调用 confirm_payment:将 trade_nohypay_noreal_amount 传给 RPC 函数执行核心业务逻辑

响应约定

  • 返回 HTTP 200 + body "success":Hypay 认为回调成功,停止重试
  • 返回其他状态码或 body "fail:...":Hypay 会重试回调

即使业务处理失败(如金额不匹配),函数仍返回 "success" + 200,避免 Hypay 无限重试。错误信息通过 console.error 记录到日志。


query-order

前端查询订单状态,支持主动同步 Hypay 以兜底回调丢失。

属性
方法POST
JWT 校验
CORS支持

请求参数

Prop

Type

处理流程

查询本地订单(RLS 限制仅自己的订单)
  → status === 'paid'?
    → 是:直接返回(快速路径)
    → 否 & sync_hypay === true & status === 'pending'?
      → 查询 Hypay API
        → Hypay 显示已支付?
          → 调用 confirm_payment RPC(补偿回调丢失)
          → 返回更新后的订单 + synced: true
        → Hypay 未支付?
          → 返回本地订单 + hypay_status
  → 否:返回本地订单状态

回调丢失兜底机制

sync_hypay = true 且本地订单仍为 pending 时,函数会主动调用 Hypay 查询接口。如果 Hypay 显示已支付但本地未更新,说明回调丢失,此时函数使用 SERVICE_ROLE_KEY 创建 admin 客户端,调用 confirm_payment RPC 完成支付确认。

sync_hypay 功能需要 SERVICE_ROLE_KEY,因为它要绕过 RLS 更新订单状态。此路径仅在 pending 状态下触发,已支付订单不会重复处理。

响应示例

{
  "success": true,
  "data": {
    "id": "uuid",
    "trade_no": "20250615143052028491",
    "amount": "30.00",
    "status": "paid",
    "paid_at": "2025-06-15T14:32:00Z",
    "product_snapshot": { "..." : "..." },
    "hypay_status": 1
  }
}
{
  "success": true,
  "data": {
    "id": "uuid",
    "trade_no": "20250615143052028491",
    "amount": "30.00",
    "status": "pending",
    "pay_url": "https://hyzf.yuyuanai.top/pay/..."
  }
}
{
  "success": true,
  "data": {
    "id": "uuid",
    "trade_no": "20250615143052028491",
    "amount": "30.00",
    "status": "paid",
    "paid_at": "2025-06-15T14:35:00Z",
    "hypay_status": 1,
    "synced": true
  }
}

synced: true 表示此次状态更新是通过主动同步而非回调完成的。


confirm_payment RPC

支付确认的核心业务逻辑,由 payment-notifyquery-order(兜底同步)调用。这是一个 PostgreSQL 函数,在数据库层面保证事务一致性。

参数

Prop

Type

执行逻辑

锁定订单SELECT ... FOR UPDATE 防止并发重复确认

基础检查:订单是否存在、是否已支付(幂等)、金额是否匹配

读取快照:从 product_snapshot 中提取 daystypequantity

更新订单status → paid,写入 hypay_trade_nopaid_at

更新商品销量products.sales_count += quantity

按商品类型分支处理(见下方)

商品类型处理

计算 总天数 = duration_days × quantity,更新 profiles.vip_expiration_date

  • 当前 VIP 未过期:在现有到期时间上追加天数
  • 当前 VIP 已过期:从当前时间起算追加天数
vip_expiration_date =
  CASE
    WHEN vip_expiration_date > now()
      THEN vip_expiration_date + total_days
    ELSE
      now() + total_days
  END

捐赠商品不按天数计算,而是根据累计捐赠金额授予永久 VIP:

累计捐赠VIP 到期时间含义
不足 50 元2055-12-31基础永久 VIP
达到 50 元2155-12-31高级永久 VIP

保护逻辑:如果用户已是高级永久 VIP(到期时间 ≥ 2100 年),不会因后续小额捐赠被降级为基础永久。

额外操作:

  • 更新 profiles.total_donated_amount
  • 自动发放捐赠徽章(badge_id = 4),使用 INSERT ... WHERE NOT EXISTS 防止重复

捐赠通道已经于 2026-03-15 正式结束业务,代码逻辑依然保持,为之后的再开做准备。

返回值

// 订阅
{ "success": true, "type": "subscription", "days_added": 30, "quantity": 1 }

// 捐赠
{ "success": true, "type": "donation", "donated_amount": 30, "total_donated": 80 }

// 失败
{ "success": false, "msg": "Amount mismatch" }

相关数据表

Prop

Type

order_status 枚举pending(待支付)→ paid(已支付)→ refunded(已退款);下单失败为 failed

Prop

Type

Secrets 依赖

Secret用于说明
HYPAY_PID全部三个函数Hypay 商户 ID
HYPAY_PRIVATE_KEYcreate-order, query-order商户 RSA 私钥,用于请求签名
HYPAY_PUBLIC_KEYpayment-notifyHypay 平台 RSA 公钥,用于回调验签
PAYMENT_TEST_MODEpayment-notify测试模式开关,"false" 为生产模式(启用验签)
SUPABASE_URL全部系统自带
SUPABASE_ANON_KEYcreate-order, query-order系统自带,配合用户 JWT 使用
SUPABASE_SERVICE_ROLE_KEYpayment-notify, query-order回调处理和兜底同步需要绕过 RLS

安全注意事项

  • 金额校验confirm_payment 会比对 orders.amount 与实际支付金额 p_real_amount,不匹配时拒绝确认并记录 CRITICAL 日志
  • 幂等性:订单级别 FOR UPDATE 行锁 + 状态检查,确保同一订单不会被重复确认
  • 回调安全payment-notify 不校验 JWT,完全依赖 RSA 签名验证。生产环境必须确保 PAYMENT_TEST_MODE"false"
  • 商品快照:下单时冻结商品信息到 product_snapshot,后续确认支付时从快照读取天数和类型,不受商品实时变更影响
  • RLS 保护create-orderquery-order 使用 ANON_KEY + 用户 JWT,受 RLS 约束(用户只能操作自己的订单);payment-notify 使用 SERVICE_ROLE_KEY 绕过 RLS

On this page