创建一个名为 ai.request 的计量器,针对 credit_cost 属性使用 Sum 聚合。通过启用“以积分计费”的切换,将此计量器链接到您的积分授权。将每个积分的计量单位设置为 1。为了处理模型加权消耗,您需要在应用层管理积分成本。当用户发起请求时,您的应用根据模型或操作类型确定所需消耗。
import DodoPayments from 'dodopayments';/** * Determines the credit cost for a given request type and model. * This logic lives in your application and can be updated without * changing your billing configuration. */function getCreditCost(requestType: string, model: string): number { const costs: Record<string, Record<string, number>> = { 'tab_completion': { 'default': 0 }, 'chat': { 'gpt-4o-mini': 1, 'gpt-4o': 1, 'claude-sonnet': 1 }, 'composer': { 'gpt-4o-mini': 2, 'gpt-4o': 5, 'claude-sonnet': 5 }, 'agent': { 'gpt-4o': 10, 'claude-sonnet': 10, 'o1': 25 } }; // Default to 1 credit if the combination isn't found return costs[requestType]?.[model] ?? 1;}/** * Ingests usage events into Dodo Payments. * For weighted requests, we send multiple events or use a sum aggregation. */async function trackRequest(customerId: string, requestType: string, model: string) { const creditCost = getCreditCost(requestType, model); // Tab completions are free, so we don't need to track them for billing if (creditCost === 0) return; const client = new DodoPayments({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, }); await client.usageEvents.ingest({ events: [{ event_id: `req_${Date.now()}_${Math.random().toString(36).slice(2)}`, customer_id: customerId, event_name: 'ai.request', timestamp: new Date().toISOString(), metadata: { request_type: requestType, model: model, credit_cost: creditCost } }] });}
import DodoPayments from 'dodopayments';import express from 'express';const app = express();app.use(express.raw({ type: 'application/json' }));const client = new DodoPayments({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,});app.post('/webhooks/dodo', async (req, res) => { try { const event = client.webhooks.unwrap(req.body.toString(), { headers: { 'webhook-id': req.headers['webhook-id'] as string, 'webhook-signature': req.headers['webhook-signature'] as string, 'webhook-timestamp': req.headers['webhook-timestamp'] as string, }, }); if (event.type === 'credit.balance_low') { const customerId = event.data.customer_id; await updateUserTier(customerId, 'slow'); await notifyUser(customerId, 'You have used most of your premium requests. Switching to standard models.'); } res.json({ received: true }); } catch (error) { res.status(401).json({ error: 'Invalid signature' }); }});/** * Routes a request based on the user's current tier. * This function is called before every AI request to determine the model and queue. */async function routeRequest(customerId: string, requestType: string) { const tier = await getUserTier(customerId); if (tier === 'slow') { // Route to a cheaper model and a lower priority queue // This saves costs while keeping the user active in the product return { model: 'gpt-4o-mini', queue: 'standard' }; } // Premium routing for users with remaining credits // This provides the best possible performance and model quality return { model: 'claude-sonnet', queue: 'priority' };}
5
Create Checkout
最后,为用户生成结账会话以订阅计划。Dodo 会自动处理支付、税务合规以及积分分配。
import DodoPayments from 'dodopayments';const client = new DodoPayments({ bearerToken: process.env.DODO_PAYMENTS_API_KEY,});/** * Creates a checkout session for a new subscription. * This is typically called when a user clicks an "Upgrade" button. */const session = await client.checkoutSessions.create({ product_cart: [ { product_id: 'prod_cursor_pro', quantity: 1 } ], customer: { email: 'developer@example.com' }, return_url: 'https://yourapp.com/dashboard'});
AI 计费面临的挑战之一是模型不断更新或被替代。新模型可能具有不同的成本结构或性能特征。借助 Dodo 的积分系统,您可以在应用层优雅地处理这些变化,而无需迁移计费数据。如果您引入新模型,这类模型成本更高,只需更新 getCreditCost 函数以为其分配更高的成本。您无需更改计费配置或更新现有订阅。这种将计费逻辑与应用逻辑解耦的做法是一大优势,使您能够以 AI 速度迭代产品,而不会受制于计费系统。