creem
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCREEM API Integration Skill
CREEM API集成技能
You are an expert at integrating CREEM, a Merchant of Record (MoR) payment platform for SaaS and digital businesses. You help developers implement checkout flows, manage subscriptions, handle webhooks, and work with license keys.
您是CREEM集成方面的专家,CREEM是面向SaaS和数字业务的商户收单方(MoR)支付平台。您可以帮助开发者实现结账流程、管理订阅、处理Webhook以及操作许可证密钥。
Core Concepts
核心概念
CREEM acts as the legal seller (Merchant of Record), handling tax compliance, payment processing, and refunds. When integrating:
- Production API:
https://api.creem.io - Test API:
https://test-api.creem.io - Authentication: header with API key from dashboard
x-api-key - Prices: Always in cents (1000 = $10.00)
- Currencies: Three-letter ISO codes in uppercase (USD, EUR, etc.)
CREEM作为合法的销售方(商户收单方),负责处理税务合规、支付处理和退款事宜。集成时需注意:
- 生产环境API:
https://api.creem.io - 测试环境API:
https://test-api.creem.io - 身份验证:使用仪表板获取的API key放在请求头中
x-api-key - 价格单位:始终以美分为单位(1000 = 10.00美元)
- 货币代码:使用大写的三位ISO代码(如USD、EUR等)
Authentication Setup
身份验证设置
Always configure authentication properly:
typescript
// 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'
};请务必正确配置身份验证:
typescript
// 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'
};Quick Reference: API Endpoints
快速参考:API端点
Checkouts
结账
| Method | Endpoint | Description |
|---|---|---|
| POST | | Create checkout session |
| GET | | Retrieve checkout |
| 请求方法 | 端点 | 描述 |
|---|---|---|
| POST | | 创建结账会话 |
| GET | | 获取结账会话信息 |
Products
产品
| Method | Endpoint | Description |
|---|---|---|
| POST | | Create product |
| GET | | Retrieve product |
| GET | | List all products |
| 请求方法 | 端点 | 描述 |
|---|---|---|
| POST | | 创建产品 |
| GET | | 获取产品信息 |
| GET | | 列出所有产品 |
Customers
客户
| Method | Endpoint | Description |
|---|---|---|
| GET | | Retrieve customer |
| GET | | Retrieve by email |
| GET | | List all customers |
| POST | | Generate portal link |
| 请求方法 | 端点 | 描述 |
|---|---|---|
| GET | | 获取客户信息 |
| GET | | 通过邮箱获取客户信息 |
| GET | | 列出所有客户 |
| POST | | 生成客户门户链接 |
Subscriptions
订阅
| Method | Endpoint | Description |
|---|---|---|
| GET | | Retrieve subscription |
| POST | | Update subscription |
| POST | | Upgrade plan |
| POST | | Cancel subscription |
| POST | | Pause subscription |
| POST | | Resume subscription |
| 请求方法 | 端点 | 描述 |
|---|---|---|
| GET | | 获取订阅信息 |
| POST | | 更新订阅 |
| POST | | 升级订阅计划 |
| POST | | 取消订阅 |
| POST | | 暂停订阅 |
| POST | | 恢复订阅 |
Licenses
许可证
| Method | Endpoint | Description |
|---|---|---|
| POST | | Activate license |
| POST | | Validate license |
| POST | | Deactivate license |
| 请求方法 | 端点 | 描述 |
|---|---|---|
| POST | | 激活许可证 |
| POST | | 验证许可证 |
| POST | | 停用许可证 |
Discounts
折扣
| Method | Endpoint | Description |
|---|---|---|
| POST | | Create discount |
| GET | | Retrieve discount |
| GET | | Retrieve by code |
| DELETE | | Delete discount |
| 请求方法 | 端点 | 描述 |
|---|---|---|
| POST | | 创建折扣 |
| GET | | 获取折扣信息 |
| GET | | 通过折扣码获取折扣信息 |
| DELETE | | 删除折扣 |
Transactions
交易
| Method | Endpoint | Description |
|---|---|---|
| GET | | Get transaction |
| GET | | List transactions |
| 请求方法 | 端点 | 描述 |
|---|---|---|
| GET | | 获取交易信息 |
| GET | | 列出交易记录 |
Implementation Patterns
实现模式
1. Create a Checkout Session
1. 创建结账会话
The most common integration pattern - redirect users to CREEM's hosted checkout:
typescript
// 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;
};Success URL query parameters after payment:
- - Checkout session ID
checkout_id - - Order created
order_id - - Customer ID
customer_id - - Subscription (if recurring)
subscription_id - - Product purchased
product_id - - Your tracking ID (if provided)
request_id - - HMAC signature for verification
signature
最常见的集成模式 - 将用户重定向到CREEM托管的结账页面:
typescript
// 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;
};付款成功后Success URL的查询参数:
- - 结账会话ID
checkout_id - - 创建的订单ID
order_id - - 客户ID
customer_id - - 订阅ID(如果是定期订阅)
subscription_id - - 购买的产品ID
product_id - - 您的跟踪ID(如果提供)
request_id - - 用于验证的HMAC签名
signature
2. Webhook Handler
2. Webhook处理器
CRITICAL: Always verify webhook signatures to prevent fraud.
typescript
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 });
}重要提示:务必验证Webhook签名以防止欺诈。
typescript
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. License Key Management
3. 许可证密钥管理
For desktop apps, CLI tools, or software requiring activation:
typescript
// 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();
};适用于桌面应用、CLI工具或需要激活的软件:
typescript
// 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. Subscription Management
4. 订阅管理
typescript
// 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();
};typescript
// 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. Customer Portal
5. 客户门户
Let customers manage their own subscriptions:
typescript
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;
};让客户自行管理他们的订阅:
typescript
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 Events Reference
Webhook事件参考
| Event | When | Action |
|---|---|---|
| Payment successful | Grant access, create user |
| New subscription created | Sync to database |
| Recurring payment processed | Extend access period |
| User/merchant canceled | Revoke at period end |
| Period ended without payment | Retries may happen |
| Trial started | Grant trial access |
| Subscription paused | Pause features |
| Subscription modified | Sync changes |
| Refund processed | May revoke access |
| Chargeback opened | Handle dispute |
| 事件 | 触发时机 | 操作建议 |
|---|---|---|
| 付款成功 | 授予访问权限、创建用户 |
| 新订阅创建 | 同步到数据库 |
| 定期付款处理完成 | 延长访问期限 |
| 用户/商户取消订阅 | 到期后撤销访问权限 |
| 订阅期结束且未付款 | 可能会有付款重试 |
| 试用开始 | 授予试用访问权限 |
| 订阅暂停 | 暂停相关功能 |
| 订阅被修改 | 同步变更信息 |
| 退款处理完成 | 可能需要撤销访问权限 |
| 退单申请发起 | 处理争议 |
Error Handling
错误处理
All endpoints return standard HTTP status codes:
typescript
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}`);
}
};所有端点返回标准HTTP状态码:
typescript
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}`);
}
};Test Mode
测试模式
Always develop in test mode first:
- Use as base URL
https://test-api.creem.io - Use test API key from dashboard
- Test cards:
- - Success
4242 4242 4242 4242 - - Declined
4000 0000 0000 0002 - - Insufficient funds
4000 0000 0000 9995
请始终先在测试模式下开发:
- 使用作为基础URL
https://test-api.creem.io - 使用仪表板提供的测试API密钥
- 测试卡号:
- - 付款成功
4242 4242 4242 4242 - - 付款被拒绝
4000 0000 0000 0002 - - 余额不足
4000 0000 0000 9995
Common Integration Checklist
常见集成检查清单
When implementing CREEM:
-
Environment Setup
- Store API key in environment variables
- Configure base URL for test/production
- Set up webhook endpoint
-
Checkout Flow
- Create checkout session with product_id
- Include request_id for tracking
- Set success_url with verification
- Handle checkout.completed webhook
-
Subscription Handling
- Handle subscription.paid for renewals
- Handle subscription.canceled for access revocation
- Implement customer portal link
- Store subscription_id for management
-
License Keys (if applicable)
- Implement activate on first use
- Validate on each app start
- Handle deactivation for device transfer
-
Security
- Verify webhook signatures
- Never expose API keys client-side
- Validate success URL signatures
实现CREEM集成时:
-
环境设置
- 将API密钥存储在环境变量中
- 为测试/生产环境配置基础URL
- 设置Webhook端点
-
结账流程
- 使用product_id创建结账会话
- 包含request_id用于跟踪
- 设置带验证的success_url
- 处理checkout.completed Webhook
-
订阅处理
- 处理subscription.paid事件以完成续费
- 处理subscription.canceled事件以撤销访问权限
- 实现客户门户链接
- 存储subscription_id用于管理
-
许可证密钥(如适用)
- 首次使用时激活许可证
- 每次应用启动时验证许可证
- 处理设备转移时的许可证停用
-
安全
- 验证Webhook签名
- 切勿在客户端暴露API密钥
- 验证Success URL的签名
File References
文件参考
For detailed information, see:
- - Complete API reference with all fields
REFERENCE.md - - Webhook payload examples
WEBHOOKS.md - - Step-by-step integration patterns
WORKFLOWS.md
如需详细信息,请查看:
- - 包含所有字段的完整API参考
REFERENCE.md - - Webhook负载示例
WEBHOOKS.md - - 分步集成模式
WORKFLOWS.md
Need Help?
需要帮助?
- Documentation: https://docs.creem.io
- Dashboard: https://creem.io/dashboard
- Support: support@creem.io
- 文档:https://docs.creem.io
- 仪表板:https://creem.io/dashboard
- 支持:support@creem.io