andydefer/laravel-pawapay

Laravel SDK for integrating Pawapay Mobile Money payments (pay-ins, pay-outs, and webhooks) across African markets.

Installs: 15

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/andydefer/laravel-pawapay

0.6.6 2026-01-24 19:54 UTC

This package is auto-updated.

Last update: 2026-01-24 19:54:37 UTC


README

PHP Version Laravel Version License Tests Coverage Mobile Money API Routes

Laravel PawaPay SDK est un package Laravel complet et type-safe pour intégrer les paiements mobile money PawaPay dans 21 marchés africains. Construit avec des pratiques PHP modernes, il fournit une interface fluide pour les pay-ins, pay-outs, la prédiction de fournisseurs, la gestion des webhooks et inclut une API REST complète prête à l'emploi.

🚀 Installation

1. Installation via Composer

composer require andydefer/laravel-pawapay

2. Installation Rapide (Recommandée)

Utilisez la commande d'installation pour publier toutes les ressources en une fois :

# Installez tout en une seule commande
php artisan pawapay:install

# Installation forcée (écrase les fichiers existants)
php artisan pawapay:install --force

3. Installation Manuelle (Optionnel)

Si vous préférez un contrôle manuel, publiez des composants spécifiques :

# Publier uniquement la configuration
php artisan vendor:publish --provider="Pawapay\\PawapayServiceProvider" --tag="pawapay-config"

# Publier les définitions de types TypeScript
php artisan vendor:publish --provider="Pawapay\\PawapayServiceProvider" --tag="pawapay-types"

# Publier le contrôleur API
php artisan vendor:publish --provider="Pawapay\\PawapayServiceProvider" --tag="pawapay-controller"

# Publier les routes personnalisées (optionnel - les routes fonctionnent automatiquement)
php artisan vendor:publish --provider="Pawapay\\PawapayServiceProvider" --tag="pawapay-routes"

# Générer les définitions TypeScript
php artisan pawapay:generate-types

4. Configuration des Variables d'Environnement

Ajoutez à votre fichier .env :

# Environnement (sandbox/production)
PAWAPAY_ENVIRONMENT=sandbox

# Token API de PawaPay
PAWAPAY_API_TOKEN=votre_token_api_ici

# Optionnel : Personnaliser les timeouts et tentatives
PAWAPAY_TIMEOUT=30
PAWAPAY_RETRY_TIMES=3
PAWAPAY_RETRY_SLEEP=100

📡 Deux Manières d'Utiliser le Package

Option 1 : Utilisation Directe du SDK (Recommandé pour les Intégrations Personnalisées)

Utilisez le SDK directement dans vos contrôleurs ou services :

use Pawapay\Facades\Pawapay;

// Prédire le fournisseur mobile money
$response = Pawapay::predictProvider('+260763456789');
// Retourne: PredictProviderSuccessResponse ou PredictProviderFailureResponse

// Créer une page de paiement
$requestData = PaymentPageRequestData::fromArray([...]);
$response = Pawapay::createPaymentPage($requestData);
// Retourne: PaymentPageSuccessResponseData ou PaymentPageErrorResponseData

// Initier un dépôt direct
$requestData = InitiateDepositRequestData::fromArray([...]);
$response = Pawapay::initiateDeposit($requestData);
// Retourne: InitiateDepositResponseData

// Vérifier le statut du dépôt
$response = Pawapay::checkDepositStatus('deposit_uuid');
// Retourne: CheckDepositStatusWrapperData

Option 2 : API REST Intégrée (Prête à l'Emploi)

Le package inclut une API REST complète disponible automatiquement via les routes :

POST   /api/pawapay/predict-provider
POST   /api/pawapay/payment-page
POST   /api/pawapay/deposits
GET    /api/pawapay/deposits/{depositId}

Points de Terminaison API Disponibles :

Méthode Endpoint Type de Retour Description
POST /api/pawapay/predict-provider PredictProviderSuccessResponse ou PredictProviderFailureResponse Prédire le fournisseur mobile money depuis un numéro de téléphone
POST /api/pawapay/payment-page PaymentPageSuccessResponseData ou PaymentPageErrorResponseData Créer une page de paiement hébergée
POST /api/pawapay/deposits InitiateDepositResponseData Initier un dépôt direct (sans redirection)
GET /api/pawapay/deposits/{depositId} CheckDepositStatusWrapperData Vérifier le statut d'un dépôt

Structure des Réponses API :

L'API retourne directement les objets DTO sans wrapper supplémentaire :

Réponse de succès (exemple pour predict-provider) :

{
  "country": "ZMB",
  "provider": "MTN_MOMO_ZMB",
  "phoneNumber": "260763456789"
}

Réponse d'erreur (exemple pour predict-provider) :

{
  "failureReason": {
    "failureCode": "INVALID_PHONE_NUMBER",
    "failureMessage": "Invalid phone number"
  }
}

Exemples d'Utilisation de l'API :

// Utilisation de l'API fetch en JavaScript - Prédiction de fournisseur
const response = await fetch('/api/pawapay/predict-provider', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
    },
    body: JSON.stringify({
        phoneNumber: '+260763456789'
    })
});

const data = await response.json();

// Vérifiez si c'est une réponse de succès
if (data.country && data.provider) {
    console.log('Pays:', data.country); // "ZMB"
    console.log('Fournisseur:', data.provider); // "MTN_MOMO_ZMB"
    console.log('Téléphone:', data.phoneNumber); // "260763456789"
} else if (data.failureReason) {
    console.log('Erreur:', data.failureReason.failureMessage);
    console.log('Code:', data.failureReason.failureCode);
}
# Utilisation de cURL - Création de page de paiement
curl -X POST "http://votre-app.test/api/pawapay/payment-page" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "depositId": "order_123",
    "returnUrl": "https://votre-boutique.com/payment/callback",
    "customerMessage": "Paiement pour la Commande #12345",
    "amountDetails": {
      "amount": "150.00",
      "currency": "ZMW"
    },
    "phoneNumber": "260763456789",
    "language": "EN",
    "country": "ZMB",
    "reason": "Achat en Ligne",
    "metadata": [
      {"orderId": "ORD-123"},
      {"customerId": "cust-456"}
    ]
  }'

Format des Requêtes/Réponses de l'API :

1. Prédiction de Fournisseur :

// Requête
{
  "phoneNumber": "+260763456789"
}

// Réponse de succès
{
  "country": "ZMB",
  "provider": "MTN_MOMO_ZMB",
  "phoneNumber": "260763456789"
}

// Réponse d'erreur
{
  "failureReason": {
    "failureCode": "INVALID_PHONE_NUMBER",
    "failureMessage": "Invalid phone number"
  }
}

2. Création de Page de Paiement :

// Requête
{
  "depositId": "order_123",
  "returnUrl": "https://votre-boutique.com/payment/callback",
  "customerMessage": "Paiement pour la Commande #12345",
  "amountDetails": {
    "amount": "150.00",
    "currency": "ZMW"
  },
  "phoneNumber": "260763456789",
  "language": "EN",
  "country": "ZMB",
  "reason": "Achat en Ligne",
  "metadata": [
    {"orderId": "ORD-123"},
    {"customerId": "cust-456"}
  ]
}

// Réponse de succès
{
  "redirectUrl": "https://sandbox.pawapay.io/payment/abc123"
}

// Réponse d'erreur
{
  "depositId": "order_123",
  "status": "REJECTED",
  "failureReason": {
    "failureCode": "INVALID_AMOUNT",
    "failureMessage": "Amount is invalid"
  }
}

3. Initiation de Dépôt Direct :

// Requête
{
  "depositId": "deposit_123",
  "payer": {
    "type": "MMO",
    "accountDetails": {
      "phoneNumber": "+260763456789",
      "provider": "MTN_MOMO_ZMB"
    }
  },
  "amount": "100.00",
  "currency": "ZMW",
  "clientReferenceId": "INV-123456",
  "customerMessage": "Paiement pour services",
  "metadata": [
    {"orderId": "ORD-123"},
    {"customerId": "cust-456"}
  ]
}

// Réponse
{
  "depositId": "deposit_123",
  "status": "ACCEPTED",
  "created": "2024-01-15T10:30:00Z",
  "failureReason": null
}

4. Vérification de Statut de Dépôt :

// Requête (via paramètre de route)
GET /api/pawapay/deposits/deposit_123

// Réponse (dépôt trouvé)
{
  "status": "FOUND",
  "data": {
    "depositId": "deposit_123",
    "status": "COMPLETED",
    "amount": "100.00",
    "currency": "ZMW",
    "country": "ZMB",
    "payer": {
      "type": "MMO",
      "accountDetails": {
        "phoneNumber": "260763456789",
        "provider": "MTN_MOMO_ZMB"
      }
    },
    "customerMessage": "Paiement pour services",
    "clientReferenceId": "INV-123456",
    "providerTransactionId": "txn_789",
    "created": "2024-01-15T10:30:00Z"
  }
}

// Réponse (dépôt non trouvé)
{
  "status": "NOT_FOUND",
  "data": null
}

🌍 Pays et Fournisseurs Supportés

PawaPay supporte 21 pays africains avec leurs fournisseurs mobile money respectifs :

Couverture Complète des Pays

Pays Code Fournisseurs Supportés Devise
Bénin BEN MTN_MOMO_BEN, MOOV_BEN XOF
Burkina Faso BFA MOOV_BFA, ORANGE_BFA XOF
Cameroun CMR MTN_MOMO_CMR, ORANGE_CMR XAF
Côte d'Ivoire CIV MTN_MOMO_CIV, ORANGE_CIV, WAVE_CIV XOF
RDC COD VODACOM_MPESA_COD, AIRTEL_COD, ORANGE_COD CDF, USD
Éthiopie ETH MPESA_ETH ETB
Gabon GAB AIRTEL_GAB XAF
Ghana GHA MTN_MOMO_GHA, AIRTELTIGO_GHA, VODAFONE_GHA GHS
Kenya KEN MPESA_KEN KES
Lesotho LSO MPESA_LSO LSL
Malawi MWI AIRTEL_MWI, TNM_MWI MWK
Mozambique MOZ MOVITEL_MOZ, VODACOM_MOZ MZN
Nigeria NGA AIRTEL_NGA, MTN_MOMO_NGA NGN
République du Congo COG AIRTEL_COG, MTN_MOMO_COG XAF
Rwanda RWA AIRTEL_RWA, MTN_MOMO_RWA RWF
Sénégal SEN FREE_SEN, ORANGE_SEN, WAVE_SEN XOF
Sierra Leone SLE ORANGE_SLE SLE
Tanzanie TZA AIRTEL_TZA, VODACOM_TZA, TIGO_TZA, HALOTEL_TZA TZS
Ouganda UGA AIRTEL_OAPI_UGA, MTN_MOMO_UGA UGX
Zambie ZMB AIRTEL_OAPI_ZMB, MTN_MOMO_ZMB, ZAMTEL_ZMB ZMW

💰 Fonctionnalités Principales

1. Prédiction de Fournisseur Mobile Money

Détectez automatiquement le fournisseur mobile money à partir d'un numéro de téléphone :

use Pawapay\Facades\Pawapay;

$response = Pawapay::predictProvider('+260763456789');

if ($response instanceof \Pawapay\Data\Responses\PredictProviderSuccessResponse) {
    echo "Pays : " . $response->country->value; // ZMB
    echo "Fournisseur : " . $response->provider->value; // MTN_MOMO_ZMB
    echo "Téléphone : " . $response->phoneNumber; // 260763456789
} else {
    echo "Erreur : " . $response->failureReason->failureMessage;
}

2. Création de Page de Paiement

Créez des pages de paiement hébergées pour les clients :

use Pawapay\Data\PaymentPage\PaymentPageRequestData;
use Pawapay\Enums\Currency;
use Pawapay\Enums\Language;
use Pawapay\Enums\SupportedCountry;
use Pawapay\Facades\Pawapay;

$requestData = PaymentPageRequestData::fromArray([
    'depositId' => (string) \Illuminate\Support\Str::uuid(),
    'returnUrl' => 'https://votre-boutique.com/payment/complete',
    'customerMessage' => 'Paiement pour la Commande #12345',
    'amountDetails' => [
        'amount' => '150.00',
        'currency' => Currency::ZMW->value,
    ],
    'phoneNumber' => '260763456789',
    'language' => Language::EN->value,
    'country' => SupportedCountry::ZMB->value,
    'reason' => 'Achat en ligne - Électronique',
    'metadata' => [
        ['orderId' => 'ORD-123456789'],
        ['customerId' => 'cust-789012'],
        ['productId' => 'PROD-345678'],
    ],
]);

$response = Pawapay::createPaymentPage($requestData);

if ($response instanceof \Pawapay\Data\PaymentPage\PaymentPageSuccessResponseData) {
    // Redirigez le client vers la page de paiement
    return redirect($response->redirectUrl);
} else {
    // Gérez l'erreur
    return back()->withErrors([
        'payment' => $response->failureReason->failureMessage
    ]);
}

3. Initiation de Dépôt Direct

Initiez des dépôts de manière programmatique sans rediriger les utilisateurs :

use Pawapay\Data\Deposit\InitiateDepositRequestData;
use Pawapay\Enums\Currency;
use Pawapay\Enums\SupportedProvider;
use Pawapay\Facades\Pawapay;

$requestData = InitiateDepositRequestData::fromArray([
    'depositId' => (string) \Illuminate\Support\Str::uuid(),
    'payer' => [
        'type' => 'MMO',
        'accountDetails' => [
            'phoneNumber' => '260763456789',
            'provider' => SupportedProvider::MTN_MOMO_ZMB->value,
        ],
    ],
    'amount' => '100.00',
    'currency' => Currency::ZMW->value,
    'clientReferenceId' => 'INV-123456',
    'customerMessage' => 'Paiement pour services rendus',
    'metadata' => [
        ['orderId' => 'ORD-123456'],
        ['customerId' => 'customer@email.com'],
        ['isPII' => true],
    ],
]);

$response = Pawapay::initiateDeposit($requestData);

if ($response->isAccepted()) {
    // Dépôt accepté pour traitement
    echo "ID de dépôt : " . $response->depositId;
    echo "Statut : " . $response->status->value; // ACCEPTED
    echo "Créé le : " . $response->created;
} elseif ($response->isRejected()) {
    // Dépôt rejeté
    echo "Rejeté : " . $response->failureReason->failureMessage;
    echo "Code d'erreur : " . $response->failureReason->failureCode->value;
} elseif ($response->isDuplicateIgnored()) {
    // Requête dupliquée (idempotent)
    echo "Duplication ignorée pour : " . $response->depositId;
}

4. Vérification du Statut des Dépôts

Surveillez le statut des transactions en temps réel :

use Pawapay\Facades\Pawapay;

$depositId = 'votre_depot_uuid';
$response = Pawapay::checkDepositStatus($depositId);

if ($response->isFound()) {
    $deposit = $response->data;

    echo "ID de dépôt : " . $deposit->depositId;
    echo "Montant : " . $deposit->amount . " " . $deposit->currency->value;
    echo "Statut : " . $deposit->status->value;
    echo "Pays : " . $deposit->country->value;
    echo "Téléphone : " . $deposit->payer->accountDetails->phoneNumber;
    echo "Fournisseur : " . $deposit->payer->accountDetails->provider->value;

    // Vérifiez si la transaction est terminée
    if ($deposit->isFinalStatus()) {
        echo "Transaction terminée";
    } elseif ($deposit->isProcessing()) {
        echo "Transaction en cours";
    }

    // Accédez aux métadonnées
    if ($deposit->metadata) {
        foreach ($deposit->metadata as $meta) {
            print_r($meta);
        }
    }

    // Vérifiez la raison de l'échec
    if ($deposit->failureReason) {
        echo "Échec : " . $deposit->failureReason->failureMessage;
        echo "Code : " . $deposit->failureReason->failureCode->value;
    }
} else {
    echo "Dépôt non trouvé";
}

📊 Référence API Complète

Énumérations (Enums)

Le package fournit des énumérations complètes pour la sécurité des types :

SupportedCountry (21 pays)

use Pawapay\Enums\SupportedCountry;

$country = SupportedCountry::ZMB;
echo $country->value; // "ZMB"
echo $country->name; // "Zambie"

// Obtenez tous les fournisseurs pour un pays
$providers = SupportedCountry::ZMB->getProviders();
// Retourne : [SupportedProvider::MTN_MOMO_ZMB, ...]

SupportedProvider (40+ fournisseurs)

use Pawapay\Enums\SupportedProvider;

$provider = SupportedProvider::MTN_MOMO_ZMB;
echo $provider->value; // "MTN_MOMO_ZMB"

// Obtenez le pays du fournisseur
$country = $provider->getCountry();
echo $country->value; // "ZMB"

Currency (17 devises)

use Pawapay\Enums\Currency;

$currency = Currency::ZMW;
echo $currency->value; // "ZMW"

// Communément utilisées :
Currency::ZMW; // Kwacha zambien
Currency::KES; // Shilling kényan
Currency::GHS; // Cédi ghanéen
Currency::NGN; // Naira nigérian
Currency::USD; // Dollar US (RDC)

TransactionStatus

use Pawapay\Enums\TransactionStatus;

// Statuts d'initiation
TransactionStatus::ACCEPTED
TransactionStatus::REJECTED
TransactionStatus::DUPLICATE_IGNORED

// Statuts finaux
TransactionStatus::COMPLETED
TransactionStatus::FAILED

// Statuts intermédiaires
TransactionStatus::SUBMITTED
TransactionStatus::ENQUEUED
TransactionStatus::PROCESSING
TransactionStatus::IN_RECONCILIATION

// Statuts de recherche
TransactionStatus::FOUND
TransactionStatus::NOT_FOUND

FailureCode (27 codes détaillés)

use Pawapay\Enums\FailureCode;

// Erreurs techniques
FailureCode::NO_AUTHENTICATION
FailureCode::INVALID_INPUT
FailureCode::MISSING_PARAMETER
FailureCode::INVALID_AMOUNT
FailureCode::INVALID_PHONE_NUMBER

// Erreurs de transaction
FailureCode::PAYMENT_NOT_APPROVED
FailureCode::INSUFFICIENT_BALANCE
FailureCode::PAYER_NOT_FOUND
FailureCode::MANUALLY_CANCELLED

// Obtenez le code de statut HTTP
$code = FailureCode::INVALID_INPUT;
echo $code->httpStatusCode(); // 400

Language

use Pawapay\Enums\Language;

Language::EN; // Anglais
Language::FR; // Français

Data Transfer Objects (DTOs)

Toutes les interactions API utilisent des DTOs fortement typés :

DTOs de Requête

use Pawapay\Data\PaymentPage\PaymentPageRequestData;
use Pawapay\Data\Deposit\InitiateDepositRequestData;

// Depuis un tableau
$paymentRequest = PaymentPageRequestData::fromArray($data);

// Depuis le constructeur (type-safe)
$depositRequest = new InitiateDepositRequestData(
    depositId: 'uuid',
    payer: $payerData,
    amount: '100.00',
    currency: Currency::ZMW,
    // ... autres paramètres
);

DTOs de Réponse

use Pawapay\Data\PaymentPage\PaymentPageSuccessResponseData;
use Pawapay\Data\PaymentPage\PaymentPageErrorResponseData;
use Pawapay\Data\Deposit\InitiateDepositResponseData;
use Pawapay\Data\Responses\CheckDepositStatusWrapperData;
use Pawapay\Data\Responses\PredictProviderSuccessResponse;
use Pawapay\Data\Responses\PredictProviderFailureResponse;

// Toutes les réponses ont des méthodes d'aide
$response->isAccepted();
$response->isRejected();
$response->isFound();
$response->isNotFound();
$response->isSuccess(); // Pour PredictProviderResponseData

Méthodes du Service

Classe PawapayService

// 1. Prédiction de Fournisseur
predictProvider(string $phoneNumber): PredictProviderSuccessResponse|PredictProviderFailureResponse

// 2. Pages de Paiement
createPaymentPage(PaymentPageRequestData $request): PaymentPageSuccessResponseData|PaymentPageErrorResponseData

// 3. Dépôts Directs
initiateDeposit(InitiateDepositRequestData $request): InitiateDepositResponseData

// 4. Vérification de Statut
checkDepositStatus(string $depositId): CheckDepositStatusWrapperData

🎨 Génération de Types TypeScript

Générer les Définitions TypeScript

php artisan pawapay:generate-types

Ce Que Cela Fait

Crée des fichiers TypeScript dans resources/js/pawapay/ :

  • enums.ts - Toutes les énumérations Pawapay
  • types.ts - Toutes les interfaces
  • index.ts - Exports principaux avec fonctions utilitaires

Exemple d'Utilisation

import {
  SupportedProvider,
  Currency,
  TransactionStatus,
  isTransactionFinal
} from '@/js/pawapay';

const provider: SupportedProvider = SupportedProvider.MTN_MOMO_ZMB;
const currency: Currency = Currency.ZMW;
const status: TransactionStatus = TransactionStatus.COMPLETED;

if (isTransactionFinal(status)) {
  console.log('Paiement terminé');
}

Régénération Forcée

php artisan pawapay:generate-types --force

🔧 Utilisation Avancée

Idempotence

Toutes les opérations de dépôt sont idempotentes. Utiliser le même depositId plusieurs fois donnera un statut DUPLICATE_IGNORED :

// Première requête
$response1 = Pawapay::initiateDeposit($requestData);
// Statut : ACCEPTED

// Deuxième requête identique
$response2 = Pawapay::initiateDeposit($requestData);
// Statut : DUPLICATE_IGNORED (pas de transaction dupliquée)

Support des Métadonnées

Attachez des métadonnées personnalisées aux paiements pour le suivi :

$requestData = PaymentPageRequestData::fromArray([
    // ... autres champs
    'metadata' => [
        ['orderId' => 'ORD-123'],
        ['userId' => 456],
        ['cartId' => 'CART-789'],
        ['channel' => 'web'],
        ['version' => '2.0'],
        ['items' => json_encode(['item1', 'item2'])],
        ['custom_field' => 'custom_value'],
    ],
]);

Les métadonnées sont conservées tout au long du cycle de paiement et peuvent être récupérées lors de la vérification du statut du dépôt.

Normalisation des Numéros de Téléphone

Le package normalise automatiquement les numéros de téléphone :

$response = Pawapay::predictProvider('+260 763-456-789');
echo $response->phoneNumber; // "260763456789" (normalisé)

Bonnes Pratiques de Gestion des Erreurs

use Illuminate\Http\Client\RequestException;
use Pawapay\Exceptions\PawapayApiException;

try {
    $response = Pawapay::predictProvider($phoneNumber);

    if ($response instanceof \Pawapay\Data\Responses\PredictProviderFailureResponse) {
        // L'API a retourné une erreur métier
        $errorCode = $response->failureReason->failureCode;
        $errorMessage = $response->failureReason->failureMessage;

        // Gérez des codes d'erreur spécifiques
        if ($errorCode === FailureCode::INVALID_PHONE_NUMBER) {
            return back()->withErrors(['phone' => 'Numéro de téléphone invalide']);
        }

        if ($errorCode === FailureCode::INSUFFICIENT_BALANCE) {
            return back()->withErrors(['payment' => 'Solde insuffisant']);
        }
    }

    // Traitez la réponse réussie
    return redirect($response->redirectUrl);

} catch (RequestException $e) {
    // Erreur réseau ou HTTP
    Log::error('Requête API PawaPay échouée', [
        'message' => $e->getMessage(),
        'status' => $e->response->status(),
        'body' => $e->response->body(),
    ]);

    return back()->withErrors([
        'payment' => 'Service de paiement temporairement indisponible'
    ]);

} catch (PawapayApiException $e) {
    // Exception spécifique au package
    Log::error('Erreur SDK PawaPay', [
        'message' => $e->getMessage(),
        'data' => $e->getErrorData(),
    ]);

    return back()->withErrors([
        'payment' => 'Erreur de traitement du paiement'
    ]);
}

⚙️ Détails de Configuration

Timeouts et Tentatives

Configurez dans .env :

# Timeout de requête en secondes
PAWAPAY_TIMEOUT=30

# Nombre de tentatives pour les requêtes échouées
PAWAPAY_RETRY_TIMES=3

# Délai entre les tentatives en millisecondes
PAWAPAY_RETRY_SLEEP=100

En-têtes Personnalisés

Étendez les en-têtes par défaut dans la configuration :

// config/pawapay.php
'defaults' => [
    'headers' => [
        'Content-Type' => 'application/json',
        'Accept' => 'application/json',
        'X-Custom-Header' => 'Votre-Valeur',
    ],
],

Basculement d'Environnement

// Basculer vers la production
config()->set('pawapay.environment', 'production');

// Ou utilisez .env
PAWAPAY_ENVIRONMENT=production

🔄 Exemples de Workflow Complet

Flux de Paiement E-commerce (Utilisant l'API Intégrée)

// Frontend JavaScript (React/Vue/etc)
async function processPayment(phoneNumber, amount, orderId) {
    try {
        // Étape 1 : Prédire le fournisseur
        const providerResponse = await fetch('/api/pawapay/predict-provider', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ phoneNumber })
        });

        const providerData = await providerResponse.json();

        // Vérifiez si c'est une réponse de succès
        if (!providerData.country || !providerData.provider) {
            throw new Error(providerData.failureReason?.failureMessage || 'Impossible de détecter le fournisseur');
        }

        // Étape 2 : Créer la page de paiement
        const paymentResponse = await fetch('/api/pawapay/payment-page', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                depositId: orderId,
                returnUrl: `${window.location.origin}/payment/callback`,
                customerMessage: `Paiement pour la Commande #${orderId}`,
                amountDetails: {
                    amount: amount.toString(),
                    currency: providerData.country === 'ZMB' ? 'ZMW' : 'XOF'
                },
                phoneNumber: providerData.phoneNumber,
                language: navigator.language.startsWith('fr') ? 'FR' : 'EN',
                country: providerData.country,
                reason: 'Achat en ligne',
                metadata: [
                    { orderId },
                    { customerId: 'id-utilisateur-actuel' }
                ]
            })
        });

        const paymentData = await paymentResponse.json();

        // Vérifiez si c'est une réponse de succès
        if (paymentData.redirectUrl) {
            // Redirigez vers la page de paiement PawaPay
            window.location.href = paymentData.redirectUrl;
        } else {
            throw new Error(paymentData.failureReason?.failureMessage || 'Échec de la création du paiement');
        }

    } catch (error) {
        console.error('Erreur de paiement :', error);
        alert('Échec du paiement : ' + error.message);
    }
}

Service d'Abonnement avec Dépôts Directs (Utilisant le SDK)

class SubscriptionController
{
    public function renewSubscription(Subscription $subscription)
    {
        // Obtenez le téléphone de l'utilisateur depuis son profil
        $user = $subscription->user;

        // Créez une requête de dépôt en utilisant le SDK
        $depositId = (string) Str::uuid();

        $response = Pawapay::initiateDeposit([
            'depositId' => $depositId,
            'payer' => [
                'type' => 'MMO',
                'accountDetails' => [
                    'phoneNumber' => $user->phone_number,
                    'provider' => $user->mobile_money_provider,
                ],
            ],
            'amount' => $subscription->amount,
            'currency' => 'ZMW',
            'clientReferenceId' => 'SUB-' . $subscription->id,
            'customerMessage' => 'Renouvellement d\'abonnement mensuel',
            'metadata' => [
                ['subscriptionId' => $subscription->id],
                ['userId' => $user->id],
                ['plan' => $subscription->plan],
            ],
        ]);

        // Gérez la réponse
        if ($response->isAccepted()) {
            // Mettez en file d'attente la vérification du statut
            CheckDepositStatus::dispatch($depositId)
                ->delay(now()->addMinutes(5));

            return response()->json([
                'message' => 'Paiement initié',
                'depositId' => $depositId,
            ]);
        } else {
            return response()->json([
                'error' => $response->failureReason->failureMessage,
            ], 422);
        }
    }

    public function webhook(Request $request)
    {
        // Vérifiez la signature du webhook
        $payload = $request->all();
        $depositId = $payload['depositId'];

        // Mettez à jour l'abonnement basé sur le statut
        $status = Pawapay::checkDepositStatus($depositId);

        if ($status->isFound() && $status->data->status === TransactionStatus::COMPLETED) {
            // Mettez à jour l'abonnement
            $metadata = collect($status->data->metadata);
            $subscriptionId = $metadata->firstWhere('subscriptionId');

            Subscription::find($subscriptionId)->update([
                'status' => 'active',
                'renewed_at' => now(),
            ]);
        }

        return response()->json(['status' => 'traité']);
    }
}

🔐 Bonnes Pratiques de Sécurité

1. Stockez les Tokens API de Manière Sécurisée

# Ne committez jamais les tokens dans le contrôle de version
PAWAPAY_API_TOKEN=${PAWAPAY_API_TOKEN}

2. Validez les Données d'Entrée

use Illuminate\Validation\Rule;

$validated = $request->validate([
    'phone' => [
        'required',
        'string',
        'regex:/^(?:\+?\d{1,3}[- ]?)?\d{6,14}$/'
    ],
    'amount' => [
        'required',
        'numeric',
        'min:1',
        'max:100000' // Définissez des limites raisonnables
    ],
    'currency' => [
        'required',
        Rule::in(array_column(Currency::cases(), 'value'))
    ],
]);

3. Implémentez la Vérification de Signature des Webhooks

public function handleWebhook(Request $request)
{
    $signature = $request->header('X-PawaPay-Signature');
    $payload = $request->getContent();
    $secret = config('services.pawapay.webhook_secret');

    $expectedSignature = hash_hmac('sha256', $payload, $secret);

    if (!hash_equals($expectedSignature, $signature)) {
        abort(401, 'Signature de webhook invalide');
    }

    // Traitez le webhook
}

4. Surveillez et Journalisez les Transactions

use Illuminate\Support\Facades\Log;

class PaymentService
{
    public function initiatePayment($data)
    {
        try {
            $response = Pawapay::createPaymentPage($data);

            Log::info('Paiement initié', [
                'depositId' => $data['depositId'],
                'amount' => $data['amountDetails']['amount'],
                'currency' => $data['amountDetails']['currency'],
                'response_status' => $response->status ?? 'inconnu',
            ]);

            return $response;

        } catch (Exception $e) {
            Log::error('Échec d\'initiation de paiement', [
                'depositId' => $data['depositId'] ?? 'inconnu',
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            throw $e;
        }
    }
}

🔄 Guide de Migration

Des Appels API Bruts au Package

Avant :

public function makePayment($data)
{
    $response = Http::withToken(config('pawapay.token'))
        ->post('https://api.sandbox.pawapay.io/v2/paymentpage', $data);

    if ($response->failed()) {
        throw new Exception('Échec du paiement : ' . $response->body());
    }

    return $response->json();
}

Après :

use Pawapay\Facades\Pawapay;
use Pawapay\Data\PaymentPage\PaymentPageRequestData;

public function makePayment($data)
{
    $requestData = PaymentPageRequestData::fromArray($data);
    $response = Pawapay::createPaymentPage($requestData);

    if ($response instanceof \Pawapay\Data\PaymentPage\PaymentPageErrorResponseData) {
        throw new Exception('Échec du paiement : ' . $response->failureReason->failureMessage);
    }

    return $response;
}

API Intégrée vs Implémentation Personnalisée

Approche Idéal Pour Avantages
API Intégrée Configuration rapide, SPAs, applications mobiles Configuration zéro, validation automatique, prêt à l'emploi
SDK Direct Logique métier personnalisée, workflows complexes Contrôle total, intégration directe, gestion d'erreurs personnalisée
Routes Personnalisées Personnalisation API avancée Contrôle complet sur les routes et middleware

🤝 Contribution

Nous accueillons les contributions ! Voici comment commencer :

1. Forkez le Dépôt

git clone https://github.com/andydefer/laravel-pawapay.git
cd laravel-pawapay
composer install

2. Exécutez les Tests

# Tests unitaires
composer test

# Tests d'intégration (nécessite un token sandbox)
PAWAPAY_API_TOKEN=votre_token composer test --group=integration

# Style de code
composer lint

# Analyse statique
composer analyse

3. Workflow de Développement

# 1. Créez une branche de fonctionnalité
git checkout -b feature/nouveau-support-fournisseur

# 2. Faites vos modifications
# 3. Ajoutez des tests
# 4. Exécutez les tests
composer test

# 5. Vérifiez le style de code
composer lint

# 6. Committez avec un message descriptif
git commit -m "feat: ajouter le support pour un nouveau fournisseur mobile money"

# 7. Poussez et créez une PR
git push origin feature/nouveau-support-fournisseur

4. Standards de Codage

  • Suivez les standards de codage PSR-12
  • Écrivez du code compatible PHPStan niveau 9
  • Ajoutez des indications de type pour toutes les méthodes
  • Incluez des tests complets
  • Mettez à jour la documentation pour les nouvelles fonctionnalités

📚 Ressources Supplémentaires

Documentation Officielle

Communauté

Packages Similaires

📄 Licence

Ce package est un logiciel open-source sous licence MIT.

🏆 Support

Si ce package vous a été utile, pensez à :

  • ⭐ Étoiler le dépôt sur GitHub
  • 📢 Partager avec votre réseau
  • 💼 L'utiliser dans vos projets commerciaux
  • 🐛 Signaler des problèmes et suggérer des fonctionnalités

📞 Besoin d'Aide ?

Laravel PawaPay SDK - Autonomiser le commerce africain avec des paiements mobile money fluides. Construit avec ❤️ pour la communauté Laravel en Afrique et au-delà.