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

# Go

> 使用 Dodo Payments Go SDK 创建结账会话、客户、订阅和用量事件，并支持 context、重试和中间件。

Go SDK 为 Go 应用提供对 Dodo Payments REST API 的类型化访问。每个方法都接收一个 `context.Context`，请求参数使用 `Field` 包装器，以区分零值和省略的字段；你还可以为每个请求添加中间件。

## 安装

将模块添加到项目中：

```bash theme={null}
go get github.com/dodopayments/dodopayments-go
```

固定到特定版本：

```bash theme={null}
go get -u 'github.com/dodopayments/dodopayments-go@v1.118.0'
```

> [!NOTE]
>
> SDK 要求 Go 1.22 或更高版本。

## 快速开始

创建客户端，然后创建结账会话：

```go expandable theme={null}
package main

import (
	"context"
	"fmt"
	"os"

	"github.com/dodopayments/dodopayments-go"
	"github.com/dodopayments/dodopayments-go/option"
)

func main() {
	client := dodopayments.NewClient(
		option.WithBearerToken(os.Getenv("DODO_PAYMENTS_API_KEY")), // This is the default and can be omitted
		option.WithEnvironmentTestMode(),                            // defaults to option.WithEnvironmentLiveMode()
	)

	checkoutSessionResponse, err := client.CheckoutSessions.New(context.TODO(), dodopayments.CheckoutSessionNewParams{
		CheckoutSessionRequest: dodopayments.CheckoutSessionRequestParam{
			ProductCart: dodopayments.F([]dodopayments.ProductItemReqParam{{
				ProductID: dodopayments.F("pdt_123"),
				Quantity:  dodopayments.F(int64(1)),
			}}),
		},
	})
	if err != nil {
		panic(err.Error())
	}
	fmt.Printf("Session ID: %s\n", checkoutSessionResponse.SessionID)
}
```

如果省略 `option.WithBearerToken`，`NewClient` 会读取 `DODO_PAYMENTS_API_KEY` 环境变量。如果省略 `option.WithEnvironmentTestMode()`，客户端会连接到 live mode。test mode API key 只能在 test mode 中使用。

> [!WARNING]
>
> 请将 API key 保存在环境变量或 secrets manager 中。切勿将其硬编码到源代码中。

## 核心功能

> **Context Support**
>
> 每个方法都接收一个 `context.Context`，用于取消操作和设置超时。

> **Strong Typing**
>
> 类型化的请求参数和响应结构体可用于编译时检查。

> **Middleware**
>
> 使用 `option.WithMiddleware` 添加中间件，以实现日志记录、指标采集和自定义逻辑。

> **Goroutine Safe**
>
> 在多个 goroutine 之间共享同一个客户端。

## 配置

`NewClient` 会从环境中读取 `DODO_PAYMENTS_API_KEY`、`DODO_PAYMENTS_WEBHOOK_KEY`（你的 webhook signing secret）和 `DODO_PAYMENTS_BASE_URL`。你传入的选项（例如 `option.WithBearerToken`、`option.WithWebhookKey` 和 `option.WithBaseURL`）会覆盖这些值。

要验证 webhook，请将原始请求正文和 headers 传递给 `client.Webhooks.Unwrap(rawBody, r.Header)`。它会使用你的 webhook key 检查签名，并返回解析后的事件。`client.Webhooks.UnsafeUnwrap(rawBody)` 会在不验证的情况下解析正文，因此只能用于测试。请参阅 [Webhooks](/developer-resources/webhooks)。

本页示例使用 [Quick Start](#quick-start) 中的 `client`。

### Context 和超时

请求默认不会超时。context deadline 会限制整个调用过程，包括重试。要限制每次尝试的时长，请添加 `option.WithRequestTimeout()`：

```go expandable theme={null}
import (
	"context"
	"log"
	"time"
	
	"github.com/dodopayments/dodopayments-go"
	"github.com/dodopayments/dodopayments-go/option"
)

// Create a context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

// Note: POST /payments is deprecated — prefer checkout sessions for new
// integrations. Shown here only to illustrate context handling.
_, err := client.Payments.New(ctx, dodopayments.PaymentNewParams{
	Billing: dodopayments.F(dodopayments.BillingAddressParam{
		Country: dodopayments.F(dodopayments.CountryCodeUs),
		City:    dodopayments.F("San Francisco"),
		State:   dodopayments.F("CA"),
		Street:  dodopayments.F("1 Market St"),
		Zipcode: dodopayments.F("94105"),
	}),
	Customer: dodopayments.F[dodopayments.CustomerRequestUnionParam](
		dodopayments.AttachExistingCustomerParam{
			CustomerID: dodopayments.F("cus_123"),
		},
	),
	ProductCart: dodopayments.F([]dodopayments.OneTimeProductCartItemParam{{
		ProductID: dodopayments.F("pdt_456"),
		Quantity:  dodopayments.F(int64(1)),
	}}),
}, option.WithRequestTimeout(5*time.Second)) // Per-attempt timeout
if err != nil {
	if ctx.Err() == context.DeadlineExceeded {
		log.Println("Request timed out")
	} else {
		log.Fatal(err)
	}
}
```

### 重试配置

SDK 会重试连接错误，以及状态为 408、409、429 或 500 及以上的响应。默认重试两次，并使用指数退避。请在客户端或单个请求上设置 `option.WithMaxRetries`：

```go theme={null}
// Configure default for all requests (default is 2)
client := dodopayments.NewClient(
	option.WithMaxRetries(0), // disable retries
)

// Override per-request
client.CheckoutSessions.New(
	context.TODO(),
	dodopayments.CheckoutSessionNewParams{
		CheckoutSessionRequest: dodopayments.CheckoutSessionRequestParam{
			ProductCart: dodopayments.F([]dodopayments.ProductItemReqParam{{
				ProductID: dodopayments.F("pdt_123"),
				Quantity:  dodopayments.F(int64(1)),
			}}),
		},
	},
	option.WithMaxRetries(5),
)
```

## 常见操作

本节中的示例同样使用 context，例如 `ctx := context.Background()`。

### 创建结账会话

创建结账会话，然后将客户重定向到返回的 `CheckoutURL`：

```go theme={null}
session, err := client.CheckoutSessions.New(ctx, dodopayments.CheckoutSessionNewParams{
	CheckoutSessionRequest: dodopayments.CheckoutSessionRequestParam{
		ProductCart: dodopayments.F([]dodopayments.ProductItemReqParam{{
			ProductID: dodopayments.F("pdt_123"),
			Quantity:  dodopayments.F(int64(1)),
		}}),
		ReturnURL: dodopayments.F("https://yourdomain.com/return"),
	},
})
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Checkout URL: %s\n", session.CheckoutURL)
```

每个结账 URL 只能使用一次，并会在 24 小时后过期。有关每个会话选项，请参阅 [Checkout Sessions](/developer-resources/checkout-session)。

### 管理客户

使用电子邮件地址和姓名创建客户，然后通过 ID 获取该客户。Metadata 值使用 `shared` 包中的 union types：

```go theme={null}
import "github.com/dodopayments/dodopayments-go/shared"

// Create a customer
customer, err := client.Customers.New(ctx, dodopayments.CustomerNewParams{
	Email: dodopayments.F("customer@example.com"),
	Name:  dodopayments.F("John Doe"),
	Metadata: dodopayments.F(dodopayments.MetadataParam{
		"user_id": shared.UnionString("12345"),
	}),
})
if err != nil {
	log.Fatal(err)
}

// Retrieve customer
customer, err = client.Customers.Get(ctx, "cus_123")
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Customer: %s (%s)\n", customer.Name, customer.Email)
```

### 处理订阅

创建订阅、为按需订阅收费，并读取订阅的用量历史记录。

> [!WARNING]
>
> `POST /subscriptions`（SDK 的 `Subscriptions.New` 方法）已被 **弃用**。它仍可用于现有集成，但新集成应通过 [Checkout Session](/developer-resources/checkout-session) 创建订阅。

```go expandable theme={null}
import "time"

// Create a subscription
subscription, err := client.Subscriptions.New(ctx, dodopayments.SubscriptionNewParams{
	Billing: dodopayments.F(dodopayments.BillingAddressParam{
		Country: dodopayments.F(dodopayments.CountryCodeUs),
		City:    dodopayments.F("San Francisco"),
		State:   dodopayments.F("CA"),
		Street:  dodopayments.F("1 Market St"),
		Zipcode: dodopayments.F("94105"),
	}),
	Customer: dodopayments.F[dodopayments.CustomerRequestUnionParam](
		dodopayments.AttachExistingCustomerParam{
			CustomerID: dodopayments.F("cus_123"),
		},
	),
	ProductID: dodopayments.F("pdt_456"),
	Quantity:  dodopayments.F(int64(1)),
})
if err != nil {
	log.Fatal(err)
}

// Charge an on-demand subscription
// ProductPrice is in the lowest currency denomination (e.g., 2500 = $25.00 USD)
chargeResponse, err := client.Subscriptions.Charge(ctx, subscription.SubscriptionID,
	dodopayments.SubscriptionChargeParams{
		ProductPrice: dodopayments.F(int64(2500)),
	},
)
if err != nil {
	log.Fatal(err)
}
fmt.Printf("Payment ID: %s\n", chargeResponse.PaymentID)

// Get usage history (for metered subscriptions)
usageHistory, err := client.Subscriptions.GetUsageHistory(
	ctx,
	subscription.SubscriptionID,
	dodopayments.SubscriptionGetUsageHistoryParams{
		StartDate: dodopayments.F(time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)),
		EndDate:   dodopayments.F(time.Date(2024, 3, 31, 23, 59, 59, 0, time.UTC)),
	},
)
if err != nil {
	log.Fatal(err)
}
fmt.Printf("Usage periods on this page: %d\n", len(usageHistory.Items))
```

> [!NOTE]
>
> `Billing` 只需要 `Country`，即两个字母的 ISO 国家代码。`Customer` 是一个 `CustomerRequestUnionParam`：对于现有客户，请传递 `AttachExistingCustomerParam{CustomerID: ...}`；如需创建客户，请传递 `NewCustomerParam{Email: ..., Name: ...}`。`Charge` 用于[按需订阅](/developer-resources/ondemand-subscriptions)，而 `ProductPrice` 使用最小货币单位表示金额。`GetUsageHistory` 返回一页结果；`GetUsageHistoryAutoPaging` 遍历所有页面。

## 基于用量的计费

### 摄取用量事件

为客户发送用量事件：

```go theme={null}
import (
	"time"

	"github.com/dodopayments/dodopayments-go"
)

response, err := client.UsageEvents.Ingest(ctx, dodopayments.UsageEventIngestParams{
	Events: dodopayments.F([]dodopayments.EventInputParam{{
		EventID:    dodopayments.F("api_call_12345"),
		CustomerID: dodopayments.F("cus_abc123"),
		EventName:  dodopayments.F("api_request"),
		Timestamp:  dodopayments.F(time.Now()),
	}}),
})
if err != nil {
	log.Fatal(err)
}
fmt.Printf("Ingested %d events\n", response.IngestedCount)
```

> [!NOTE]
>
> `EventID` 是幂等键，因此请为每个事件提供唯一值。如果同一个 `EventID` 在一次请求中出现两次，整个请求都会被拒绝。如果某个 `EventID` 已经被摄取，则会忽略新事件。一次请求最多接受 1,000 个事件。`Timestamp` 默认为当前时间；如果时间早于当前时间超过 1 小时，或晚于当前时间超过 5 分钟，则会被拒绝。

### 列出用量事件

按客户和事件名称筛选并列出事件：

```go theme={null}
// List events with filters
params := dodopayments.UsageEventListParams{
	CustomerID: dodopayments.F("cus_abc123"),
	EventName:  dodopayments.F("api_request"),
}

events, err := client.UsageEvents.List(ctx, params)
if err != nil {
	log.Fatal(err)
}

for _, event := range events.Items {
	fmt.Printf("Event %s: %s at %s\n", event.EventID, event.EventName, event.Timestamp)
}
```

`List` 返回一页结果。要遍历所有页面，请调用 `client.UsageEvents.ListAutoPaging(ctx, params)`，并使用 `iter.Next()`、`iter.Current()` 和 `iter.Err()` 循环处理。其他 list 方法也有相同的 `AutoPaging` 变体，并且每个页面都有一个 `GetNextPage()` 方法。

## 错误处理

当 API 返回非成功状态码时，SDK 会返回 `*dodopayments.Error` 类型的错误。它包含 `StatusCode`、`*http.Request` 和 `*http.Response`，以及错误正文的 JSON。使用 `errors.As` 检查错误，并根据 `StatusCode` 分支来处理特定情况：

```go expandable theme={null}
// Note: POST /payments is deprecated — prefer checkout sessions for new
// integrations. Shown here only to illustrate error handling.
_, err := client.Payments.New(ctx, dodopayments.PaymentNewParams{
	Billing: dodopayments.F(dodopayments.BillingAddressParam{
		Country: dodopayments.F(dodopayments.CountryCodeUs),
		City:    dodopayments.F("San Francisco"),
		State:   dodopayments.F("CA"),
		Street:  dodopayments.F("1 Market St"),
		Zipcode: dodopayments.F("94105"),
	}),
	Customer: dodopayments.F[dodopayments.CustomerRequestUnionParam](
		dodopayments.AttachExistingCustomerParam{CustomerID: dodopayments.F("cus_123")},
	),
	ProductCart: dodopayments.F([]dodopayments.OneTimeProductCartItemParam{{
		ProductID: dodopayments.F("pdt_456"),
		Quantity:  dodopayments.F(int64(1)),
	}}),
})
if err != nil {
	var apiErr *dodopayments.Error
	if errors.As(err, &apiErr) {
		fmt.Printf("Status Code: %d\n", apiErr.StatusCode)
		fmt.Println(string(apiErr.DumpResponse(true))) // Serialized HTTP response

		// Handle specific status codes
		switch apiErr.StatusCode {
		case 401:
			log.Println("Authentication failed")
		case 422:
			log.Println("Invalid request parameters")
		case 429:
			log.Println("Rate limit exceeded")
		default:
			log.Printf("API error: %s", apiErr.Error())
		}
	} else {
		log.Fatal(err)
	}
}
```

其他错误会以未包装的形式返回。例如，如果 HTTP transport 失败，你可能会收到一个 `*url.Error`，其中包装了一个 `*net.OpError`。`apiErr.DumpRequest(true)` 返回序列化后的请求。

## 中间件

使用 `option.WithMiddleware` 添加中间件。中间件会接收每个请求，以及一个用于发送请求的 `next` 函数：

```go theme={null}
func Logger(req *http.Request, next option.MiddlewareNext) (res *http.Response, err error) {
	// Before the request
	start := time.Now()
	log.Printf("Request: %s %s\n", req.Method, req.URL)

	// Forward the request to the next handler
	res, err = next(req)

	// After the request (res is nil when err is not nil)
	if err != nil {
		log.Printf("Request failed after %v: %v\n", time.Since(start), err)
		return res, err
	}
	log.Printf("Response: %d in %v\n", res.StatusCode, time.Since(start))

	return res, err
}

client := dodopayments.NewClient(
	option.WithMiddleware(Logger),
)
```

在一次 `option.WithMiddleware` 调用中传入的多个中间件会从左到右运行。传递给 `NewClient` 的中间件会先于传递给单个请求的中间件运行。

## 并发

客户端可安全地并发使用，因此你可以在多个 goroutine 之间共享同一个客户端：

```go theme={null}
package main

import (
	"context"
	"sync"
	"log"

	"github.com/dodopayments/dodopayments-go"
)

func main() {
	client := dodopayments.NewClient()
	
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(idx int) {
			defer wg.Done()
			
			// Note: POST /payments is deprecated — prefer checkout sessions
			// for new integrations. Shown here only to illustrate concurrency.
			payment, err := client.Payments.New(context.Background(), dodopayments.PaymentNewParams{
				Billing: dodopayments.F(dodopayments.BillingAddressParam{
					Country: dodopayments.F(dodopayments.CountryCodeUs),
					City:    dodopayments.F("San Francisco"),
					State:   dodopayments.F("CA"),
					Street:  dodopayments.F("1 Market St"),
					Zipcode: dodopayments.F("94105"),
				}),
				Customer: dodopayments.F[dodopayments.CustomerRequestUnionParam](
					dodopayments.AttachExistingCustomerParam{CustomerID: dodopayments.F("cus_123")},
				),
				ProductCart: dodopayments.F([]dodopayments.OneTimeProductCartItemParam{{
					ProductID: dodopayments.F("pdt_456"),
					Quantity:  dodopayments.F(int64(1)),
				}}),
			})
			if err != nil {
				log.Printf("Failed to create payment %d: %v", idx, err)
				return
			}
			
			log.Printf("Created payment %d: %s", idx, payment.PaymentID)
		}(i)
	}
	
	wg.Wait()
}
```

## 资源

> **[GitHub Repository](https://github.com/dodopayments/dodopayments-go)**
>
> 源代码、版本发布信息和完整的方法列表。

> **[API Reference](/api-reference/introduction)**
>
> 每个 endpoint、参数和响应。

> **[Discord Community](https://discord.gg/bYqAp4ayYh)**
>
> 提出问题并与其他开发者交流。

> **[Report Issues](https://github.com/dodopayments/dodopayments-go/issues)**
>
> 报告 bug 或请求新功能。

## 支持

如需 Go SDK 方面的帮助：

* **Discord**：加入[社区服务器](https://discord.gg/bYqAp4ayYh)获取实时帮助。
* **Email**：联系 [support@dodopayments.com](mailto:support@dodopayments.com)。
* **GitHub**：在[代码仓库](https://github.com/dodopayments/dodopayments-go)中提交 issue。

## 贡献

如需贡献代码，请阅读[贡献指南](https://github.com/dodopayments/dodopayments-go/blob/main/CONTRIBUTING.md)。


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