mrdulal / laravel-vipps
Laravel package for MobilePay Vipps payment integration with ePayment, Checkout, Express, and recurring payments support
Fund package maintenance!
mrdulal
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.0
- illuminate/config: ^9.0|^10.0|^11.0|^12.0
- illuminate/http: ^9.0|^10.0|^11.0|^12.0
- illuminate/routing: ^9.0|^10.0|^11.0|^12.0
- illuminate/support: ^9.0|^10.0|^11.0|^12.0
- illuminate/validation: ^9.0|^10.0|^11.0|^12.0
Requires (Dev)
- mockery/mockery: ^1.4
- orchestra/testbench: ^7.0|^8.0|^9.0|^10.0
- phpunit/phpunit: ^9.0|^10.0|^11.0
README
A comprehensive Laravel package for MobilePay Vipps payment integration with support for all major Vipps payment methods. This package provides a clean, modern API for integrating with the Vipps MobilePay payment platform used across Norway, Denmark, and Finland.
โ ๏ธ Important Notice: This is an unofficial package and is not affiliated with, endorsed by, or officially supported by MobilePay or Vipps. This package is developed independently by a solo developer for Laravel developers. For official MobilePay Vipps integration documentation, please visit the Vipps Developer Portal.
๐ฏ Features
Payment Methods
- ๐ช MobilePay Vipps ePayment - Standard payment method integration
- ๐ MobilePay Vipps Checkout - Complete checkout solution with card payments
- โก MobilePay Vipps Express - Quick checkout from product pages with QR codes
- ๐ Recurring Payments - Subscription and recurring payment support
Management & Operations
- ๐ Order Management API - Capture, refund, cancel, and order management
- ๐ Webhook Support - Handle payment status updates automatically with signature verification
- ๐ก Event-Driven Architecture - Laravel events for all payment actions
- ๐ก๏ธ Security - Built-in signature verification and request validation
Developer Experience
- โ Comprehensive Testing - Full test suite with PHPUnit and Orchestra Testbench
- ๐ Type Safety - Full type hints and PHPDoc annotations
- ๐ง Configurable - Extensive configuration options
- ๐ Debugging - Built-in logging and error handling
- ๐ฆ Laravel Integration - Service providers, facades, and Artisan commands
๐ Countries Supported
Country | Service | Market Penetration | Users |
---|---|---|---|
๐ณ๐ด Norway | Vipps | 77% population | 4.2M users |
๐ฉ๐ฐ Denmark | MobilePay | 75% population | 4.4M users |
๐ซ๐ฎ Finland | MobilePay | 50% population | 2.8M users |
Total Market Reach: 11+ million active users across Nordic countries
๐ Requirements
Requirement | Version | Notes |
---|---|---|
PHP | 8.1+ | Uses modern PHP features like enums, readonly properties |
Laravel | 9.0+ | Compatible with Laravel 9, 10, 11, and 12 |
Guzzle HTTP | 7.0+ | For API communication |
ext-json | * | JSON processing |
ext-openssl | * | SSL/TLS support for API calls |
Development Requirements
- PHPUnit 9.0+ or 10.0+
- Orchestra Testbench 7.0+, 8.0+, or 9.0+
- Mockery 1.4+ (for testing)
๐ Installation
1. Install via Composer
From Packagist (Recommended)
composer require mrdulal/laravel-vipps
From GitHub Packages (Alternative)
See GitHub Packages Installation Guide for detailed instructions on installing from GitHub Packages.
2. Publish Configuration
# Publish configuration file php artisan vendor:publish --provider="Mrdulal\LaravelVipps\VippsServiceProvider" --tag="config" # Publish migrations php artisan vendor:publish --provider="Mrdulal\LaravelVipps\VippsServiceProvider" --tag="migrations" # Publish views (optional) php artisan vendor:publish --provider="Mrdulal\LaravelVipps\VippsServiceProvider" --tag="views"
3. Run Migrations
php artisan migrate
This will create the following tables:
vipps_payments
- Store payment recordsvipps_recurring_agreements
- Store recurring payment agreementsvipps_recurring_charges
- Store individual recurring charges
4. Laravel Auto-Discovery
The package uses Laravel's auto-discovery feature. The service provider and facade will be registered automatically.
Manual Registration (if needed):
// config/app.php 'providers' => [ // ... Mrdulal\LaravelVipps\VippsServiceProvider::class, ], 'aliases' => [ // ... 'Vipps' => Mrdulal\LaravelVipps\Facades\Vipps::class, ],
โ๏ธ Configuration
Environment Variables
Add your Vipps credentials to your .env
file:
# Required: Basic Configuration VIPPS_ENVIRONMENT=test # test or production VIPPS_CLIENT_ID=your_client_id VIPPS_CLIENT_SECRET=your_client_secret VIPPS_SUBSCRIPTION_KEY=your_ocp_apim_subscription_key VIPPS_MERCHANT_SERIAL_NUMBER=123456 # 6-digit MSN # Required: Webhook Configuration VIPPS_WEBHOOK_SECRET=your_webhook_secret # Optional: Logging & Debugging VIPPS_LOGGING_ENABLED=false VIPPS_LOG_CHANNEL=default VIPPS_LOG_LEVEL=info
Configuration File
The package configuration is located at config/vipps.php
. Key sections include:
return [ // Environment (test/production) 'environment' => env('VIPPS_ENVIRONMENT', 'test'), // API Credentials 'client_id' => env('VIPPS_CLIENT_ID'), 'client_secret' => env('VIPPS_CLIENT_SECRET'), 'subscription_key' => env('VIPPS_SUBSCRIPTION_KEY'), 'merchant_serial_number' => env('VIPPS_MERCHANT_SERIAL_NUMBER'), // Feature toggles 'features' => [ 'epayment' => true, 'checkout' => true, 'express' => true, 'recurring' => true, 'order_management' => true, 'webhooks' => true, ], // HTTP client settings 'http' => [ 'timeout' => 30, 'connect_timeout' => 10, 'verify' => true, ], // Express checkout UI settings 'express' => [ 'button_theme' => 'orange', // orange, white, white-outline 'button_size' => 'large', // small, medium, large 'show_on_cart' => true, 'show_on_product' => true, ], ];
๐ Getting Started
1. Get Vipps MobilePay Account Keys
Step-by-Step Account Setup:
- Sign up at Vipps MobilePay Portal
- Wait for approval (1-2 days) - you'll receive login details via email
- Access Developer Portal:
- Login to Vipps MobilePay Business Portal
- Navigate to "Developer" tab
- Select "Test Keys" for development or "Production Keys" for live environment
- Retrieve Credentials:
- Merchant Serial Number (MSN) - 6-digit number displayed prominently
- Client ID - Click "Show keys" to reveal
- Client Secret - Click "Show keys" to reveal
- Subscription Key - Listed as "Ocp-Apim-Subscription-Key"
Test vs Production
Environment | Purpose | API Endpoint | Features |
---|---|---|---|
Test | Development & Testing | https://apitest.vipps.no |
Full API access, test cards |
Production | Live transactions | https://api.vipps.no |
Real payments, KYC required |
2. Webhook Setup
Vipps requires a webhook endpoint for payment status updates:
// routes/web.php or routes/api.php Route::post('/vipps/webhook', [\Mrdulal\LaravelVipps\Http\Controllers\WebhookController::class, 'handle']);
Register your webhook URL in the Vipps portal:
- Format:
https://yourdomain.com/vipps/webhook
- Must be HTTPS in production
- Must respond with HTTP 200 for successful processing
3. Basic Usage
ePayment (Standard Payment)
use Mrdulal\LaravelVipps\Facades\Vipps; // Create a payment $payment = Vipps::ePayment()->create([ 'amount' => 10000, // Amount in รธre (100.00 NOK) 'currency' => 'NOK', 'orderId' => 'order-123', 'description' => 'Payment for order #123', 'redirectUrl' => 'https://yoursite.com/payment/callback', 'userFlow' => 'WEB_REDIRECT' ]); // Get payment details $details = Vipps::ePayment()->getPayment($payment['orderId']); // Capture payment $capture = Vipps::ePayment()->capture($payment['orderId'], [ 'amount' => 10000, 'description' => 'Capture for order #123' ]);
Express Checkout
use Mrdulal\LaravelVipps\Facades\Vipps; // Create express checkout session $session = Vipps::express()->create([ 'amount' => 10000, 'currency' => 'NOK', 'orderId' => 'order-123', 'description' => 'Express checkout for order #123', 'redirectUrl' => 'https://yoursite.com/express/callback', 'userInfo' => [ 'userId' => 'user-123' ] ]); // Generate QR code for express checkout $qrCode = Vipps::express()->generateQrCode([ 'orderId' => 'order-123', 'amount' => 10000, 'currency' => 'NOK', 'description' => 'Product purchase', 'redirectUrl' => 'https://yoursite.com/callback', 'qrFormat' => 'SVG', // or 'PNG' 'qrSize' => 300 ]); // Create shareable payment link $shareableLink = Vipps::express()->createShareableLink([ 'orderId' => 'order-456', 'amount' => 15000, 'currency' => 'NOK', 'description' => 'Shareable payment link', 'redirectUrl' => 'https://yoursite.com/callback', 'expiresAt' => now()->addHours(24) ]);
Recurring Payments
use Mrdulal\LaravelVipps\Facades\Vipps; // Create recurring agreement $agreement = Vipps::recurring()->createAgreement([ 'currency' => 'NOK', 'price' => 9900, // 99.00 NOK 'productName' => 'Monthly Subscription', 'productDescription' => 'Premium subscription service', 'merchantRedirectUrl' => 'https://yoursite.com/recurring/callback', 'merchantAgreementUrl' => 'https://yoursite.com/agreement/123', 'interval' => 'MONTH', 'intervalCount' => 1, 'isApp' => false ]); // Create charge for agreement $charge = Vipps::recurring()->createCharge($agreement['agreementId'], [ 'amount' => 9900, 'currency' => 'NOK', 'description' => 'Monthly subscription charge', 'orderId' => 'charge-456' ]); // List all charges for an agreement $charges = Vipps::recurring()->listCharges($agreement['agreementId']); // Stop an agreement $result = Vipps::recurring()->stopAgreement($agreement['agreementId'], [ 'reason' => 'Customer requested cancellation' ]);
๐ API Reference
Services Overview
Service | Purpose | Key Methods |
---|---|---|
ePayment | Standard payments | create() , capture() , refund() , cancel() |
Express | Quick checkout | create() , generateQrCode() , createShareableLink() |
Checkout | Full checkout flow | create() , updateSession() , getPaymentDetails() |
Recurring | Subscriptions | createAgreement() , createCharge() , stopAgreement() |
OrderManagement | Payment operations | capture() , refund() , cancel() , getPaymentHistory() |
Webhook | Event handling | handleWebhook() , verifySignature() |
ePayment Service
Create Payment
Vipps::ePayment()->create([ 'amount' => 10000, // Required: Amount in รธre (100.00 NOK) 'currency' => 'NOK', // Optional: NOK, DKK, EUR (default: NOK) 'orderId' => 'order-123', // Required: Unique order identifier 'description' => 'Payment for order #123', // Required: Payment description 'redirectUrl' => 'https://yoursite.com/callback', // Required: Callback URL 'userFlow' => 'WEB_REDIRECT', // Optional: WEB_REDIRECT, NATIVE_REDIRECT 'paymentMethod' => 'WALLET', // Optional: WALLET, CARD 'skipLandingPage' => false, // Optional: Skip Vipps landing page 'userInfo' => [ // Optional: Pre-fill user information 'userId' => 'user-123', 'mobileNumber' => '+4712345678', 'email' => 'user@example.com' ], 'reference' => 'ref-456' // Optional: Your internal reference ]);
Capture Payment
Vipps::ePayment()->capture('order-123', [ 'amount' => 10000, // Required: Amount to capture 'description' => 'Capture for order #123', // Required 'reference' => 'capture-ref' // Optional ]);
Refund Payment
Vipps::ePayment()->refund('order-123', [ 'amount' => 5000, // Required: Amount to refund 'description' => 'Partial refund', // Required 'reference' => 'refund-ref' // Optional ]);
Express Service
Generate QR Code
Vipps::express()->generateQrCode([ 'orderId' => 'qr-order-123', 'amount' => 15000, 'currency' => 'NOK', 'description' => 'QR Code Payment', 'redirectUrl' => 'https://yoursite.com/callback', 'qrFormat' => 'SVG', // SVG or PNG 'qrSize' => 300 // Size in pixels (100-2000) ]);
Create Shareable Link
Vipps::express()->createShareableLink([ 'orderId' => 'share-order-123', 'amount' => 20000, 'currency' => 'NOK', 'description' => 'Shareable Payment Link', 'redirectUrl' => 'https://yoursite.com/callback', 'expiresAt' => now()->addDays(7) // Optional: Link expiration ]);
Recurring Service
Create Agreement
Vipps::recurring()->createAgreement([ 'currency' => 'NOK', 'price' => 9900, // Monthly price in รธre 'productName' => 'Premium Plan', // Max 45 characters 'productDescription' => 'Monthly premium subscription', // Max 100 chars 'merchantRedirectUrl' => 'https://yoursite.com/recurring/success', 'merchantAgreementUrl' => 'https://yoursite.com/terms', 'interval' => 'MONTH', // WEEK, MONTH, YEAR 'intervalCount' => 1, // How many intervals between charges 'isApp' => false, // True if initiated from mobile app 'phoneNumber' => '+4712345678', // Optional: Pre-fill phone 'campaign' => [ // Optional: Campaign pricing 'start' => now(), 'end' => now()->addMonths(3), 'price' => 4900 // Campaign price in รธre ] ]);
Order Management Service
Get Payment History
$history = Vipps::orderManagement()->getPaymentHistory('order-123'); // Returns array of all transactions for the order
Send Receipt
Vipps::orderManagement()->sendReceipt('order-123', [ 'orderLines' => [ [ 'name' => 'Premium Subscription', 'id' => 'sub-001', 'totalAmount' => 9900, 'totalAmountExcludingTax' => 7920, 'totalTaxAmount' => 1980, 'taxRate' => 25.0, 'unitInfo' => [ 'unitPrice' => 9900, 'quantity' => '1', 'quantityUnit' => 'month' ] ] ], 'bottomLine' => [ 'currency' => 'NOK', 'tipAmount' => 500, 'barcode' => [ 'format' => 'EAN-13', 'data' => '1234567890123' ] ] ]);
๐ Webhook Handling
Setup Webhook Route
// routes/web.php or routes/api.php Route::post('/vipps/webhook', [\Mrdulal\LaravelVipps\Http\Controllers\WebhookController::class, 'handle']);
Event Listeners
// app/Providers/EventServiceProvider.php use Mrdulal\LaravelVipps\Events\{ PaymentCompleted, PaymentCancelled, PaymentCaptured, PaymentRefunded, RecurringAgreementCreated, RecurringChargeCreated }; protected $listen = [ PaymentCompleted::class => [ \App\Listeners\HandlePaymentCompleted::class, ], PaymentCancelled::class => [ \App\Listeners\HandlePaymentCancelled::class, ], PaymentCaptured::class => [ \App\Listeners\HandlePaymentCaptured::class, ], PaymentRefunded::class => [ \App\Listeners\HandlePaymentRefunded::class, ], RecurringAgreementCreated::class => [ \App\Listeners\HandleRecurringAgreementCreated::class, ], RecurringChargeCreated::class => [ \App\Listeners\HandleRecurringChargeCreated::class, ], ];
Event Listener Example
<?php namespace App\Listeners; use Mrdulal\LaravelVipps\Events\PaymentCompleted; use Illuminate\Contracts\Queue\ShouldQueue; class HandlePaymentCompleted implements ShouldQueue { public function handle(PaymentCompleted $event): void { $orderId = $event->orderId; $payload = $event->payload; // Update your order status $order = Order::where('order_id', $orderId)->first(); if ($order) { $order->update(['status' => 'paid']); // Send confirmation email Mail::to($order->customer_email)->send(new PaymentConfirmation($order)); } } }
Webhook Security
The package automatically verifies webhook signatures using HMAC-SHA256:
// Webhook verification is automatic, but you can customize it 'webhook' => [ 'secret' => env('VIPPS_WEBHOOK_SECRET'), 'tolerance' => 300, // 5 minutes tolerance 'verify_signature' => true, // Enable signature verification ],
๐ญ Events
The package fires Laravel events for all major payment actions:
Event | Trigger | Payload |
---|---|---|
PaymentCreated |
Payment initiated | orderId , webhook payload |
PaymentCompleted |
Payment authorized/completed | orderId , webhook payload |
PaymentCancelled |
Payment cancelled | orderId , webhook payload |
PaymentCaptured |
Payment captured | orderId , webhook payload |
PaymentRefunded |
Payment refunded | orderId , webhook payload |
RecurringAgreementCreated |
Recurring agreement created | agreementId , webhook payload |
RecurringChargeCreated |
Recurring charge created | chargeId , webhook payload |
Custom Event Handling
// Listen to all Vipps events with a single listener Event::listen('Mrdulal\LaravelVipps\Events\*', function ($eventName, array $data) { $eventObject = $data[0]; Log::info("Vipps event fired: {$eventName}", [ 'order_id' => $eventObject->orderId, 'payload' => $eventObject->payload ]); });
๐งช Testing
Running Tests
# Run all tests composer test # Run tests with coverage report composer test-coverage # Run specific test file ./vendor/bin/phpunit tests/Unit/VippsClientTest.php # Run tests with verbose output ./vendor/bin/phpunit --verbose
Writing Tests
The package provides a comprehensive test suite. Here's how to test your Vipps integration:
Mock Vipps Responses
<?php namespace Tests\Feature; use Tests\TestCase; use Mrdulal\LaravelVipps\Facades\Vipps; use Mrdulal\LaravelVipps\Services\VippsClient; use Mockery; class VippsPaymentTest extends TestCase { public function test_can_create_payment() { // Mock the VippsClient $mockClient = Mockery::mock(VippsClient::class); $mockClient->shouldReceive('post') ->once() ->with('https://apitest.vipps.no/ecomm/v2/payments', Mockery::type('array')) ->andReturn([ 'orderId' => 'test-order-123', 'url' => 'https://test.vipps.no/redirect-url', 'token' => 'test-token' ]); $this->app->instance(VippsClient::class, $mockClient); // Test payment creation $response = Vipps::ePayment()->create([ 'amount' => 10000, 'currency' => 'NOK', 'orderId' => 'test-order-123', 'description' => 'Test payment', 'redirectUrl' => 'https://example.com/callback' ]); $this->assertEquals('test-order-123', $response['orderId']); $this->assertArrayHasKey('url', $response); } }
Test Webhook Handling
<?php namespace Tests\Feature; use Tests\TestCase; use Mrdulal\LaravelVipps\Events\PaymentCompleted; use Illuminate\Support\Facades\Event; class WebhookTest extends TestCase { public function test_payment_completed_webhook() { Event::fake(); $payload = [ 'eventType' => 'SALE', 'transactionInfo' => [ 'orderId' => 'test-order-123', 'amount' => 10000, 'status' => 'COMPLETED' ] ]; $response = $this->postJson('/vipps/webhook', $payload); $response->assertStatus(200); Event::assertDispatched(PaymentCompleted::class); } }
Test Environment Configuration
// phpunit.xml <phpunit> <php> <env name="VIPPS_ENVIRONMENT" value="test"/> <env name="VIPPS_CLIENT_ID" value="test_client_id"/> <env name="VIPPS_CLIENT_SECRET" value="test_client_secret"/> <env name="VIPPS_SUBSCRIPTION_KEY" value="test_subscription_key"/> <env name="VIPPS_MERCHANT_SERIAL_NUMBER" value="123456"/> </php> </phpunit>
๐ ๏ธ Advanced Configuration
Complete Configuration Options
// config/vipps.php return [ // Environment & Credentials 'environment' => env('VIPPS_ENVIRONMENT', 'test'), 'client_id' => env('VIPPS_CLIENT_ID'), 'client_secret' => env('VIPPS_CLIENT_SECRET'), 'subscription_key' => env('VIPPS_SUBSCRIPTION_KEY'), 'merchant_serial_number' => env('VIPPS_MERCHANT_SERIAL_NUMBER'), // API Endpoints (auto-configured based on environment) 'api_endpoints' => [ 'test' => [ 'base_url' => 'https://apitest.vipps.no', 'ecom_url' => 'https://apitest.vipps.no/ecomm/v2', 'checkout_url' => 'https://apitest.vipps.no/checkout/v3', 'recurring_url' => 'https://apitest.vipps.no/recurring/v3', 'order_management_url' => 'https://apitest.vipps.no/order-management/v1', ], 'production' => [ 'base_url' => 'https://api.vipps.no', 'ecom_url' => 'https://api.vipps.no/ecomm/v2', 'checkout_url' => 'https://api.vipps.no/checkout/v3', 'recurring_url' => 'https://api.vipps.no/recurring/v3', 'order_management_url' => 'https://api.vipps.no/order-management/v1', ], ], // HTTP Client Settings 'http' => [ 'timeout' => 30, // Request timeout in seconds 'connect_timeout' => 10, // Connection timeout 'verify' => true, // SSL certificate verification 'headers' => [ 'Content-Type' => 'application/json', 'Accept' => 'application/json', ], ], // Webhook Security 'webhook' => [ 'secret' => env('VIPPS_WEBHOOK_SECRET'), 'tolerance' => 300, // 5 minutes tolerance for timestamp 'verify_signature' => true, // Enable HMAC signature verification ], // Default Payment Settings 'defaults' => [ 'currency' => 'NOK', 'country' => 'NO', 'language' => 'no', 'skip_landing_page' => false, 'user_flow' => 'WEB_REDIRECT', ], // Recurring Payment Limits 'recurring' => [ 'default_interval' => 'MONTH', 'default_interval_count' => 1, 'max_amount' => 200000, // 2000.00 NOK in รธre 'default_currency' => 'NOK', ], // Express Checkout UI 'express' => [ 'enabled' => true, 'button_theme' => 'orange', // orange, white, white-outline 'button_size' => 'large', // small, medium, large 'show_on_cart' => true, 'show_on_product' => true, 'show_on_category' => false, ], // Logging & Debugging 'logging' => [ 'enabled' => env('VIPPS_LOGGING_ENABLED', false), 'channel' => env('VIPPS_LOG_CHANNEL', 'default'), 'level' => env('VIPPS_LOG_LEVEL', 'info'), 'log_requests' => true, // Log API requests 'log_responses' => true, // Log API responses 'log_headers' => false, // Log headers (may contain sensitive data) ], // Caching (for access tokens) 'cache' => [ 'enabled' => true, 'ttl' => 3600, // 1 hour cache TTL 'prefix' => 'vipps_', 'store' => null, // Use default cache store ], // Error Handling 'error_handling' => [ 'retry_attempts' => 3, 'retry_delay' => 1000, // milliseconds 'throw_on_error' => true, ], // Feature Toggles 'features' => [ 'epayment' => true, 'checkout' => true, 'express' => true, 'recurring' => true, 'order_management' => true, 'webhooks' => true, ], ];
๐ฏ Order Management & Payment Lifecycle
Payment States & Transitions
graph LR A[Created] --> B[Reserved] B --> C[Captured] B --> D[Cancelled] C --> E[Refunded] B --> F[Expired]Loading
Comprehensive Order Management
// Capture full or partial amount $capture = Vipps::orderManagement()->capture('order-123', [ 'amount' => 5000, // Partial capture (original: 10000) 'description' => 'Partial shipment - Item 1' ]); // Multiple captures are allowed until full amount is captured $capture2 = Vipps::orderManagement()->capture('order-123', [ 'amount' => 5000, // Remaining amount 'description' => 'Final shipment - Item 2' ]); // Refund captured payment $refund = Vipps::orderManagement()->refund('order-123', [ 'amount' => 2000, // Partial refund 'description' => 'Product return - defective item' ]); // Cancel uncaptured payment $cancel = Vipps::orderManagement()->cancel('order-123', [ 'description' => 'Order cancelled by customer' ]); // Get complete payment history $history = Vipps::orderManagement()->getPaymentHistory('order-123'); // Returns: reservations, captures, refunds, cancellations // Get current order status $status = Vipps::orderManagement()->getOrderStatus('order-123');
Payment Reservations & Timeouts
Payment Method | Reservation Period | Auto-Cancel |
---|---|---|
Vipps | 180 days | Yes |
MobilePay | 14 days | Yes |
Credit Card (via Checkout) | 7 days | Yes |
โ ๏ธ Important: Capture payments before the reservation expires to avoid automatic cancellation.
โ ๏ธ Error Handling & Troubleshooting
Exception Hierarchy
VippsException // Base exception โโโ VippsApiException // API-related errors โโโ VippsValidationException // Input validation errors โโโ Custom exceptions...
Comprehensive Error Handling
use Mrdulal\LaravelVipps\Exceptions\{VippsException, VippsValidationException, VippsApiException}; try { $payment = Vipps::ePayment()->create($paymentData); } catch (VippsValidationException $e) { // Handle validation errors (400-level client errors) $errors = $e->getValidationErrors(); foreach ($errors as $field => $messages) { Log::warning("Validation error on {$field}: " . implode(', ', $messages)); } return response()->json(['errors' => $errors], 422); } catch (VippsApiException $e) { // Handle API errors (Vipps API returned an error) $apiError = $e->getApiError(); $statusCode = $e->getCode(); Log::error('Vipps API Error', [ 'status_code' => $statusCode, 'error' => $apiError, 'message' => $e->getMessage() ]); // Handle specific API errors switch ($statusCode) { case 401: // Unauthorized - check credentials break; case 403: // Forbidden - check permissions break; case 429: // Rate limited - implement backoff break; case 500: // Server error - retry later break; } } catch (VippsException $e) { // Handle general Vipps errors (network issues, etc.) Log::error('Vipps General Error: ' . $e->getMessage()); } catch (\Exception $e) { // Handle unexpected errors Log::critical('Unexpected error in Vipps integration', [ 'exception' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); }
Common Error Scenarios
1. Authentication Errors
// Error: Invalid credentials [ 'code' => 401, 'message' => 'Access denied', 'details' => 'Invalid client credentials' ] // Solution: Verify your credentials in .env file
2. Validation Errors
// Error: Invalid amount [ 'field' => 'amount', 'errors' => ['The amount must be at least 100 รธre (1.00 NOK)'] ] // Solution: Ensure amount is in รธre (multiply by 100) $amount = 99.50 * 100; // 9950 รธre
3. Order ID Conflicts
// Error: Duplicate order ID [ 'code' => 409, 'message' => 'Order ID already exists' ] // Solution: Ensure unique order IDs $orderId = 'order-' . time() . '-' . uniqid();
Debugging Tools
Enable Logging
VIPPS_LOGGING_ENABLED=true VIPPS_LOG_LEVEL=debug
View Logs
# View Vipps-specific logs tail -f storage/logs/laravel.log | grep -i vipps # Or use Laravel's log viewer php artisan log:monitor
Test API Connectivity
// Test basic API connectivity try { $client = app(VippsClient::class); $token = $client->getAccessToken(); echo "โ API connection successful. Token: " . substr($token, 0, 20) . "..."; } catch (Exception $e) { echo "โ API connection failed: " . $e->getMessage(); }
Production Checklist
- Environment: Set
VIPPS_ENVIRONMENT=production
- Credentials: Use production API keys
- HTTPS: Ensure webhook URL uses HTTPS
- Webhook: Test webhook endpoint responds with HTTP 200
- Signature: Verify webhook signature validation is enabled
- Logging: Disable detailed logging in production
- Error Handling: Implement proper error handling for all payment flows
- Monitoring: Set up alerts for payment failures
Common Issues & Solutions
Issue | Cause | Solution |
---|---|---|
"Access denied" | Wrong credentials | Verify API keys in Vipps portal |
"Invalid amount" | Amount < 100 รธre | Ensure minimum 1.00 NOK (100 รธre) |
"Order ID exists" | Duplicate order ID | Use unique order identifiers |
"Webhook failed" | Invalid signature | Check webhook secret configuration |
"Payment expired" | Not captured in time | Capture within reservation period |
"Merchant not found" | Wrong MSN | Verify Merchant Serial Number |
๐จ Frontend Integration
Express Checkout Button
The package includes a pre-built Blade component for express checkout:
{{-- resources/views/checkout.blade.php --}} @include('vipps::express-button', [ 'amount' => 9900, 'currency' => 'NOK', 'orderId' => 'order-' . time(), 'description' => 'Product purchase', 'redirectUrl' => route('payment.callback'), 'theme' => 'orange', // orange, white, white-outline 'size' => 'large', // small, medium, large 'text' => 'Buy with Vipps' ])
Custom Frontend Implementation
// Custom JavaScript integration async function createVippsPayment(paymentData) { try { const response = await fetch('/api/vipps/payment/create', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify(paymentData) }); const result = await response.json(); if (result.url) { // Redirect to Vipps window.location.href = result.url; } else { throw new Error('Payment creation failed'); } } catch (error) { console.error('Payment error:', error); showErrorMessage('Payment failed. Please try again.'); } } // Usage document.getElementById('vipps-pay-button').addEventListener('click', () => { createVippsPayment({ amount: 9900, currency: 'NOK', orderId: 'order-' + Date.now(), description: 'Product purchase', redirectUrl: '/payment/callback' }); });
React/Vue.js Integration
// React component example import { useState } from 'react'; const VippsPaymentButton = ({ amount, orderId, description }) => { const [loading, setLoading] = useState(false); const handlePayment = async () => { setLoading(true); try { const response = await fetch('/api/vipps/payment/create', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify({ amount, orderId, description, currency: 'NOK', redirectUrl: window.location.origin + '/payment/callback' }) }); const result = await response.json(); if (result.url) { window.location.href = result.url; } } catch (error) { console.error('Payment failed:', error); } finally { setLoading(false); } }; return ( <button onClick={handlePayment} disabled={loading} className="vipps-button" > {loading ? 'Processing...' : 'Pay with Vipps'} </button> ); };
๐ Deployment Guide
Environment Setup
Production Environment Variables
# Production Configuration VIPPS_ENVIRONMENT=production VIPPS_CLIENT_ID=your_production_client_id VIPPS_CLIENT_SECRET=your_production_client_secret VIPPS_SUBSCRIPTION_KEY=your_production_subscription_key VIPPS_MERCHANT_SERIAL_NUMBER=your_production_msn # Webhook Security VIPPS_WEBHOOK_SECRET=your_production_webhook_secret # Performance & Monitoring VIPPS_LOGGING_ENABLED=true VIPPS_LOG_LEVEL=error
Server Requirements
- PHP 8.1+ with extensions:
json
,openssl
,curl
- HTTPS required for production webhooks
- Firewall: Allow outbound HTTPS (443) to
api.vipps.no
- Memory: Minimum 128MB PHP memory limit
Docker Deployment
# Dockerfile FROM php:8.1-fpm-alpine # Install required extensions RUN docker-php-ext-install json openssl # Install Composer COPY --from=composer:latest /usr/bin/composer /usr/bin/composer # Copy application COPY . /var/www/html WORKDIR /var/www/html # Install dependencies RUN composer install --no-dev --optimize-autoloader # Set permissions RUN chown -R www-data:www-data /var/www/html
# docker-compose.yml version: '3.8' services: app: build: . environment: - VIPPS_ENVIRONMENT=production - VIPPS_CLIENT_ID=${VIPPS_CLIENT_ID} - VIPPS_CLIENT_SECRET=${VIPPS_CLIENT_SECRET} - VIPPS_SUBSCRIPTION_KEY=${VIPPS_SUBSCRIPTION_KEY} - VIPPS_MERCHANT_SERIAL_NUMBER=${VIPPS_MERCHANT_SERIAL_NUMBER} - VIPPS_WEBHOOK_SECRET=${VIPPS_WEBHOOK_SECRET} ports: - "80:80" - "443:443"
Load Balancer Configuration
# nginx.conf upstream laravel { server app1:9000; server app2:9000; } server { listen 443 ssl; server_name yourdomain.com; # SSL configuration ssl_certificate /path/to/certificate.pem; ssl_certificate_key /path/to/private.key; # Webhook endpoint location /vipps/webhook { proxy_pass http://laravel; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Important: Preserve request body for signature verification proxy_request_buffering off; } }
Monitoring & Alerting
// app/Console/Commands/VippsHealthCheck.php <?php namespace App\Console\Commands; use Illuminate\Console\Command; use Mrdulal\LaravelVipps\Services\VippsClient; class VippsHealthCheck extends Command { protected $signature = 'vipps:health-check'; protected $description = 'Check Vipps API connectivity'; public function handle() { try { $client = app(VippsClient::class); $token = $client->getAccessToken(); $this->info('โ Vipps API is accessible'); $this->info('Token: ' . substr($token, 0, 20) . '...'); return 0; } catch (\Exception $e) { $this->error('โ Vipps API check failed: ' . $e->getMessage()); return 1; } } }
# Cron job for health checks # /etc/cron.d/vipps-health-check */5 * * * * php /path/to/artisan vipps:health-check || echo "Vipps API down" | mail -s "Alert: Vipps API Issue" admin@yourdomain.com
๐ Performance Optimization
Caching Strategy
// Optimize access token caching 'cache' => [ 'enabled' => true, 'ttl' => 3600, // 1 hour (tokens are valid for 1 hour) 'store' => 'redis', // Use Redis for better performance ],
Queue Configuration
// Queue webhook processing for better performance // app/Listeners/HandlePaymentCompleted.php class HandlePaymentCompleted implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $queue = 'vipps-webhooks'; public $timeout = 60; public $tries = 3; public function handle(PaymentCompleted $event): void { // Process payment completion } }
Database Optimization
// Add indexes for better query performance Schema::table('vipps_payments', function (Blueprint $table) { $table->index(['status', 'created_at']); $table->index(['merchant_serial_number', 'order_id']); });
๐ Resources & References
Official Documentation
- Vipps MobilePay Developer Portal - Official API documentation
- Vipps MobilePay Business Portal - Get API credentials
- API Reference - Complete API specification
Laravel Resources
- Laravel Package Development - Official Laravel documentation
- Service Providers - Understanding Laravel service providers
- Events & Listeners - Laravel event system
Community & Support
- GitHub Issues - Bug reports and feature requests
- Discussions - Community discussions
- Stack Overflow - Q&A with the community
๐ค Contributing
We welcome contributions! Please see CONTRIBUTING.md for details on:
- Development setup
- Coding standards (PSR-12)
- Testing requirements
- Pull request process
- Code review guidelines
Quick Contribution Guide
# 1. Fork and clone the repository git clone https://github.com/yourusername/laravel-vipps.git cd laravel-vipps # 2. Install dependencies composer install # 3. Run tests composer test # 4. Make your changes and add tests # 5. Ensure all tests pass composer test # 6. Submit a pull request
๐ Security
Reporting Security Vulnerabilities
Please DO NOT report security vulnerabilities through public GitHub issues. Instead:
- Email security issues to: security@example.com
- Include detailed information about the vulnerability
- Allow reasonable time for response before public disclosure
Security Best Practices
- Always use HTTPS in production
- Keep webhook secrets secure and rotate regularly
- Validate all webhook signatures
- Log security events for monitoring
- Keep the package updated to latest version
๐ License
The MIT License (MIT). Please see License File for more information.
๐ Credits
Core Contributors
- Mr Dulal - Package author and maintainer
Acknowledgments
- Vipps MobilePay - For providing the payment platform and API
- Laravel Framework - For the excellent framework and ecosystem
- Solo Development - This package is built and maintained by a single developer
โญ Star this repository if it helped you!
Report Bug โข Request Feature โข Documentation