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

# PHP

> Use the Dodo Payments PHP SDK to create checkout sessions, customers, and subscriptions from PHP 8.1+, with typed value objects, retries, and pagination.

The PHP SDK gives PHP 8.1+ applications access to the Dodo Payments REST API. Methods take named parameters, responses are typed objects, and Composer loads the SDK with PSR-4 autoloading.

## Installation

Install the SDK with Composer:

```bash theme={null}
composer require "dodopayments/client 6.27.0"
```

<Info>
  The SDK requires PHP 8.1.0 or later and Composer. It sends requests through a PSR-18 HTTP client in your project, such as Guzzle, which it finds with `php-http/discovery`.
</Info>

## Quick Start

Create a client, then create a checkout session:

```php theme={null}
<?php

use Dodopayments\Client;

$client = new Client(
  bearerToken: getenv('DODO_PAYMENTS_API_KEY') ?: 'My Bearer Token',
  baseUrl: 'https://test.dodopayments.com',
);

$checkoutSessionResponse = $client->checkoutSessions->create(
  productCart: [["productID" => "pdt_123", "quantity" => 1]]
);

var_dump($checkoutSessionResponse->sessionID);
```

If you omit `bearerToken`, the client reads the `DODO_PAYMENTS_API_KEY` environment variable. If you omit `baseUrl`, the client reads `DODO_PAYMENTS_BASE_URL`, and connects to live mode (`https://live.dodopayments.com`) when that isn't set either. A test mode API key works only with the test mode URL, `https://test.dodopayments.com`.

<Warning>
  Keep API keys in environment variables or a secrets manager. Never expose them in
  your codebase or commit them to version control.
</Warning>

## Core Features

<CardGroup cols={2}>
  <Card title="PSR-4 Compliant" icon="check">
    Composer loads the `Dodopayments` namespace with PSR-4 autoloading.
  </Card>

  <Card title="Modern PHP" icon="php">
    Built for PHP 8.1 or later, with typed parameters and strict types.
  </Card>

  <Card title="Extensive Testing" icon="vial">
    The SDK repository includes a test suite for the API services.
  </Card>

  <Card title="Exception Handling" icon="shield-check">
    An exception class for each HTTP error status, plus timeout and connection exceptions.
  </Card>
</CardGroup>

## Value Objects

Methods take named parameters, and parameters that have a default value must be passed by name. To build a value object, use its static `with` constructor with named parameters:

```php theme={null}
<?php

use Dodopayments\Payments\AttachExistingCustomer;

// Recommended: Use static 'with' constructor with named parameters
$customer = AttachExistingCustomer::with(customerID: "cus_123");
```

Each value object also has a builder:

```php theme={null}
<?php

use Dodopayments\Payments\AttachExistingCustomer;

// Alternative: Use builder pattern
$customer = (new AttachExistingCustomer)->withCustomerID("cus_123");
```

Methods also accept plain arrays with the same camelCase keys, such as `["productID" => "pdt_123", "quantity" => 1]`. Response properties use camelCase names too, for example `$session->checkoutURL`.

## Configuration

The `Client` constructor takes `bearerToken`, `webhookKey`, `environment` (for example, `'test_mode'`), `baseUrl`, and `requestOptions`. When you omit them, it reads `DODO_PAYMENTS_API_KEY`, `DODO_PAYMENTS_WEBHOOK_KEY` (your webhook signing secret), and `DODO_PAYMENTS_BASE_URL` from the environment.

To verify a webhook, pass the raw request body and headers to `$client->webhooks->unwrap($body, headers: $headers)`. It checks the signature with your webhook key, returns the parsed event, and throws `WebhookException` if the check fails. If you omit `headers`, `unwrap` doesn't verify the signature. `$client->webhooks->unsafeUnwrap($body)` parses the body without verifying it, so use it only for testing. See [Webhooks](/developer-resources/webhooks).

### Retry Configuration

The SDK retries some errors twice by default, with a short exponential backoff. These errors trigger a retry:

* Connection errors (network connectivity problems)
* 408 Request Timeout
* 409 Conflict
* 429 Rate Limit
* 500+ Internal errors
* Timeouts

Set `maxRetries` in `requestOptions`, on the client or on a single request:

```php theme={null}
<?php

use Dodopayments\Client;


// Configure default for all requests (disable retries)
$client = new Client(requestOptions: ['maxRetries' => 0]);

// Or, configure per-request
$result = $client->checkoutSessions->create(
  productCart: [["productID" => "pdt_123", "quantity" => 1]],
  requestOptions: ['maxRetries' => 5],
);
```

Requests time out after 60 seconds by default. To change the limit, set `timeout`, in seconds, in the same `requestOptions` array.

## Common Operations

The examples in this section use the `$client` from [Quick Start](#quick-start).

### Create a Checkout Session

Create a checkout session, then redirect the customer to the returned `checkoutURL`:

```php theme={null}
$session = $client->checkoutSessions->create(
  productCart: [
    ["productID" => "pdt_123", "quantity" => 1]
  ],
  returnURL: "https://yourdomain.com/return"
);

header('Location: ' . $session->checkoutURL);
```

Each checkout URL works once and expires after 24 hours. For every session option, see [Checkout Sessions](/developer-resources/checkout-session).

### Manage Customers

Create a customer with an email address and name, then retrieve it by ID:

```php theme={null}
// Create a customer
$customer = $client->customers->create(
  email: "customer@example.com",
  name: "John Doe",
  metadata: [
    "user_id" => "12345"
  ]
);

// Retrieve customer
$customer = $client->customers->retrieve("cus_123");
echo "Customer: {$customer->name} ({$customer->email})";
```

### Handle Subscriptions

Create a subscription, then charge it if it's an on-demand subscription.

<Warning>
  `POST /subscriptions` (the SDK's `subscriptions->create` method) is **deprecated**. It still works for existing integrations, but new integrations should create subscriptions through a [Checkout Session](/developer-resources/checkout-session).
</Warning>

```php theme={null}
use Dodopayments\Payments\AttachExistingCustomer;
use Dodopayments\Payments\BillingAddress;

// Create a subscription
$subscription = $client->subscriptions->create(
  billing: BillingAddress::with(
    country: 'US',
    city: 'San Francisco',
    state: 'CA',
    street: '1 Market St',
    zipcode: '94105',
  ),
  customer: AttachExistingCustomer::with(customerID: 'cus_123'),
  productID: 'pdt_456',
  quantity: 1,
);

// Charge an on-demand subscription
// productPrice is in the lowest currency denomination (e.g., 2500 = $25.00 USD)
$charge = $client->subscriptions->charge(
  $subscription->subscriptionID,
  productPrice: 2500,
);
```

<Info>
  `billing` requires only `country`, a two-letter ISO country code. Pass `AttachExistingCustomer::with(customerID: '...')` to attach an existing customer, or `NewCustomer::with(email: '...', name: '...')` to create one. Both classes are in the `Dodopayments\Payments` namespace. `charge` is for [on-demand subscriptions](/developer-resources/ondemand-subscriptions), and `productPrice` is in the smallest currency unit.
</Info>

## Pagination

List methods return a page object. `getItems()` returns the items on the current page, and `pagingEachItem()` returns every item from the current page onward, requesting more pages as needed:

```php theme={null}
$page = $client->payments->list();

var_dump($page);

// Fetch items from the current page
foreach ($page->getItems() as $item) {
  var_dump($item->brandID);
}

// Auto-paginate: fetch items from all pages
foreach ($page->pagingEachItem() as $item) {
  var_dump($item->brandID);
}
```

To move one page at a time, call `hasNextPage()` and `getNextPage()`.

## Error Handling

When the SDK can't connect to the API, or the API returns a 4xx or 5xx status, the SDK throws a subclass of `Dodopayments\Core\Exceptions\APIException`:

```php theme={null}
<?php

use Dodopayments\Core\Exceptions\APIConnectionException;
use Dodopayments\Core\Exceptions\RateLimitException;
use Dodopayments\Core\Exceptions\APIStatusException;

try {
  $checkoutSessionResponse = $client->checkoutSessions->create(
    productCart: [["productID" => "pdt_123", "quantity" => 1]]
  );
} catch (APIConnectionException $e) {
  echo "The server could not be reached", PHP_EOL;
  var_dump($e->getPrevious());
} catch (RateLimitException $_) {
  echo "A 429 status code was received; we should back off a bit.", PHP_EOL;
} catch (APIStatusException $e) {
  echo "Another non-200-range status code was received", PHP_EOL;
  echo $e->getMessage();
}
```

### Error Types

The exception class depends on the cause. All classes are in the `Dodopayments\Core\Exceptions` namespace:

| Cause | Error Type |
| - | - |
| HTTP 400 | `BadRequestException` |
| HTTP 401 | `AuthenticationException` |
| HTTP 403 | `PermissionDeniedException` |
| HTTP 404 | `NotFoundException` |
| HTTP 409 | `ConflictException` |
| HTTP 422 | `UnprocessableEntityException` |
| HTTP 429 | `RateLimitException` |
| HTTP >= 500 | `InternalServerException` |
| Other HTTP error | `APIStatusException` |
| Timeout | `APITimeoutException` |
| Network error | `APIConnectionException` |

<Tip>
  Catch these exceptions around API calls so that your application can show a clear
  message or try again later. For a retryable error, the SDK throws only after its
  automatic retries fail.
</Tip>

## Advanced Usage

### Undocumented Endpoints

To call an endpoint that has no SDK method, use `$client->request`. It applies the same authentication and retries as the SDK methods:

```php theme={null}
<?php

$response = $client->request(
  method: "post",
  path: '/undocumented/endpoint',
  query: ['dog' => 'woof'],
  headers: ['useful-header' => 'interesting-value'],
  body: ['hello' => 'world']
);
```

### Undocumented Parameters

To send parameters that the SDK doesn't define, pass them in `requestOptions`:

```php theme={null}
<?php

use Dodopayments\RequestOptions;

$checkoutSessionResponse = $client->checkoutSessions->create(
  productCart: [["productID" => "pdt_123", "quantity" => 1]],
  requestOptions: [
    'extraQueryParams' => ["my_query_parameter" => "value"],
    'extraBodyParams' => ["my_body_parameter" => "value"],
    'extraHeaders' => ["my-header" => "value"],
  ],
);
```

<Note>
  An `extra*` parameter that has the same name as a documented parameter overrides it.
</Note>

## Framework Integration

### Laravel

Wrap the client in a service class. This example sets the API URL from the configured environment:

```php theme={null}
<?php

namespace App\Services;

use Dodopayments\Client;

class PaymentService
{
    protected $client;

    public function __construct()
    {
        $this->client = new Client(
            bearerToken: config('services.dodo.api_key'),
            baseUrl: config('services.dodo.environment') === 'live_mode'
                ? 'https://live.dodopayments.com'
                : 'https://test.dodopayments.com',
        );
    }

    public function createCheckout(array $items)
    {
        return $this->client->checkoutSessions->create(
            productCart: $items,
            returnURL: route('checkout.return')
        );
    }
}
```

Add the settings to `config/services.php`:

```php theme={null}
'dodo' => [
    'api_key' => env('DODO_PAYMENTS_API_KEY'),
    'environment' => env('DODO_PAYMENTS_ENVIRONMENT', 'test_mode')
],
```

### Symfony

Create a service that receives the API key through its constructor:

```php theme={null}
<?php

namespace App\Service;

use Dodopayments\Client;

class DodoPaymentService
{
    private Client $client;

    public function __construct(string $apiKey)
    {
        $this->client = new Client(bearerToken: $apiKey);
    }

    public function createPayment(string $productId, string $customerId, array $billing): object
    {
        // Note: POST /payments is deprecated — prefer checkout sessions for new integrations
        return $this->client->payments->create(
            billing: $billing,
            customer: ['customerID' => $customerId],
            productCart: [['productID' => $productId, 'quantity' => 1]],
        );
    }
}
```

Register the service in `config/services.yaml`:

```yaml theme={null}
services:
  App\Service\DodoPaymentService:
    arguments:
      $apiKey: "%env(DODO_PAYMENTS_API_KEY)%"
```

## Resources

<CardGroup cols={2}>
  <Card title="GitHub Repository" icon="github" href="https://github.com/dodopayments/dodopayments-php">
    Source code, releases, and the full method list.
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/introduction">
    Every endpoint, parameter, and response.
  </Card>

  <Card title="Discord Community" icon="discord" href="https://discord.gg/bYqAp4ayYh">
    Ask questions and talk with other developers.
  </Card>

  <Card title="Report Issues" icon="bug" href="https://github.com/dodopayments/dodopayments-php/issues">
    Report bugs or request features.
  </Card>
</CardGroup>

## Support

For help with the PHP SDK:

* **Discord**: Join the [community server](https://discord.gg/bYqAp4ayYh) for real-time help.
* **Email**: Contact [support@dodopayments.com](mailto:support@dodopayments.com).
* **GitHub**: Open an issue on the [repository](https://github.com/dodopayments/dodopayments-php).

## Contributing

To contribute, read the [contributing guidelines](https://github.com/dodopayments/dodopayments-php/blob/main/CONTRIBUTING.md).


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