alphapay/alphapay-laravel enveloppe alphapay/alphapay-php — 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
composer require alphapay/alphapay-laravel
php artisan alphapay:install # publie config/alphapay.php + migration alphapay_transactions (au choix)
# .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
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
- Checkout (session hébergée)
- Softpay (encaissement direct)
- Lien de paiement
- Balance
- Webhooks et Events Laravel
- Trait
HasAlphaPayPayments - Gestion des erreurs
1. Checkout (session hébergée)
<?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
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
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']]);
}
}
// 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
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
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
// 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
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
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 quealphapay/alphapay-php
(ce package ne les redéfinit pas) — capturables directement via la Facade.
<?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);
}
}
}