serenity_technologies / ghana-payments
A payment gateway protocols for Ghanaian banks and payment systems
Requires
- illuminate/cache: ^12.21
- illuminate/http: ^12.21
- illuminate/support: ^12.21
- omnipay/common: ^3.4
- php-http/guzzle7-adapter: ^1.1
Requires (Dev)
- barryvdh/laravel-ide-helper: ^3.6
- phpunit/phpunit: ^12.3
README
This package provides an Omnipay implementation for the MyGHPay Checkout payment gateway, allowing you to integrate Ghanaian payment methods into your Laravel application.
Features
- Authentication Management: The package automatically handles authentication with the MyGHPay API, including token caching for one hour to minimize authentication requests.
- Payment Session Creation: Create payment sessions with full support for itemized purchases.
- Payment Status Checking: Check the status of payment sessions at any time.
- Callback Handling: Process real-time payment status updates from MyGHPay.
- Environment Support: Works with both test and live environments.
Installation
Install the package via Composer:
composer require serenity_technologies/ghana-payments
Publish the configuration file:
php artisan vendor:publish --provider="SerenityTechnologies\GhanaPayments\GhanaPaymentsServiceProvider" --tag="config"
Configuration
Add the following environment variables to your .env
file:
GHANA_PAY_MERCHANT_ID=your_merchant_username
GHANA_PAY_MERCHANT_KEY=your_merchant_password
GHANA_PAY_MERCHANT_SECRET=your_merchant_secret
GHANA_PAY_MERCHANT_URL=https://your-merchant-url.com
GHANA_PAY_CALLBACK_URL=https://your-merchant-url.com/callback
You can also configure multiple drivers by modifying the config/ghana-payments.php
file:
'default' => env('GHANA_PAY_DRIVER', 'myghpay'),
'drivers' => [ 'myghpay' => [
'merchant_id' => env('GHANA_PAY_MERCHANT_ID'),
'merchant_key' => env('GHANA_PAY_MERCHANT_KEY'),
'merchant_secret' => env('GHANA_PAY_MERCHANT_SECRET'),
'merchant_url' => env('GHANA_PAY_MERCHANT_URL'),
'merchant_callback_url' => env('GHANA_PAY_CALLBACK_URL'),
'test_mode' => env('GHANA_PAY_TEST_MODE', false),
],
'other_driver' => [
'api_key' => env('OTHER_DRIVER_API_KEY'),
'secret' => env('OTHER_DRIVER_SECRET'),
'test_mode' => env('OTHER_DRIVER_TEST_MODE', false),
],
],
To switch between drivers, simply change the GHANA_PAY_DRIVER
environment variable:
GHANA_PAY_DRIVER=other_driver
Usage
Initialize the Gateway
use Omnipay\Omnipay;
use SerenityTechnologies\GhanaPayments\Drivers\GTBGhana\MyGHPayCheckout\MyGHPayCheckout;
$gateway = Omnipay::create(MyGHPayCheckout::class);
$gateway->setUsername(config('ghana-payments.merchant_id'));
$gateway->setPassword(config('ghana-payments.merchant_key'));
$gateway->setTestMode(true); // For testing environment
Or in Laravel, you can resolve it from the service container:
$gateway = app('ghana-payments');
Create a Payment Session
To initiate a payment, you need to create a purchase request:
$response = $gateway->purchase([
'amount' => '100.00',
'transactionReference' => uniqid(), // Unique transaction reference
'callbackUrl' => route('payment.callback'), // URL for payment status updates
'returnUrl' => route('payment.return'), // URL to redirect user after payment
'description' => 'Order #12345', // Optional description
'title' => 'Store Purchase', // Optional title
'paymentItems' => [ // Optional itemized list
[
'name' => 'Product 1',
'description' => 'Product 1 description',
'amount' => '50.00',
'quantity' => 1
],
[
'name' => 'Product 2',
'description' => 'Product 2 description',
'amount' => '50.00',
'quantity' => 1
]
]
])->send();
if ($response->isSuccessful()) {
// Payment session created successfully
echo "Transaction ID: " . $response->getTransactionReference() . "\n";
} elseif ($response->isRedirect()) {
// Redirect to offsite payment gateway
$response->redirect();
} else {
// Payment failed
echo "Error: " . $response->getMessage();
}
You can also use the facade
use SerenityTechnologies\GhanaPayments\Facades\GhanaPayments;
$response = GhanaPayments::purchase([
'amount' => '100.00',
'transactionReference' => uniqid(),
'callbackUrl' => route('payment.callback'),
'returnUrl' => route('payment.return'),
'description' => 'Order #12345', // Optional description
'title' => 'Store Purchase', // Optional title
'paymentItems' => [ // Optional itemized list
[
'name' => 'Product 1',
'description' => 'Product 1 description',
'amount' => '50.00',
'quantity' => 1
],
[
'name' => 'Product 2',
'description' => 'Product 2 description',
'amount' => '50.00',
'quantity' => 1
]
]
])->send();
if ($response->isSuccessful()) {
// Payment session created successfully
echo "Transaction ID: " . $response->getTransactionReference() . "\n";
} elseif ($response->isRedirect()) {
// Redirect to offsite payment gateway
$response->redirect();
} else {
// Payment failed
echo "Error: " . $response->getMessage();
}
Handle Callbacks
MyGHPay will send callbacks to your callbackUrl to notify you of payment status changes. You can handle these in your callback controller:
use SerenityTechnologies\GhanaPayments\Drivers\GTBGhana\MyGHPayCheckout\Messages\MyGHPayNotification;
public function handleCallback(Request $request)
{
$notification = new MyGHPayNotification($request);
$transactionReference = $notification->getTransactionReference();
$status = $notification->getTransactionStatus();
$message = $notification->getMessage();
switch ($status) {
case \Omnipay\Common\Message\NotificationInterface::STATUS_COMPLETED:
// Payment was successful
// Update your order status
break;
case \Omnipay\Common\Message\NotificationInterface::STATUS_PENDING:
// Payment is pending
// Wait for another callback
break;
case \Omnipay\Common\Message\NotificationInterface::STATUS_FAILED:
// Payment failed or was cancelled
// Update your order status accordingly
break;
}
// Always return a 200 response to acknowledge the callback
return response('OK', 200);
}
Check Payment Status
You can manually check the status of a payment session:
$response = $gateway->completePurchase([
'sessionCode' => $transactionReference, // The session code from the purchase response
])->send();
if ($response->isSuccessful()) {
// Payment completed successfully
echo "Payment successful!";
} elseif ($response->isPending()) {
// Payment is still pending
echo "Payment pending";
} elseif ($response->isCancelled()) {
// Payment was cancelled or failed
echo "Payment failed or cancelled";
}
Testing
For testing, set the gateway to test mode:
$gateway->setTestMode(true);
The package includes a test suite that you can run to verify the functionality of the package. To run the tests, execute the following command in your terminal:
php vendor/bin/phpunit
This will use the test endpoint: https://testbed.gtbghana.com/MyghpayCheckoutApi/api For production, ensure test mode is disabled (default):
$gateway->setTestMode(false);
This will use the live endpoint: https://client.myghpay.com/myghpaycheckoutapi/api
Adding a New Driver
To add a new payment driver to this package, follow these steps:
Create the Driver Class
Create a new driver class that extends
SerenityTechnologies\GhanaPayments\Drivers\AbstractDriver
:<?php namespace YourNamespace\YourDriver; use SerenityTechnologies\GhanaPayments\Drivers\AbstractDriver; use YourNamespace\YourDriver\Messages\PurchaseRequest; use YourNamespace\YourDriver\Messages\CompletePurchaseRequest; use YourNamespace\YourDriver\Messages\YourDriverNotification; class YourDriverName extends AbstractDriver { public function getName(): string { return 'YourDriverName'; } public function getDefaultParameters(): array { return [ 'apiKey' => '', 'secret' => '', 'testMode' => false, ]; } public function purchase(array $options = []): \Omnipay\Common\Message\RequestInterface { return $this->createRequest(PurchaseRequest::class, $options); } public function completePurchase(array $options = []): \Omnipay\Common\Message\RequestInterface { return $this->createRequest(CompletePurchaseRequest::class, $options); } public function acceptNotification(array $options = []): \Omnipay\Common\Message\NotificationInterface { return new YourDriverNotification(); } // Add other required methods like setUsername(), setPassword(), etc. depending on your driver's authentication requirements }
Create Request Classes
Create request classes for your driver in a
Messages
subdirectory:<?php namespace YourNamespace\YourDriver\Messages; use SerenityTechnologies\GhanaPayments\Drivers\AbstractRequest; class PurchaseRequest extends AbstractRequest { public function getData() { $this->validate('amount', 'transactionReference'); return [ 'amount' => $this->getAmount(), 'transactionReference' => $this->getTransactionReference(), // Add other required fields ]; } public function sendData($data): \Omnipay\Common\Message\ResponseInterface { // Implement your API call logic here $response = $this->httpClient->request('POST', $this->getEndpoint().'/payment',[ 'Authorization' => 'Bearer ' . $this->getApiKey(), 'Content-Type' => 'application/json', ], json_encode($data)); $responseData = json_decode((string) $response->getBody(), true); return $this->response = new PurchaseResponse($this, $responseData); } public function getApiKey() { return $this->getParameter('apiKey'); } public function setApiKey($value) { return $this->setParameter('apiKey', $value); } }
Create Response Classes
Create response classes for handling API responses:
<?php namespace YourNamespace\YourDriver\Messages; use Omnipay\Common\Message\AbstractResponse; use Omnipay\Common\Message\RedirectResponseInterface; class PurchaseResponse extends AbstractResponse implements RedirectResponseInterface { public function isSuccessful(): bool { return isset($this->data['status']) && $this->data['status'] === 'success'; } public function isRedirect(): bool { return isset($this->data['redirectUrl']); } public function getRedirectUrl(): ?string { if ($this->isRedirect()) { return $this->data['redirectUrl']; } return null; } public function getTransactionReference(): ?string { return $this->data['transactionId'] ?? null; } public function getMessage(): ?string { return $this->data['message'] ?? null; } public function getRedirectMethod(): string { return 'GET'; } public function getRedirectData(): ?array { return null; } }
Create Notification Class
Create a notification class to handle callbacks from your payment provider:
<?php namespace YourNamespace\YourDriver\Messages; use Omnipay\Common\Message\AbstractRequest; use Omnipay\Common\Message\NotificationInterface; use Illuminate\Http\Request as HttpRequest; class YourDriverNotification implements NotificationInterface { protected mixed $data; protected HttpRequest $httpRequest; /** * Constructor */ public function __construct(HttpRequest $request = null) { $this->httpRequest = $request ?: HttpRequest::createFromGlobals(); $this->data = json_decode($this->httpRequest->getContent(), true); } public function getData(): mixed { return $this->data; } public function sendData($data): static { return $this; } public function getTransactionReference(): ?string { return $this->data['transactionId'] ?? null; } public function getTransactionStatus() { if (isset($this->data['status'])) { switch ($this->data['status']) { case 'completed': return NotificationInterface::STATUS_COMPLETED; case 'pending': return NotificationInterface::STATUS_PENDING; case 'failed': return NotificationInterface::STATUS_FAILED; } } return null; } public function getMessage(): ?string { return $this->data['message'] ?? null; } }
Update the Service Provider
Update the
GhanaPaymentsServiceProvider
to register your new driver:public function register(): void { $this->app->singleton('ghana-payments', function ($app) { $driver = config('ghana-payments.default', 'myghpay'); $config = config('ghana-payments.drivers.' . $driver, []); $gateway = match($driver) { 'myghpay' => Omnipay::create(MyGHPayCheckout::class), 'your_driver' => Omnipay::create(YourDriver::class), // Add this line default => Omnipay::create(MyGHPayCheckout::class), }; // Apply configuration to the gateway if ($gateway instanceof \SerenityTechnologies\GhanaPayments\Drivers\DriverInterface) { $gateway->setConfig($config); } else if ($driver === 'myghpay') { $gateway->setUsername($config['merchant_id'] ?? config('ghana-payments.merchant_id')); $gateway->setPassword($config['merchant_key'] ?? config('ghana-payments.merchant_key')); } // Add similar configuration for your driver return $gateway; }); }
Update Configuration
Add your driver configuration to the config/ghana-payments.php file:
'drivers' => [ 'myghpay' => [ // ... existing configuration ], 'your_driver' => [ 'api_key' => env('YOUR_DRIVER_API_KEY'), 'secret' => env('YOUR_DRIVER_SECRET'), 'test_mode' => env('YOUR_DRIVER_TEST_MODE', false), ], ],
Add Environment Variables
Add your driver's environment variables to your
.env
file:YOUR_DRIVER_API_KEY=your_api_key YOUR_DRIVER_SECRET=your_secret YOUR_DRIVER_TEST_MODE=true
Users can now switch to your driver by setting:
GHANA_PAY_DRIVER=your_driver
Using the Facade
This package includes a Laravel facade for easier access to the payment gateway. You can use the facade in two ways:
Automatic Aliasing (Laravel 5.5+): The package automatically registers the facade alias, so you can use it directly:
use SerenityTechnologies\GhanaPayments\Facades\GhanaPayments; // Create a payment $response = GhanaPayments::purchase([ 'amount' => '50.00', 'transactionReference' => uniqid(), 'callbackUrl' => route('payment.callback'), 'returnUrl' => route('payment.return'), ])->send(); // Check payment status $response = GhanaPayments::completePurchase([ 'sessionCode' => $sessionCode, ])->send(); // Handle notifications $notification = GhanaPayments::acceptNotification();
Manual Aliasing:
'aliases' => [ // ... other aliases
'GhanaPayments' => SerenityTechnologies\GhanaPayments\Facades\GhanaPayments::class,
];
If you prefer to manually register the facade alias, add it to the aliases
array in your config/app.php
:
The facade provides access to all methods available on the payment gateway, making it easy to process payments:
Security
- Authentication tokens are cached securely using Laravel's cache system
- Tokens are automatically refreshed when they expire
- All communication with the MyGHPay API is done over HTTPS
- Sensitive configuration values should be stored in environment variables
Troubleshooting
If you encounter issues:
- Ensure your merchant credentials are correct
- Check that your callback URL is publicly accessible
- Verify that your server can make outbound HTTPS requests
- Confirm that your cache system is working properly For any authentication issues, the package will automatically attempt to re-authenticate and obtain a new token.