This package provides an interface for the Viva Wallet Payment API. It handles the Smart Checkout integration, the ISV Payment API, and Webhooks.
Check out the official Viva Wallet Developer Portal for detailed instructions on the APIs and more: https://developer.vivawallet.com
Note: This project is not a official package, and I'm not affiliated with Viva Payments in any way.
Install the package through Composer.
This package requires PHP 8.1 and Laravel 9.0 or higher, and uses Guzzle 7.0 to make API calls. Use the appropriate version according to your dependencies.
Viva Payments for Laravel | Guzzle | Laravel |
---|---|---|
^6.0 | ^7.0 | ^9.0 |
^5.2 | ^7.0 | ^9.0 |
^5.1 | ^7.0 | ^8.0 |
^5.0 | ^6.0|^7.0 | ^7.0 |
^4.3 | ^6.0|^7.0 | ^7.0 |
~4.1 | ~6.0 | ~7.0 |
~4.0 | ~6.0 | ~6.0 |
~3.0 | ~6.0 | ~5.5 |
~2.0 | ~6.0 | ~5.0 |
~1.0 | ~5.0 | ~5.0 |
composer require sebdesign/laravel-viva-payments
The package will automatically register its service provider.
Add the following array in your config/services.php
.
'viva' => [
'api_key' => env('VIVA_API_KEY'),
'merchant_id' => env('VIVA_MERCHANT_ID'),
'environment' => env('VIVA_ENVIRONMENT', 'production'),
'client_id' => env('VIVA_CLIENT_ID'),
'client_secret' => env('VIVA_CLIENT_SECRET'),
'isv_partner_id' => env('VIVA_ISV_PARTNER_ID'),
'isv_partner_api_key' => env('VIVA_ISV_PARTNER_API_KEY'),
],
The api_key
and merchant_id
can be found in the Settings > API Access section of your profile.
The client_id
and client_secret
are needed for the Smart Checkout. You can generate the Smart Checkout Credentials in the Settings > API Access section of your profile.
The isv_partner_id
and isv_partner_api_key
are required for using the ISV Payment API with Basic authentication.
Read more about API authentication on the Developer Portal: https://developer.vivawallet.com/getting-started/find-your-account-credentials/client-smart-checkout-credentials/
The environment
can be either production
or demo
.
Read more about the Smart Checkout process on the Developer portal: https://developer.vivawallet.com/smart-checkout/
The \Sebdesign\VivaPayments\Facades\Viva
facade provides all the methods needed to interact with the Smart Checkout integration.
The following guide will walk you through the necessary steps:
The amount requested in cents is required. All the other parameters are optional. Check out the request body schema.
use Sebdesign\VivaPayments\Facades\Viva;
$orderCode = Viva::orders()->create(
order: new CreatePaymentOrder(amount: 1000),
);
use Sebdesign\VivaPayments\Facades\Viva;
$redirectUrl = Viva::orders()->redirectUrl(
ref: $orderCode,
color: '0000ff',
paymentMethod: 23,
);
return redirect()->away(path: $redirectUrl);
use Sebdesign\VivaPayments\Facades\Viva;
$response = Viva::transactions()->retrieve(transactionId: request('t'));
<?php
namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Sebdesign\VivaPayments\Enums\TransactionStatus;
use Sebdesign\VivaPayments\Facades\Viva;
use Sebdesign\VivaPayments\Requests\CreatePaymentOrder;
use Sebdesign\VivaPayments\Requests\Customer;
use Sebdesign\VivaPayments\VivaException;
class CheckoutController extends Controller
{
/**
* Create a payment order and redirect to the checkout page.
*/
public function checkout(): RedirectResponse
{
try {
$orderCode = Viva::orders()->create(new CreatePaymentOrder(
amount: 1000,
customerTrns: 'Short description of purchased items/services to display to your customer',
customer: new Customer(
email: '[email protected]',
fullName: 'John Doe',
countryCode: 'GB',
requestLang: 'en-GB',
),
));
} catch (VivaException $e) {
report($e);
return back()->withErrors($e->getMessage());
}
$redirectUrl = Viva::orders()->redirectUrl(
ref: $orderCode,
color: '0000ff',
paymentMethod: 23,
);
return redirect()->away($redirectUrl);
}
/**
* Redirect from the checkout page and get the order details from the API.
*/
public function confirm(Request $request): RedirectResponse
{
try {
$transaction = Viva::transactions()->retrieve($request->input('t'));
} catch (VivaException $e) {
report($e);
return back()->withErrors($e->getMessage());
}
$status = match ($transaction->statusId) {
case TransactionStatus::PaymentPending: 'The order is pending.',
case TransactionStatus::PaymentSuccessful: 'The order is paid.',
case TransactionStatus::Error: 'The order was not paid.',
}
return view('order/success', compact('status'));
}
}
Viva Payments supports Webhooks, and this package offers a controller which verifies and handles incoming notification events.
Read more about the Webhooks on the Developer Portal: https://developer.vivawallet.com/webhooks-for-payments/
In your routes/web.php
define the following route for each webhook you have in your profile, replacing the URI(s) and your controller(s) accordingly.
Route::get('viva/webhooks', [WebhookController::class, 'verify']);
Route::post('viva/webhooks', [WebhookController::class, 'handle']);
Don't forget to add your webhook URI(s) to the $except
array on your VerifyCsrfToken
middleware.
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array<int, string>
*/
protected $except = ['viva/webhooks'];
}
To handle any request from Viva Wallet, you may listen to the WebhookEvent
.
According to the EventTypeId
, you can handle any event.
use Sebdesign\VivaPayments\Enums\WebhookEventType;
use Sebdesign\VivaPayments\Events\WebhookEvent;
class EventServiceProvider
{
protected $listen = [
WebhookEvent::class => [WebhookEventListener::class],
];
}
class WebhookEventListener
{
public function handle(WebhookEvent $event): void
{
match ($event->EventTypeId) {
WebhookEventType::TransactionPaymentCreated => ...,
WebhookEventType::TransactionFailed => ...,
WebhookEventType::TransactionReversalCreated => ...,
default => ...,
};
}
}
The EventData
property contains an object with the actual notification.
The class of the object depends on the notification type:
Event | Class |
---|---|
Transaction Payment Created | TransactionPaymentCreated |
Transaction Failed | TransactionFailed |
All other events | stdClass |
In addition, the TransactionPaymentCreated
and TransactionFailed
events are dispatched. You can listen to these specific events instead of listening to the WebhookEvent
.
use Sebdesign\VivaPayments\Enums\WebhookEventType;
use Sebdesign\VivaPayments\Events\TransactionFailed;
use Sebdesign\VivaPayments\Events\TransactionPaymentCreated;
class EventServiceProvider
{
protected $listen = [
TransactionPaymentCreated::class => [
ConfirmOrder::class,
],
TransactionFailed::class => [
CancelOrder::class,
],
];
}
class ConfirmOrder
{
public function handle(TransactionPaymentCreated $event): void
{
//
}
}
class CancelOrder
{
public function handle(TransactionFailed $event): void
{
//
}
}
All methods accept a $guzzleOptions
array argument as their last parameter. This argument is entirely optional, and it allows you to specify additional request options to the Guzzle
client.
use Sebdesign\VivaPayments\Facades\Viva;
use Sebdesign\VivaPayments\Requests\CreatePaymentOrder;
use Sebdesign\VivaPayments\Requests\Customer;
$orderCode = Viva::orders()->create(
order: new CreatePaymentOrder(
amount: 1000,
customerTrns: 'Short description of purchased items/services to display to your customer',
customer: new Customer(
email: '[email protected]',
fullName: 'John Doe',
phone: '+30999999999',
countryCode: 'GB',
requestLang: 'en-GB',
),
paymentTimeOut: 300,
preauth: false,
allowRecurring: false,
maxInstallments: 12,
paymentNotification: true,
tipAmount: 100,
disableExactAmount: false,
disableCash: true,
disableWallet: true,
sourceCode: '1234',
merchantTrns: 'Short description of items/services purchased by customer',
tags: [
'tags for grouping and filtering the transactions',
'this tag can be searched on VivaWallet sales dashboard',
'Sample tag 1',
'Sample tag 2',
'Another string',
],
cardTokens: ['ct_5d0a4e3a7e04469f82da228ca98fd661'],
),
guzzleOptions: [],
);
use Sebdesign\VivaPayments\Facades\Viva;
$url = Viva::orders()->redirectUrl(
ref: $orderCode,
color: '0000ff',
paymentMethod: 23,
);
use Sebdesign\VivaPayments\Facades\Viva;
$transaction = Viva::transactions()->retrieve(
transactionId: 'c90d4902-6245-449f-b2b0-51d99cd09cfe',
guzzleOptions: [],
);
use Sebdesign\VivaPayments\Facades\Viva;
use Sebdesign\VivaPayments\Requests\CreateRecurringTransaction;
$response = Viva::transactions()->createRecurring(
transactionId: '252b950e-27f2-4300-ada1-4dedd7c17904',
transaction: new CreateRecurringTransaction(
amount: 100,
installments: 1,
customerTrns: 'A description of products / services that is displayed to the customer',
merchantTrns: 'Your merchant reference',
sourceCode: '6054',
tipAmount: 0,
),
guzzleOptions: [],
);
See: https://developer.vivawallet.com/authentication/#step-2-request-access-token
You don't need to call this method, because the client requests the access token automatically when needed. However, you can specify the client credentials at runtime if you want.
use Sebdesign\VivaPayments\Facades\Viva;
Viva::withOAuthCredentials(
clientId: 'client_id',
clientSecret: 'client_secret',
);
If you need to request access tokens manually, you can use the requestToken
method.
This method returns the token as an AccessToken
object.
use Sebdesign\VivaPayments\Facades\Viva;
// Using `client_id` and `client_secret` from `config/services.php`:
$token = Viva::oauth()->requestToken();
// Using custom client credentials
$token = Viva::oauth()->requestToken(
clientId: 'client_id',
clientSecret: 'client_secret',
guzzleOptions: [],
);
If you are storing the token somewhere, e.g. in your database or in the cache, you can set the access token string on the client to be used as a Bearer token.
use Sebdesign\VivaPayments\Facades\Viva;
Viva::withToken(token: 'eyJhbGciOiJSUzI1...');
use Sebdesign\VivaPayments\Facades\Viva;
$cardToken = Viva::cards()->createToken(
transactionId: '6cffe5bf-909c-4d69-b6dc-2bef1a6202f7',
guzzleOptions: [],
);
See: https://developer.vivawallet.com/webhooks-for-payments/#generate-a-webhook-verification-key
use Sebdesign\VivaPayments\Facades\Viva;
$key = Viva::webhooks()->getVerificationKey(
guzzleOptions: [],
);
The ISV Payment API methods are available through the Viva::isv()
service.
use Sebdesign\VivaPayments\Facades\Viva;
use Sebdesign\VivaPayments\Requests\CreatePaymentOrder;
use Sebdesign\VivaPayments\Requests\Customer;
$orderCode = Viva::isv()->orders()->create(
order: new CreatePaymentOrder(
amount: 1000,
customerTrns: 'Short description of purchased items/services to display to your customer',
customer: new Customer(
email: '[email protected]',
fullName: 'John Doe',
phone: '+30999999999',
countryCode: 'GB',
requestLang: 'en-GB',
),
paymentTimeOut: 300,
preauth: false,
allowRecurring: false,
maxInstallments: 12,
paymentNotification: true,
tipAmount: 100,
disableExactAmount: false,
disableCash: true,
disableWallet: true,
sourceCode: '1234',
merchantTrns: 'Short description of items/services purchased by customer',
tags: [
'tags for grouping and filtering the transactions',
'this tag can be searched on VivaWallet sales dashboard',
'Sample tag 1',
'Sample tag 2',
'Another string',
],
isvAmount: 10,
resellerSourceCode: '2345',
),
guzzleOptions: [],
);
use Sebdesign\VivaPayments\Facades\Viva;
$transaction = Viva::isv()->transactions()->retrieve(
transactionId: 'c90d4902-6245-449f-b2b0-51d99cd09cfe',
guzzleOptions: [],
);
use Sebdesign\VivaPayments\Facades\Viva;
use Sebdesign\VivaPayments\Requests\CreateRecurringTransaction;
Viva::withBasicAuthCredentials(
config('services.viva.isv_partner_id').':'.config('services.viva.merchant_id'),
config('services.viva.isv_partner_api_key'),
);
$transaction = Viva::isv()->transactions()->createRecurring(
transactionId: 'c90d4902-6245-449f-b2b0-51d99cd09cfe',
transaction: new CreateRecurringTransaction(
amount: 100,
isvAmount: 1,
customerTrns: 'A description of products / services that is displayed to the customer',
merchantTrns: 'Your merchant reference',
sourceCode: '4929333',
resellerSourceCode: '1565',
),
guzzleOptions: [],
);
When the VivaPayments API returns an error, a Sebdesign\VivaPayments\VivaException
is thrown.
For any other HTTP error a GuzzleHttp\Exception\ClientException
is thrown.
Unit tests are triggered by running phpunit --group unit
.
To run functional tests you have to include a .env
file in the root folder, containing the credentials (VIVA_API_KEY
, VIVA_MERCHANT_ID
, VIVA_CLIENT_ID
, VIVA_CLIENT_SECRET
), in order to hit the VivaPayments demo API. Then run phpunit --group functional
to trigger the tests.