# PHP SDK

Official server-side SDK for integrating ZevPay Checkout in PHP applications. Provides full REST API coverage with Guzzle HTTP client.

## Installation

```bash
composer require zevpay/zevpay-php
```

Requires PHP 8.1 or later.

## Quick start

```php
use ZevPay\ZevPay;

$zevpay = new ZevPay('sk_test_your_secret_key');

// Initialize a checkout session
$session = $zevpay->checkout->initialize([
    'amount' => 500000, // ₦5,000 in kobo
    'email' => 'customer@example.com',
    'reference' => 'ORDER-123',
    'callback_url' => 'https://yoursite.com/callback',
]);

echo $session['checkout_url'];
```

## Configuration

```php
$zevpay = new ZevPay('sk_live_xxx', [
    'base_url' => 'https://api.zevpaycheckout.com', // default
    'timeout' => 30,     // 30s request timeout (default)
    'max_retries' => 2,  // retries on 5xx errors (default)
]);
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `base_url` | string | `https://api.zevpaycheckout.com` | API base URL |
| `timeout` | int | `30` | Request timeout in seconds |
| `max_retries` | int | `2` | Max retries on server errors |

::: warning Secret keys only
The SDK only accepts secret keys (`sk_live_*` or `sk_test_*`). Public keys cannot be used server-side.
:::

## Checkout sessions

### Initialize a session

```php
$session = $zevpay->checkout->initialize([
    'amount' => 500000,
    'email' => 'customer@example.com',
    'currency' => 'NGN',
    'reference' => 'ORDER-123',
    'callback_url' => 'https://yoursite.com/callback',
    'metadata' => ['order_id' => '123'],
    'payment_methods' => ['bank_transfer', 'payid'],
]);

// Redirect your customer to:
echo $session['checkout_url'];
```

### Verify a payment

```php
$result = $zevpay->checkout->verify($session['session_id']);

if ($result['status'] === 'completed') {
    echo 'Payment confirmed at: ' . $result['paid_at'];
}
```

### Get session details

```php
$details = $zevpay->checkout->get('ses_abc123');
```

## Transfers

### Bank transfer

```php
$transfer = $zevpay->transfers->create([
    'type' => 'bank_transfer',
    'amount' => 1000000, // ₦10,000
    'account_number' => '0123456789',
    'bank_code' => '044',
    'account_name' => 'John Doe',
    'narration' => 'Payout',
    'reference' => 'TXN-123',
]);
```

### PayID transfer

```php
$transfer = $zevpay->transfers->create([
    'type' => 'payid',
    'amount' => 500000,
    'pay_id' => 'johndoe',
    'narration' => 'Payment',
]);
```

### List, verify, resolve

```php
// List transfers
$transfers = $zevpay->transfers->list([
    'page' => 1,
    'page_size' => 20,
    'status' => 'completed',
]);

// Verify a transfer
$result = $zevpay->transfers->verify('TXN-123');

// List banks
$banks = $zevpay->transfers->listBanks();

// Resolve bank account
$account = $zevpay->transfers->resolveAccount([
    'account_number' => '0123456789',
    'bank_code' => '044',
]);
echo $account['account_name']; // 'JOHN DOE'

// Calculate fees
$charges = $zevpay->transfers->calculateCharges(['amount' => 1000000]);

// Get wallet balance
$balance = $zevpay->transfers->getBalance();
```

## Invoices

```php
// Create
$invoice = $zevpay->invoices->create([
    'customer_name' => 'Jane Doe',
    'customer_email' => 'jane@example.com',
    'due_date' => '2026-04-01',
    'line_items' => [
        ['description' => 'Web Design', 'quantity' => 1, 'unit_price' => 5000000],
        ['description' => 'Hosting (1yr)', 'quantity' => 1, 'unit_price' => 1200000],
    ],
    'tax_rate' => 7.5,
]);

// Send (draft → sent)
$zevpay->invoices->send($invoice['public_id']);

// List
$invoices = $zevpay->invoices->list(['status' => 'sent']);

// Cancel
$zevpay->invoices->cancel($invoice['public_id']);
```

## Static PayIDs

```php
// Create
$payid = $zevpay->staticPayId->create([
    'pay_id' => 'mystore',
    'name' => 'My Store',
    'description' => 'Accept payments to my store',
]);

// Deactivate / Reactivate
$zevpay->staticPayId->deactivate($payid['id']);
$zevpay->staticPayId->reactivate($payid['id']);
```

## Dynamic PayIDs

```php
// Create a time-limited payment link
$dpayid = $zevpay->dynamicPayId->create([
    'amount' => 1000000,
    'name' => 'Donation Drive',
    'expires_in_minutes' => 60,
]);

echo $dpayid['full_pay_id']; // e.g. 'donation-drive-xyz.dpay'

// Deactivate
$zevpay->dynamicPayId->deactivate($dpayid['id']);
```

## Virtual accounts

```php
// Create
$va = $zevpay->virtualAccounts->create([
    'amount' => 1000000,
    'validity_minutes' => 60,
]);

echo $va['account_number']; // Customer pays to this account

// List
$accounts = $zevpay->virtualAccounts->list(['status' => 'pending']);
```

## Wallet

```php
// Get wallet details
$wallet = $zevpay->wallet->get();

// List members
$members = $zevpay->wallet->listMembers();

// Add / remove members
$zevpay->wallet->addMember(['pay_id' => 'johndoe']);
$zevpay->wallet->removeMember('johndoe');
```

## Webhook verification

Verify incoming webhook signatures using your webhook secret:

```php
use ZevPay\Webhook;

$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_ZEVPAY_SIGNATURE'] ?? '';
$secret = getenv('ZEVPAY_WEBHOOK_SECRET');

try {
    $event = Webhook::constructEvent($payload, $signature, $secret);

    switch ($event['event']) {
        case 'charge.success':
            // Handle successful checkout payment
            break;
        case 'transfer.success':
            // Handle successful transfer
            break;
        case 'transfer.failed':
            // Handle failed transfer
            break;
        case 'invoice.paid':
            // Handle paid invoice
            break;
    }

    http_response_code(200);
    echo 'OK';
} catch (\ZevPay\Exceptions\ZevPayException $e) {
    http_response_code(400);
    echo 'Invalid signature';
}
```

::: warning Always verify
Never process webhook events without verifying the signature. See [Verifying Signatures](/webhooks/signatures) for details.
:::

## Error handling

All API errors throw typed exceptions:

```php
use ZevPay\Exceptions\ValidationException;
use ZevPay\Exceptions\AuthenticationException;
use ZevPay\Exceptions\NotFoundException;
use ZevPay\Exceptions\ConflictException;
use ZevPay\Exceptions\RateLimitException;
use ZevPay\Exceptions\ApiException;

try {
    $zevpay->transfers->create([/* ... */]);
} catch (ValidationException $e) {
    // 400 — invalid parameters
    echo $e->errorCode;   // e.g. 'VALIDATION_ERROR'
    echo $e->getMessage(); // Human-readable message
    print_r($e->details);  // Field-level errors
} catch (AuthenticationException $e) {
    // 401 — invalid API key
} catch (NotFoundException $e) {
    // 404 — resource not found
} catch (ConflictException $e) {
    // 409 — duplicate transaction
} catch (RateLimitException $e) {
    // 429 — rate limit exceeded
    echo $e->retryAfter; // seconds to wait
} catch (ApiException $e) {
    // 500+ — server error
}
```

| Exception class | Status | When |
|-----------------|--------|------|
| `ValidationException` | 400 | Invalid request parameters |
| `AuthenticationException` | 401 | Invalid or missing API key |
| `PermissionException` | 403 | Insufficient permissions |
| `NotFoundException` | 404 | Resource not found |
| `ConflictException` | 409 | Duplicate resource |
| `RateLimitException` | 429 | Too many requests |
| `ApiException` | 500+ | Server error |

## Full example — Laravel

```php
// routes/web.php
use Illuminate\Http\Request;
use ZevPay\ZevPay;
use ZevPay\Webhook;

Route::post('/api/pay', function (Request $request) {
    $zevpay = new ZevPay(config('services.zevpay.secret'));

    $session = $zevpay->checkout->initialize([
        'amount' => $request->input('amount'),
        'email' => $request->input('email'),
        'reference' => 'ORDER-' . time(),
        'callback_url' => url('/api/callback'),
    ]);

    return response()->json(['checkout_url' => $session['checkout_url']]);
});

Route::get('/api/callback', function (Request $request) {
    $zevpay = new ZevPay(config('services.zevpay.secret'));
    $result = $zevpay->checkout->verify($request->query('session_id'));

    if ($result['status'] === 'completed') {
        return 'Payment successful!';
    }

    return 'Payment not completed yet.';
});

Route::post('/webhooks/zevpay', function (Request $request) {
    try {
        $event = Webhook::constructEvent(
            $request->getContent(),
            $request->header('x-zevpay-signature', ''),
            config('services.zevpay.webhook_secret'),
        );

        logger()->info('Webhook received', $event);
        return response('OK', 200);
    } catch (\Exception $e) {
        return response('Invalid signature', 400);
    }
});
```

## Resources

- [API Reference](/api/overview) — Full endpoint documentation
- [Webhook Events](/webhooks/events) — All event types
- [Packagist](https://packagist.org/packages/zevpay/zevpay-php) — Package registry
