跳到正文

SaaSdemo:最小第三方 SaaS 接入 ​

SaaSdemo 演示第三方 SaaS 如何接入 GoodVision 登录、订阅和支付。示例应用提供自己的业务页面和登录会话,套餐、订阅、支付、账单和权益管理由 GoodVision 客户工作区提供。

SaaSdemo 的订阅页展示当前已发布的免费或付费套餐,支持月/年切换、Credits、席位、FAQ 和原订单恢复。应用分别查询免费激活、付款、额度和产品开通状态,不维护独立的余额或资格表。

升级时按订阅 ID 分别保存状态。旧免费订阅终止后,仍需检查当前成员权限。

示例边界

SaaSdemo 是给第三方 SaaS 团队参考的独立接入样例,不是 GoodVision 内部微应用或内部服务 Demo。它演示已发布的 OIDC 接入和页面功能。使用商业 API 前,请在开发者控制台确认当前应用已开通 SaaS Billing API Product,并使用产品提供的路径、Scope、Audience 和事件 Schema。

页面功能 ​

能力SaaSdemoGoodVision 平台
注册、登录、退出提供入口和同源 BFF SessionGoodVision 统一身份服务处理身份、凭证和 OIDC Session
合作方业务页面展示第三方产品自己的功能不托管第三方业务功能
套餐、订阅、支付、账单只提供平台入口客户工作区展示并执行商业操作
Application、API Product、Webhook只提供开发者平台入口Developer Console 管理技术接入
API 参数和错误码使用当前版本说明API Product 提供对应 OpenAPI/AsyncAPI

套餐、订单、支付、账单和使用权限由平台管理。第三方页面不能维护另一套结果,也不能用 localStorage 模拟这些状态。

顶部账号入口与用户信息 ​

示例顶部提供开发者后台、应用入口和登录按钮;登录后显示账号头像、姓名与邮箱,可读取用户信息、切换语言、登录其他账号或退出。切换应用只打开服务端登记的独立入口,不转移当前应用的会话或权限。

以下路径属于 SaaSdemo 自己的同源 BFF,供第三方参考实现,不是平台新增的业务 API:

接口用途
GET /auth/session检查是否登录,读取当前用户投影和本应用 CSRF 信息
GET /api/me重新核验当前会话和应用范围,读取姓名、邮箱、本地用户 ID、open_id 与 union_id
GET /auth/login?switch_account=true通过统一登录确认其他账号;登录后返回原页面
POST /auth/logout携带本应用的 CSRF 校验,退出并清理当前会话
js
const response = await fetch('/api/me', {
  credentials: 'same-origin',
  cache: 'no-store',
  headers: { Accept: 'application/json' },
});
if (!response.ok) throw new Error(`Profile unavailable: ${response.status}`);
const { user } = await response.json();

接口只读取当前登录用户,不接受用户、企业或应用覆盖参数。未登录返回 401,范围拒绝返回 403,依赖暂不可用返回 503;失败时不返回旧用户资料。资料弹窗关闭或用户变化后清除临时数据。Token 留在服务端,应用权限和订阅状态继续由对应平台接口核验。

OIDC 配置 ​

SaaSdemo 的用户登录使用“用户身份权限”和 Authorization Code + PKCE(S256)。如果还需要定时任务或后台 API,再单独启用“应用身份权限”。两种用途使用不同 Client,Sandbox 和 Production 也不共用 Client。

配置示例值或来源
Issuer当前环境提供的 GOODVISION_IDENTITY_ISSUER
Client IDDeveloper Console 中“用户身份权限”的 Client ID
Client Secret无;用户身份使用 public Client + PKCE
Redirect URI第三方域名上的精确 /auth/callback 地址
Post logout URI已登记的第三方产品首页
Scopeopenid profile email 以及当前用户身份已批准的最小 Scope
Token 存储BFF 服务端 Session;浏览器仅持有 HttpOnly Cookie

BFF 在回调中校验 state、nonce、PKCE、Issuer、Audience、azp、exp 和 iat。浏览器脚本、静态环境变量、URL、日志和埋点都不能出现 Token。只有可选的应用身份才有 Client Secret,且它只能保存在服务端 Secret 存储中。

从 Developer Console 获取接入配置 ​

进入目标 Application 后,按模块取得 SaaSdemo 的服务端配置:

Developer Console 模块SaaSdemo 配置
OAuth/OIDC 配置用户身份的 GOODVISION_IDENTITY_ISSUER、GOODVISION_USER_CLIENT_ID、Redirect URI、Post Logout URI;无 Client Secret
凭证与基础信息只在需要后台 API 时使用应用身份的 GOODVISION_SERVICE_CLIENT_ID,并按专用权限即时查看或轮换 GOODVISION_SERVICE_CLIENT_SECRET
API 与权限先选择用户身份或应用身份,再复制该身份已批准的 GOODVISION_API_BASE、操作 Path、Audience 和 Scope
事件与 WebhookGOODVISION_WEBHOOK_PATH、GOODVISION_WEBHOOK_SECRET_VERSION 和签名密钥交付状态

API Base、操作路径、Audience 和 Scope 只从当前环境、当前身份已批准的 API 调用权限中复制。批量复制只包含非敏感配置,不包含 OAuth Client Secret、Webhook Secret 或 Token。

在「事件与 Webhook」的投递日志中查看 event_id、delivery_id 和 correlation_id。event_id 用于去重,delivery_id 用于定位投递与重试,correlation_id 用于关联付款、订阅和使用权限的处理记录。日志仅提供脱敏状态,不展示事件正文、签名 Secret、Authorization 或 Cookie。

Demo BFF 路由 ​

这些路由属于示例项目,不是 GoodVision Open API:

路由用途
GET /auth/login生成 OIDC 登录请求
GET /auth/register生成带 prompt=create 的注册请求
GET /auth/callback交换授权码并建立同源 Session
GET /auth/session返回必要的用户信息;匿名状态也返回 200
GET /auth/config返回脱敏的 Application、OIDC、安全和平台入口配置
POST /auth/logout校验 CSRF、销毁 Session 并返回统一身份服务的退出地址

/auth/config 只能返回 clientSecretConfigured: true|false,绝不能返回 Secret 内容。

配置订阅与账单入口 ​

SaaSdemo 从服务端运行时配置读取以下入口:

text
platform.subscriptionUrl       # 客户工作区的订阅与账单页面
platform.billingUrl            # 客户工作区的付费账单页面
platform.developerConsoleUrl   # Developer Console
platform.developerPortalOrigin # 开发者文档

SaaSdemo /app/pricing 在工作区左侧导航内用 iframe 承载平台 /console/pricing,展示价格、比较与FAQ;侧栏“定价”进入该路由。/pricing 保留为带官网顶栏的独立宿主页。两个入口共用同一定价 iframe,“我的订阅”用弹窗嵌入 /console/subscription,并保留独立打开入口。支持已公布的范围选择参数 application=<slug>、environment=sandbox|production 和可选 product=<code>,以及 embed=1、locale=zh-CN|en-US、view=subscription|billing、theme=light|dark。参数只选择已授权范围,SaaSdemo 服务端绑定不可由 URL 改写;错误或重复范围参数拒绝。Production 枚举不表示 Production 调用已开放。第三方不得附加以下值:

  • application_id、organization_id 或 product_tenant_id;
  • plan_code、金额、币种、Provider 或支付状态;
  • 任意 return_url、Token、Client Secret 或 Webhook Secret。

客户工作区独立打开时使用平台 Session;已批准嵌入合同可使用 auth=parent-bff,沿宿主同源 BFF 转发,平台仍核验当前真人、企业、应用、环境与能力。双方验证精确 Origin 和原 iframe,不通过 URL 或消息传递 Token、Cookie、CSRF Secret 或第三方 handoff Token。

  1. iframe 未登录时,平台发送 goodvision.customer-billing.auth_required。
  2. SaaSdemo 使用固定 targetOrigin,在顶层窗口完成 GoodVision 登录,再刷新 iframe。
  3. iframe 发起付款时,平台在顶层窗口打开 Stripe Hosted Checkout。

Organization、ProductTenant、套餐、金额、币种、Provider、Token、Secret 和任意回跳地址不能由浏览器指定。已公布的应用范围选择不能授予权限。

最小实现流程 ​

  1. 在 Developer Console 为目标环境申请 Application,并启用用户身份权限;只有需要后台 API 时才再启用应用身份权限。
  2. 在第三方 BFF 完成 Authorization Code + PKCE、Session 和 CSRF。
  3. 登录后展示产品页面与平台定价宿主页;新客户先创建企业、提交本人客户资料并完成平台审核和独立商业准备。
  4. 定价单独展示,订阅管理通过弹窗嵌入,也可独立打开 platform.subscriptionUrl 或 platform.billingUrl;技术配置打开 platform.developerConsoleUrl。
  5. 用户返回产品后,通过已开通接口重新查询 ProductAccess/Entitlement。确认产品权限后再开放对应功能。
  6. 只有当对应身份的 API 调用权限已批准时,才实现后台 API 或签名 Webhook 消费。

Webhook Receiver 参考实现 ​

SaaSdemo 提供以下可运行的服务端实现和测试:

  • server/index.mjs:接收地址连接校验、原始 Body 接收、能力与应用/环境检查、快速应答和后台权限查询;
  • server/integration.mjs:时间窗、Nonce、HMAC、Verification Token、可选解密、封闭事件 Schema、event_id 去重、版本检查和脱敏状态记录;
  • server/integration.test.mjs 覆盖支付、退款、订阅、履约、产品开通和权限失效事件,以及跨 Application/环境拒绝;
  • server/access-readback.integration.test.mjs:启动示例进程,检查签名接收、应用身份 Token、access.reconcile.read 查询,以及查询失败时限制访问、查询成功时更新必要权限;

接收事件时,按以下顺序处理:

  1. 验证传输安全及当前应用/环境。
  2. 持久化到 Inbox,然后快速应答。
  3. 后台任务查询当前状态。

oem.product.activated.v1 更新产品级摘要。收到 oem.access_context.changed.v1 后先限制本地访问,查询成功后再一次性替换成员权限记录。

自动履约接入(可选) ​

选择 fulfillment.webhook 时,当前仅支持创建第三方产品租户。平台将 oem.fulfillment.requested.v1 投递到已批准的 Webhook。产品完成开通后,使用接入配置中的服务端结果入口返回 succeeded、failed 或 unknown。

此入口仅供服务端使用,不向浏览器开放。产品不能修改事件中的 Organization、Application、订阅或 Edition 引用。

接收端必须:

  • 按 event_id 去重,并按 resource_version 处理乱序和缺口;
  • 使用服务端配置的结果入口和 Idempotency-Key 提交结果;
  • 成功时只返回脱敏的 product_tenant_id,失败或未知时返回稳定错误码;
  • 将超时、依赖不可用和未知状态保留为不可用,等待平台重试或人工补偿;
  • 收到成功结果并确认产品访问权限后,开放受保护功能。

SaaSdemo 提供该流程的参考接收端。请根据自己的业务实现开通逻辑,并以当前应用的 Profile、Grant 和 Webhook 订阅状态确定可用能力。

套餐与Credits参考 ​

SaaSdemo的独立定价宿主页复用平台iframe,提供个人/企业、月/年切换、价格卡、权益比较、购买确认和FAQ;订阅管理使用独立平台页的弹窗。已接入应用可以沿用此接入方式,按已授权URL范围选择产品。应用的套餐、价格、Credits、席位和条款在Developer Console「交易 → 订阅套餐」配置,批准后发布给客户。首次创建可从当前产品和服务商配置开始,无需先复制别人的套餐。Free 可显式配置;纯付费产品不强制提供免费运营。

按席位购买时,让客户确认人数、每席位金额和总价。年付 Credits 仍逐月发放。具有可用模型目录和完整商业配置的应用,可提供同一额度范围的一次性加量包。

加量包有效期和换算比例使用商品及原订单的值。余额按声明的 Credits 比例展示,使用记录保留实际 Token 和扣减依据。切换企业、应用或环境时清除旧结果;付款回跳后,查询实际到账记录。

请求与恢复规则请参见 SaaS 订阅与支付 API。

付费流程为订阅按钮 → 确认弹窗 → 顶层 Stripe → 可信付款回调 → 原订单与订阅 → 月度 Credits → 签名履约及访问权限回读。Free 显式接受后激活,不进入 Stripe。页面回跳本身不证明付款、额度或产品开通;失败和结果不明保留原订单恢复。

验收清单 ​

  • 登录和注册请求使用当前环境 Client、精确回调地址和 PKCE S256。
  • 用户 Token 只存在于 BFF 服务端 Session;用户身份没有 Client Secret。
  • 启用应用身份时,它的 Client Secret 只存在受控服务端 Secret 存储。
  • 定价宿主页与订阅管理弹窗复用平台页面,不维护第二套套餐、Checkout、账务或权益事实。
  • 平台商业入口和 Developer Console 入口来自服务端配置,不由浏览器猜测。
  • 商业跳转没有未发布的 Query 参数或任意回跳地址。
  • API、Scope、Audience、Webhook 和错误码使用当前 API 产品的接口说明。
  • 支付页面回跳、登录成功和 Webhook 投递都不直接代表 ProductAccess 生效。

继续阅读第三方 SaaS 订阅与支付、认证与授权、可复制接入模板和接入入口与权限。

参考案例中的客户空间 ​

案例 BFF 消费用户与客户空间并按当前合法客户范围请求;空间故障保留已验证登录和原操作。案例中受控的平台内部回查与组织管理 Adapter 不代表标准第三方开放接口,实际产品只使用当前已发布合同。

开发者文档。第三方接入不需要内部运营平台权限。