> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dodopayments.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 构建一个基于使用计费的 AI 图像生成器

> 跟随我们构建 PixelGen AI - 一个基于使用计费的 AI 图像生成服务

在本教程中，您将构建 **PixelGen AI** - 一个示例 AI 图像生成服务，演示基于使用的计费。我们将从头开始创建所有内容：计费计量器、产品配置以及生成图像并实时跟踪使用情况的示例应用程序代码。

<Note>
  本教程提供了用于终端应用程序的示例实现代码。您可以将这段代码修改为适用于特定框架（React、Vue、Angular 等）并根据应用需求自定义用户输入方式。
</Note>

到本教程结束时，您将拥有一个可工作的示例服务：

* 使用 OpenAI 的 DALL-E API 生成图像
* 跟踪每次图像生成以进行计费
* 根据使用情况自动向客户收费
* 处理不同的质量层级（标准与高清）

## 我们要构建的内容

让我们先了解一下我们的 PixelGen AI 服务：

* **服务**：使用 OpenAI 的 DALL-E API 进行 AI 图像生成
* **定价模型**：按图像计费（每张图像 \$0.05）
* **免费层**：每位客户每月 10 张免费图像
* **质量选项**：标准和高清图像（为简单起见，价格相同）

<Info>
  在开始之前，请确保您具备：

  * Dodo Payments 帐户
  * 访问 OpenAI 的 API
  * 对 TypeScript/Node.js 的基本熟悉度
</Info>

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

我们将首先在您的 Dodo Payments 仪表板中创建一个计量器，以跟踪我们的服务生成的每张图像。可以将其视为跟踪可计费事件的“计数器”。

<Frame>
  <img src="https://mintcdn.com/dodopayments/w9oVTi6CzZMAOQA3/images/usage-based/UBB-2.png?fit=max&auto=format&n=w9oVTi6CzZMAOQA3&q=85&s=f9d85a463fba231437151db3d4a2052a" alt="" style={{ maxHeight: '500px', width: 'auto' }} width="2324" height="1600" data-path="images/usage-based/UBB-2.png" />
</Frame>

<Tip>
  **我们要构建的是**：一个名为“Image Generation Meter”的计量器，用于统计每次有人使用我们的服务生成图像。
</Tip>

<Steps>
  <Step title="Open the Meters section">
    1. 登录您的 Dodo Payments 控制台
    2. 在左侧边栏点击 **Meters**
    3. 点击 **Create Meter** 按钮

    您应该会看到一个表单，我们将在此配置图像生成的追踪。
  </Step>

  <Step title="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`

    <Warning>
      事件名称 `image.generated` 必须与我们稍后在应用代码中发送的名称完全相同。事件名称区分大小写！
    </Warning>
  </Step>

  <Step title="Configure how we count images">
    设置聚合方式（计量器如何计算事件）：

    **聚合类型**：从下拉菜单中选择 **计数**

    **计量单位**：输入 → `images`

    <Info>
      我们使用“Count”，因为我们按生成的图像数量计费，而不是按大小或生成时间。每成功生成一张图像 = 1 个计费单位。
    </Info>
  </Step>

  <Step title="Add quality filtering">
    <Frame>
      <img src="https://mintcdn.com/dodopayments/w9oVTi6CzZMAOQA3/images/usage-based/UBB-3.png?fit=max&auto=format&n=w9oVTi6CzZMAOQA3&q=85&s=ce231b0559d31723bc12c22cd9ff9d64" alt="" style={{ maxHeight: '500px', width: 'auto' }} width="1558" height="942" data-path="images/usage-based/UBB-3.png" />
    </Frame>

    我们希望确保只计数合法图像（而不是测试运行或失败）：

    1. **启用事件过滤**：将其切换为 **启用**
    2. **过滤逻辑**：选择 **OR**（表示“如果任一条件满足即计数”）
    3. **添加第一个条件**：
       * 属性键：`quality`
       * 比较符：`equals`
       * 值：`standard`
    4. **点击“Add Condition”** 添加第二个条件：
       * 属性键：`quality`
       * 比较符：`equals`
       * 值：`hd`

    <Tip>
      此设置意味着我们只会统计质量为“standard”或“hd”的事件，从而过滤掉测试事件或错误的请求。
    </Tip>
  </Step>

  <Step title="Create your meter">
    1. 仔细检查所有设置是否与上述值一致
    2. 点击 **Create Meter**

    <Check>
      **计量器已创建！** 您的“Image Generation Meter”现在可以开始统计图像生成次数。接下来，我们将把它连接到一个计费产品。
    </Check>
  </Step>
</Steps>

## 第二步：创建您的计费产品

现在我们需要创建一个定义我们定价的产品（每张图像 \$0.05，包含 10 张免费图像）。这将我们的计量器与实际计费连接起来。

<Tip>
  **我们要构建的是**：一个名为“PixelGen AI - Image Generation”的产品，每月前 10 张图像免费，之后每张图像收费 \$0.05。
</Tip>

<Steps>
  <Step title="Navigate to Products">
    1. 在 Dodo Payments 控制台中，点击左侧边栏的 **Products**
    2. 点击 **Create Product**
    3. 选择 **Usage-Based** 作为产品类型

    这将告诉 Dodo Payments 计费将基于计量器使用情况，而非固定订阅费。
  </Step>

  <Step title="Enter product details">
    为我们的 PixelGen AI 服务填写以下精确值：

    **产品名称**：复制此内容 → `PixelGen AI - Image Generation`

    **描述**：复制此内容 → `AI-powered image generation service with pay-per-use billing`

    **产品图像**：上传一张清晰、相关的图像。

    <Info>
      这些信息将出现在客户发票上，请保持清晰且专业。
    </Info>
  </Step>

  <Step title="Connect your meter">
    <Frame>
      <img src="https://mintcdn.com/dodopayments/w9oVTi6CzZMAOQA3/images/usage-based/UBB-5.png?fit=max&auto=format&n=w9oVTi6CzZMAOQA3&q=85&s=fd3851a1a070714baf0aec01cff717b8" alt="" style={{ maxHeight: '500px', width: 'auto' }} width="2272" height="1422" data-path="images/usage-based/UBB-5.png" />
    </Frame>

    在连接您的计量器之前，请确保您已选择 **基于使用的计费** 作为产品的价格类型。

    另外，将 **固定价格** 设置为 `0`，确保客户仅根据使用量计费，无基础费用。

    现在，链接您刚刚创建的计量器：

    1. 向下滚动到 **关联计量器** 部分
    2. 点击 **添加计量器**
    3. 从下拉菜单中选择 **"图像生成计量器"**（您之前创建的那个）
    4. 确认它出现在您的产品配置中

    <Check>
      您的计量器已成功连接到该产品。
    </Check>
  </Step>

  <Step title="Set your pricing">
    以下是我们定义的商业模式：

    <Frame>
      <img src="https://mintcdn.com/dodopayments/w9oVTi6CzZMAOQA3/images/usage-based/UBB-4.png?fit=max&auto=format&n=w9oVTi6CzZMAOQA3&q=85&s=b2d07bb408fd79e1b788c1b7092b8bca" alt="" style={{ maxHeight: '500px', width: 'auto' }} width="732" height="712" data-path="images/usage-based/UBB-4.png" />
    </Frame>

    **每单位价格**：输入 → `0.05`（即每张图像 \$0.05）

    **免费额度**：输入 → `10`（客户每月可免费生成 10 张图像）

    <Tip>
      **计费说明**：若客户一个月生成 25 张图像，则需为 15 张图像付费（25 - 10 免费）= 15 × $0.05 = $0.75
    </Tip>
  </Step>

  <Step title="Save your product">
    1. 检查所有设置：
       * 名称：PixelGen AI - Image Generation
       * 计量器：Image Generation Meter
       * 价格：每张图像 \$0.05
       * 免费层：10 张图像
    2. 点击 **Save Changes**

    <Check>
      **产品已创建！** 您的计费现已配置完成。客户将根据其图像生成使用情况自动收取费用。
    </Check>
  </Step>
</Steps>

## 第三步：进行测试购买

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

<Steps>
  <Step title="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`
  </Step>

  <Step title="Complete a test purchase">
    1. 在新的浏览器标签页中打开支付链接
    2. 输入测试支付信息并完成购买。

    <Check>
      支付成功后，您将获得一个客户 ID，我们将在应用代码中使用该 ID。
    </Check>
  </Step>

  <Step title="Find your customer ID">
    1. 返回到您的 Dodo Payments 仪表板
    2. 在侧边栏中导航到 **销售 → 客户**
    3. 找到您刚创建的客户（使用测试邮箱）
    4. 复制客户 ID - 它将类似于 `cus_abc123def456`

    <Note>
      保存此客户 ID——我们会把它硬编码在示例应用代码中，以确保事件被正确追踪。
    </Note>
  </Step>
</Steps>

## 第四步：构建示例应用程序

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

<Steps>
  <Step title="Set up your project">
    创建一个新目录并初始化项目：

    ```bash theme={null}
    mkdir pixelgen-ai
    cd pixelgen-ai
    npm init -y
    ```
  </Step>

  <Step title="Install dependencies">
    安装我们所需的依赖包：

    ```bash theme={null}
    npm install openai dotenv
    npm install -D typescript @types/node ts-node
    ```
  </Step>

  <Step title="Create the main application">
    创建一个名为 `index.ts` 的文件，并复制以下完整应用代码：
  </Step>
</Steps>

这是完整的 PixelGen AI 应用程序，集成了计费：

<CodeGroup>
  ```typescript index.ts expandable theme={null}
  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);
  }
  ```

  ```bash .env theme={null}
  # Create .env file with your API keys
  OPENAI_API_KEY=your_openai_api_key_here
  DODO_PAYMENTS_API_KEY=your_DODO_PAYMENTS_payments_api_key_here
  # Note: Customer ID is hardcoded in the application code
  ```

  ```typescript package.json theme={null}
  {
    "name": "pixelgen-ai",
    "version": "1.0.0",
    "description": "AI Image Generation with Usage-Based Billing",
    "main": "index.js",
    "scripts": {
      "start": "ts-node index.ts",
      "build": "tsc",
      "dev": "ts-node --watch index.ts"
    },
    "dependencies": {
      "openai": "^4.0.0",
      "dotenv": "^16.0.0"
    },
    "devDependencies": {
      "@types/node": "^20.0.0",
      "typescript": "^5.0.0",
      "ts-node": "^10.0.0"
    }
  }
  ```
</CodeGroup>

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

是时候测试我们的示例 PixelGen AI 服务并查看计费的实际效果了！让我们确保一切正常工作。

<Tip>
  **我们正在测试的是**：生成一些图像，确认事件是否到达 Dodo Payments，并验证计费计算是否正确。
</Tip>

<Steps>
  <Step title="Set up your environment">
    首先，确保您已经完成所有配置：

    1. 在您的 `pixelgen-ai` 目录中创建一个 `.env` 文件
    2. 添加您实际的 API 密钥：

    ```bash theme={null}
    OPENAI_API_KEY=sk-your-actual-openai-key
    DODO_PAYMENTS_API_KEY=your-actual-dodo-api-key
    # Customer ID is hardcoded in the application
    ```

    3. 安装依赖项并运行应用程序：

    ```bash theme={null}
    npm install
    npm start
    ```

    <Warning>
      确保使用真实 API 密钥，并将代码中的硬编码客户 ID 更新为第三步中获取的真实客户 ID！
    </Warning>
  </Step>

  <Step title="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...
    ```

    <Check>
      如果看到“Usage event sent successfully”，说明您的计费集成已正常工作！
    </Check>
  </Step>

  <Step title="Generate a few more images">
    让我们再生成 2-3 张图像以测试多个事件。尝试以下提示词：

    1. **"紫色云彩下的山脉日落"**
    2. **"维多利亚厨房中的蒸汽朋克咖啡机"**
    3. **"一只友好的龙在图书馆里看书"**

    每次都注意“Usage event sent successfully”消息。
  </Step>

  <Step title="Check your Dodo Payments dashboard">
    现在我们来验证事件是否已接收：

    1. 打开您的 Dodo Payments 仪表板
    2. 转到 *使用计费* → \***计量器** → **图像生成计量器**
    3. 点击 **事件** 选项卡
    4. 您应该看到列出的图像生成事件

    **观察点**：

    * 事件名称：`image.generated`
    * 客户 ID：您的测试客户 ID

    <Check>
      您应该能看到每张生成的图像对应一个事件！
    </Check>
  </Step>

  <Step title="Verify billing calculations">
    让我们检查一下用量计数是否正常：

    <Frame>
      <img src="https://mintcdn.com/dodopayments/w9oVTi6CzZMAOQA3/images/usage-based/UBB-1.png?fit=max&auto=format&n=w9oVTi6CzZMAOQA3&q=85&s=7fb86a266b5aa84f281b680601bd998e" alt="" style={{ maxHeight: '500px', width: 'auto' }} width="1536" height="1252" data-path="images/usage-based/UBB-1.png" />
    </Frame>

    1. 在您的计量器中，转到 **客户** 选项卡
    2. 找到您的测试客户
    3. 检查 "消耗单位" 列
  </Step>

  <Step title="Test the billing threshold">
    让我们超过免费额度，看看计费效果：

    1. 生成 8 张更多图像（以达到总共 12 张）
    2. 再次检查您的计量器仪表板
    3. 您现在应该看到：
       * 消耗单位：12
       * 可计费单位：2（12 - 10 免费）
       * 计费金额：\$0.10

    <Check>
      **成功！** 您的基于使用量的计费运行正常。客户将根据实际图像生成使用量自动收取费用。
    </Check>
  </Step>
</Steps>

## 故障排除

常见问题及其解决方案：

<AccordionGroup>
  <Accordion title="Events not appearing in dashboard">
    **可能原因：**

    * 事件名称与计量器配置不完全匹配
    * 客户 ID 在您的帐户中不存在
    * API 密钥无效或已过期
    * 网络连接问题

    **解决方案：**

    1. 确认事件名称与计量器配置完全一致（区分大小写）
    2. 检查客户 ID 是否存在于 Dodo Payments 中
    3. 通过简单 API 调用测试 API 密钥
    4. 检查网络连接和防火墙设置
  </Accordion>
</AccordionGroup>

## 恭喜！您构建了 PixelGen AI

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

<CardGroup cols={2}>
  <Card title="Usage Meter" icon="sliders">
    创建了“Image Generation Meter”来追踪每次图像生成事件
  </Card>

  <Card title="Billing Product" icon="credit-card">
    配置了每张图像 \$0.05，且每月前 10 张图像免费
  </Card>

  <Card title="AI Application" icon="robot">
    构建了一个使用 OpenAI DALL·E 生成图像的可运行 TypeScript 应用
  </Card>

  <Card title="Automated Billing" icon="bolt">
    集成了实时事件追踪，可自动为客户计费
  </Card>
</CardGroup>
