mcpField
W
whop

whop

github.com/raintree-technology营销与增长42 次安装
代码仓库

关于 whop

whop 是面向 Whop 平台开发者的技能指南,涵盖数字产品、会员、课程与社区访问的销售和管理。它以 API v5 示例说明结账、会员权限、支付、Webhook 与 OAuth 集成。

主要功能

  1. 01创建产品、计划与结账配置
  2. 02查询和管理会员资格
  3. 03检查访问权限与许可证
  4. 04查询支付并处理退款
  5. 05管理课程、章节与课时
  6. 06配置 Webhook 与 OAuth 应用

适用场景

  1. 01搭建会员订阅产品
  2. 02接入 Whop 结账与支付
  3. 03按会员状态限制内容访问
  4. 04处理支付事件并管理课程

安装

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

$npx skills add https://github.com/raintree-technology/claude-starter --skill whop

安装后在 Claude Code 中输入/whop确认已加载。

详细介绍

Whop 平台专家

Whop 是一个用于销售数字产品、会员服务、课程和社群访问权限的平台。

适用场景

  • 构建会员制/订阅制产品
  • 集成 Whop 结账和支付
  • 管理课程和教育内容
  • 根据会员状态控制内容访问权限
  • 处理支付事件的 Webhook
  • 使用 OAuth 构建 Whop 应用

快速入门

身份验证
const headers = {
  Authorization: `Bearer ${process.env.WHOP_API_KEY}`,
  "Content-Type": "application/json",
};

const response = await fetch("https://api.whop.com/api/v5/me", { headers });

API 密钥类型:

  • 公司 API 密钥 - 访问你自己的公司数据(Dashboard → Settings → API Keys)
  • 应用 API 密钥 - 用于多租户应用(Dashboard → Developer → Your App)

核心操作

创建产品
const product = await fetch("https://api.whop.com/api/v5/products", {
  method: "POST",
  headers,
  body: JSON.stringify({
    title: "Premium Membership",
    description: "Access to all content",
    visibility: "visible",
  }),
});
创建方案
const plan = await fetch("https://api.whop.com/api/v5/plans", {
  method: "POST",
  headers,
  body: JSON.stringify({
    product_id: "prod_xxx",
    billing_period: 1,
    billing_period_unit: "month",
    price: 2999, // $29.99 in cents
    currency: "usd",
  }),
});
创建结账
const checkout = await fetch(
  "https://api.whop.com/api/v5/checkout-configurations",
  {
    method: "POST",
    headers,
    body: JSON.stringify({
      plan_id: "plan_xxx",
      success_url: "https://yourapp.com/success",
      cancel_url: "https://yourapp.com/cancel",
      metadata: { user_id: "123" },
    }),
  }
);

const { url } = await checkout.json();
// Redirect user to url

会员管理

列出会员
const response = await fetch(
  "https://api.whop.com/api/v5/memberships?valid=true",
  { headers }
);

const { data: memberships } = await response.json();
检查用户访问权限
async function checkAccess(userId: string, productId: string): Promise<boolean> {
  const response = await fetch(
    `https://api.whop.com/api/v5/memberships?user_id=${userId}&product_id=${productId}&valid=true`,
    { headers }
  );

  const { data } = await response.json();
  return data.length > 0;
}
验证许可证密钥
async function validateLicense(licenseKey: string): Promise<boolean> {
  const response = await fetch(
    `https://api.whop.com/api/v5/memberships?license_key=${licenseKey}`,
    { headers }
  );

  const { data } = await response.json();
  return data.length > 0 && data[0].valid;
}
取消会员资格
await fetch(
  `https://api.whop.com/api/v5/memberships/${membershipId}/cancel`,
  {
    method: "POST",
    headers,
  }
);

支付

获取支付信息
const payment = await fetch(
  `https://api.whop.com/api/v5/payments/${paymentId}`,
  { headers }
);
退款
await fetch(`https://api.whop.com/api/v5/payments/${paymentId}/refund`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    amount: 1000, // Optional: partial refund in cents
  }),
});

课程

创建课程
const course = await fetch("https://api.whop.com/api/v5/courses", {
  method: "POST",
  headers,
  body: JSON.stringify({
    product_id: "prod_xxx",
    title: "Complete Web Development",
    description: "Learn fullstack from scratch",
    visibility: "visible",
  }),
});
创建章节
const chapter = await fetch("https://api.whop.com/api/v5/course-chapters", {
  method: "POST",
  headers,
  body: JSON.stringify({
    course_id: "course_xxx",
    title: "Introduction to JavaScript",
    order: 1,
  }),
});
创建课程
const lesson = await fetch("https://api.whop.com/api/v5/course-lessons", {
  method: "POST",
  headers,
  body: JSON.stringify({
    chapter_id: "chapter_xxx",
    title: "Variables and Data Types",
    content: "Lesson content in markdown...",
    type: "video", // or 'text', 'quiz', 'assignment'
    video_url: "https://youtube.com/watch?v=...",
    order: 1,
  }),
});
标记课程已完成
await fetch(
  `https://api.whop.com/api/v5/course-lessons/${lessonId}/mark-as-completed`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${userAccessToken}`,
    },
  }
);

Webhook

设置端点
import crypto from "crypto";

export async function POST(req: Request) {
  const body = await req.text();
  const signature = req.headers.get("x-whop-signature");

  // Verify signature
  const hash = crypto
    .createHmac("sha256", process.env.WHOP_WEBHOOK_SECRET!)
    .update(body)
    .digest("hex");

  if (hash !== signature) {
    return new Response("Invalid signature", { status: 401 });
  }

  const event = JSON.parse(body);

  switch (event.action) {
    case "payment.succeeded":
      await handlePaymentSuccess(event.data);
      break;

    case "membership.activated":
      await grantAccess(event.data);
      break;

    case "membership.deactivated":
      await revokeAccess(event.data);
      break;

    case "dispute.created":
      await handleDispute(event.data);
      break;
  }

  return Response.json({ received: true });
}

关键 Webhook 事件:

事件触发条件
payment.succeeded支付完成
payment.failed支付失败
membership.activated会员资格已生效
membership.deactivated会员资格已结束
invoice.paid定期付款成功
dispute.created已发起拒付

OAuth 应用

重定向至 Whop OAuth
const authUrl = new URL("https://whop.com/oauth");
authUrl.searchParams.set("client_id", process.env.WHOP_CLIENT_ID!);
authUrl.searchParams.set("redirect_uri", "https://yourapp.com/callback");
authUrl.searchParams.set("scope", "memberships:read payments:read");

// Redirect user
window.location.href = authUrl.toString();
处理回调
export async function GET(req: Request) {
  const url = new URL(req.url);
  const code = url.searchParams.get("code");

  const response = await fetch("https://api.whop.com/api/v5/oauth/token", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      client_id: process.env.WHOP_CLIENT_ID!,
      client_secret: process.env.WHOP_CLIENT_SECRET!,
      code,
      grant_type: "authorization_code",
      redirect_uri: "https://yourapp.com/callback",
    }),
  });

  const { access_token, refresh_token } = await response.json();
  // Store tokens securely
}

中间件(访问控制)

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export async function middleware(request: NextRequest) {
  const token = request.cookies.get("whop_user_token")?.value;

  if (!token) {
    return NextResponse.redirect(new URL("/login", request.url));
  }

  const response = await fetch("https://api.whop.com/api/v5/me/memberships", {
    headers: { Authorization: `Bearer ${token}` },
  });

  const { data } = await response.json();
  const hasAccess = data.some((m: any) => m.status === "active" && m.valid);

  if (!hasAccess) {
    return NextResponse.redirect(new URL("/subscribe", request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ["/dashboard/:path*", "/premium/:path*"],
};

TypeScript 类型

interface WhopMembership {
  id: string;
  user_id: string;
  product_id: string;
  plan_id: string;
  status: "active" | "paused" | "canceled" | "expired";
  valid: boolean;
  license_key: string;
  renews_at: string | null;
  expires_at: string | null;
  cancel_at_period_end: boolean;
}

interface WhopPayment {
  id: string;
  amount: number;
  currency: string;
  status: "succeeded" | "pending" | "failed";
  user_id: string;
  product_id: string;
}

错误处理

async function safeWhopRequest(url: string, options: RequestInit) {
  const response = await fetch(url, options);

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.message || "Whop API error");
  }

  return response.json();
}
错误含义
401 UnauthorizedAPI 密钥无效
403 Forbidden权限不足
404 Not Found资源不存在
429 Too Many Requests请求频率超出限制

安全最佳实践

应该:

  • 验证 Webhook 签名
  • 加密存储令牌
  • 在服务端检查会员状态
  • 始终使用 HTTPS

不应该:

  • 在客户端暴露 API 密钥
  • 信任客户端发送的会员数据
  • 跳过 Webhook 验证
  • 记录敏感数据

实施检查清单

  • 创建 Whop 账号和公司
  • 从 Dashboard 获取 API 密钥
  • 设置环境变量
  • 创建产品和方案
  • 实现结账流程
  • 添加会员访问权限检查
  • 设置 Webhook 端点
  • 处理支付事件
  • 测试端到端流程

资源

评论

支持 Markdown