> ## 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.

# Inngest

> 从 Dodo Payments 的支付、订阅和争议事件触发 Inngest 无服务器函数和后台任务。

## 介绍

在支付事件发生时运行 Inngest 函数和后台任务。Dodo Payments 会将每个事件发送到 Inngest，您的函数可以在其中发送通知、更新数据库，并借助 Inngest 的重试和错误处理功能运行多步骤工作流。

> [!NOTE]
>
> 您需要一个 Inngest webhook URL。您可以在 Inngest 控制台的 **Manage → Webhooks** 下创建该 URL。

## 开始使用

**1. Open the Webhook Section**

在 Dodo Payments 控制面板中，前往 **Developer → Webhooks**，然后点击 **Add endpoint**。

![Add endpoint dialog with Inngest selected in the Integration dropdown](https://mintcdn.com/dodopayments/DL_ADtkdH7ph5YST/images/integrations/inngest.png?fit=max\&auto=format\&n=DL_ADtkdH7ph5YST\&q=85\&s=aec7e984d42cf73845e6f5bd9386e68c)

**2. Select Inngest**

在 **Integration** 中选择 **Inngest**。在 **Subscribed events** 下，选择应触发函数的事件。

**3. Create an Inngest Webhook**

在 Inngest 控制台中，打开 **Manage** 标签页并选择 **Webhooks**。创建一个 webhook 并复制其 URL。Inngest 会在每个请求上运行 webhook 的转换函数，因此请确认该函数保留了您的函数所需的 `name` 和 `data` 字段。

**4. Paste Webhook URL**

将 Inngest webhook URL 粘贴到 **Endpoint URL** 中。

**5. Configure Transformation**

在 **Transformation code** 中编辑代码，以便为 Inngest 函数格式化事件。请参阅[下面的示例](#transformation-code-examples)。

**6. Test & Create**

在 **Test this code** 下选择事件类型，然后点击 **Simulate**，使用示例负载运行转换。然后点击 **Create endpoint** 以激活集成。

**7. Done**

> [!TIP]
>
> Dodo Payments 事件现在会触发您的 Inngest 函数。

## 转换代码示例

每个示例都会将 `webhook.payload` 重写为 Inngest 事件。`name` 是函数触发所依据的事件，`data` 存储事件详情，`user` 存储客户信息，`ts` 是以毫秒为单位的事件时间，这是 Inngest 的事件格式所要求的。

### 基本事件负载

```javascript basic_event.js icon="js" expandable theme={null}
function handler(webhook) {
  if (webhook.eventType === "payment.succeeded") {
    const p = webhook.payload.data;
    webhook.payload = {
      name: "payment.succeeded",
      data: {
        payment_id: p.payment_id,
        amount: (p.total_amount / 100).toFixed(2),
        currency: p.currency || "USD",
        customer_email: p.customer.email,
        customer_name: p.customer.name,
        payment_method: p.payment_method || "unknown"
      },
      user: {
        email: p.customer.email
      },
      ts: new Date(webhook.payload.timestamp).getTime()
    };
  }
  return webhook;
}
```

### 订阅事件处理程序

```javascript subscription_event.js icon="js" expandable theme={null}
function handler(webhook) {
  const s = webhook.payload.data;
  switch (webhook.eventType) {
    case "subscription.active":
      webhook.payload = {
        name: "subscription.started",
        data: {
          subscription_id: s.subscription_id,
          customer_email: s.customer.email,
          customer_name: s.customer.name,
          product_id: s.product_id,
          amount: (s.recurring_pre_tax_amount / 100).toFixed(2),
          frequency: s.payment_frequency_interval,
          next_billing: s.next_billing_date
        },
        user: {
          email: s.customer.email
        },
        ts: new Date(webhook.payload.timestamp).getTime()
      };
      break;
    case "subscription.cancelled":
      webhook.payload = {
        name: "subscription.cancelled",
        data: {
          subscription_id: s.subscription_id,
          customer_email: s.customer.email,
          cancelled_at: s.cancelled_at,
          cancel_at_next_billing: s.cancel_at_next_billing_date
        },
        user: {
          email: s.customer.email
        },
        ts: new Date(webhook.payload.timestamp).getTime()
      };
      break;
  }
  return webhook;
}
```

### 争议事件处理程序

此处理程序会转发每个 `dispute.*` 事件，并将 `dispute.opened` 标记为紧急。争议负载不包含客户详情，因此不会发送 `user` 对象。

```javascript dispute_event.js icon="js" expandable theme={null}
function handler(webhook) {
  if (webhook.eventType.startsWith("dispute.")) {
    const d = webhook.payload.data;
    webhook.payload = {
      name: webhook.eventType,
      data: {
        dispute_id: d.dispute_id,
        payment_id: d.payment_id,
        amount: (d.amount / 100).toFixed(2),
        status: d.dispute_status,
        stage: d.dispute_stage,
        remarks: d.remarks || "",
        urgent: webhook.eventType === "dispute.opened"
      },
      ts: new Date(webhook.payload.timestamp).getTime()
    };
  }
  return webhook;
}
```

## Inngest 常见使用场景

> **Payment Processing**
>
> * 发送确认邮件
> * 更新客户记录
> * 处理退款
> * 生成发票
> * 更新库存

> **Subscription Management**
>
> * 欢迎新订阅者
> * 处理取消
> * 发送续订提醒
> * 更新计费周期
> * 处理失败的支付

> **Analytics & Reporting**
>
> * 更新收入指标
> * 跟踪客户行为
> * 生成报告
> * 将数据同步到分析平台
> * 计算流失率

## 提示

* 使用描述性事件名称。Inngest 建议在名称前加上来源前缀，例如 `dodo/payment.succeeded`。
* 将客户详情放入 `user`，Inngest 会对静态数据进行加密。
* 从 webhook 的 `timestamp` 设置 `ts`，并使用毫秒为单位，这样事件可以保留原始顺序和时间。
* 在不同事件之间使用相同的数据结构。
* 使用 Inngest 的重试和错误处理功能。

## 故障排除

> **Functions not triggering**
>
> * 检查 endpoint URL 是否与您的 Inngest webhook URL 匹配，并确认 webhook 处于活动状态。
> * 检查您的 Inngest 函数是否已部署并处于活动状态。
> * 检查事件名称是否与函数触发器匹配。
> * 查看 Inngest 函数日志中的错误。

> **Data not received correctly**
>
> * 检查负载结构是否符合 Inngest 事件格式。
> * 检查事件名称的格式是否正确。
> * 检查 Inngest webhook 的转换函数是否保留了 Dodo Payments 转换所设置的字段。
> * 检查是否包含所有必需字段。
> * 使用 Inngest 的 webhook 测试工具进行测试。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.