> ## Documentation Index
> Fetch the complete documentation index at: https://docs.alphapay.me/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK Laravel

> Intégrez les paiements AlphaPay dans une application Laravel via la Facade, les Events et le trait Eloquent officiels.

Le **package `alphapay/alphapay-laravel`** enveloppe [`alphapay/alphapay-php`](/php-sdk) — toute la mécanique HTTP/retry/idempotence vient de là. Ce package ajoute par-dessus : une Facade `AlphaPay`, la réception de webhooks convertie en Events Laravel, un stockage local optionnel des transactions, et un trait Eloquent (`HasAlphaPayPayments`).

## Installation

```bash theme={null}
composer require alphapay/alphapay-laravel
php artisan alphapay:install   # publie config/alphapay.php + migration alphapay_transactions (au choix)
```

```env theme={null}
# .env
ALPHAPAY_SECRET_KEY=sk_test_votre_cle
ALPHAPAY_WEBHOOK_SECRET=whsec_...      # depuis le dashboard, à la création/rotation de l'endpoint
ALPHAPAY_WEBHOOK_PATH=webhooks/alphapay
```

La route webhook (`POST /webhooks/alphapay` par défaut) est enregistrée
automatiquement par le package — déclarez cette URL complète dans le
dashboard AlphaPay, rien à ajouter dans `routes/`.

## Sommaire

1. [Checkout (session hébergée)](#1-checkout-session-hébergée)
2. [Softpay (encaissement direct)](#2-softpay-encaissement-direct)
3. [Lien de paiement](#3-lien-de-paiement)
4. [Balance](#4-balance)
5. [Webhooks et Events Laravel](#5-webhooks-et-events-laravel)
6. [Trait `HasAlphaPayPayments`](#6-trait-hasalphapaypayments)
7. [Gestion des erreurs](#7-gestion-des-erreurs)

***

## 1. Checkout (session hébergée)

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

declare(strict_types=1);

namespace App\Http\Controllers;

use AlphaPay\Exceptions\AlphaPayException;
use AlphaPay\Laravel\Facades\AlphaPay;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;

class CheckoutController extends Controller
{
    public function store(Request $request): RedirectResponse
    {
        $order = $request->user()->orders()->findOrFail($request->input('order_id'));

        try {
            $session = AlphaPay::checkoutSessions()->create([
                'amount' => $order->total,
                'currency' => 'XOF',
                'description' => "Commande #{$order->id}",
                'customer_email' => $order->customer_email,
                'customer_name' => $order->customer_name,
                'customer_phone' => $order->customer_phone,
                'return_url' => route('orders.thank-you', $order),
            ], idempotencyKey: "order-{$order->id}"); // stable : un retry sur la même commande ne recrée pas de session
        } catch (AlphaPayException $e) {
            report($e);
            return back()->withErrors(['checkout' => "Paiement indisponible : {$e->getMessage()}"]);
        }

        $order->update(['alphapay_checkout_slug' => $session['slug']]);

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

    /** Le client revient ici via `return_url` -- ne JAMAIS créditer la commande depuis cette page, seul le webhook `transaction.success` (section 5) fait foi. */
    public function thankYou(Request $request, \App\Models\Order $order)
    {
        $session = AlphaPay::checkoutSessions()->get($order->alphapay_checkout_slug);
        return view('orders.thank-you', ['status' => $session['status']]); // affichage seulement
    }
}
```

***

## 2. Softpay (encaissement direct)

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

declare(strict_types=1);

namespace App\Http\Controllers;

use AlphaPay\Exceptions\AlphaPayValidationException;
use AlphaPay\Laravel\Facades\AlphaPay;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class SoftpayController extends Controller
{
    public function store(Request $request): JsonResponse
    {
        $data = $request->validate([
            'amount' => ['required', 'integer', 'min:1'],
            'network' => ['required', 'string'], // ex. "mtn_bj" -- toujours en minuscules
            'country' => ['required', 'string', 'size:2'],
            'phone' => ['required', 'string'],
        ]);

        try {
            $payment = AlphaPay::transactions()->payinInitialize([
                'amount' => $data['amount'],
                'currency' => 'XOF',
                'country' => $data['country'],
                'network' => $data['network'],
                'customer' => [
                    'email' => $request->user()->email,
                    'first_name' => $request->user()->first_name,
                    'last_name' => $request->user()->last_name,
                    'phone' => $data['phone'],
                ],
                'metadata' => ['user_id' => $request->user()->id],
            ], idempotencyKey: true);
        } catch (AlphaPayValidationException $e) {
            return response()->json(['errors' => $e->getFieldErrors()], 422);
        }

        // otp_required/instructions à renvoyer tels quels au frontend -- c'est
        // lui qui affiche "composez ce code" ou le champ OTP le cas échéant.
        return response()->json([
            'transaction_id' => $payment['id'],
            'status' => $payment['status'],
            'otp_required' => $payment['otp_required'] ?? false,
            'instructions' => $payment['instructions'] ?? null,
        ], 201);
    }

    public function confirmOtp(Request $request, string $transactionId): JsonResponse
    {
        $data = $request->validate(['otp' => ['required', 'string']]);
        $result = AlphaPay::transactions()->payinConfirmOtp($transactionId, $data['otp']);
        return response()->json($result);
    }
}
```

***

## 3. Lien de paiement

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

declare(strict_types=1);

namespace App\Http\Controllers;

use AlphaPay\Laravel\Facades\AlphaPay;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class PaymentLinkController extends Controller
{
    /** Créé UNE fois par le marchand (dashboard interne, commande artisan, ...), pas à chaque visite client. */
    public function store(Request $request): JsonResponse
    {
        $link = AlphaPay::paymentLinks()->create([
            'name' => $request->string('name'),
            'amount_type' => 'FIXED',
            'amount' => $request->integer('amount'),
            'currency' => 'XOF',
            'require_phone' => true,
            'custom_fields' => [
                ['key' => 'reference_client', 'label' => 'Référence client', 'required' => true],
            ],
        ]);

        return response()->json($link, 201);
    }

    /** Route PUBLIQUE (pas de middleware `auth`) -- affiche le formulaire de paiement du lien. */
    public function show(string $slug)
    {
        $link = AlphaPay::paymentLinks()->getPublic($slug);
        return view('payment-links.show', ['link' => $link]);
    }

    /** Route PUBLIQUE -- soumission du formulaire de la page ci-dessus. */
    public function checkout(Request $request, string $slug): JsonResponse
    {
        $data = $request->validate([
            'email' => ['required', 'email'],
            'first_name' => ['required', 'string'],
            'last_name' => ['required', 'string'],
            'phone' => ['required', 'string'],
            'reference_client' => ['nullable', 'string'],
        ]);

        $checkout = AlphaPay::paymentLinks()->createPublicCheckout($slug, [
            'customer' => [
                'email' => $data['email'],
                'first_name' => $data['first_name'],
                'last_name' => $data['last_name'],
                'phone' => $data['phone'],
            ],
            'custom_field_values' => ['reference_client' => $data['reference_client'] ?? null],
        ]);

        return response()->json(['redirect' => $checkout['checkout_url']]);
    }
}
```

```php theme={null}
// routes/web.php
Route::post('/dashboard/payment-links', [PaymentLinkController::class, 'store'])->middleware('auth');
Route::get('/pay/{slug}', [PaymentLinkController::class, 'show'])->name('payment-links.show');
Route::post('/pay/{slug}/checkout', [PaymentLinkController::class, 'checkout'])->name('payment-links.checkout');
```

***

## 4. Balance

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

declare(strict_types=1);

namespace App\Console\Commands;

use AlphaPay\Laravel\Facades\AlphaPay;
use Illuminate\Console\Command;

class ShowAlphaPayBalances extends Command
{
    protected $signature = 'alphapay:balances';
    protected $description = 'Affiche le solde disponible par pays/devise';

    public function handle(): int
    {
        $balances = AlphaPay::balances()->list();

        $this->table(
            ['Pays', 'Devise', 'Disponible'],
            array_map(
                fn ($b) => [$b['country'], $b['currency'], $b['available_amount']],
                $balances['results']
            )
        );

        $this->info('Environnement : ' . AlphaPay::environment());

        return self::SUCCESS;
    }
}
```

***

## 5. Webhooks et Events Laravel

Le package écoute déjà `POST /webhooks/alphapay` (route auto-enregistrée),
vérifie la signature (`AlphaPay\Webhook::verifySignature`), stocke
optionnellement une copie locale dans `alphapay_transactions`
(`config('alphapay.store_transactions')`) puis émet un Event Laravel selon
le préfixe de l'événement. **Vous n'écrivez jamais le contrôleur webhook
vous-même** — seulement les listeners.

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

declare(strict_types=1);

namespace App\Providers;

use AlphaPay\Laravel\Events\SettlementEvent;
use AlphaPay\Laravel\Events\TransactionEvent;
use AlphaPay\Laravel\Events\WalletTransferEvent;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Event::listen(function (TransactionEvent $event) {
            if ($event->isSuccess()) {
                $order = \App\Models\Order::where('reference', $event->getReference())->first();
                $order?->markAsPaid(); // idempotent -- le webhook peut être livré plusieurs fois
            } elseif ($event->isFailed()) {
                logger()->warning('Paiement AlphaPay échoué', [
                    'reference' => $event->getReference(),
                    'network' => $event->getNetwork(),
                ]);
            }
        });

        Event::listen(function (SettlementEvent $event) {
            logger()->info('Reversement AlphaPay', ['event' => $event->eventType, 'data' => $event->data]);
        });

        Event::listen(function (WalletTransferEvent $event) {
            logger()->info('Transfert wallet AlphaPay', ['event' => $event->eventType, 'data' => $event->data]);
        });
    }
}
```

```php theme={null}
<?php
// Lecture directe de la copie locale (tenue à jour par le webhook ci-dessus) --
// pratique pour un tableau de bord, mais l'API AlphaPay reste la source de
// vérité en cas de doute (AlphaPay::transactions()->get($id)).

use AlphaPay\Laravel\Models\AlphaPayTransaction;

$recentSuccesses = AlphaPayTransaction::successful()
    ->where('created_at', '>=', now()->subDay())
    ->orderByDesc('created_at')
    ->get();

foreach ($recentSuccesses as $transaction) {
    echo "{$transaction->reference} : {$transaction->amount} {$transaction->currency} ({$transaction->network})\n";
}
```

***

## 6. Trait `HasAlphaPayPayments`

À ajouter sur n'importe quel modèle (généralement `User`, ou un modèle
`Order`) pour le lier aux transactions stockées localement et déclencher un
paiement directement depuis l'instance. Nécessite la migration publiée
(`php artisan alphapay:install`).

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

declare(strict_types=1);

namespace App\Models;

use AlphaPay\Laravel\Traits\HasAlphaPayPayments;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    use HasAlphaPayPayments;
    // ... reste du modèle
}
```

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

declare(strict_types=1);

namespace App\Http\Controllers;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class SubscriptionController extends Controller
{
    public function pay(Request $request): JsonResponse
    {
        $user = $request->user();

        // payAlphaPay() rattache automatiquement metadata.payable_type/payable_id
        // à $user -- la ligne alphapay_transactions correspondante n'apparaît
        // qu'au webhook `transaction.*` suivant, pas à cet appel.
        $payment = $user->payAlphaPay([
            'amount' => 2000,
            'currency' => 'XOF',
            'country' => 'BJ',
            'network' => 'mtn_bj',
            'customer' => [
                'email' => $user->email,
                'first_name' => $user->first_name,
                'last_name' => $user->last_name,
                'phone' => $user->phone,
            ],
            'description' => 'Abonnement mensuel',
        ], idempotencyKey: true);

        return response()->json($payment, 201);
    }

    public function status(Request $request): JsonResponse
    {
        $user = $request->user();

        return response()->json([
            'has_paid' => $user->hasSuccessfulAlphaPayPayments(),
            'total_paid' => $user->totalAlphaPayPaid(),
            'pending' => $user->pendingAlphaPayPayments()->count(),
        ]);
    }
}
```

***

## 7. Gestion des erreurs

Mêmes exceptions typées que [`alphapay/alphapay-php`](/php-sdk#9-gestion-des-erreurs)
(ce package ne les redéfinit pas) — capturables directement via la Facade.

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

declare(strict_types=1);

namespace App\Http\Controllers;

use AlphaPay\Exceptions\AlphaPayException;
use AlphaPay\Exceptions\AlphaPayRateLimitException;
use AlphaPay\Exceptions\AlphaPayValidationException;
use AlphaPay\Laravel\Facades\AlphaPay;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class PaymentController extends Controller
{
    public function store(Request $request): JsonResponse
    {
        try {
            $payment = AlphaPay::transactions()->payinInitialize($request->validated());
            return response()->json($payment, 201);
        } catch (AlphaPayValidationException $e) {
            return response()->json(['errors' => $e->getFieldErrors()], 422);
        } catch (AlphaPayRateLimitException $e) {
            // Déjà retenté automatiquement par le SDK (backoff + Retry-After) avant d'arriver ici.
            return response()->json(['error' => 'Service temporairement indisponible, réessayez.'], 503);
        } catch (AlphaPayException $e) {
            report($e); // capturé par votre handler d'exceptions habituel (Sentry, Bugsnag, logs...)
            return response()->json(['error' => 'Paiement indisponible.'], 502);
        }
    }
}
```
