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

# Java

> 使用 Dodo Payments Java SDK 从 Java 8+ 创建 checkout sessions、customers、subscriptions、meters 和 usage events，并使用同步和异步客户端。

Java SDK 为 Java 应用提供对 Dodo Payments REST API 的类型化访问。它在各处使用 Java 类型：`Optional` 表示可能缺失的字段，`Stream` 用于遍历结果，`CompletableFuture` 用于异步调用。

## 安装

### Maven

将依赖添加到您的`pom.xml`：

```xml pom.xml theme={null}
<dependency>
  <groupId>com.dodopayments.api</groupId>
  <artifactId>dodo-payments-java</artifactId>
  <version>1.118.0</version>
</dependency>
```

### Gradle

将依赖项添加到你的 `build.gradle.kts`：

```kotlin build.gradle.kts theme={null}
implementation("com.dodopayments.api:dodo-payments-java:1.118.0")
```

> [!TIP]
>
> SDK 版本会增加对 API 变更的支持。要查找最新版本，请查看 [Maven Central](https://central.sonatype.com/artifact/com.dodopayments.api/dodo-payments-java)。

> [!NOTE]
>
> SDK 要求 Java 8 或更高版本，因此也可在 Java 11、17 和 21 上运行。

## 快速开始

创建一个客户端，然后创建 checkout session：

```java theme={null}
import com.dodopayments.api.client.DodoPaymentsClient;
import com.dodopayments.api.client.okhttp.DodoPaymentsOkHttpClient;
import com.dodopayments.api.models.checkoutsessions.CheckoutSessionRequest;
import com.dodopayments.api.models.checkoutsessions.CheckoutSessionResponse;
import com.dodopayments.api.models.checkoutsessions.ProductItemReq;

// Configure using environment variables (DODO_PAYMENTS_API_KEY, DODO_PAYMENTS_WEBHOOK_KEY, DODO_PAYMENTS_BASE_URL)
// Or system properties (dodopayments.apiKey, dodopayments.webhookKey, dodopayments.baseUrl)
DodoPaymentsClient client = DodoPaymentsOkHttpClient.fromEnv();

CheckoutSessionRequest params = CheckoutSessionRequest.builder()
    .addProductCart(ProductItemReq.builder()
        .productId("pdt_123")
        .quantity(1)
        .build())
    .build();
    
CheckoutSessionResponse checkoutSessionResponse = client.checkoutSessions().create(params);
System.out.println(checkoutSessionResponse.sessionId());
```

`fromEnv()` 连接到 live mode，除非 `DODO_PAYMENTS_BASE_URL` 或 `dodopayments.baseUrl` 另有说明。要使用 test mode，请参阅 [Test Mode](#test-mode)。test mode API key 只能在 test mode 中使用。

> [!WARNING]
>
> 将 API keys 保存在环境变量、系统属性或 secrets manager 中。切勿将其硬编码到源代码中。

## 核心功能

> **Type Safety**
>
> 提供类型化的 request 和 response 类，以便在编译时进行检查。

> **Shared Client**
>
> 创建一个客户端并在多个请求之间重复使用：它会持有 connection pool 和 thread pools。Request 和 response 对象不可变。

> **Builder Pattern**
>
> 每个 request 类都有一个 builder，`toBuilder()` 会创建其修改后的副本。

> **Async Support**
>
> `client.async()` 返回一个客户端，其方法会返回 `CompletableFuture`。

## 配置

### 环境变量

`fromEnv()` 会读取以下环境变量或对应的系统属性。系统属性优先：

```bash .env theme={null}
DODO_PAYMENTS_API_KEY=your_api_key_here
DODO_PAYMENTS_BASE_URL=https://live.dodopayments.com
```

```java theme={null}
// Automatically reads from environment variables
DodoPaymentsClient client = DodoPaymentsOkHttpClient.fromEnv();
```

API key 来自 `DODO_PAYMENTS_API_KEY` 或 `dodopayments.apiKey`。webhook signing secret 来自 `DODO_PAYMENTS_WEBHOOK_KEY` 或 `dodopayments.webhookKey`，base URL 来自 `DODO_PAYMENTS_BASE_URL` 或 `dodopayments.baseUrl`。请创建一个客户端并重复使用，因为每个客户端都有自己的 connection pool 和 thread pools。

要验证 webhook，请将原始 request body 和 headers 传递给 `client.webhooks().unwrap(UnwrapWebhookParams.builder().body(rawBody).headers(headers).build())`，其中 `headers` 是一个 `com.dodopayments.api.core.http.Headers`。它会使用你的 webhook key 检查签名并返回解析后的 event，否则抛出 `DodoPaymentsWebhookException`。没有 headers 时，`unwrap` 不会验证签名。`client.webhooks().unsafeUnwrap(rawBody)` 会在不验证签名的情况下解析 body，因此只能将其用于测试。请参阅 [Webhooks](/developer-resources/webhooks)。

### 手动配置

在 builder 上设置每个选项：

```java theme={null}
import java.time.Duration;

DodoPaymentsClient client = DodoPaymentsOkHttpClient.builder()
    .bearerToken("your_api_key_here")
    .baseUrl("https://live.dodopayments.com")
    .maxRetries(4)
    .timeout(Duration.ofSeconds(30))
    .responseValidation(true)
    .build();
```

默认情况下，客户端会重试两次，并在 1 分钟后超时。它会重试 connection errors，以及状态为 408、409、429 或 500 及以上的 responses。要为某次调用覆盖 timeout，请将 `RequestOptions.builder().timeout(Duration.ofSeconds(30)).build()` 作为 method 的第二个参数传入。`responseValidation(true)` 会预先检查整个 response 是否符合预期类型。如果不使用它，SDK 只有在你读取类型异常的 property 时才会抛出 `DodoPaymentsInvalidDataException`。

### Test Mode

要使用 test mode（`https://test.dodopayments.com`），请在 builder 上调用 `testMode()`：

```java theme={null}
DodoPaymentsClient testClient = DodoPaymentsOkHttpClient.builder()
    .fromEnv()
    .testMode()
    .build();
```

## 常见操作

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

### 创建 Checkout Session

创建 checkout session，然后将客户重定向到返回的 checkout URL：

```java theme={null}
CheckoutSessionRequest params = CheckoutSessionRequest.builder()
    .addProductCart(ProductItemReq.builder()
        .productId("pdt_123")
        .quantity(1)
        .build())
    .returnUrl("https://yourdomain.com/return")
    .build();

CheckoutSessionResponse session = client.checkoutSessions().create(params);
System.out.println("Checkout URL: " + session.checkoutUrl().orElse(""));
```

`checkoutUrl()` 返回一个 `Optional<String>`。每个 checkout URL 只能使用一次，并会在 24 小时后过期。有关每个 session 选项的信息，请参阅 [Checkout Sessions](/developer-resources/checkout-session)。

### 管理 Customers

使用 email address、name 和 metadata 创建 customer，然后通过 ID 检索它：

```java theme={null}
import com.dodopayments.api.core.JsonValue;
import com.dodopayments.api.models.customers.Customer;
import com.dodopayments.api.models.customers.CustomerCreateParams;
import com.dodopayments.api.models.misc.Metadata;

// Create a customer
CustomerCreateParams createParams = CustomerCreateParams.builder()
    .email("customer@example.com")
    .name("John Doe")
    .metadata(Metadata.builder()
        .putAdditionalProperty("user_id", JsonValue.from("12345"))
        .build())
    .build();

Customer customer = client.customers().create(createParams);

// Retrieve customer
Customer retrieved = client.customers().retrieve("cus_123");
System.out.println("Customer: " + retrieved.name() + " (" + retrieved.email() + ")");
```

### 处理 Subscriptions

使用 payment link 创建 subscription，然后在其为 on-demand subscription 时对其收费。

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

```java theme={null}
import com.dodopayments.api.models.payments.AttachExistingCustomer;
import com.dodopayments.api.models.payments.BillingAddress;
import com.dodopayments.api.models.misc.CountryCode;
import com.dodopayments.api.models.subscriptions.SubscriptionChargeParams;
import com.dodopayments.api.models.subscriptions.SubscriptionChargeResponse;
import com.dodopayments.api.models.subscriptions.SubscriptionCreateParams;
import com.dodopayments.api.models.subscriptions.SubscriptionCreateResponse;

// Create a subscription
SubscriptionCreateParams subscriptionParams = SubscriptionCreateParams.builder()
    .billing(BillingAddress.builder()
        .city("San Francisco")
        .country(CountryCode.US)
        .state("CA")
        .street("1 Market St")
        .zipcode("94105")
        .build())
    .customer(AttachExistingCustomer.builder()
        .customerId("cus_123")
        .build())
    .productId("pdt_456")
    .quantity(1)
    .paymentLink(true)
    .returnUrl("https://yourdomain.com/return")
    .build();

SubscriptionCreateResponse subscription = client.subscriptions().create(subscriptionParams);
System.out.println("Subscription ID: " + subscription.subscriptionId());

// Charge an on-demand subscription
// product_price is in the lowest currency denomination (e.g., 2500 = $25.00 USD)
SubscriptionChargeParams chargeParams = SubscriptionChargeParams.builder()
    .subscriptionId(subscription.subscriptionId())
    .productPrice(2500)
    .build();

SubscriptionChargeResponse chargeResponse = client.subscriptions().charge(chargeParams);
System.out.println("Payment ID: " + chargeResponse.paymentId());
```

> [!NOTE]
>
> `productPrice` 使用货币的最小单位表示，例如 USD 的 cents 或 INR 的 paise。要收取 \$25.00，请传入 `2500`。

> [!TIP]
>
> `subscriptions().charge(...)` 用于 [on-demand subscriptions](/developer-resources/ondemand-subscriptions)。Dodo Payments 会按照产品的 billing schedule 自动为其他 subscriptions 计费。

## 基于用量的计费

### 配置 Meters

创建一个用于统计 events 的 meter，然后列出你的 meters。`autoPager()` 会遍历每个 meter，并在需要时获取更多页面：

```java theme={null}
import com.dodopayments.api.models.meters.*;

// Create API calls meter
MeterCreateParams apiMeterParams = MeterCreateParams.builder()
    .name("API Requests")
    .eventName("api_request")
    .aggregation(MeterAggregation.builder()
        .type(MeterAggregation.Type.COUNT)
        .build())
    .measurementUnit("calls")
    .build();

Meter apiMeter = client.meters().create(apiMeterParams);
System.out.println("Meter created: " + apiMeter.id());

// List all meters
client.meters().list()
    .autoPager()
    .forEach(m -> System.out.println("Meter: " + m.name() + " - " + m.aggregation()));
```

### 写入 Usage Events

为 customer 发送 usage event。Event metadata 的值是 `JsonValue` 对象：

```java theme={null}
import com.dodopayments.api.core.JsonValue;
import com.dodopayments.api.models.usageevents.EventInput;
import com.dodopayments.api.models.usageevents.*;
import java.time.OffsetDateTime;

// Ingest single event
UsageEventIngestParams singleEventParams = UsageEventIngestParams.builder()
    .addEvent(EventInput.builder()
        .eventId("api_call_" + System.currentTimeMillis())
        .customerId("cus_abc123")
        .eventName("api_request")
        .timestamp(OffsetDateTime.now())
        .metadata(EventInput.Metadata.builder()
            .putAdditionalProperty("endpoint", JsonValue.from("/api/v1/users"))
            .putAdditionalProperty("method", JsonValue.from("GET"))
            .putAdditionalProperty("tokens_used", JsonValue.from("150"))
            .build())
        .build())
    .build();

UsageEventIngestResponse response = client.usageEvents().ingest(singleEventParams);
System.out.println("Processed: " + response.ingestedCount());
```

`eventId` 是幂等键，因此请为每个 event 提供唯一值。时间早于当前时间 1 小时以上或晚于当前时间 5 分钟以上的 `timestamp` 会被拒绝。

### 批量写入 Events

在一个 request 中发送最多 1,000 个 events。本示例使用前一个示例中的 imports：

```java theme={null}
UsageEventIngestParams.Builder batchBuilder = UsageEventIngestParams.builder();

for (int i = 0; i < 100; i++) {
    batchBuilder.addEvent(EventInput.builder()
        .eventId("batch_event_" + i + "_" + System.currentTimeMillis())
        .customerId("cus_abc123")
        .eventName("video_transcode")
        .timestamp(OffsetDateTime.now().minusSeconds(i))
        .metadata(EventInput.Metadata.builder()
            .putAdditionalProperty("video_id", JsonValue.from("video_" + i))
            .putAdditionalProperty("duration_seconds", JsonValue.from(String.valueOf(120 + i)))
            .build())
        .build());
}

UsageEventIngestResponse batchResponse = client.usageEvents().ingest(batchBuilder.build());
System.out.println("Batch processed: " + batchResponse.ingestedCount() + " events");
```

## 错误处理

SDK 会抛出 unchecked exceptions。对于错误状态，它会抛出 `DodoPaymentsServiceException` 的子类，该类包含 `statusCode()`、`headers()` 和 `body()`。请在捕获 base class 之前，先捕获你希望处理的特定 classes：

```java theme={null}
import com.dodopayments.api.errors.*;
import com.dodopayments.api.models.payments.Payment;

try {
    Payment payment = client.payments().retrieve("pay_invalid");
} catch (NotFoundException e) {
    System.err.println("Payment not found: " + e.getMessage());
} catch (UnauthorizedException e) {
    System.err.println("Authentication failed: " + e.getMessage());
} catch (PermissionDeniedException e) {
    System.err.println("Permission denied: " + e.getMessage());
} catch (BadRequestException e) {
    System.err.println("Invalid request: " + e.getMessage());
} catch (UnprocessableEntityException e) {
    System.err.println("Validation error: " + e.getMessage());
} catch (RateLimitException e) {
    System.err.println("Rate limit exceeded: " + e.getMessage());
    // Thrown after the SDK's automatic retries are used up
} catch (InternalServerException e) {
    System.err.println("Server error: " + e.getMessage());
} catch (DodoPaymentsServiceException e) {
    System.err.println("API error: " + e.statusCode() + " - " + e.getMessage());
}
```

没有专属 class 的状态（例如 409）会抛出 `UnexpectedStatusCodeException`。网络故障会抛出 `DodoPaymentsIoException`，SDK 无法解释的 responses 会抛出 `DodoPaymentsInvalidDataException`。所有这些都继承自 `DodoPaymentsException`。

> [!TIP]
>
> SDK 会重试 connection errors，以及状态为 408、409、429 或 500 及以上的 responses，默认重试两次，并使用 exponential backoff。

## 异步操作

在客户端上调用 `async()` 以获取异步客户端。其 methods 会返回 `CompletableFuture`：

```java theme={null}
import java.util.concurrent.CompletableFuture;

CompletableFuture<CheckoutSessionResponse> future = client.async()
    .checkoutSessions()
    .create(params);

// Handle response asynchronously
future.thenAccept(response -> {
    System.out.println("Session created: " + response.sessionId());
}).exceptionally(ex -> {
    System.err.println("Error: " + ex.getMessage());
    return null;
});
```

要从一开始就创建异步客户端，请使用 `DodoPaymentsOkHttpClientAsync.fromEnv()`。

## Spring Boot 集成

### 配置类

将一个客户端注册为 bean，并通过 property 选择环境：

```java theme={null}
import com.dodopayments.api.client.DodoPaymentsClient;
import com.dodopayments.api.client.okhttp.DodoPaymentsOkHttpClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DodoPaymentsConfig {
    
    @Value("${dodo.api.key}")
    private String apiKey;
    
    @Value("${dodo.environment:test}")
    private String environment;
    
    @Bean
    public DodoPaymentsClient dodoPayments() {
        return DodoPaymentsOkHttpClient.builder()
            .bearerToken(apiKey)
            .baseUrl(environment.equals("live") 
                ? "https://live.dodopayments.com" 
                : "https://test.dodopayments.com")
            .build();
    }
}
```

### 服务层

将客户端注入 service：

```java theme={null}
import com.dodopayments.api.client.DodoPaymentsClient;
import com.dodopayments.api.models.checkoutsessions.*;
import java.util.List;
import org.springframework.stereotype.Service;

@Service
public class PaymentService {
    
    private final DodoPaymentsClient client;
    
    public PaymentService(DodoPaymentsClient client) {
        this.client = client;
    }
    
    public CheckoutSessionResponse createCheckout(List<ProductItemReq> items) {
        CheckoutSessionRequest params = CheckoutSessionRequest.builder()
            .productCart(items)
            .returnUrl("https://yourdomain.com/return")
            .build();
            
        return client.checkoutSessions().create(params);
    }
}
```

## 资源

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

> **[API Reference](/api-reference/introduction)**
>
> 每个 endpoint、parameter 和 response。

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

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

## 支持

如需 Java SDK 帮助：

* **Discord**：加入 [community server](https://discord.gg/bYqAp4ayYh) 获取实时帮助。
* **Email**：联系 [support@dodopayments.com](mailto:support@dodopayments.com)。
* **GitHub**：在 [repository](https://github.com/dodopayments/dodopayments-java) 中提交 issue。

## 贡献

如需贡献，请阅读 [contributing guidelines](https://github.com/dodopayments/dodopayments-java/blob/main/CONTRIBUTING.md)。


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