跳转到主要内容
在本教程中,您将构建 PixelGen AI - 一个示例 AI 图像生成服务,演示基于使用的计费。我们将从头开始创建所有内容:计费计量器、产品配置以及生成图像并实时跟踪使用情况的示例应用程序代码。
本教程提供了用于终端应用程序的示例实现代码。您可以将这段代码修改为适用于特定框架(React、Vue、Angular 等)并根据应用需求自定义用户输入方式。
到本教程结束时,您将拥有一个可工作的示例服务:
  • 使用 OpenAI 的 DALL-E API 生成图像
  • 跟踪每次图像生成以进行计费
  • 根据使用情况自动向客户收费
  • 处理不同的质量层级(标准与高清)

我们要构建的内容

让我们先了解一下我们的 PixelGen AI 服务:
  • 服务:使用 OpenAI 的 DALL-E API 进行 AI 图像生成
  • 定价模型:按图像计费(每张图像 $0.05)
  • 免费层:每位客户每月 10 张免费图像
  • 质量选项:标准和高清图像(为简单起见,价格相同)
在开始之前,请确保您具备:
  • Dodo Payments 帐户
  • 访问 OpenAI 的 API
  • 对 TypeScript/Node.js 的基本熟悉度

第一步:创建您的使用计量器

我们将首先在您的 Dodo Payments 仪表板中创建一个计量器,以跟踪我们的服务生成的每张图像。可以将其视为跟踪可计费事件的“计数器”。
我们要构建的是:一个名为“Image Generation Meter”的计量器,用于统计每次有人使用我们的服务生成图像。
1

Open the Meters section

  1. 登录您的 Dodo Payments 控制台
  2. 在左侧边栏点击 Meters
  3. 点击 Create Meter 按钮
您应该会看到一个表单,我们将在此配置图像生成的追踪。
2

Fill in the basic meter information

现在我们将输入针对 PixelGen AI 服务的具体设置:Meter 名称:精确复制并粘贴此内容 → Image Generation Meter描述:复制此内容 → Tracks each AI image generation request made by customers using our DALL-E powered service事件名称:这是关键——请精确复制 → image.generated
事件名称 image.generated 必须与我们稍后在应用代码中发送的名称完全相同。事件名称区分大小写!
3

Configure how we count images

设置聚合方式(计量器如何计算事件):聚合类型:从下拉菜单中选择 计数计量单位:输入 → images
我们使用“Count”,因为我们按生成的图像数量计费,而不是按大小或生成时间。每成功生成一张图像 = 1 个计费单位。
4

Add quality filtering

我们希望确保只计数合法图像(而不是测试运行或失败):
  1. 启用事件过滤:将其切换为 启用
  2. 过滤逻辑:选择 OR(表示“如果任一条件满足即计数”)
  3. 添加第一个条件
    • 属性键:quality
    • 比较符:equals
    • 值:standard
  4. 点击“Add Condition” 添加第二个条件:
    • 属性键:quality
    • 比较符:equals
    • 值:hd
此设置意味着我们只会统计质量为“standard”或“hd”的事件,从而过滤掉测试事件或错误的请求。
5

Create your meter

  1. 仔细检查所有设置是否与上述值一致
  2. 点击 Create Meter
计量器已创建! 您的“Image Generation Meter”现在可以开始统计图像生成次数。接下来,我们将把它连接到一个计费产品。

第二步:创建您的计费产品

现在我们需要创建一个定义我们定价的产品(每张图像 $0.05,包含 10 张免费图像)。这将我们的计量器与实际计费连接起来。
我们要构建的是:一个名为“PixelGen AI - Image Generation”的产品,每月前 10 张图像免费,之后每张图像收费 $0.05。
1

Navigate to Products

  1. 在 Dodo Payments 控制台中,点击左侧边栏的 Products
  2. 点击 Create Product
  3. 选择 Usage-Based 作为产品类型
这将告诉 Dodo Payments 计费将基于计量器使用情况,而非固定订阅费。
2

Enter product details

为我们的 PixelGen AI 服务填写以下精确值:产品名称:复制此内容 → PixelGen AI - Image Generation描述:复制此内容 → AI-powered image generation service with pay-per-use billing产品图像:上传一张清晰、相关的图像。
这些信息将出现在客户发票上,请保持清晰且专业。
3

Connect your meter

在连接您的计量器之前,请确保您已选择 基于使用的计费 作为产品的价格类型。另外,将 固定价格 设置为 0,确保客户仅根据使用量计费,无基础费用。现在,链接您刚刚创建的计量器:
  1. 向下滚动到 关联计量器 部分
  2. 点击 添加计量器
  3. 从下拉菜单中选择 “图像生成计量器”(您之前创建的那个)
  4. 确认它出现在您的产品配置中
您的计量器已成功连接到该产品。
4

Set your pricing

以下是我们定义的商业模式:
每单位价格:输入 → 0.05(即每张图像 $0.05)免费额度:输入 → 10(客户每月可免费生成 10 张图像)
计费说明:若客户一个月生成 25 张图像,则需为 15 张图像付费(25 - 10 免费)= 15 × 0.05=0.05 = 0.75
5

Save your product

  1. 检查所有设置:
    • 名称:PixelGen AI - Image Generation
    • 计量器:Image Generation Meter
    • 价格:每张图像 $0.05
    • 免费层:10 张图像
  2. 点击 Save Changes
产品已创建! 您的计费现已配置完成。客户将根据其图像生成使用情况自动收取费用。

第三步:进行测试购买

在我们开始接收使用事件之前,我们需要进行一次测试购买。
1

Get your payment link

  1. 在 Dodo Payments 控制台中转到 Products
  2. 找到您的“PixelGen AI - Image Generation”产品
  3. 点击产品旁边的 Share 按钮
  4. 复制显示的支付链接
支付链接看起来类似于:https://test.checkout.dodopayments.com/buy/pdt_IgPWlRsfpbPd5jQKezzW1?quantity=1
2

Complete a test purchase

  1. 在新的浏览器标签页中打开支付链接
  2. 输入测试支付信息并完成购买。
支付成功后,您将获得一个客户 ID,我们将在应用代码中使用该 ID。
3

Find your customer ID

  1. 返回 Dodo Payments 控制台
  2. 在左侧边栏导航至 Customers
  3. 找到您刚创建的客户(用测试邮箱)
  4. 复制客户 ID——它看起来像 cus_abc123def456
保存此客户 ID——我们会把它硬编码在示例应用代码中,以确保事件被正确追踪。

第四步:构建示例应用程序

现在我们已经完成了计费设置并创建了测试客户。让我们构建示例 PixelGen AI 应用程序,该应用程序生成图像并自动跟踪计费使用情况。
1

Set up your project

创建一个新目录并初始化项目:
mkdir pixelgen-ai
cd pixelgen-ai
npm init -y
2

Install dependencies

安装我们所需的依赖包:
npm install openai dotenv
npm install -D typescript @types/node ts-node
3

Create the main application

创建一个名为 index.ts 的文件,并复制以下完整应用代码:
这是完整的 PixelGen AI 应用程序,集成了计费:
import 'dotenv/config';
import OpenAI from 'openai';
import * as readline from 'readline';
import { randomUUID } from 'crypto';

// Initialize OpenAI client
const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

// Dodo Payments configuration
const DODO_PAYMENTS_CONFIG = {
  apiKey: process.env.DODO_PAYMENTS_API_KEY,
  baseUrl: 'https://test.dodopayments.com',
  customerId: 'cus_FX5FAB43aShGyiHJGIqjB', // Replace with your actual customer ID from Step 3
};

// DALL-E 3 pricing (as of 2024-2025)
const PRICING = {
  'standard': 0.040, // $0.040 per image (1024×1024)
  'hd': 0.080,       // $0.080 per image (1024×1024, HD quality)
};

interface ImageGenerationOptions {
  prompt: string;
  model?: 'dall-e-3' | 'dall-e-2';
  quality?: 'standard' | 'hd';
  size?: '1024x1024' | '1792x1024' | '1024x1792';
  style?: 'vivid' | 'natural';
}

interface UsageEvent {
  event_id: string;
  customer_id: string;
  event_name: string;
  timestamp: string;
  metadata: {
    quality: string;
  };
}

/**
 * Send usage event to Dodo Payments for billing tracking
 */
async function sendUsageEvent(event: UsageEvent): Promise<void> {
  try {
    console.log('Sending usage event to Dodo Payments...');
    console.log(`URL: ${DODO_PAYMENTS_CONFIG.baseUrl}/events/ingest`);
    console.log(`API Key present: ${!!DODO_PAYMENTS_CONFIG.apiKey}`);
    console.log(`API Key length: ${DODO_PAYMENTS_CONFIG.apiKey?.length || 0}`);
    console.log(`Customer ID: ${DODO_PAYMENTS_CONFIG.customerId}`);
    
    const requestBody = {
      events: [event]
    };
    console.log('Request body:', JSON.stringify(requestBody, null, 2));
    
    const headers = {
      'Authorization': `Bearer ${DODO_PAYMENTS_CONFIG.apiKey}`,
      'Content-Type': 'application/json',
    }
    console.log('Headers:', headers);
    const response = await fetch(`${DODO_PAYMENTS_CONFIG.baseUrl}/events/ingest`, {
      method: 'POST',
      headers: headers,
      body: JSON.stringify(requestBody),
    });

    console.log(`Response status: ${response.status}`);
    console.log(`Response headers:`, Object.fromEntries(response.headers.entries()));

    if (!response.ok) {
      const errorData = await response.text();
      console.log(`Error response body: ${errorData}`);
      throw new Error(`HTTP ${response.status}: ${errorData}`);
    }

    const result = await response.json();
    console.log('Usage event sent successfully');
    console.log(`   • Event ID: ${event.event_id}`);
    console.log(`   • Customer: ${event.customer_id}`);
    console.log(`   • Quality: ${event.metadata.quality}`);
    
  } catch (error) {
    console.error('Failed to send usage event:', error);
    // In production, you might want to queue failed events for retry
    throw error;
  }
}

async function generateImage(options: ImageGenerationOptions) {
  const startTime = Date.now();
  const eventId = randomUUID();
  
  try {
    console.log('Generating image...');
    console.log(`Prompt: "${options.prompt}"`);
    console.log(`Quality: ${options.quality || 'standard'}`);
    console.log(`Size: ${options.size || '1024x1024'}`);
    
    const response = await openai.images.generate({
      model: options.model || 'dall-e-3',
      prompt: options.prompt,
      n: 1,
      size: options.size || '1024x1024',
      quality: options.quality || 'standard',
      style: options.style || 'vivid',
    });

    const endTime = Date.now();
    const duration = (endTime - startTime) / 1000;
    const cost = PRICING[options.quality || 'standard'];
    
    // Create usage event for Dodo Payments
    const usageEvent: UsageEvent = {
      event_id: eventId,
      customer_id: DODO_PAYMENTS_CONFIG.customerId!,
      event_name: 'image.generated',
      timestamp: new Date().toISOString(),
      metadata: {
        quality: options.quality || 'standard',
      }
    };

    // Send usage event to Dodo Payments for billing
    await sendUsageEvent(usageEvent);
    
    console.log('\nImage generated successfully!');
    console.log(`Generation Stats:`);
    console.log(`   • Duration: ${duration.toFixed(2)} seconds`);
    console.log(`   • Quality: ${options.quality || 'standard'}`);
    console.log(`   • Cost: $${cost.toFixed(3)}`);
    console.log(`   • Image URL: ${response.data?.[0]?.url}`);
    
    if (response.data?.[0]?.revised_prompt) {
      console.log(`   • Revised prompt: "${response.data[0].revised_prompt}"`);
    }

    return {
      imageUrl: response.data?.[0].url,
      revisedPrompt: response.data?.[0].revised_prompt,
      cost: cost,
      duration: duration,
      eventId: eventId,
    };

  } catch (error) {
    console.error('Error generating image:', error);
    
    // Send failure event for monitoring (optional)
    try {
      const failureEvent: UsageEvent = {
        event_id: eventId,
        customer_id: DODO_PAYMENTS_CONFIG.customerId!,
        event_name: 'image.generation.failed',
        timestamp: new Date().toISOString(),
        metadata: {
          quality: options.quality || 'standard',
        }
      };
      
      // Note: You might want to create a separate meter for failed attempts
      // await sendUsageEvent(failureEvent);
    } catch (eventError) {
      console.error('Failed to send failure event:', eventError);
    }
    
    throw error;
  }
}

async function getUserInput(): Promise<string> {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });

  return new Promise((resolve) => {
    rl.question('Enter your image prompt: ', (answer) => {
      rl.close();
      resolve(answer);
    });
  });
}

async function main() {
  console.log('PixelGen AI - Image Generator with Usage Billing\n');
  
  // Validate environment variables
  const requiredEnvVars = [
    'OPENAI_API_KEY',
    'DODO_PAYMENTS_API_KEY'
  ];
  
  for (const envVar of requiredEnvVars) {
    if (!process.env[envVar]) {
      console.error(`Error: ${envVar} environment variable is not set.`);
      console.log('Please set all required environment variables:');
      console.log('export OPENAI_API_KEY="your-openai-key"');
      console.log('export DODO_PAYMENTS_API_KEY="your-dodo-api-key"');
      console.log('Note: Customer ID is hardcoded in the application');
      process.exit(1);
    }
  }

  try {
    const prompt = await getUserInput();
    
    if (!prompt.trim()) {
      console.log('No prompt provided. Exiting...');
      return;
    }

    const result = await generateImage({
      prompt: prompt.trim(),
      quality: 'standard', // Change to 'hd' for higher quality (costs more)
      size: '1024x1024',
      style: 'vivid'
    });

    console.log('\nProcess completed successfully!');
    console.log(`Billing Information:`);
    console.log(`   • Total cost: $${result.cost.toFixed(3)}`);
    console.log(`   • Event ID: ${result.eventId}`);
    console.log(`   • Billing will be processed automatically via Dodo Payments`);
    
  } catch (error) {
    console.error('Application error:', error);
    process.exit(1);
  }
}

// Run the application
if (require.main === module) {
  main().catch(console.error);
}

第五步:测试您的示例应用程序

是时候测试我们的示例 PixelGen AI 服务并查看计费的实际效果了!让我们确保一切正常工作。
我们正在测试的是:生成一些图像,确认事件是否到达 Dodo Payments,并验证计费计算是否正确。
1

Set up your environment

首先,确保您已经完成所有配置:
  1. 在您的 pixelgen-ai 目录中创建一个 .env 文件
  2. 添加您实际的 API 密钥:
OPENAI_API_KEY=sk-your-actual-openai-key
DODO_PAYMENTS_API_KEY=your-actual-dodo-api-key
# Customer ID is hardcoded in the application
  1. 安装依赖项并运行应用程序:
npm install
npm start
确保使用真实 API 密钥,并将代码中的硬编码客户 ID 更新为第三步中获取的真实客户 ID!
2

Generate your first test image

当应用启动时,您会看到:
PixelGen AI - Image Generator with Usage Billing

Enter your image prompt:
尝试这个提示:“一只可爱的机器人在画风景”您应该看到如下输出:
Generating image...
Prompt: "A cute robot painting a landscape"
Quality: standard
Size: 1024x1024

Sending usage event to Dodo Payments...
Usage event sent successfully
   • Event ID: 550e8400-e29b-41d4-a716-446655440000
   • Customer: cus_atXa1lklCRRzMicTqfiw2
   • Quality: standard

Image generated successfully!
Generation Stats:
   • Duration: 8.45 seconds
   • Quality: standard
   • Cost: $0.040
   • Image URL: https://oaidalleapi...
如果看到“Usage event sent successfully”,说明您的计费集成已正常工作!
3

Generate a few more images

让我们再生成 2-3 张图像以测试多个事件。尝试以下提示词:
  1. “紫色云彩下的山脉日落”
  2. “维多利亚厨房中的蒸汽朋克咖啡机”
  3. “一只友好的龙在图书馆里看书”
每次都注意“Usage event sent successfully”消息。
4

Check your Dodo Payments dashboard

现在我们来验证事件是否已接收:
  1. 打开您的 Dodo Payments 仪表板
  2. 转到 使用计费 → *计量器图像生成计量器
  3. 点击 事件 选项卡
  4. 您应该看到列出的图像生成事件
观察点
  • 事件名称:image.generated
  • 客户 ID:您的测试客户 ID
您应该能看到每张生成的图像对应一个事件!
5

Verify billing calculations

让我们检查一下用量计数是否正常:
  1. 在您的计量器中,转到 客户 选项卡
  2. 找到您的测试客户
  3. 检查 “消耗单位” 列
6

Test the billing threshold

让我们超过免费额度,看看计费效果:
  1. 生成 8 张更多图像(以达到总共 12 张)
  2. 再次检查您的计量器仪表板
  3. 您现在应该看到:
    • 消耗单位:12
    • 可计费单位:2(12 - 10 免费)
    • 计费金额:$0.10
成功! 您的基于使用量的计费运行正常。客户将根据实际图像生成使用量自动收取费用。

故障排除

常见问题及其解决方案:
可能原因:
  • 事件名称与计量器配置不完全匹配
  • 客户 ID 在您的帐户中不存在
  • API 密钥无效或已过期
  • 网络连接问题
解决方案:
  1. 确认事件名称与计量器配置完全一致(区分大小写)
  2. 检查客户 ID 是否存在于 Dodo Payments 中
  3. 通过简单 API 调用测试 API 密钥
  4. 检查网络连接和防火墙设置

恭喜!您构建了 PixelGen AI

您已成功创建了一个用于 AI 图像生成的基于使用计费的代码片段!以下是您完成的内容:

Usage Meter

创建了“Image Generation Meter”来追踪每次图像生成事件

Billing Product

配置了每张图像 $0.05,且每月前 10 张图像免费

AI Application

构建了一个使用 OpenAI DALL·E 生成图像的可运行 TypeScript 应用

Automated Billing

集成了实时事件追踪,可自动为客户计费