跳转到主要内容
在本教程中,您将构建 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 仪表板中创建一个计量器,以跟踪我们的服务生成的每张图像。可以将其视为跟踪可计费事件的“计数器”。
我们要构建的内容:一个名为 “图像生成计量器” 的计量器,每当有人使用我们的服务生成图像时,它都会计数。
1

打开计量器部分

  1. 登录到您的 Dodo Payments 仪表板
  2. 在左侧边栏中点击 计量器
  3. 点击 创建计量器 按钮
您应该会看到一个表单,我们将在其中配置图像生成跟踪。
2

填写基本计量器信息

现在我们将输入 PixelGen AI 服务的具体细节:计量器名称:准确复制并粘贴 → Image Generation Meter描述:复制此内容 → Tracks each AI image generation request made by customers using our DALL-E powered service事件名称:这非常重要 - 精确复制 → image.generated
事件名称 image.generated 必须与我们稍后从应用程序代码发送的内容完全匹配。事件名称区分大小写!
3

配置我们如何计数图像

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

添加质量过滤

我们希望确保只计数合法图像(而不是测试运行或失败):
  1. 启用事件过滤:将此选项切换为 开启
  2. 过滤逻辑:选择 (这意味着“如果这些条件中的任何一个为真,则计数”)
  3. 添加第一个条件
    • 属性键:quality
    • 比较器:equals
    • 值:standard
  4. 点击“添加条件” 以添加第二个条件:
    • 属性键:quality
    • 比较器:equals
    • 值:hd
此设置意味着我们只会计数质量为 “标准” 或 “高清” 的事件 - 过滤掉任何测试事件或格式错误的请求。
5

创建您的计量器

  1. 仔细检查所有设置是否与上述值匹配
  2. 点击 创建计量器
计量器已创建! 您的 “图像生成计量器” 现在可以开始计数图像生成。接下来,我们将其连接到计费产品。

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

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

导航到产品

  1. 在您的 Dodo Payments 仪表板中,点击左侧边栏中的 产品
  2. 点击 创建产品
  3. 选择 基于使用的 作为产品类型
这告诉 Dodo Payments 计费将基于计量器的使用,而不是固定订阅。
2

输入产品详细信息

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

连接您的计量器

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

设置您的定价

在这里我们定义我们的商业模型:
每单位价格:输入 → 0.05(这是每张图像 $0.05)免费阈值:输入 → 10(客户每月获得 10 张免费图像)
计费如何工作:如果客户在一个月内生成 25 张图像,他们将被收取 15 张图像的费用(25 - 10 免费)= 15 × 0.05=0.05 = 0.75
5

保存您的产品

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

第三步:进行测试购买

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

获取您的支付链接

  1. 在您的 Dodo Payments 仪表板中,转到 产品
  2. 找到您的 “PixelGen AI - 图像生成” 产品
  3. 点击产品旁边的 分享 按钮
  4. 复制出现的支付链接
支付链接看起来像: https://test.checkout.dodopayments.com/buy/pdt_IgPWlRsfpbPd5jQKezzW1?quantity=1
2

完成测试购买

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

找到您的客户 ID

  1. 返回到您的 Dodo Payments 仪表板
  2. 在左侧边栏中导航到 客户
  3. 找到您刚刚创建的客户(使用测试电子邮件)
  4. 复制客户 ID - 它看起来像 cus_abc123def456
保存此客户 ID - 我们将在示例应用程序代码中硬编码它,以确保事件被正确跟踪。

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

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

设置您的项目

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

安装依赖项

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

创建主应用程序

创建一个名为 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

设置您的环境

首先,确保您已配置好所有内容:
  1. 在您的 .env 文件中创建一个 pixelgen-ai 目录
  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

生成您的第一个测试图像

当应用程序启动时,您将看到:
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...
如果您看到 “使用事件成功发送”,则您的计费集成正常工作!
3

生成更多图像

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

检查您的 Dodo Payments 仪表板

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

验证计费计算

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

测试计费阈值

让我们超过免费层以查看计费的实际效果:
  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 图像生成的基于使用计费的代码片段!以下是您完成的内容:

使用计量器

创建了 “图像生成计量器”,跟踪每个图像生成事件

计费产品

配置了每张图像 $0.05 的定价,每月 10 张免费图像

AI 应用程序

构建了一个使用 OpenAI 的 DALL-E 生成图像的工作 TypeScript 应用程序

自动计费

集成了实时事件跟踪,自动向客户收费