A comprehensive payment gateway for Bow Framework supporting multiple African mobile money providers.
- ✅ Orange Money (Ivory Coast) - Fully implemented
- ✅ MTN Mobile Money (Ivory Coast) - Fully implemented
- 📦 Moov Money (Flooz) - Gateway ready, pending API documentation
- 📦 Wave - Gateway ready, pending API documentation
- 📦 Djamo - Gateway ready, pending API documentation
composer require bowphp/paymentConfigure your payment providers in config/payment.php:
use Bow\Payment\Payment;
return [
'default' => [
'gateway' => Payment::ORANGE,
'country' => 'ci',
],
'ivory_coast' => [
'orange' => [
'client_key' => env('ORANGE_CLIENT_KEY'),
'client_secret' => env('ORANGE_CLIENT_SECRET'),
'webhook_secret' => env('ORANGE_WEBHOOK_SECRET'),
],
'mtn' => [
'subscription_key' => env('MTN_SUBSCRIPTION_KEY'),
'api_user' => env('MTN_API_USER'),
'api_key' => env('MTN_API_KEY'),
'environment' => 'sandbox', // or 'production'
'webhook_secret' => env('MTN_WEBHOOK_SECRET'),
],
],
];use Bow\Payment\Payment;
// Configure the payment gateway
Payment::configure($config);
// Make a payment
$result = Payment::payment([
'amount' => 1000,
'reference' => 'ORDER-123',
'notif_url' => 'https://your-app.com/webhook',
'return_url' => 'https://your-app.com/success',
'cancel_url' => 'https://your-app.com/cancel',
]);
// Verify a transaction
$status = Payment::verify([
'amount' => 1000,
'order_id' => 'ORDER-123',
'pay_token' => 'TOKEN',
]);
if ($status->isSuccess()) {
// Payment successful
echo "Payment completed!";
}Payment::configure([
'default' => [
'gateway' => Payment::ORANGE,
'country' => 'ci',
],
'ivory_coast' => [
'orange' => [
'client_key' => 'YOUR_CLIENT_KEY',
'client_secret' => 'YOUR_CLIENT_SECRET',
],
],
]);
$result = Payment::payment([
'amount' => 1000,
'reference' => 'ORDER-123',
'notif_url' => 'https://your-app.com/webhook',
'return_url' => 'https://your-app.com/success',
'cancel_url' => 'https://your-app.com/cancel',
]);Payment::configure([
'default' => [
'gateway' => Payment::MTN,
'country' => 'ci',
],
'ivory_coast' => [
'mtn' => [
'subscription_key' => 'YOUR_SUBSCRIPTION_KEY',
'api_user' => 'YOUR_API_USER',
'api_key' => 'YOUR_API_KEY',
'environment' => 'sandbox', // or 'production'
],
],
]);
$result = Payment::payment([
'amount' => 1000,
'phone' => '0707070707',
'reference' => 'ORDER-123',
]);
// Verify transaction
$status = Payment::verify(['reference_id' => $result['reference_id']]);
// Check balance
$balance = Payment::balance();// Start with Orange Money
Payment::configure($config);
// Switch to MTN for a specific transaction
Payment::withProvider('ci', Payment::MTN);
Payment::payment($data);
// Switch back to default provider
Payment::withProvider('ci', Payment::ORANGE);Add the UserPayment trait to your User model:
use Bow\Payment\UserPayment;
class User extends Model
{
use UserPayment;
}
// Now you can use payment methods on your user model
$user->payment(1000, 'ORDER-123');
$user->transfer(5000, 'TRANSFER-456');
$user->balance();Automatically retry failed API calls with exponential backoff:
use Bow\Payment\Support\RetryHandler;
$retry = new RetryHandler(
maxAttempts: 3,
retryDelay: 1000,
exponentialBackoff: true
);
$result = $retry->execute(function() {
return Payment::payment([
'amount' => 1000,
'reference' => 'ORDER-123',
]);
});Protect your application from exceeding API rate limits:
use Bow\Payment\Support\RateLimiter;
$limiter = new RateLimiter(
maxRequests: 60,
timeWindow: 60
);
if ($limiter->isAllowed('orange')) {
$limiter->hit('orange');
Payment::payment($data);
} else {
// Rate limit exceeded, wait before retrying
$waitTime = $limiter->getRetryAfter('orange');
}Comprehensive audit trail for all payment operations:
use Bow\Payment\Support\TransactionLogger;
$logger = new TransactionLogger('/path/to/logs');
// Logs are automatically created with detailed context
$logger->logPaymentRequest('mtn', [
'amount' => 1000,
'reference' => 'ORDER-123'
]);
$logger->logPaymentResponse('mtn', true, $response);Secure webhook processing with signature validation:
use Bow\Payment\Webhook\WebhookHandler;
$handler = new WebhookHandler('orange', $config['orange']['webhook_secret']);
$request = WebhookHandler::parseRequest();
$event = $handler->handle($request['payload'], $request['signature']);
if ($event->isPaymentSuccess()) {
$transactionId = $event->getTransactionId();
$amount = $event->getAmount();
$status = $event->getStatus();
// Update your order status
Order::where('transaction_id', $transactionId)->update([
'status' => 'paid',
'amount' => $amount,
]);
}The package provides comprehensive custom exceptions:
use Bow\Payment\Exceptions\PaymentRequestException;
use Bow\Payment\Exceptions\RateLimitException;
use Bow\Payment\Exceptions\TokenGenerationException;
use Bow\Payment\Exceptions\InvalidProviderException;
use Bow\Payment\Exceptions\TransactionVerificationException;
use Bow\Payment\Exceptions\ConfigurationException;
try {
Payment::payment($data);
} catch (RateLimitException $e) {
// Rate limit exceeded
$retryAfter = $e->getCode();
Log::warning("Rate limit exceeded. Retry after: {$retryAfter} seconds");
} catch (PaymentRequestException $e) {
// Payment request failed
Log::error("Payment failed: " . $e->getMessage());
} catch (TokenGenerationException $e) {
// Token generation failed
Log::error("Token generation error: " . $e->getMessage());
} catch (InvalidProviderException $e) {
// Invalid provider specified
Log::error("Invalid provider: " . $e->getMessage());
} catch (TransactionVerificationException $e) {
// Transaction verification failed
Log::error("Verification failed: " . $e->getMessage());
} catch (ConfigurationException $e) {
// Configuration error
Log::error("Config error: " . $e->getMessage());
}For advanced use cases, you can use providers directly:
use Bow\Payment\Gateway\IvoryCost\Orange\OrangeGateway;
use Bow\Payment\Gateway\IvoryCost\Orange\OrangeTokenGenerator;
$config = [
'client_key' => 'YOUR_CLIENT_KEY',
'client_secret' => 'YOUR_CLIENT_SECRET',
];
$tokenGenerator = new OrangeTokenGenerator(
$config['client_key'],
$config['client_secret']
);
$gateway = new OrangeGateway($tokenGenerator, $config);
$result = $gateway->payment([
'amount' => 1000,
'reference' => 'ORDER-123',
'notif_url' => 'https://your-app.com/webhook',
'return_url' => 'https://your-app.com/success',
'cancel_url' => 'https://your-app.com/cancel',
]);
// Verify transaction
$status = $gateway->verify([
'amount' => 1000,
'order_id' => 'ORDER-123',
'pay_token' => $result['pay_token'],
]);use Bow\Payment\Gateway\IvoryCost\Mono\MonoGateway;
use Bow\Payment\Gateway\IvoryCost\Mono\MomoEnvironment;
use Bow\Payment\Gateway\IvoryCost\Mono\MomoTokenGenerator;
$config = [
'subscription_key' => 'YOUR_SUBSCRIPTION_KEY',
'api_user' => 'YOUR_API_USER',
'api_key' => 'YOUR_API_KEY',
'environment' => 'sandbox', // or 'production'
];
$environment = new MomoEnvironment($config['environment']);
$tokenGenerator = new MomoTokenGenerator(
$config['subscription_key'],
$config['api_user'],
$config['api_key'],
$environment
);
$gateway = new MonoGateway($tokenGenerator, $config, $environment);
$result = $gateway->payment([
'amount' => 1000,
'phone' => '0707070707',
'reference' => 'ORDER-123',
]);
// Verify transaction
$status = $gateway->verify([
'reference_id' => $result['reference_id'],
]);
// Check balance
$balance = $gateway->balance();The package includes comprehensive tests:
composer testTests cover:
- Orange Money payment flow
- MTN Mobile Money payment flow
- Transaction logging
- Retry logic
- Rate limiting
- Webhook handling
- Exception handling
- PHP >= 7.4 (PHP 8.0+ recommended)
- Bow Framework >= 4.0
- GuzzleHTTP >= 6.5
Contributions are welcome! Please follow PSR-12 coding standards and add tests for new features.
MIT License. See LICENSE file for details.