jeffreyvanhees / laravel-online-payment-platform
Laravel connector for Online Payment Platform API using SaloonPHP
Requires
- php: ^8.2
- saloonphp/laravel-http-sender: ^3.0
- saloonphp/pagination-plugin: ^2.2
- saloonphp/saloon: ^3.0
- spatie/laravel-data: ^4.0
Requires (Dev)
- jonpurvis/lawman: ^1.0
- laravel/framework: ^11.0|^12.0
- laravel/pint: ^1.24
- mockery/mockery: ^1.6
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^2.0
- phpunit/phpunit: ^10.0
README
A modern Laravel package for integrating with the Online Payment Platform API. Built with SaloonPHP and Spatie Laravel Data for an excellent developer experience.
Warning
This package is not affiliated with, endorsed by, or officially connected to Online Payment Platform B.V. It is an independent, community-driven implementation for integrating with their API. Also, this package is not intended for production use yet. It is still in development and may contain breaking changes.
โจ Features
- ๐ Laravel 11 & 12 Support - Full support for the latest Laravel versions
- ๐ก๏ธ Type Safety - Fully typed DTOs using Spatie Laravel Data
- ๐๏ธ Service Container - Native Laravel service container integration
- ๐ญ Facade Support - Clean, expressive API using Laravel facades
- ๐ง SaloonPHP Foundation - Built on the robust SaloonPHP HTTP client
- ๐งช Comprehensive Testing - HTTP recording/replay for reliable tests
- ๐ Intuitive API - Fluent interface:
Opp::merchants()->ubos()->create()
- ๐ Environment Support - Seamless sandbox/production switching
- โก Exception Handling - Detailed custom exceptions for all error scenarios
- ๐ Pagination Support - Built-in pagination with SaloonPHP
๐ Requirements
- PHP 8.2 or higher
- Laravel 11.0 or 12.0
๐ Installation
Install the package via Composer:
composer require jeffreyvanhees/laravel-online-payment-platform
Publish the configuration file:
php artisan vendor:publish --tag=opp-config
Configure your API credentials and URLs in your .env
file:
# API Configuration OPP_API_KEY=your_production_api_key_here OPP_SANDBOX_API_KEY=your_sandbox_api_key_here OPP_SANDBOX=true # URL Configuration (optional - set default URLs for webhooks/notifications) OPP_NOTIFY_URL=https://yourapp.com/webhooks/opp OPP_RETURN_URL=https://yourapp.com/payment/return # Webhook Configuration (optional) OPP_NOTIFY_SECRET=your_webhook_secret
๐ฏ Usage
The package provides multiple ways to interact with the Online Payment Platform API:
Using the Facade (Recommended)
The facade provides the cleanest and most Laravel-like API:
<?php use JeffreyVanHees\OnlinePaymentPlatform\OnlinePaymentPlatformFacade as Opp; use JeffreyVanHees\OnlinePaymentPlatform\Data\Requests\Merchants\CreateConsumerMerchantData; // Create a consumer merchant using configured URLs $response = Opp::merchants()->create([ 'type' => 'consumer', 'country' => 'NLD', 'emailaddress' => 'john.doe@example.com', 'first_name' => 'John', 'last_name' => 'Doe', ]); if ($response->successful()) { $merchant = $response->dto(); echo "Created merchant: {$merchant->uid}"; }
Using Dependency Injection
Inject the connector directly into your classes:
<?php namespace App\Services; use JeffreyVanHees\OnlinePaymentPlatform\OnlinePaymentPlatformConnector; use JeffreyVanHees\OnlinePaymentPlatform\Data\Requests\Merchants\CreateConsumerMerchantData; class PaymentService { public function __construct( private OnlinePaymentPlatformConnector $opp ) {} public function createMerchant(array $data): string { $response = $this->opp->merchants()->create($data); if (!$response->successful()) { throw new \Exception('Failed to create merchant'); } return $response->dto()->uid; } }
Using Type-Safe DTOs
For maximum type safety and IDE support, use the provided Data Transfer Objects:
<?php use JeffreyVanHees\OnlinePaymentPlatform\OnlinePaymentPlatformFacade as Opp; use JeffreyVanHees\OnlinePaymentPlatform\Data\Requests\Merchants\CreateConsumerMerchantData; use JeffreyVanHees\OnlinePaymentPlatform\Data\Requests\Transactions\CreateTransactionData; use JeffreyVanHees\OnlinePaymentPlatform\Data\Common\ProductData; // Create merchant using DTO $merchantData = new CreateConsumerMerchantData( type: 'consumer', country: 'NLD', emailaddress: 'jane.doe@example.com', first_name: 'Jane', last_name: 'Doe', notify_url: 'https://yoursite.com/webhooks/opp' ); $merchantResponse = Opp::merchants()->create($merchantData); $merchant = $merchantResponse->dto(); // Create transaction with products $transactionData = new CreateTransactionData( merchant_uid: $merchant->uid, total_price: 2500, // โฌ25.00 in cents return_url: 'https://yoursite.com/payment/return', notify_url: 'https://yoursite.com/webhooks/opp', products: ProductData::collect([ [ 'name' => 'Premium Subscription', 'quantity' => 1, 'price' => 2500, ], ]) ); $transactionResponse = Opp::transactions()->create($transactionData); $transaction = $transactionResponse->dto(); echo "Payment URL: {$transaction->redirect_url}";
๐ API Documentation
Merchants
// Create merchants $consumer = Opp::merchants()->create([ 'type' => 'consumer', 'country' => 'NLD', 'emailaddress' => 'user@example.com', 'first_name' => 'John', 'last_name' => 'Doe', 'notify_url' => 'https://yoursite.com/webhooks/opp', ]); $business = Opp::merchants()->create([ 'type' => 'business', 'country' => 'NLD', 'emailaddress' => 'business@example.com', 'coc_nr' => '12345678', 'legal_name' => 'Example B.V.', 'notify_url' => 'https://yoursite.com/webhooks/opp', ]); // Retrieve and list merchants $merchant = Opp::merchants()->get('mer_123456789'); $merchants = Opp::merchants()->list(['limit' => 50]); // Add contacts and addresses $contact = Opp::merchants()->contacts('mer_123456789')->add([ 'type' => 'representative', 'gender' => 'm', 'title' => 'mr', 'name' => [ 'first' => 'John', 'last' => 'Smith', 'initials' => 'J.S.', 'names_given' => 'John', ], 'emailaddresses' => [ ['emailaddress' => 'john@example.com'] ], 'phonenumbers' => [ ['phonenumber' => '+31612345678'] ], ]); $address = Opp::merchants()->addresses('mer_123456789')->add([ 'type' => 'business', 'address_line_1' => 'Main Street 123', 'city' => 'Amsterdam', 'zipcode' => '1000 AA', 'country' => 'NLD', ]); // Manage Ultimate Beneficial Owners (UBOs) for business merchants $ubo = Opp::merchants()->ubos('mer_123456789')->create([ 'name_first' => 'John', 'name_last' => 'Doe', 'date_of_birth' => '1980-01-15', 'country_of_residence' => 'NLD', 'is_decision_maker' => true, 'percentage_of_shares' => 25.5, ]); // Create merchant profiles for different configurations $profile = Opp::merchants()->profiles('mer_123456789')->create([ 'name' => 'E-commerce Profile', 'description' => 'Settings for online store', 'webhook_url' => 'https://store.example.com/webhook', 'return_url' => 'https://store.example.com/success', 'is_default' => false, ]);
Transactions
// Create transactions $transaction = Opp::transactions()->create([ 'merchant_uid' => 'mer_123456789', 'total_price' => 1000, // โฌ10.00 in cents 'products' => [ [ 'name' => 'Product Name', 'quantity' => 1, 'price' => 1000, ], ], 'return_url' => 'https://yoursite.com/payment/return', 'notify_url' => 'https://yoursite.com/webhooks/opp', ]); // Retrieve and list transactions $transaction = Opp::transactions()->get('tra_987654321'); $transactions = Opp::transactions()->list(['limit' => 100]); // Update transaction $updated = Opp::transactions()->update('tra_987654321', [ 'description' => 'Updated description', ]);
Charges
// Create charges for balance transfers between merchants $charge = Opp::charges()->create([ 'type' => 'balance', 'amount' => 1500, // โฌ15.00 in cents 'from_owner_uid' => 'mer_123456789', 'to_owner_uid' => 'mer_987654321', 'description' => 'Monthly platform fee', 'metadata' => ['invoice_id' => 'INV-2024-001'], ]); // Retrieve charge details $charge = Opp::charges()->get('cha_123456789'); // List charges with filters $charges = Opp::charges()->list([ 'from_owner_uid' => 'mer_123456789', 'status' => 'completed', 'limit' => 50, ]);
Mandates
// Create SEPA Direct Debit mandate $mandate = Opp::mandates()->create([ 'merchant_uid' => 'mer_123456789', 'holder_name' => 'John Doe', 'iban' => 'NL91ABNA0417164300', 'bic' => 'ABNANL2A', 'description' => 'Monthly subscription mandate', 'reference' => 'SUBSCRIPTION-2024', ]); // Retrieve mandate $mandate = Opp::mandates()->get('man_123456789'); // Create transaction using mandate $transaction = Opp::mandates()->transactions('man_123456789')->create([ 'amount' => 2500, // โฌ25.00 in cents 'description' => 'Monthly subscription payment', ]); // Delete mandate Opp::mandates()->delete('man_123456789');
Withdrawals
// Create withdrawal to merchant's bank account $withdrawal = Opp::withdrawals()->create('mer_123456789', [ 'amount' => 50000, // โฌ500.00 in cents 'currency' => 'EUR', 'bank_account_uid' => 'ban_123456789', 'description' => 'Weekly payout', 'reference' => 'PAYOUT-2024-W01', ]); // Retrieve withdrawal status $withdrawal = Opp::withdrawals()->get('wit_123456789'); // List withdrawals for a merchant $withdrawals = Opp::withdrawals()->list([ 'merchant_uid' => 'mer_123456789', 'status' => 'completed', 'limit' => 25, ]); // Cancel pending withdrawal Opp::withdrawals()->delete('wit_123456789');
Disputes
// Create dispute for a transaction $dispute = Opp::disputes()->create([ 'transaction_uid' => 'tra_123456789', 'amount' => 1000, // โฌ10.00 in cents 'reason' => 'Product not received', 'message' => 'Customer claims product was never delivered', 'evidence' => [ 'tracking_number' => 'TRACK123456', 'shipping_date' => '2024-01-15', ], ]); // Retrieve dispute with transaction details $dispute = Opp::disputes()->get('dis_123456789', [ 'include' => 'transaction', ]); // List all disputes $disputes = Opp::disputes()->list([ 'status' => 'pending', 'created_after' => '2024-01-01', ]);
Files
// Create file upload token $upload = Opp::files()->createUpload([ 'filename' => 'invoice.pdf', 'purpose' => 'dispute_evidence', ]); // Upload the actual file $file = Opp::files()->upload( fileUid: $upload->dto()->uid, token: $upload->dto()->token, filePath: '/path/to/invoice.pdf', fileName: 'invoice.pdf' ); // List uploaded files $files = Opp::files()->list([ 'purpose' => 'dispute_evidence', 'created_after' => '2024-01-01', ]);
Partners
// Get partner configuration $config = Opp::partners()->getConfiguration(); // Update partner settings $updated = Opp::partners()->updateConfiguration([ 'webhook_url' => 'https://partner.example.com/webhooks', 'notification_email' => 'notifications@partner.com', 'settings' => [ 'auto_approve_merchants' => false, 'require_vat_number' => true, ], ]);
Pagination
The package supports automatic pagination through SaloonPHP:
// Get paginated results $paginator = Opp::merchants()->list(['limit' => 25]); // Iterate through all pages foreach ($paginator->paginate() as $response) { $merchants = $response->dto(); foreach ($merchants->data as $merchant) { echo "Merchant: {$merchant->uid} - {$merchant->emailaddress}\n"; } }
Benefits
- Centralized Configuration: Set URLs once in
.env
file - Environment-Specific: Different URLs for development, staging, production
- Default Values: Automatically use configured URLs across your app
- Override Capability: Still override URLs per request when needed
โ๏ธ Configuration
The configuration file (config/opp.php
) allows you to customize various aspects:
<?php return [ /* |-------------------------------------------------------------------------- | API Credentials |-------------------------------------------------------------------------- */ 'api_key' => env('OPP_API_KEY'), 'sandbox_api_key' => env('OPP_SANDBOX_API_KEY'), /* |-------------------------------------------------------------------------- | Environment |-------------------------------------------------------------------------- */ 'sandbox' => env('OPP_SANDBOX', true), /* |-------------------------------------------------------------------------- | HTTP Configuration |-------------------------------------------------------------------------- */ 'timeout' => env('OPP_TIMEOUT', 30), 'retry' => [ 'times' => env('OPP_RETRY_TIMES', 3), 'sleep' => env('OPP_RETRY_SLEEP', 1000), ], ];
๐จ Error Handling
The package provides detailed exception handling:
use JeffreyVanHees\OnlinePaymentPlatform\Exceptions\{ OppException, AuthenticationException, ValidationException, RateLimitException, ApiException }; try { $response = Opp::merchants()->create($invalidData); } catch (ValidationException $e) { // Handle validation errors $errors = $e->getValidationErrors(); foreach ($errors as $field => $messages) { echo "{$field}: " . implode(', ', $messages); } } catch (AuthenticationException $e) { // Handle authentication issues echo "Authentication failed: " . $e->getMessage(); } catch (RateLimitException $e) { // Handle rate limiting echo "Rate limit exceeded. Retry after: " . $e->getRetryAfter(); } catch (OppException $e) { // Handle general API errors echo "API Error: " . $e->getMessage(); }
๐งช Testing
Run the test suite:
# Run all tests composer test # Run tests with coverage report composer test-coverage # Generate HTML coverage report composer test-coverage-html # Generate Clover XML coverage report composer test-coverage-clover # Record new HTTP interactions (requires real API credentials) composer record # Run tests using recorded interactions composer replay
Test Coverage
The package includes comprehensive tests covering all API endpoints with 70.2% code coverage:
- โ 214 tests covering all major endpoints and DTOs
- โ 907 assertions ensuring functionality
- โ Merchant operations - CRUD, contacts, addresses, bank accounts, settlements, UBOs, profiles
- โ Transaction lifecycle - create, retrieve, update, delete
- โ Payment flows - charges, mandates, withdrawals, disputes
- โ File operations - upload, retrieval, management
- โ Partner configuration - settings management
- โ Error handling - graceful sandbox environment handling
Coverage reports are generated in multiple formats:
- Terminal: Real-time coverage during test runs
- HTML: Detailed browsable report in
coverage-report/
- XML: Machine-readable format for CI/CD integration
๐ Advanced Usage
Service Provider Registration
You can bind custom configurations in your AppServiceProvider
:
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use JeffreyVanHees\OnlinePaymentPlatform\OnlinePaymentPlatformConnector; class AppServiceProvider extends ServiceProvider { public function register(): void { $this->app->singleton(OnlinePaymentPlatformConnector::class, function ($app) { return new OnlinePaymentPlatformConnector( apiKey: config('opp.api_key'), sandbox: config('opp.sandbox') ); }); } }
Custom HTTP Client Configuration
use JeffreyVanHees\OnlinePaymentPlatform\OnlinePaymentPlatformConnector; $connector = new OnlinePaymentPlatformConnector( apiKey: 'your-api-key', sandbox: true ); // Add custom middleware $connector->middleware()->onRequest(function ($request) { $request->headers()->add('Custom-Header', 'value'); return $request; }); // Add retry logic $connector->middleware()->onResponse(function ($response) { if ($response->status() === 429) { sleep(1); return $response->throw(); // Retry } return $response; });
๐ Releases
This package uses automated versioning and releases:
- Automatic: Patch version bump on every push to
main
branch - Manual: Use commit messages to control version bumps:
[major]
in commit message โ Major version bump (e.g., 1.0.0 โ 2.0.0)[minor]
in commit message โ Minor version bump (e.g., 1.0.0 โ 1.1.0)- Default โ Patch version bump (e.g., 1.0.0 โ 1.0.1)
- Skip Release: Add
[skip release]
to commit message to skip version bump
Manual Release Trigger
You can also manually trigger a release via GitHub Actions:
- Go to Actions โ Release workflow
- Click "Run workflow"
- Select the version bump type (patch/minor/major)
๐ค Contributing
Please see CONTRIBUTING.md for details on how to contribute.
๐ License
The MIT License (MIT). Please see License File for more information.
๐ Credits
- Built with SaloonPHP
- DTOs powered by Spatie Laravel Data
- Testing with Pest PHP