mrroot / orange-money-bundle
Symfony bundle for Orange Money payment integration with complete webhook, callback and error handling support
Installs: 4
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
Type:symfony-bundle
Requires
- php: >=8.1
- ext-ctype: *
- ext-curl: *
- ext-json: *
- psr/log: ^1.0|^2.0|^3.0
- symfony/config: ^5.4|^6.0|^7.0
- symfony/console: ^5.4|^6.0|^7.0
- symfony/dependency-injection: ^5.4|^6.0|^7.0
- symfony/framework-bundle: ^5.4|^6.0|^7.0
- symfony/http-client: ^5.4|^6.0|^7.0
- symfony/http-foundation: ^5.4|^6.0|^7.0
- symfony/http-kernel: ^5.4|^6.0|^7.0
- symfony/routing: ^5.4|^6.0|^7.0
- symfony/yaml: ^5.4|^6.0|^7.0
Requires (Dev)
- phpunit/phpunit: ^9.0|^10.0
- symfony/phpunit-bridge: ^6.0|^7.0
- symfony/test-pack: ^1.0
Suggests
- symfony/monolog-bundle: For advanced transaction logging
- symfony/twig-bundle: For customizing response templates
- symfony/validator: For advanced parameter validation
This package is auto-updated.
Last update: 2025-09-04 16:52:54 UTC
README
Symfony bundle for Orange Money payment integration with complete webhook support, callbacks, and advanced error handling.
Features
- Orange Money payment initiation with comprehensive validation
- Transaction status verification with enhanced parameters
- Advanced webhook and notification handling with token validation
- Robust error handling with detailed logging
- Flexible configuration via environment variables
- OAuth2 support for secure API authentication
- Symfony compatibility 5.4, 6.x and 7.x
- Automatic data validation and sanitization
- Configurable callback URLs with security features
- Webhook spoofing protection via notification token validation
- Enhanced payment response handling with comprehensive data access
Bundle Architecture
File Structure
src/OrangeMoneyBundle/
├── DependencyInjection/
│ ├── Configuration.php # Bundle configuration
│ └── OrangeMoneyExtension.php # Symfony extension
├── Exception/
│ └── OrangeMoneyException.php # Custom exceptions
├── Model/
│ ├── PaymentRequest.php # Payment request model
│ └── PaymentResponse.php # Payment response model
├── Service/
│ └── OrangeMoneyService.php # Main service
└── OrangeMoneyBundle.php # Main bundle class
Flow Diagram
Application → PaymentRequest → OrangeMoneyService → Orange Money API
↓ ↓ ↓ ↓
Controller ← PaymentResponse ← Token Management ← OAuth2 Response
Installation
1. Installation via Composer
composer require mrroot/orange-money-bundle
2. Bundle Registration
Add the bundle to config/bundles.php
:
<?php return [ Mrroot\OrangeMoneyBundle\OrangeMoneyBundle::class => ['all' => true], ];
3. Configuration
Create the file config/packages/orange_money.yaml
:
orange_money: client_id: '%env(OM_CLIENT_ID)%' client_secret: '%env(OM_CLIENT_SECRET)%' api_base_url: '%env(OM_API_BASE_URL)%' token_endpoint: '%env(OM_TOKEN_ENDPOINT)%' payment_endpoint: '%env(OM_PAYMENT_ENDPOINT)%' transaction_status_endpoint: '%env(OM_TRANSACTIONSTATUS_ENDPOINT)%' parameters: OM_RETURN_URL: '%env(OM_RETURN_URL)%' OM_CANCEL_URL: '%env(OM_CANCEL_URL)%' OM_NOTIF_URL: '%env(OM_NOTIF_URL)%' OM_MERCHANT_KEY: '%env(OM_MERCHANT_KEY)%'
4. Environment Variables
Add these variables to your .env
file:
# Orange Money API Configuration OM_CLIENT_ID=your_client_id OM_CLIENT_SECRET=your_client_secret OM_MERCHANT_KEY=your_merchant_key OM_API_BASE_URL=https://api.orange.com # API Endpoints OM_TOKEN_ENDPOINT=/oauth/v3/token OM_PAYMENT_ENDPOINT=/orange-money-webpay/dev/v1/webpayment OM_TRANSACTIONSTATUS_ENDPOINT=/orange-money-webpay/dev/v1/transactionstatus # Callback URLs OM_RETURN_URL=https://your-site.com/payment/success OM_CANCEL_URL=https://your-site.com/payment/cancel OM_NOTIF_URL=https://your-site.com/payment/webhook
Usage
Complete Controller Example
For a complete and detailed implementation example, see: Controller Example
Payment Initiation
<?php namespace App\Controller; use Mrroot\OrangeMoneyBundle\Model\PaymentRequest; use Mrroot\OrangeMoneyBundle\Service\OrangeMoneyService; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Annotation\Route; class PaymentController extends AbstractController { #[Route('/payment/initiate', name: 'payment_initiate', methods: ['POST'])] public function initiatePayment( Request $request, OrangeMoneyService $orangeMoneyService ): JsonResponse { try { // Create payment request $paymentRequest = new PaymentRequest( orderId: 'ORDER_' . uniqid(), amount: 10000, currency: 'GNF', reference: 'REF_' . time(), merchantKey: $this->getParameter('OM_MERCHANT_KEY'), returnUrl: $this->getParameter('OM_RETURN_URL'), cancelUrl: $this->getParameter('OM_CANCEL_URL'), notifUrl: $this->getParameter('OM_NOTIF_URL') ); // Initiate payment $paymentResponse = $orangeMoneyService->initiatePayment($paymentRequest); // IMPORTANT: Save these values for webhook validation // - $paymentResponse->getPayToken() (payToken) // - $paymentRequest->getOrderId() (orderId) // - $paymentResponse->getNotifToken() (notifToken) return new JsonResponse([ 'success' => true, 'payment_url' => $paymentResponse->getPaymentUrl(), 'pay_token' => $paymentResponse->getPayToken(), 'notif_token' => $paymentResponse->getNotifToken(), 'order_id' => $paymentRequest->getOrderId() ]); } catch (\Exception $e) { return new JsonResponse([ 'success' => false, 'error' => $e->getMessage() ], 400); } } }
Payment Status Verification
#[Route('/payment/status/{payToken}', name: 'payment_status')] public function checkPaymentStatus( string $payToken, string $orderId, float $amount, OrangeMoneyService $orangeMoneyService ): JsonResponse { try { // Enhanced status check with required parameters $response = $orangeMoneyService->checkTransactionStatus($payToken, $orderId, $amount); return new JsonResponse([ 'pay_token' => $payToken, 'order_id' => $response->getOrderId(), 'status' => $response->getStatus(), 'amount' => $response->getAmount(), 'currency' => $response->getCurrency(), 'txn_id' => $response->getTxnId() ]); } catch (\Exception $e) { return new JsonResponse([ 'error' => $e->getMessage() ], 400); } }
Webhook Handling with Security Validation
#[Route('/payment/webhook', name: 'payment_webhook', methods: ['POST'])] public function handleWebhook( Request $request, OrangeMoneyService $orangeMoneyService ): JsonResponse { try { // Retrieve stored values from your database/session // These values should have been saved during payment initiation: $payToken = "your-stored-pay-token"; // Get from database $orderId = "your-stored-order-id"; // Get from database $notifToken = "your-stored-notif-token"; // Get from database $amount = 50.0; // Get from database // Optional: Check transaction status separately if needed // $checkResponse = $orangeMoneyService->checkTransactionStatus( // $payToken, // $orderId, // $amount // ); $webhookData = json_decode($request->getContent(), true); // Automatic notif_token verification $paymentStatus = $orangeMoneyService->processNotification($webhookData, $notifToken); // Your business logic here if ($paymentStatus->getStatus() === 'SUCCESS') { // Confirm order $this->confirmOrder($paymentStatus->getOrderId()); } return new JsonResponse([ 'status' => 'success', 'message' => 'Notification processed', 'txn_id' => $paymentStatus->getTxnId() ]); } catch (\Exception $e) { return new JsonResponse([ 'status' => 'error', 'message' => $e->getMessage() ], 500); } private function getStoredNotifToken(?string $orderId): ?string { // Retrieve the notification token stored during payment initiation // This could be from database, cache, session, etc. return $orderId ? $this->paymentRepository->getNotifTokenByOrderId($orderId) : null; } private function confirmOrder(string $orderId): void { // Your order confirmation logic } }
Transaction Status Verification (Optional)
public function checkStatus(OrangeMoneyService $orangeMoneyService): JsonResponse { try { // Use values saved during payment initiation $response = $orangeMoneyService->checkTransactionStatus( 'pay-token-from-initiation', // Saved payment token 'ORDER-123', // Saved order ID 10000 // Saved amount ); return new JsonResponse([ 'status' => $response->getStatus(), 'transaction_id' => $response->getTxnId(), 'order_id' => $response->getOrderId() ]); } catch (OrangeMoneyException $e) { return new JsonResponse([ 'error' => $e->getMessage() ], 400); } }
Data Models
PaymentRequest
$paymentRequest = new PaymentRequest( orderId: 'ORDER_123', // Unique order ID amount: 10000, // Amount currency: 'OUV', // Currency (OUV for Ouguiya) reference: 'REF_123', // Optional reference merchantKey: 'merchant_key', // Merchant key returnUrl: 'https://...', // Success return URL cancelUrl: 'https://...', // Cancellation URL notifUrl: 'https://...' // Notification URL );
PaymentResponse
// Available methods $response->getPaymentUrl(); // Payment URL $response->getPayToken(); // Payment token $response->getNotifToken(); // Notification token (for webhook validation) $response->getTxnId(); // Transaction ID $response->getStatus(); // Payment status $response->getOrderId(); // Order ID $response->getAmount(); // Amount $response->getCurrency(); // Currency $response->isSuccessful(); // Success verification $response->hasError(); // Error verification $response->getErrorMessage(); // Error message $response->getErrorCode(); // Error code $response->hasPaymentUrl(); // Payment URL validation $response->getRawData(); // Raw data
Security
Enhanced Webhook Validation
The bundle provides comprehensive webhook security:
// Required fields validated automatically $requiredFields = ['status', 'notif_token', 'txnid']; // Process notification with token validation $paymentStatus = $orangeMoneyService->processNotification($data, $expectedNotifToken);
Webhook Spoofing Protection
To prevent webhook spoofing attacks:
- Store the notification token during payment initiation:
$paymentResponse = $orangeMoneyService->initiatePayment($paymentRequest); $notifToken = $paymentResponse->getNotifToken(); // Store this token securely (database, cache, etc.) $this->storeNotifToken($paymentRequest->getOrderId(), $notifToken);
- Validate the token in webhook processing:
$expectedToken = $this->getStoredNotifToken($orderId); $paymentStatus = $orangeMoneyService->processNotification($webhookData, $expectedToken);
Error Handling
All errors are encapsulated in OrangeMoneyException
:
try { $paymentResponse = $orangeMoneyService->initiatePayment($paymentRequest); } catch (OrangeMoneyException $e) { // Orange Money specific error $errorMessage = $e->getMessage(); $errorData = $e->getData(); }
Logging
The bundle uses Symfony's logging system:
# config/packages/monolog.yaml monolog: channels: ['orange_money'] handlers: orange_money: type: rotating_file path: '%kernel.logs_dir%/orange_money.log' level: info channels: ['orange_money']
Advanced Configuration
Endpoint Customization for Development or Production Environment
# config/packages/orange_money.yaml orange_money: api_base_url: 'https://api.orange.com' payment_endpoint: '/orange-money-webpay/dev/v1/webpayment' transaction_status_endpoint: '/orange-money-webpay/dev/v1/transactionstatus'
Complete Examples
Implementation examples are integrated directly into this documentation and include:
- Complete payment controller
- Callback handling
- Transaction status verification
- Webhook handling
Contributing
Contributions are welcome! Please:
- Fork the project
- Create a branch for your feature
- Commit your changes
- Push to the branch
- Open a Pull Request
License
This project is licensed under the MIT License. See the LICENSE file for more details.
Support
- Issues: GitHub Issues
- Email: camaraabdoulayeroo@mail.com
Changelog
v1.1.0
- Enhanced webhook security with notification token validation
- Improved transaction status checking with required parameters (payToken, orderId, amount)
- Extended PaymentResponse model with notifToken and txnId properties
- Code quality improvements and consistency fixes
- Better error handling and validation
- Webhook spoofing protection implementation
v1.0.0
- First stable version
- Complete Orange Money API support
- Webhook handling
- Complete documentation
Developed with ❤️ by Abdoulaye CAMARA alias MrROOT