mcpField
F
freemius-customer-portal

freemius-customer-portal

github.com/freemius开发399 次安装14 周前更新
代码仓库

关于 freemius-customer-portal

用于在应用中接入 Freemius 购后客户门户的 Skill,面向开发者提供服务端驱动的接口与 React 组件方案,涵盖订阅、账单、取消和发票。

主要功能

  1. 01单一服务端门户接口
  2. 02查看订阅并升级许可证
  3. 03取消续订并提供留存优惠券
  4. 04更新账单信息及获取发票 PDF

适用场景

  1. 01添加自助订阅管理
  2. 02取消续订前提供优惠券
  3. 03允许客户更新账单信息
  4. 04展示付款记录并下载发票

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/freemius/freemius-ai --skill freemius-customer-portal

安装后在 Claude Code 中输入/freemius-customer-portal确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
billing.md
cancellation-and-coupon.md
headless-endpoint.md
payments-and-invoices.md
subscription-and-upgrade.md

详细介绍

Freemius 客户门户

为已登录用户提供一个页面,管理购买后的所有事项: 订阅、升级、取消(附带挽留优惠)、账单信息 和发票 — 无需在应用中重新实现任何账单逻辑。

来源:React Starter Kit Customer Portal 组件 https://freemius.com/help/documentation/saas-sdk/react-starter/components/#customer-portal-component

框架说明。 本技能中的路由示例采用 Hono 编写,但 SDK 的门户处理器适用于支持 Web 标准 Request/Response 路由的任何框架 — 请根据应用实际使用的 HTTP 框架调整这个轻量路由。React Starter Kit CustomerPortal 组件面向 React 19+、shadcn UI 和 Tailwind(通过 npx shadcn@latest add https://shadcn.freemius.com/all.json 安装);如果应用 不使用 shadcn/React,请将其用作参考实现。

沙盒。 验证期间,请确保处理器的 isSandbox 绑定到专用的 FREEMIUS_SANDBOX 环境变量(绝不要使用 NODE_ENV),使其与 结账的沙盒模式相匹配 — 沙盒是一种使用测试信用卡的结账模式, 由 freemius-core 持有。

黄金规则(不得违反)

  1. 门户采用无头设计,由服务端驱动。 SDK 提供了一个 PortalRequestProcessor:后端只需公开一个端点 (GET|POST /api/portal),SDK 就会通过签名的单次操作 URL 处理数据检索_和_所有操作。组件绝不会直接调用 Freemius。
  2. 操作使用令牌签名。 门户数据会携带预签名的操作 URL (取消、优惠券、账单更新、发票)。组件只需访问这些 URL;SDK 会在服务端验证令牌。你只需提供 已通过身份验证的用户身份。(升级是个例外 — 它会通过服务端构建的许可证升级结账流程(setLicenseUpgradeByKey)进行, 避免重复订阅 / 续订许可证时出现 500 错误。)
  3. 身份验证由你负责;其他一切都交给 SDK。 处理器的 getUser 会从你的会话中解析 Freemius 用户(例如通过电子邮件查找)。所有 Freemius 凭据都保留在服务端。
  4. 取消订阅前应先尝试挽留。 如果有续订取消优惠券, 请在完成取消前先提供该优惠。

唯一的端点

// src/server/services/freemius/portal.ts
import { freemius, IS_SANDBOX } from './client';
import { env } from '../../env';

const PORTAL_ENDPOINT = `${env.publicAppUrl}/api/portal`;

export async function processPortalRequest(
  request: Request,
  email: string
): Promise<Response> {
  const processor = freemius.customerPortal.request.createProcessor({
    portalEndpoint: PORTAL_ENDPOINT, // must match the public URL the browser hits
    isSandbox: IS_SANDBOX,
    getUser: async () => {
      const fsUser = await freemius.api.user.retrieveByEmail(email);
      return fsUser?.id ? { id: String(fsUser.id), email } : { email };
    },
  });
  return processor(request); // dispatches by ?action= and verifies tokens
}
// src/server/routes/portal.ts — route stays thin: auth, then delegate.
portalRoute.on(['GET', 'POST'], '/', requireUser, async (c) =>
  processPortalRequest(c.req.raw, c.get('user').email)
);

处理器会根据 ?action= 查询参数进行分发:

方法?action=结果
GETportal_data订阅 + 账单 + 付款 + 签名 URL
POSTbilling更新账单信息
POSTsubscription_cancellation取消续订(+ 可选反馈)
POSTsubscription_cancellation_coupon应用挽留优惠券
GETinvoice流式传输发票 PDF

参见 references/headless-endpoint.md。

组件

将 CustomerPortal 移植为可编辑的 shadcn/ui 风格 React 源码。它会获取一次 ?action=portal_data,并渲染四个部分,每个部分都由该数据中包含的签名 URL 驱动:

部分用户操作参考
订阅查看当前方案;通过服务器构建的许可证升级结账(setLicenseUpgradeByKey)进行升级——这是稳健的方案(可避免重复订阅 / 续订许可证时报 500 错误)references/subscription-and-upgrade.md
取消取消续订——免责声明 → 留存优惠券 → 反馈references/cancellation-and-coupon.md
账单编辑公司名称 / 税号 / 地址references/billing.md
付款与发票查看付款历史,下载发票 PDFreferences/payments-and-invoices.md

快速开始(前端)

import { CustomerPortal } from './react-starter/components/customer-portal';
import { PORTAL_ENDPOINT, portalFetch } from './api';

// portalFetch attaches the auth header; the component does the rest.
<CustomerPortal endpoint={PORTAL_ENDPOINT} fetcher={portalFetch} />;
// Loading portal data:
const res = await fetcher(`${endpoint}?action=portal_data`);
const data = (await res.json()) as PortalData;
// data.subscriptions.{primary,active,past}, data.billing, data.payments,
// data.cancellationCoupons — each carries signed action URLs.

基本概念

Frontend <CustomerPortal>               Backend (one endpoint)                Freemius
  GET  /api/portal?action=portal_data ─▶ processPortalRequest()
  { subscriptions, billing, payments, ◀─ freemius.customerPortal.request
    + signed action URLs }                .createProcessor() → getUser()
  POST <signed cancelRenewalUrl> ───────▶ processor verifies token ──────────▶ cancel renewal
  POST <signed couponUrl> ──────────────▶ processor verifies token ──────────▶ apply coupon
  POST <signed billing updateUrl> ──────▶ processor verifies token ──────────▶ update billing
  GET  <signed invoiceUrl> ─────────────▶ processor verifies token ──────────▶ invoice PDF
  GET  /api/checkout/upgrade ───────────▶ license.retrieve + checkout.create()
  { link } ◀──────────────────────────── .setLicenseUpgradeByKey(secret_key) ─▶ plan-change
                                          .getLink() — browser navigates ─────▶ checkout

⚠️ 升级路径是上文介绍的服务器构建的许可证升级结账。并且不要为此使用 checkout.create({ licenseId }):对于正在续订的许可证,此选项会返回 500 错误(SDK 在内部调用了一个付款方式更新端点,该端点会将 expiration: null 的许可证判定为“永久许可证”并拒绝请求)。在服务端获取许可证,并改用 setLicenseUpgradeByKey(license.secret_key)。请参阅 references/subscription-and-upgrade.md。

Dashboard 配置(交由相关人员处理)

  • Product → Coupons → 创建续订取消(留存)优惠券,以便填充 cancellationCoupons,让向导能够提供该优惠券。
  • 负责保持本地权益同步的 webhook 和重定向配置由 freemius-core 管理;升级 / 取消操作会以 webhook 事件的形式到达那里 (license.plan.changed、license.cancelled,……)。

操作后同步(更改如何传递到应用)

Portal 操作会先在 Freemius 中更改状态;本地 user_fs_entitlement 镜像会通过以下两种方式同步:

  • 立即在 Portal UI 中同步——任何操作后重新获取 ?action=portal_data (它会读取 Freemius 实时数据)。
  • 同步到权益镜像中——通过 freemius-core 的 webhooks (升级时为 license.plan.changed,取消时为 license.cancelled / 到期事件)。⚠️ 在 Dashboard 中设置或更改 webhook URL 后,请等待约 5 分钟让其生效——URL 激活前触发的事件永远不会送达(不会补发)。请参阅 freemius-core → references/webhooks.md(有关事件遗漏的情况,请参阅 freemius-troubleshooting)。

评论

支持 Markdown