SaaS 订阅与支付 API
SaaS 订阅与支付 API 提供套餐查询、付款创建和订阅管理等接口。使用平台现成页面的接入方式,请参见第三方 SaaS 订阅与支付。
调用前,请确认当前应用已开通对应 API 产品,并使用指定环境和版本。Production 尚未开放,产品登记不会自动开通正式环境。
1. 准备调用权限
在开发者控制台中为当前应用配置所需的 API 权限,然后使用客户用户的身份调用接口。客户还需要有效的企业成员关系、商业资料和付款资格。
每次请求携带以下请求头:
Authorization: Bearer ${GOODVISION_USER_ACCESS_TOKEN}
X-GV-Organization-ID: ${GOODVISION_CUSTOMER_ORGANIZATION_ID}
X-GV-Application-ID: ${GOODVISION_APPLICATION_ID}| Endpoint | 最小 Scope |
|---|---|
GET /open-platform/v1/products | commerce.catalog.read |
POST /open-platform/v1/checkout-sessions | commerce.checkout.create |
POST /open-platform/v1/free-activations | commerce.checkout.create |
GET /open-platform/v1/operations/{id} | commerce.checkout.read |
GET /open-platform/v1/subscriptions | commerce.subscription.read |
GET /open-platform/v1/subscriptions/{id} | commerce.subscription.read |
POST /open-platform/v1/subscriptions/{id}:cancel | commerce.subscription.manage |
POST /open-platform/v1/subscriptions/{id}:change-plan | commerce.subscription.manage |
POST /open-platform/v1/subscriptions/{id}:change-pricing | commerce.subscription.manage |
GET /open-platform/v1/customer-quotes/{id} | commerce.catalog.read |
GET /open-platform/v1/customer-quotes | commerce.catalog.read |
POST /open-platform/v1/customer-quotes/{id}/accept | commerce.checkout.create |
POST /open-platform/v1/customer-quotes/{id}/decline | commerce.checkout.create |
2. 查询套餐并创建付款
匿名读取公开套餐
GET /open-platform/v1/public/pricing无需登录或申请调用权限。必须同时提供application、environment和product查询参数,返回该范围正式公开发布的方案、价格、条款、Credits、席位、FAQ及purchase_availability。客户专属方案不返回;首月优惠eligibility为unverified,没有selection_revision。购买状态为not_enabled或suspended时可看价格但不可下单。参数非法返回422、没有公开目录返回404、来源无法核验返回503,不使用其他应用或旧价格兜底。这个只读接口不接受客户组织,不能替代登录、条款确认或创建Checkout。
environment=sandbox 展示显式公开的当前测试套餐,不要求 Production 上线审批;暂停、撤回和客户专属可见范围继续生效。Production 始终读取已批准并显式发布的版本。登录后的测试购买使用 GET /open-platform/v1/application-listings?application_id=<application-id>&environment=sandbox 核验单一测试应用;不带参数的应用市场列表仅包含正式发布应用。
查询机会关联的报价
卖方可使用 GET /open-platform/v1/appmarket/quotes?opportunity_id=<机会ID>&limit=20 查询某个机会的报价,需要 operations.opportunity.read 权限。
- 查询包含该机会的全部报价状态,可同时使用
search和status筛选。 - 读取下一页时保留原筛选,并原样传回
next_cursor。更换机会或筛选后,从第一页重新查询。 - 返回
404表示当前范围内没有该机会。展示报价前,再次 GET 报价详情并核对来源和当前状态。
平台机会详情中的报价区块用于查看。修改报价请使用正式报价页面或已授权的报价 API。客户接受报价后,仍需另行创建订单和付款。
查看客户收到的报价
客户可在客户工作区的「应用订阅与账单 → 收到的报价」查看和处理报价,使用当前登录身份及所选应用。
自建客户页面使用 GET /open-platform/v1/customer-quotes?limit=10 查询当前客户企业与应用的报价,每页最多 10 条。下一页原样传回 next_cursor。查询失败时显示错误并提供重试;切换企业或应用时,清除旧游标和未提交的确认内容。
使用 GET /open-platform/v1/customer-quotes/{id} 获取已发送报价。客户需要与当前应用存在有效的商业关系;尚未发送的新修订无法读取。
接受或拒绝报价时,提交当前报价的 resource_version、revision、原样的 terms_sha256 和原因,并携带原 Idempotency-Key。请让客户本人确认所收到的版本,卖方不能代为决定。
提交接受时,平台会重新检查报价来源和有效期。方案停售或条款变化会返回 409;请重新读取并让客户确认。已发送且未到期的报价,即使方案停售,客户仍可拒绝。报价撤回、修订、到期或已有决定后,不能再提交新决定。
结果不明时恢复原请求,再 GET 当前报价。接受或拒绝只保存客户决定,订单、付款、订阅和产品开通需分别办理。
curl --request GET \
--url "${GOODVISION_API_BASE}/open-platform/v1/products" \
--header "Authorization: Bearer ${GOODVISION_USER_ACCESS_TOKEN}" \
--header "X-GV-Organization-ID: ${GOODVISION_CUSTOMER_ORGANIZATION_ID}" \
--header "X-GV-Application-ID: ${GOODVISION_APPLICATION_ID}"响应包含当前 product 和 plans。将返回的套餐、价格和条款展示给客户确认,成交金额由平台计算。
创建付款时,使用固定的 Idempotency-Key,并提交所选目录的版本和客户确认内容:
{
"plan_code": "<catalog plan code>",
"plan_version": 3,
"billing_interval": "month",
"sales_offer_ref": "<catalog offer reference>",
"sales_offer_version": 2,
"selection_revision": "<catalog selection revision>",
"accepted_terms_sha256": "<accepted terms SHA256>"
}请求参数要求如下:
| 参数 | 必填性 | 说明 |
|---|---|---|
Idempotency-Key | 🔴 必填 | Header,恢复原请求时保持不变 |
plan_code、plan_version | 🔴 必填 | 使用所选目录返回的值 |
billing_interval | 🔴 必填 | month 或 year |
selection_revision、accepted_terms_sha256 | 🔴 条件必填 | 所选目录返回时必填;原样使用目录版本和客户确认的条款摘要 |
sales_offer_ref、sales_offer_version | 🔴 条件必填 | 成组使用目录返回值;仅当前接口配置允许时可同时省略 |
seat_quantity | 🔴 条件必填 | seat_pricing 套餐必填且在席位上下限内;固定价方案不传 |
return_reference | 可选 | 已配置的回跳引用;不指定应用回跳时可省略 |
请求不接受任意回跳 URL、金额、币种或支付供应商。
应用内付款可使用return_reference: "application-entry",成功与取消均返回当前应用已审核发布的访问地址。请先在应用发布信息中配置正确的 HTTPS 地址;地址和应用环境必须匹配。平台在原购买中保存地址,后续修改发布信息不会改变原付款返回地址。普通平台工作区可继续使用其已配置的回跳引用。
返回参数payment、payment_order、organization、application、environment、product及订阅的payment_subscription只用于选择当前已授权原操作。payment=success不表示付款或开通已确认。先回读原操作;事件尚未到达时可有时限地自动查询,失败或超时保留手动恢复,不再次购买。
目录包含 seat_pricing 时,价格、基础月度 Credits 和活动月度 Credits 均按每席位计。购买时显式提交 seat_quantity,且在 minimum_seats 与 maximum_seats 范围内。确认页可展示预估总价,订单总价以平台核验结果为准。固定价方案不提交席位乘数。
结果不明时,使用原正文、人数和请求键恢复。恢复原订单时保持原人数。
配置套餐请打开当前应用的「交易 → 订阅套餐」,选择已准备的产品和服务商创建草稿,也可基于已有方案创建。草稿支持月/年价、Credits、席位范围或固定总容量、条款、推荐和 FAQ。新价格和方案分别通过审核,并更新应用发布信息后,客户才可购买。保存草稿不会改变历史订单。
收到 202 后,按以下步骤继续:
- 保存
operation_id、resource_version和短期有效的checkout_url。 - 跳转到
checkout_url完成付款。 - 使用
GET /open-platform/v1/operations/{id}查询原操作的status、next_step和当前版本。
202 仅表示已受理。客户回跳后,请查询付款和订阅状态,再展示完成结果。
3. 查询、取消与变更订阅
激活免费套餐
零价月套餐使用 POST /open-platform/v1/free-activations。提交与新套餐选择相同的正文,省略 return_reference,保留目录版本、选择指纹和已接受条款。
成功返回 201,响应包含 order_id、subscription_id、status 和 resource_version。免费激活无需支付方式,也不返回付款会话。重复请求使用原正文和原键。零价年套餐暂不支持;零金额请求不能提交到付费 Checkout。
免费订阅返回 billing_mode: free。月度 Credits 按原接受周期发放,未使用部分在周期结束时到期。选择周期末取消后,可继续使用到返回的截止时间。
升级免费订阅
先读取当前免费订阅,再在新付费 Checkout 正文中同时提交 upgrade_from_subscription_id 和 upgrade_from_subscription_version。来源发生变化会返回 409,此时重新读取并让客户确认;结果不明时,保持原来源、版本、正文和请求键恢复。
付款成功前,原免费套餐继续有效,新付费订阅为 pending_payment。恢复付款时选择新付费订阅及原操作。取消付款会保留免费服务;平台确认付款成功后,启用付费套餐并停止原免费套餐的剩余 Credits。
分别查询订阅、付款、额度和产品开通状态。回跳只表示客户返回页面。
查询订阅
订阅列表返回 items,详情返回当前客户与应用范围内的订阅信息。保存订阅 ID 和 resource_version。商业订阅 ID 与 API 调用授权 ID 分别使用。
取消或变更套餐
发送原操作的 Idempotency-Key,并使用刚查询到的版本设置 If-Match: "<resource_version>"。
- 取消:提交
{"cancel_mode":"end_of_period","reason":"客户停止续费"}。使用immediate则立即取消。 - 变更:提交
{"new_plan_code":"<catalog plan code>","new_plan_version":3}。可选的effective_at和reason按当前接口要求填写。
价格由平台确定。取消订阅后,退款需另行办理。
两项操作返回 202 时,表示请求已受理。重新查询原订阅,并使用已订阅的生命周期事件更新状态。到期时间、生效时间和使用权限以当前响应及 SaaS 账务契约为准。
4. 错误与恢复
| 状态码或情况 | 处理方式 |
|---|---|
401/403 | 检查当前用户、企业、应用、Scope 和客户操作资格 |
409 | 重新读取版本、目录和条款,让客户确认变化后的条件 |
400/422 | 按接口说明修正请求参数 |
503 或响应丢失 | 先查询原操作或订阅,再用原正文和原请求键恢复 |
付款结果不明时,保留原操作和原键,避免创建第二笔付款。恢复请求保持原确认条款。
产品、付款、商业订阅和使用权益是独立状态。更多示例见可复制接入模板与幂等与重试。
5. 配置订阅套餐和 Credits
开启免费激活
free_activation_enabled 控制当前应用、产品和环境下的新免费激活,首次默认关闭。
| 配置 | 行为 |
|---|---|
| 复制方案时省略字段 | 继承原配置 |
显式 false | 关闭新免费激活 |
null | 请求被拒绝 |
仅零价月方案可以开启,经审核和发布后进入客户可售目录。关闭免费激活后,已有订阅、原订单恢复和成交额度继续保留。
欢迎、每日活跃和活动赠额分别配置,数量为 0 时关闭对应赠额。付费项目也可配置这些赠额。
在开发者控制台的当前应用中打开「交易 → 订阅套餐」,选择已有商品来源后新建草稿。可设置独立USD月价/年价、套餐分组、推荐项、总成员容量、纯文本成交条款及FAQ顺序。独立价格和套餐需要审核及发布后才可销售,来源方案和历史订单不会被覆盖。首次商品来源需先完成商业接入准备。
配置 Credits
Credits 规则指定每月额度、活动期和每 Credit 对应的加权消费单位。年付套餐也按月发放额度,各月额度在对应周期结束时到期。加量包使用其购买时确定的金额、模型倍率和有效期。
订阅与加量包的范围和换算比例一致时,可共同显示 Credits 余额,同时保留原始 Token 用量和加权扣减。历史额度未声明换算比例,或混用比例时,继续显示原加权单位;浏览器不能自行改名或合并不同产品的余额。
配置首月优惠
首月优惠默认关闭。在套餐草稿中设置 introductory_pricing: {"first_month_amount_scaled":700},可将首月价格设为 7 美元。正常月费由独立价格定义,例如 10 美元。
仅固定价、付费、USD 月套餐支持首月优惠。首价必须大于 0 且低于正常价;年付、免费和按席位套餐暂不支持。复制时省略字段会继承优惠,显式 null 则关闭新草稿优惠。已有订单保留购买时的条件。
客户目录的 first_month_offer 返回优惠金额和当前 eligibility。按以下状态展示:
| 状态 | 展示与处理 |
|---|---|
eligible | 展示首月优惠价 |
not_eligible、unverified | 展示正常价 |
pending | 展示正常价,并提示用户在原工作区恢复待完成的优惠付款 |
当前应用、实际产品和环境内,每位核验用户仅可使用一次优惠。既往试用或付款会排除资格;取消、重订和退款不会恢复资格。
购买确认页分别展示首月金额和第二个月起的正常月费,并回传原 selection_revision。金额与优惠资格由平台核验。资格变化返回 409 时,重新读取目录并确认。
付款失败或结果不明时,保留原订单和请求键。仅平台确认付款已终态取消后,才释放未完成的优惠。原订单中的 recurring_amount_scaled 是购买时确定的第二个月起月费,请直接展示该值。
套餐配置还需满足以下条件:
- 免费档仅支持月周期,月价可为 0。总成员容量与按席位定价不能同时配置。
- 欢迎和每日活跃赠额需要已接入相应的资格校验与发放能力,配置策略字段不会直接发放额度。
- 全额退款可停止订阅剩余额度。自动续费取消、完整退款和使用权限政策,以当前接入配置及说明为准。
提交结果不明时使用原请求恢复,核对价格、容量、条款和FAQ回执后再更新界面;切换应用或环境清除未提交内容。客户目录、购买及取消继续使用前述用户权限和当前允许动作,开发者管理关系不等于客户付款资格。
个人版、企业版与 SaaSdemo 演示
控制台提供五个可编辑参考模板:
| 模板 | 收费 | 每月基础 Credits | 成员 |
|---|---|---|---|
| Free | $0/月 | 100 | 1 |
| Pro 月付 | 首月 $7,之后 $10/月 | 1000 | 1 |
| Pro 年付 | $96/年,月均 $8 | 1000 | 1 |
| Team 月付 | $40/席位/月 | 1000/席位 | 可选2–100席;限时赠额另行配置 |
| Team 年付 | $480/席位/年 | 1000/席位 | 可选2–100席;限时赠额另行配置 |
模板会填入新草稿,保存、审核和发布需分别完成。可删除免费档,或调整名称、价格、席位和额度。仅 SaaSdemo 应用的首次新建表单默认选中 Free;其他应用默认关闭免费运营。
保存前,请填写实际可用的模型及倍率。缺少模型引用时需补齐,示例模型不能替代真实授权。欢迎、日活和活动赠额默认关闭,须按已接入的发放能力分别配置。年付按月发放 Credits;企业额度与付款金额按购买席位数计算。
准备演示目录时:
- 在商业来源旁选择「准备套餐组」。SaaSdemo 默认包含 Free,其他应用默认包含四档付费套餐;可调整「套餐组包含 Free 演示」。
- 逐档检查价格、额度、成员容量、条款和 FAQ,并按需要修改。
- 点击「保存当前套餐并继续」。已核对的模型及倍率会带入下一档,各档赠额策略分别配置。
每档使用独立请求和幂等键。结果不明或返回内容与请求不一致时,当前档会锁定;先通过「重试原请求」恢复,确认后再提交下一档。
「跳过当前档」不创建资源;「稍后继续配置」保留已保存套餐,未保存表单不会持久化。重新准备时,仅复用当前目录中编号、产品和服务商完全匹配的方案。重名或商业来源不一致时,请先核对;自定义编号的方案在目录中单独处理。
完成后按「审批与发布清单」分别审批价格及方案并发布,再在「应用发布信息」选择当前产品与环境下已发布的方案及版本。客户页面只读取已发布目录。成套准备不会自动审批、发布或开通客户。切换模板会清除不适用的首月、固定容量或按席位策略;已有客户订单和订阅保持原成交规则。
先发布套餐,稍后配置访问地址
在「应用发布」勾选当前应用与环境内已批准并发布的套餐。若暂未准备产品访问地址,可明确清空地址并保存;至少选择一个有效套餐。保存后公开价目可读,不代表订阅支付或产品已经开通。非空地址仍须满足 HTTPS 发布校验。后续补正确产品地址,再核验实际产品进入;没有地址的发布不能用作导流或自行收费资源。订阅购买未启用或暂停时仍显示价格,并说明当前不可购买。
独立定价页与 URL 参数
从已签发接入配置的platform.consoleOrigin构造独立页面:
const url = new URL('/console/pricing', configuration.platform.consoleOrigin);
url.searchParams.set('application', configuration.application.slug);
url.searchParams.set('environment', configuration.application.environment);
url.searchParams.set('locale', 'zh-CN');
// 匿名定价页必须明确已配置的产品代码。
url.searchParams.set('product', configuredProductCode);application是应用slug;environment为sandbox或production;匿名查看时product为必需产品代码;locale支持zh-CN、en-US。SaaSdemo自身的/pricing展示同一托管内容,参数必须匹配该实例的应用绑定。独立页无工作区侧栏,保留个人/企业、月/年切换、套餐比较、FAQ和订阅管理入口;年付选择保留Free月度方案。
独立定价页无需登录,按这三个范围展示正式公开发布的套餐,客户专属报价不会公开。查看价格不会创建企业、订单或订阅。点击购买或「我的订阅」后才进入登录,保留应用、环境、产品及已选套餐;购买确认重新核验当前资格和完整条款,不自动提交。首月优惠在匿名页面说明适用条件,登录后核验资格。参数不是认证凭据,无权或错误参数不能回退到另一应用。iframe延续embed=1&view=subscription&auth=parent-bff协议,主题与语言跟随宿主,内容高度自动调整。客户付款金额、优惠资格和最终权益仍由当前目录及购买确认决定,不从URL接受金额或权限。
这一入口适用于所有已完成商业接入并发布套餐的开放平台应用,平台不按应用名称限制。SaaSdemo的定价使用iframe,页脚提供「单独打开定价页」;两者保留同一application/environment/product。定价页不混入当前订阅和支付方式;登录后的「我的订阅」通过弹窗管理,完整管理页为/console/subscription,同样保留选择范围。免费运营由每个应用显式配置并发布,没有免费方案的应用不显示Free。
同一SaaSdemo构建可用于不同应用实例;实例的应用和环境来自已签发配置,不能由访客参数改写。订阅、账单和定价之间跳转继续保留所选产品。错误或重复选择参数会停止加载,订阅管理入口也不可用。独立平台定价页可查看其他应用正式发布的公开套餐;购买时核验当前用户授权。
嵌入时复用独立URL,添加展示和宿主认证协议参数,继续使用当前应用的同源桥接:
const embedded = new URL(url);
embedded.searchParams.set('embed', '1');
embedded.searchParams.set('view', 'subscription');
embedded.searchParams.set('auth', 'parent-bff');
iframe.src = embedded.toString();
standaloneLink.href = url.toString();
standaloneLink.target = '_blank';
standaloneLink.rel = 'noreferrer';auth=parent-bff要求接入方完成宿主认证桥接,继续使用当前应用登录会话和允许的订阅操作;仅设置iframe的src不会建立登录或商业关系。宿主必须校验消息的精确来源、当前iframe窗口和字段,并在服务端核验会话、CSRF及当前权限。凭据不能进入URL或跨窗口消息。新客户尚未准备付款主体和商业资料时,需要先完成商业准备;已有其他客户的成功订阅不能替代该关系。
配置一次性Credits加量包
在当前Application打开「交易 → Credits 加量包」,选择可用模型产品,准备独立USD价格与加量包草稿。参考配置为500 Credits、15美元、1个自然月、不退款,可按产品运营策略调整。可设置Credits换算、模型及倍率、天数或自然月、适用订阅代码、条款和退款;独立购买无需订阅条件。免费订阅资格必须另行显式允许,不会自动开启免费套餐运营。
价格和加量包会一起保存为草稿。先审核价格,再单独审核并发布加量包,客户才可购买。草稿不会创建客户订单或发放额度,历史订单保留购买时的版本。
结果不明,或返回的价格、期限、资格、退款政策与请求不一致时,表单保留并锁定原内容和键。请恢复同一请求。切换企业、应用、环境或权限后,清除临时状态。
客户购买资格、订单恢复和到期规则请参见 Token API 指南。查询付款状态后,还需单独核对额度是否到账。