mcpField
C
creem

creem

github.com/armitage-labs开发270 次安装12 周前更新
代码仓库

关于 creem

creem 是面向开发者的 CREEM 支付集成 Skill,适用于 SaaS 和数字产品。它通过 API 示例与实施指引,涵盖结账、订阅、Webhook 和许可证密钥。

主要功能

  1. 01创建结账会话
  2. 02管理订阅生命周期
  3. 03验证 Webhook 签名
  4. 04激活与验证许可证
  5. 05生成客户账单门户链接

适用场景

  1. 01为 SaaS 产品接入托管结账
  2. 02处理订阅续费与取消
  3. 03激活软件许可证密钥
  4. 04保护支付 Webhook 处理程序

安装

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

$npx skills add https://github.com/armitage-labs/creem-skills --skill creem

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

详细介绍

CREEM API 集成技能

你是集成 CREEM 的专家。CREEM 是面向 SaaS 和数字业务的名义销售商(MoR)支付平台。你可以帮助开发者实现结账流程、管理订阅、处理 Webhook,并使用许可证密钥。

核心概念

CREEM 作为法定销售商(名义销售商)运营,负责税务合规、支付处理和退款。集成时:

  • 生产环境 API:https://api.creem.io
  • 测试环境 API:https://test-api.creem.io
  • 身份验证:在 x-api-key 标头中提供从仪表板获取的 API 密钥
  • 价格:始终以美分为单位(1000 = $10.00)
  • 货币:使用大写的三字母 ISO 代码(USD、EUR 等)

身份验证设置

务必正确配置身份验证:

// Environment variables (never commit API keys)
const API_KEY = process.env.CREEM_API_KEY;
const BASE_URL = process.env.NODE_ENV === 'production'
  ? 'https://api.creem.io'
  : 'https://test-api.creem.io';

// All requests require the x-api-key header
const headers = {
  'x-api-key': API_KEY,
  'Content-Type': 'application/json'
};

快速参考:API 端点

结账
方法端点描述
POST/v1/checkouts创建结账会话
GET/v1/checkouts?checkout_id={id}获取结账信息
产品
方法端点描述
POST/v1/products创建产品
GET/v1/products?product_id={id}获取产品信息
GET/v1/products/search列出所有产品
客户
方法端点描述
GET/v1/customers?customer_id={id}获取客户信息
GET/v1/customers?email={email}按电子邮件获取客户信息
GET/v1/customers/list列出所有客户
POST/v1/customers/billing生成门户链接
订阅
方法端点描述
GET/v1/subscriptions?subscription_id={id}获取订阅信息
POST/v1/subscriptions/{id}更新订阅
POST/v1/subscriptions/{id}/upgrade升级方案
POST/v1/subscriptions/{id}/cancel取消订阅
POST/v1/subscriptions/{id}/pause暂停订阅
POST/v1/subscriptions/{id}/resume恢复订阅
许可证
方法端点描述
POST/v1/licenses/activate激活许可证
POST/v1/licenses/validate验证许可证
POST/v1/licenses/deactivate停用许可证
折扣
方法端点描述
POST/v1/discounts创建折扣
GET/v1/discounts?discount_id={id}获取折扣信息
GET/v1/discounts?discount_code={code}按代码获取折扣信息
DELETE/v1/discounts/{id}/delete删除折扣
交易
方法端点描述
GET/v1/transactions?transaction_id={id}获取交易信息
GET/v1/transactions/search列出交易记录

实现模式

1. 创建结账会话

最常见的集成模式——将用户重定向到 CREEM 托管的结账页面:

// POST /v1/checkouts
const createCheckout = async (productId: string, options?: {
  requestId?: string;
  successUrl?: string;
  customerEmail?: string;
  discountCode?: string;
  units?: number;
  metadata?: Record<string, any>;
}) => {
  const response = await fetch(`${BASE_URL}/v1/checkouts`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      product_id: productId,
      request_id: options?.requestId,
      success_url: options?.successUrl,
      customer: options?.customerEmail ? { email: options.customerEmail } : undefined,
      discount_code: options?.discountCode,
      units: options?.units,
      metadata: options?.metadata
    })
  });

  const checkout = await response.json();
  // Redirect user to: checkout.checkout_url
  return checkout;
};

支付后的成功 URL 查询参数:

  • checkout_id - 结账会话 ID
  • order_id - 创建的订单
  • customer_id - 客户 ID
  • subscription_id - 订阅(如果为周期性订阅)
  • product_id - 购买的产品
  • request_id - 你的跟踪 ID(如果已提供)
  • signature - 用于验证的 HMAC 签名
2. Webhook 处理程序

重要:务必验证 Webhook 签名,以防止欺诈。

import crypto from 'crypto';

// Signature verification
function verifyWebhookSignature(payload: string, signature: string, secret: string): boolean {
  const computed = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return computed === signature;
}

// Webhook handler
export async function handleWebhook(req: Request) {
  const signature = req.headers.get('creem-signature');
  const rawBody = await req.text();

  if (!verifyWebhookSignature(rawBody, signature!, process.env.CREEM_WEBHOOK_SECRET!)) {
    return new Response('Invalid signature', { status: 401 });
  }

  const event = JSON.parse(rawBody);

  switch (event.eventType) {
    case 'checkout.completed':
      // Payment successful - grant access
      await handleCheckoutCompleted(event.object);
      break;
    case 'subscription.paid':
      // Recurring payment - extend access
      await handleSubscriptionPaid(event.object);
      break;
    case 'subscription.canceled':
      // Subscription ended - revoke access at period end
      await handleSubscriptionCanceled(event.object);
      break;
    case 'subscription.expired':
      // Subscription expired - payment retries may happen
      await handleSubscriptionExpired(event.object);
      break;
    case 'refund.created':
      // Refund processed - may need to revoke access
      await handleRefund(event.object);
      break;
  }

  return new Response('OK', { status: 200 });
}
3. 许可证密钥管理

适用于桌面应用、CLI 工具或需要激活的软件:

// Activate on first use
const activateLicense = async (licenseKey: string, instanceName: string) => {
  const response = await fetch(`${BASE_URL}/v1/licenses/activate`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      key: licenseKey,
      instance_name: instanceName // e.g., "johns-macbook-pro"
    })
  });

  const result = await response.json();
  // Store result.instance.id locally for future validation
  return result;
};

// Validate on app startup
const validateLicense = async (licenseKey: string, instanceId: string) => {
  const response = await fetch(`${BASE_URL}/v1/licenses/validate`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      key: licenseKey,
      instance_id: instanceId
    })
  });

  const result = await response.json();
  // result.status: "active" | "inactive" | "expired" | "disabled"
  return result;
};

// Deactivate when user switches device
const deactivateLicense = async (licenseKey: string, instanceId: string) => {
  const response = await fetch(`${BASE_URL}/v1/licenses/deactivate`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      key: licenseKey,
      instance_id: instanceId
    })
  });
  return response.json();
};
4. 订阅管理
// Update seat count
const updateSubscriptionSeats = async (subscriptionId: string, itemId: string, newUnits: number) => {
  const response = await fetch(`${BASE_URL}/v1/subscriptions/${subscriptionId}`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      items: [{ id: itemId, units: newUnits }],
      update_behavior: 'proration-charge-immediately' // or 'proration-charge', 'proration-none'
    })
  });
  return response.json();
};

// Upgrade to different plan
const upgradeSubscription = async (subscriptionId: string, newProductId: string) => {
  const response = await fetch(`${BASE_URL}/v1/subscriptions/${subscriptionId}/upgrade`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      product_id: newProductId,
      update_behavior: 'proration-charge-immediately'
    })
  });
  return response.json();
};

// Cancel subscription
const cancelSubscription = async (subscriptionId: string, immediate: boolean = false) => {
  const response = await fetch(`${BASE_URL}/v1/subscriptions/${subscriptionId}/cancel`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      mode: immediate ? 'immediate' : 'scheduled' // scheduled = at period end
    })
  });
  return response.json();
};
5. 客户门户

让客户自行管理订阅:

const getCustomerPortalLink = async (customerId: string) => {
  const response = await fetch(`${BASE_URL}/v1/customers/billing`, {
    method: 'POST',
    headers,
    body: JSON.stringify({ customer_id: customerId })
  });

  const { customer_portal_link } = await response.json();
  return customer_portal_link;
};

Webhook 事件参考

事件时机操作
checkout.completed付款成功授予访问权限,创建用户
subscription.active创建新订阅同步到数据库
subscription.paid处理续费付款延长访问期限
subscription.canceled用户/商家取消订阅在当前周期结束时撤销访问权限
subscription.expired周期结束且未付款可能会进行重试
subscription.trialing试用开始授予试用访问权限
subscription.paused订阅已暂停暂停功能
subscription.update订阅已修改同步变更
refund.created已处理退款可能会撤销访问权限
dispute.created已发起拒付处理争议

错误处理

所有端点都返回标准 HTTP 状态码:

const handleApiResponse = async (response: Response) => {
  if (response.ok) {
    return response.json();
  }

  switch (response.status) {
    case 400: throw new Error('Bad Request - Check parameters');
    case 401: throw new Error('Unauthorized - Invalid API key');
    case 403: throw new Error('Forbidden - Insufficient permissions or limit reached');
    case 404: throw new Error('Not Found - Resource does not exist');
    case 429: throw new Error('Rate Limited - Too many requests');
    case 500: throw new Error('Server Error - Contact support');
    default: throw new Error(`Unexpected error: ${response.status}`);
  }
};

测试模式

务必先在测试模式下开发:

  1. 使用 https://test-api.creem.io 作为基础 URL
  2. 使用控制面板中的测试 API 密钥
  3. 测试卡:
    • 4242 4242 4242 4242 - 成功
    • 4000 0000 0000 0002 - 被拒绝
    • 4000 0000 0000 9995 - 余额不足

常见集成检查清单

实现 CREEM 时:

  1. 环境设置

    • 将 API 密钥存储在环境变量中
    • 配置测试/生产环境的基础 URL
    • 设置 webhook 端点
  2. 结账流程

    • 使用 product_id 创建结账会话
    • 添加 request_id 以便跟踪
    • 设置带验证的 success_url
    • 处理 checkout.completed webhook
  3. 订阅处理

    • 处理续订的 subscription.paid 事件
    • 处理撤销访问权限的 subscription.canceled 事件
    • 实现客户门户链接
    • 存储 subscription_id 以便管理
  4. 许可证密钥(如适用)

    • 实现首次使用时激活
    • 在每次应用启动时验证
    • 处理设备转移时的停用
  5. 安全

    • 验证 webhook 签名
    • 切勿在客户端公开 API 密钥
    • 验证 success URL 签名

文件参考

详细信息请参阅:

  • REFERENCE.md - 包含所有字段的完整 API 参考
  • WEBHOOKS.md - Webhook 载荷示例
  • WORKFLOWS.md - 分步集成模式

需要帮助?

评论

支持 Markdown