> ## Documentation Index
> Fetch the complete documentation index at: https://dev.valorapayments.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Valora Payments API (Brazil: Pix, card, boleto). Base URL https://api.valorapayments.com.br, Authorization: Bearer sk_vl_... used only on the server. Request amounts are reais as strings ("149.90"); response amounts are integer cents (14990). Use POST /v1/checkouts for card and boleto; POST /v1/payments only creates Pix and should send an Idempotency-Key. Confirm payments only through the payment.succeeded webhook after verifying X-Valora-Signature (hex HMAC-SHA256 of "<t>.<raw body>"), dedupe by event.id and check livemode. Never invent endpoints or fields: check /llms.txt and /openapi.json.

# Laravel

> Integre a Valora numa aplicação Laravel com o Http Client: crie a compra, redirecione para o checkout e confirme pelo webhook.

Neste tutorial, você monta num projeto Laravel 10 ou mais novo:

* um serviço `Valora` que chama a API;
* uma rota que cria a compra e redireciona para o checkout;
* uma rota de API que recebe o webhook e verifica a assinatura.

## Pré-requisitos

* Laravel 10+ e PHP 8.1+.
* As variáveis de [Antes de começar](https://dev.valorapayments.com.br/frameworks/visao-geral.md#antes-de-começar) no `.env`.

## Integração

**1. Configure as credenciais**

```php config/services.php theme={null}
'valora' => [
    'key' => env('VALORA_API_KEY'),
    'webhook_secret' => env('VALORA_WEBHOOK_SECRET'),
    'product_id' => env('VALORA_PRODUCT_ID'),
],
```

**2. Crie o serviço**

```php app/Services/Valora.php theme={null}
<?php

namespace App\Services;

use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;

class Valora
{
    private function client(): PendingRequest
    {
        return Http::baseUrl('https://api.valorapayments.com.br')
            ->withToken(config('services.valora.key'))
            ->acceptJson()
            ->timeout(20);
    }

    public function createCheckout(array $payload): array
    {
        return $this->client()->post('/v1/checkouts', $payload)->throw()->json();
    }

    public function createPix(string $amount, string $reference): array
    {
        return $this->client()
            ->withHeaders(['Idempotency-Key' => $reference])
            ->post('/v1/payments', [
                'method' => 'pix',
                'amount' => $amount, // em reais, ex.: "149.90"
                'external_reference' => $reference,
            ])
            ->throw()
            ->json();
    }
}
```

**3. Crie a compra e redirecione**

```php app/Http/Controllers/CheckoutController.php theme={null}
<?php

namespace App\Http\Controllers;

use App\Services\Valora;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

class CheckoutController extends Controller
{
    public function store(Request $request, Valora $valora)
    {
        $orderId = (string) Str::uuid();
        // Grave o pedido como "pendente" aqui.

        $payload = [
            'product_cart' => [['product_id' => config('services.valora.product_id'), 'quantity' => 1]],
            'success_url' => url("/pedido/{$orderId}"),
            'cancel_url' => url('/'),
            'external_reference' => $orderId,
        ];
        // Campos de cliente não aceitam null: envie só o que existir.
        if ($email = $request->user()?->email) {
            $payload['customer'] = ['email' => $email];
        }

        $session = $valora->createCheckout($payload);

        return redirect()->away($session['checkout_url']);
    }
}
```

```php routes/web.php theme={null}
use App\Http\Controllers\CheckoutController;

Route::post('/checkout', [CheckoutController::class, 'store']);
```

**4. Receba o webhook**

Use `$request->getContent()` para ter o corpo cru.

```php app/Http/Controllers/ValoraWebhookController.php theme={null}
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ValoraWebhookController extends Controller
{
    public function __invoke(Request $request)
    {
        $raw = $request->getContent();
        $parts = [];
        foreach (explode(',', (string) $request->header('X-Valora-Signature')) as $p) {
            [$k, $v] = array_pad(explode('=', $p, 2), 2, '');
            $parts[$k] = $v;
        }

        $t = (int) ($parts['t'] ?? 0);
        $expected = hash_hmac('sha256', $t . '.' . $raw, config('services.valora.webhook_secret'));
        if (!hash_equals($expected, $parts['v1'] ?? '') || abs(time() - $t) > 300) {
            return response('assinatura inválida', 400);
        }

        $event = json_decode($raw, true);
        // Ignore eventos repetidos: guarde $event['id'] numa tabela com índice único.

        if ($event['type'] === 'payment.succeeded') {
            $payment = $event['data']['object'];
            // Order::where('id', $payment['external_reference'])->update(['status' => 'paid']);
        }

        return response()->noContent(200);
    }
}
```

```php routes/api.php theme={null}
use App\Http\Controllers\ValoraWebhookController;

Route::post('/webhooks/valora', ValoraWebhookController::class);
```

As rotas de `routes/api.php` ficam em `/api/...` e não passam pela verificação de CSRF. A URL do webhook fica `https://sualoja.com/api/webhooks/valora`.

> [!NOTE]
>
> No Laravel 11, rode `php artisan install:api` se o arquivo `routes/api.php` ainda não existir.

## Teste

```bash theme={null}
php artisan serve
ngrok http 8000
```

Cadastre `https://SEU-TUNEL/api/webhooks/valora` em **Desenvolvedores → Webhooks**, faça uma compra de teste e confira a entrega com status `200`.

## Próximos passos

> [card] [Webhooks](https://dev.valorapayments.com.br/integrar/webhooks.md)
>
> Novas tentativas, reenvio e boas práticas.

> [card] [Erros](https://dev.valorapayments.com.br/api-reference/erros.md)
>
> Os `type` de erro e o que fazer.


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