跳到正文

第三方接入代码示例 ​

本文提供获取 Access Token、调用 API、接入 PKCE 登录和接收 Webhook 的代码示例。接入前,请在开发者后台创建应用并开通所需 API 权限。

示例使用占位值。请从当前应用复制 Issuer、Client ID、API 地址、Audience 和 Scope,并填入服务端配置。

一、选择调用身份 ​

场景调用身份认证方式Client Secret
后台任务、定时同步、服务间调用应用身份权限Client Credentials仅服务端保存 Client Secret
用户登录后代表用户调用用户身份权限Authorization Code + PKCE(S256)无 Client Secret

如果两种场景都需要,就在同一个 Application 中分别启用、分别申请权限。两种身份的 Client、Scope 和 Token 不能混用。

二、从控制台复制配置 ​

控制台页面配置项用途
OAuth/OIDC 配置Issuer、Client ID、回调地址登录和获取 Token
API 与权限API Base、Audience、已批准 Scope、接口路径服务端调用 API
事件与 WebhookWebhook 地址、事件类型、密钥交付状态接收订阅和支付事件

只复制当前环境、当前身份已经批准的内容。用户身份不需要 Client Secret;应用身份的 Secret 只能放在服务端的 Secret Manager 中。

三、服务端配置示例 ​

dotenv
GOODVISION_IDENTITY_ISSUER=<从 OAuth/OIDC 配置复制>
GOODVISION_TOKEN_ENDPOINT=<Discovery token_endpoint>
GOODVISION_AUTHORIZATION_ENDPOINT=<Discovery authorization_endpoint>
GOODVISION_APPLICATION_ID=<当前 Application ID>
GOODVISION_API_BASE=<从 API 与权限复制>
GOODVISION_AUDIENCE=<从 API 与权限复制>
GOODVISION_SCOPES=<已批准的最小 Scope,用空格分隔>

# 只有应用身份需要下面两项;不要放进浏览器或前端代码。
GOODVISION_SERVICE_CLIENT_ID=<应用身份 Client ID>
GOODVISION_SERVICE_CLIENT_SECRET=<应用身份 Client Secret>

使用用户身份时,改为配置 GOODVISION_USER_CLIENT_ID、已经登记的 GOODVISION_REDIRECT_URI 和 GOODVISION_POST_LOGOUT_URI,不填写 Client Secret。

用精确 GOODVISION_IDENTITY_ISSUER 加 /.well-known/openid-configuration 读取 Discovery,核对 issuer,使用返回的 token_endpoint、authorization_endpoint 及按需使用的 end_session_endpoint。上面的 Endpoint 变量保存这些返回地址,不猜固定路径。

推荐事件接收可直接运行Node.js 22 长连接示例;HTTPS 接收与同步回调分别配置。

四、应用身份:获取 Token ​

这一步只能在第三方后端执行。浏览器不能执行 Client Credentials,也不能接收 Client Secret。

bash
curl --request POST \
  --url "${GOODVISION_TOKEN_ENDPOINT}" \
  --header 'content-type: application/x-www-form-urlencoded' \
  --data 'grant_type=client_credentials' \
  --data-urlencode "client_id=${GOODVISION_SERVICE_CLIENT_ID}" \
  --data-urlencode "client_secret=${GOODVISION_SERVICE_CLIENT_SECRET}"

服务端使用返回的短期 Token 调用 API;同时检查签发方、目标 API、有效期和 Scope。Token 过期后重新获取,不要自行延长有效期。

五、调用 API ​

从当前应用已开通的 API 产品获取 GOODVISION_API_BASE 和接口路径。下面以 SaaSdemo Sandbox 商业接口为例:

bash
curl --request GET \
  --url "${GOODVISION_API_BASE}/open-platform/v1/products" \
  --header "authorization: Bearer ${GOODVISION_ACCESS_TOKEN}" \
  --header "X-GV-Application-ID: ${GOODVISION_APPLICATION_ID}"
接口需要的最小 Scope
GET /open-platform/v1/productscommerce.catalog.read
POST /open-platform/v1/checkout-sessionscommerce.checkout.create
GET /open-platform/v1/operations/{id}commerce.checkout.read
GET /open-platform/v1/subscriptions、GET /open-platform/v1/subscriptions/{id}commerce.subscription.read
POST /open-platform/v1/subscriptions/{id}:cancel、:change-plancommerce.subscription.manage

只有申请并获批对应 API Product、Audience 和 Scope 后,接口才可以调用。所有写请求都要带唯一的 Idempotency-Key;修改已有资源时还要带当前版本的 If-Match。

金额、币种、支付渠道、组织 ID 和回跳地址不能由第三方前端或请求参数覆盖。订阅、支付、账单和权益页面仍由 GoodVision 客户工作区负责。

六、用户登录:PKCE ​

网页登录使用用户身份。服务端生成一次性的 code_verifier 和 state,然后将用户重定向到以下授权地址:

text
${GOODVISION_AUTHORIZATION_ENDPOINT}
  ?client_id=${GOODVISION_USER_CLIENT_ID}
  &response_type=code
  &scope=openid%20profile%20email
  &redirect_uri=${GOODVISION_REDIRECT_URI}
  &code_challenge=${GOODVISION_CODE_CHALLENGE}
  &code_challenge_method=S256
  &state=${GOODVISION_STATE}

回调后由服务端交换授权码,检查 state、PKCE、签发方、目标 API 和有效期,再把登录状态放进 HttpOnly Session。浏览器只拿到 Session Cookie;用户身份没有 Client Secret。

七、接收 Webhook ​

Webhook Secret 按控制台显示的安全方式取得,不会出现在普通配置导出中。接收方至少要做到:

  1. 校验时间窗、原子登记 Nonce,再使用 HTTP 原始 Body 计算 HMAC-SHA256,采用常量时间比较。
  2. 明文或解密后都要校验并移除 Verification Token,再核对事件 Schema 和接入范围。
  3. 按 event_id 幂等提交持久化 Inbox 后返回 2xx;存储失败返回 503,同一事件不能重复执行。
  4. 按资源版本处理乱序,发现缺口后使用授权状态接口回查;平台按响应结果决定投递重试。

完整 Header、Body、204 响应、加密流程和可运行签名脚本见接收 Webhook;取消、套餐变更和到期的完整 JSON 见订阅生命周期。

八、Node.js、Go、Python 示例 ​

本节使用标准库演示不依赖 SDK 的直接 HTTP 调用。TypeScript/Node.js 项目也可以安装官方 @goodvision/sdk;Go 和 Python 当前使用本页的直接 HTTP 示例。生产代码还需要补充错误处理、超时和重试。

Node.js (内置 fetch) ​

js
const tokenResponse = await fetch(
  process.env.GOODVISION_TOKEN_ENDPOINT,
  {
    method: "POST",
    headers: { "content-type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "client_credentials",
      client_id: process.env.GOODVISION_SERVICE_CLIENT_ID,
      client_secret: process.env.GOODVISION_SERVICE_CLIENT_SECRET,
    }),
  },
);
const { access_token: accessToken } = await tokenResponse.json();
const response = await fetch(`${process.env.GOODVISION_API_BASE}/open-platform/v1/products`, {
  headers: {
    authorization: `Bearer ${accessToken}`,
    "X-GV-Application-ID": process.env.GOODVISION_APPLICATION_ID,
  },
});

Go(标准库) ​

go
form := url.Values{
    "grant_type": {"client_credentials"},
    "client_id": {os.Getenv("GOODVISION_SERVICE_CLIENT_ID")},
    "client_secret": {os.Getenv("GOODVISION_SERVICE_CLIENT_SECRET")},
}
req, _ := http.NewRequest("POST", os.Getenv("GOODVISION_TOKEN_ENDPOINT"), strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
// 解析 access_token 后,再调用当前 API Product 的接口。

Python(标准库) ​

python
import os
import urllib.parse
import urllib.request

body = urllib.parse.urlencode({
    "grant_type": "client_credentials",
    "client_id": os.environ["GOODVISION_SERVICE_CLIENT_ID"],
    "client_secret": os.environ["GOODVISION_SERVICE_CLIENT_SECRET"],
}).encode()
token_request = urllib.request.Request(
    os.environ["GOODVISION_TOKEN_ENDPOINT"],
    data=body,
    headers={"content-type": "application/x-www-form-urlencoded"},
)
# 只在服务端读取 access_token,再调用当前 API Product 的接口。

SDK 支持说明 ​

官方 TypeScript/Node.js SDK 可通过 npm 安装并用于应用代码。Go、Python SDK 尚未提供;这些项目可继续使用本页的直接 HTTP 示例。无论使用 SDK 还是直接 HTTP,都不要把 Token、Secret、Cookie 或请求正文写入日志。

注意事项 ​

  • Token、Client Secret、Webhook Secret 只在服务端保存,不能写入前端、URL、日志或工单。
  • 一个请求只能使用一种身份;用户身份和应用身份的 Client、Scope、Token 不能互换。
  • 套餐、订阅、支付、账单和使用权限通过平台页面或已开通接口查询,SaaSdemo 不维护另一套结果。
  • TypeScript/Node.js 项目可使用官方 SDK;其他语言使用 Developer Console 的配置、OpenAPI/事件契约和本页模板。

继续阅读:认证与授权、API 产品、SaaSdemo 接入案例。

登录后的用户与空间 ​

完成身份结果核验后,服务端按用户与客户空间发送首次 {} 请求。平台负责默认空间和用户关联;示例中的业务范围不能固定为开发者企业,其他 API、事件和商业能力按需配置。

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