dahromy/mvola-bundle
Symfony bundle for integrating the MVola payment gateway. Provides MVola API authentication, services to initiate payments, check status and fetch transaction details, callback handling for notifications, plus configurable retries and token caching.
composer require dahromy/mvola-bundle
config/packages/mvola.yaml):
mvola:
environment: '%env(MVOLA_ENVIRONMENT)%'
merchant_number: '%env(MVOLA_MERCHANT_NUMBER)%'
consumer_key: '%env(MVOLA_CONSUMER_KEY)%'
consumer_secret: '%env(MVOLA_CONSUMER_SECRET)%'
auth_url: '%env(MVOLA_AUTH_URL)%'
.env):
MVOLA_ENVIRONMENT=sandbox
MVOLA_MERCHANT_NUMBER=your_merchant_number
MVOLA_CONSUMER_KEY=your_key
MVOLA_CONSUMER_SECRET=your_secret
MVOLA_AUTH_URL=https://sandbox.mvola.mg/token
// src/Controller/PaymentController.php
use DahRomy\MVola\Service\MVolaService;
use DahRomy\MVola\Model\TransactionRequest;
class PaymentController extends AbstractController
{
public function __construct(private MVolaService $mvolaService) {}
public function pay()
{
$request = new TransactionRequest();
$request->setAmount(1000)
->setCurrency('Ar')
->setDescriptionText('Order #123')
->setDebitParty([['key' => 'msisdn', 'value' => '0343500003']])
->setCallbackData(['order_id' => '123']);
$response = $this->mvolaService->initiateTransaction($request);
return new JsonResponse($response);
}
}
Ensure your server accepts PUT requests at /mvola/callback (default route). Test with:
curl -X PUT -H "Content-Type: application/json" -d '{"data": {...}}' http://your-app.test/mvola/callback
$transaction = $mvolaService->initiateTransaction($request);
// Store $transaction->getServerCorrelationId() in DB
$status = $mvolaService->getTransactionStatus($serverCorrelationId);
// In MVolaCallbackSubscriber
public function onMVolaCallback(MVolaCallbackEvent $event) {
$data = $event->getMVolaData();
$callbackData = $event->getCallbackData(); // Your stored order_id
// Update DB/order status
}
serverCorrelationId and originalTransactionReference to link callbacks to orders.max_retries and retry_delay in config for transient failures.MVOLA_ENVIRONMENT=sandbox and mock the callback endpoint during development.| Use Case | Implementation Pattern |
|---|---|
| One-time payments | initiateTransaction() + callback handling |
| Recurring payments | Store originalTransactionReference and reuse |
| Refunds | Use getTransactionDetails() to fetch transactionId |
| Webhook validation | Verify signature in MVolaCallbackEvent |
Callback Validation:
signature header. Verify it matches:
$signature = $request->headers->get('X-MVola-Signature');
$expected = hash_hmac('sha256', $rawBody, $consumerSecret);
if (!hash_equals($signature, $expected)) {
throw new \RuntimeException('Invalid callback signature');
}
MVolaCallbackSubscriber to add validation.Rate Limiting:
max_retries and implement exponential backoff:
$retryDelay = 1000 * (2 ** $attempt);
sleep($retryDelay);
Timeouts:
HttpClient (Symfony 5.3+):
# config/packages/http_client.yaml
framework:
http_client:
plugins:
- timeout: 30
Sandbox vs. Production:
sandbox first. Use .env to switch:
MVOLA_AUTH_URL=https://sandbox.mvola.mg/token # Sandbox
MVOLA_AUTH_URL=https://api.mvola.mg/token # Production
# config/packages/mvola.yaml
mvola:
debug: true # Logs raw API requests/responses
MVolaValidationException) for granular error handling.Custom Callback Logic:
# config/routes.yaml
mvola.callback:
path: /custom/mvola/callback
controller: App\Controller\CustomMVolaCallbackController::handle
callback_url in TransactionRequest.Token Caching:
MVolaAuthService to use a custom cache adapter (e.g., Redis):
$cache = new RedisAdapter();
$authService = new MVolaAuthService($cache, $config);
Webhook Retries:
// In MVolaCallbackSubscriber
$message = new MVolaCallbackMessage($data);
$this->messageBus->dispatch($message);
MVOLA_* vars with APP_ if using Symfony’s default .env parser:
APP_MVOLA_ENVIRONMENT=sandbox
cache_ttl defaults to 3600 (1 hour). Reduce for testing:
mvola:
cache_ttl: 60 # 1 minute
How can I help you explore Laravel packages today?