coretrekas/vipps
Composer 安装命令:
composer require coretrekas/vipps
包简介
PHP SDK for Vipps MobilePay APIs
README 文档
README
A comprehensive, production-ready PHP SDK for Vipps MobilePay APIs, providing easy integration with:
- ePayment API v1 - Online and in-person payments with full lifecycle management
- Checkout API v3 - Complete checkout sessions for payments and subscriptions
- Recurring Payments API v3 - Recurring payment agreements and charges
- Login API v1 - OAuth 2.0 / OpenID Connect authentication
Features
- ✅ Full API Coverage - Complete support for ePayment API v1, Checkout API v3, Recurring Payments API v3, and Login API v1
- ✅ Automatic Token Management - Access tokens cached and refreshed automatically
- ✅ Fluent Builders - Easy-to-use builder interfaces for sessions, agreements, and authorization URLs
- ✅ System Info Headers - Optional system information headers for better tracking and support
- ✅ Type-Safe - PHP 8.1+ with strict types and full type hints
- ✅ Error Handling - Comprehensive exception handling with detailed error information
- ✅ PSR Compliant - PSR-3 (Logger), PSR-4 (Autoloading), PSR-12 (Code Style), PSR-18 (HTTP Client)
- ✅ Well Tested - 89 unit tests with 187 assertions, 100% pass rate
- ✅ Code Quality - PHPStan level 5, Laravel Pint for code style
- ✅ Environment Support - Separate test and production configurations
- ✅ Production Ready - Comprehensive documentation and examples
Requirements
- PHP 8.1 or higher
- ext-json
Installation
Install via Composer:
composer require coretrekas/vipps
Note: This package requires PHP 8.1 or higher.
Package Structure
The SDK is organized under the Coretrek\Vipps namespace:
Coretrek\Vipps\
├── VippsClient # Main SDK client
├── EPayment\
│ ├── EPaymentApi # ePayment API methods
│ └── PaymentBuilder # Fluent builder for payments
├── Checkout\
│ ├── CheckoutApi # Checkout API methods
│ └── SessionBuilder # Fluent builder for sessions
├── Recurring\
│ ├── RecurringApi # Recurring API methods
│ └── AgreementBuilder # Fluent builder for agreements
├── Login\
│ ├── LoginApi # Login API methods
│ └── AuthorizationUrlBuilder # Fluent builder for OAuth URLs
└── Exceptions\
└── VippsException # Exception handling
All classes use the Coretrek\Vipps namespace prefix.
Quick Start
Initialize the Client
use Coretrek\Vipps\VippsClient; $client = new VippsClient( clientId: 'your-client-id', clientSecret: 'your-client-secret', subscriptionKey: 'your-subscription-key', merchantSerialNumber: 'your-msn', testMode: true // Set to false for production );
Optional: Set System Information Headers
You can optionally provide system information that will be sent with all API requests for better tracking and support:
$client = new VippsClient( clientId: 'your-client-id', clientSecret: 'your-client-secret', subscriptionKey: 'your-subscription-key', merchantSerialNumber: 'your-msn', testMode: true, options: [ 'systemName' => 'MyEcommercePlatform', 'systemVersion' => '2.1.0', 'pluginName' => 'VippsPaymentPlugin', 'pluginVersion' => '1.5.3', ] ); // Or set it later $client->setSystemInfo( systemName: 'MyEcommercePlatform', systemVersion: '2.1.0', pluginName: 'VippsPaymentPlugin', pluginVersion: '1.5.3' );
These headers help Vipps support team identify your integration and provide better assistance.
ePayment API
The ePayment API is designed for online and in-person payments with full lifecycle management including authorization, capture, refund, and cancellation.
Create a Payment
// Simple payment creation $payment = $client->epayment()->createSimplePayment( reference: 'order-12345', amount: 10000, // Amount in minor units (100.00 NOK) currency: 'NOK', userFlow: 'WEB_REDIRECT', options: [ 'returnUrl' => 'https://example.com/order/12345/complete', 'paymentDescription' => 'Order #12345', ] ); // Redirect user to payment page header('Location: ' . $payment['redirectUrl']);
Create a Payment with Builder (Recommended)
$payment = $client->epayment() ->buildPayment() ->amount(10000, 'NOK') ->reference('order-12345') ->userFlow('WEB_REDIRECT') ->returnUrl('https://example.com/order/12345/complete') ->paymentDescription('Order #12345') ->paymentMethod('WALLET') ->customerInteraction('CUSTOMER_NOT_PRESENT') ->idempotencyKey('payment-order-12345') ->systemInfo('my-shop', '1.0.0', 'vipps-plugin', '1.0.0') ->create(); echo "Payment URL: " . $payment['redirectUrl'];
Create a Payment with Receipt
$orderLines = [ [ 'name' => 'Premium Socks', 'id' => 'SOCK-001', 'totalAmount' => 5000, 'totalAmountExcludingTax' => 4000, 'totalTaxAmount' => 1000, 'unitInfo' => [ 'unitPrice' => 2500, 'quantity' => '2', 'quantityUnit' => 'PCS', ], ], ]; $bottomLine = [ 'currency' => 'NOK', 'receiptNumber' => 'order-12345', ]; $payment = $client->epayment() ->buildPayment() ->amount(5000, 'NOK') ->reference('order-12345') ->userFlow('WEB_REDIRECT') ->returnUrl('https://example.com/order/12345/complete') ->paymentDescription('Order with receipt') ->paymentMethod('WALLET') ->receipt($orderLines, $bottomLine) ->metadata(['orderId' => 'order-12345', 'customerId' => 'CUST-123']) ->create();
Get Payment Details
$payment = $client->epayment()->getPayment('order-12345'); echo "Payment State: " . $payment['state']; echo "Amount: " . $payment['amount']['value'] . ' ' . $payment['amount']['currency']; echo "Authorized: " . $payment['aggregate']['authorizedAmount']['value']; echo "Captured: " . $payment['aggregate']['capturedAmount']['value'];
Capture a Payment
// Capture full amount $result = $client->epayment()->captureAmount( reference: 'order-12345', amount: 10000, currency: 'NOK', headers: ['Idempotency-Key' => 'capture-order-12345'] ); // Or use the detailed method $result = $client->epayment()->capturePayment('order-12345', [ 'modificationAmount' => [ 'value' => 10000, 'currency' => 'NOK', ], ]);
Refund a Payment
// Refund partial amount $result = $client->epayment()->refundAmount( reference: 'order-12345', amount: 5000, currency: 'NOK', headers: ['Idempotency-Key' => 'refund-order-12345'] ); // Or use the detailed method $result = $client->epayment()->refundPayment('order-12345', [ 'modificationAmount' => [ 'value' => 5000, 'currency' => 'NOK', ], ]);
Cancel a Payment
$result = $client->epayment()->cancelPayment('order-12345'); echo "Payment State: " . $result['state']; // TERMINATED
Get Payment Event Log
$events = $client->epayment()->getPaymentEventLog('order-12345'); foreach ($events as $event) { echo $event['name'] . ' at ' . $event['timestamp'] . "\n"; }
QR Code Payment
$payment = $client->epayment() ->buildPayment() ->amount(5000, 'NOK') ->reference('order-12345') ->userFlow('QR') ->qrFormat('IMAGE/SVG+XML') ->paymentDescription('QR payment') ->paymentMethod('WALLET') ->create(); echo "QR Code URL: " . $payment['redirectUrl'];
Push Message Payment
$payment = $client->epayment() ->buildPayment() ->amount(7500, 'NOK') ->reference('order-12345') ->userFlow('PUSH_MESSAGE') ->customerPhoneNumber('4712345678') ->paymentDescription('Push message payment') ->paymentMethod('WALLET') ->create();
Payment with Shipping Options
$payment = $client->epayment() ->buildPayment() ->amount(15000, 'NOK') ->reference('order-12345') ->userFlow('WEB_REDIRECT') ->returnUrl('https://example.com/order/12345/complete') ->paymentDescription('Order with shipping') ->paymentMethod('WALLET') ->fixedShipping([ [ 'type' => 'HOME_DELIVERY', 'brand' => 'POSTEN', 'options' => [ [ 'id' => 'posten-standard', 'name' => 'Standard Delivery', 'amount' => ['value' => 9900, 'currency' => 'NOK'], 'estimatedDelivery' => '2-3 days', ], [ 'id' => 'posten-express', 'name' => 'Express Delivery', 'amount' => ['value' => 19900, 'currency' => 'NOK'], 'estimatedDelivery' => 'Next day', ], ], ], ]) ->profileScope('name email phoneNumber address') ->create();
Checkout API
Create a Payment Session (Checkout API)
// Simple payment session $session = $client->checkout()->createPaymentSession( reference: 'order-12345', amount: 10000, // Amount in minor units (100.00 NOK) currency: 'NOK', options: [ 'paymentDescription' => 'Order #12345', 'merchantInfo' => [ 'callbackUrl' => 'https://example.com/vipps/callback', 'returnUrl' => 'https://example.com/order/12345/complete', 'termsAndConditionsUrl' => 'https://example.com/terms', ], ] ); // Redirect user to checkout header('Location: ' . $session['checkoutFrontendUrl']);
Create a Payment Session with Builder (Recommended)
$session = $client->checkout() ->buildPaymentSession() ->reference('order-12345') ->transaction(10000, 'NOK', 'order-12345', 'Order #12345') ->merchantInfo( callbackUrl: 'https://example.com/vipps/callback', returnUrl: 'https://example.com/order/12345/complete', termsAndConditionsUrl: 'https://example.com/terms', callbackAuthorizationToken: 'your-secret-token' ) ->prefillCustomer([ 'firstName' => 'John', 'lastName' => 'Doe', 'email' => 'john@example.com', 'phoneNumber' => '+4712345678', ]) ->customerInteraction('CUSTOMER_NOT_PRESENT') ->elements('Full') ->countries(['NO', 'SE', 'DK']) ->idempotencyKey('unique-key-' . time()) ->systemInfo('my-ecommerce', '1.0.0', 'vipps-plugin', '2.0.0') ->create(); echo "Checkout URL: " . $session['checkoutFrontendUrl'];
Get Session Information
$sessionInfo = $client->checkout()->getSession('order-12345'); echo "Session State: " . $sessionInfo['sessionState']; echo "Payment Method: " . $sessionInfo['paymentMethod'];
Create a Recurring Agreement
$agreement = $client->recurring() ->buildAgreement() ->legacyPricing(2500, 'NOK') // 25.00 NOK per interval ->interval('MONTH', 1) ->product('Premium Subscription', 'Access to premium features') ->merchantUrls( redirectUrl: 'https://example.com/subscription/complete', agreementUrl: 'https://example.com/my-subscriptions' ) ->phoneNumber('4712345678') ->initialCharge(100, 'NOK', 'Activation fee', 'DIRECT_CAPTURE') ->idempotencyKey('agreement-' . time()) ->create(); // Redirect user to accept agreement header('Location: ' . $agreement['vippsConfirmationUrl']);
List Agreements
$agreements = $client->recurring()->listAgreements([ 'status' => 'ACTIVE', 'pageNumber' => 1, 'pageSize' => 50, ]); foreach ($agreements as $agreement) { echo "Agreement ID: " . $agreement['id'] . "\n"; echo "Product: " . $agreement['productName'] . "\n"; echo "Status: " . $agreement['status'] . "\n"; }
Create a Charge
$charge = $client->recurring()->createCharge( agreementId: 'agr_5kSeqz', chargeData: [ 'amount' => 2500, 'transactionType' => 'DIRECT_CAPTURE', 'description' => 'Monthly subscription - January 2024', 'due' => '2024-01-01', 'retryDays' => 5, 'type' => 'RECURRING', ], headers: ['Idempotency-Key' => 'charge-jan-2024'] ); echo "Charge ID: " . $charge['chargeId'];
Capture a Reserved Charge
$client->recurring()->captureCharge( agreementId: 'agr_5kSeqz', chargeId: 'chr_123', captureData: [ 'amount' => 2500, 'description' => 'Capture for January', ] );
Refund a Charge
$client->recurring()->refundCharge( agreementId: 'agr_5kSeqz', chargeId: 'chr_123', refundData: [ 'amount' => 2500, 'description' => 'Customer requested refund', ] );
Advanced Usage
Custom HTTP Client
You can provide your own PSR-18 compatible HTTP client:
use GuzzleHttp\Client; $httpClient = new Client([ 'timeout' => 60, 'verify' => true, ]); $client = new VippsClient( clientId: 'your-client-id', clientSecret: 'your-client-secret', subscriptionKey: 'your-subscription-key', merchantSerialNumber: 'your-msn', testMode: true, options: ['http_client' => $httpClient] );
Custom Logger
Integrate with your PSR-3 compatible logger:
use Monolog\Logger; use Monolog\Handler\StreamHandler; $logger = new Logger('vipps'); $logger->pushHandler(new StreamHandler('path/to/vipps.log', Logger::DEBUG)); $client = new VippsClient( clientId: 'your-client-id', clientSecret: 'your-client-secret', subscriptionKey: 'your-subscription-key', merchantSerialNumber: 'your-msn', testMode: true, options: ['logger' => $logger] );
Error Handling
use Coretrek\Vipps\Exceptions\VippsException; try { $session = $client->checkout()->createPaymentSession( reference: 'order-12345', amount: 10000, currency: 'NOK' ); } catch (VippsException $e) { echo "Error: " . $e->getMessage() . "\n"; echo "Status Code: " . $e->getCode() . "\n"; // Check error type if ($e->isValidationError()) { echo "Validation error occurred\n"; print_r($e->getErrorDetails()); } if ($e->isAuthenticationError()) { echo "Authentication failed - check your credentials\n"; } if ($e->isNotFoundError()) { echo "Resource not found\n"; } }
Checkout API Examples
Payment with Logistics Options
$session = $client->checkout() ->buildPaymentSession() ->reference('order-12345') ->transaction(10000, 'NOK', 'order-12345', 'Order with shipping') ->merchantInfo( 'https://example.com/callback', 'https://example.com/return', 'https://example.com/terms' ) ->logistics([ 'fixedOptions' => [ [ 'brand' => 'POSTEN', 'amount' => ['value' => 300, 'currency' => 'NOK'], 'id' => 'posten-home', 'priority' => 1, 'isDefault' => true, 'description' => 'Home delivery', ], [ 'brand' => 'POSTEN', 'amount' => ['value' => 200, 'currency' => 'NOK'], 'type' => 'PICKUP_POINT', 'id' => 'posten-pickup', 'priority' => 2, 'isDefault' => false, 'description' => 'Pickup point', ], ], ]) ->create();
Subscription Session
$session = $client->checkout() ->buildSubscriptionSession() ->reference('sub-12345') ->transaction(100, 'NOK', 'sub-12345', 'Initial charge') ->subscription([ 'productName' => 'Premium Membership', 'amount' => ['value' => 2500, 'currency' => 'NOK'], 'interval' => ['unit' => 'MONTH', 'count' => 1], 'merchantAgreementUrl' => 'https://example.com/my-subscriptions', 'productDescription' => 'Monthly premium membership', ]) ->merchantInfo( 'https://example.com/callback', 'https://example.com/return', 'https://example.com/terms' ) ->create();
Recurring API Examples
Agreement with Campaign
// Price campaign - reduced price until a date $agreement = $client->recurring() ->buildAgreement() ->legacyPricing(3900, 'NOK') ->interval('MONTH', 1) ->product('News Subscription') ->merchantUrls('https://example.com/redirect', 'https://example.com/manage') ->phoneNumber('4712345678') ->priceCampaign(100, '2024-12-31T23:59:59Z') // 1 NOK until end of year ->create(); // Period campaign - fixed price for a period $agreement = $client->recurring() ->buildAgreement() ->legacyPricing(3900, 'NOK') ->interval('MONTH', 1) ->product('News Subscription') ->merchantUrls('https://example.com/redirect', 'https://example.com/manage') ->phoneNumber('4712345678') ->periodCampaign(100, 'WEEK', 4) // 1 NOK for 4 weeks ->initialCharge(100, 'NOK', 'Campaign activation', 'DIRECT_CAPTURE') ->create();
Variable Amount Agreement
$agreement = $client->recurring() ->buildAgreement() ->variablePricing(5000, 'NOK') // User can be charged up to 50 NOK ->interval('MONTH', 1) ->product('Usage-based Service') ->merchantUrls('https://example.com/redirect', 'https://example.com/manage') ->phoneNumber('4712345678') ->create();
Create Multiple Charges Asynchronously
$charges = [ [ 'agreementId' => 'agr_123', 'amount' => 2500, 'transactionType' => 'DIRECT_CAPTURE', 'description' => 'January charge', 'due' => '2024-01-01', 'retryDays' => 5, 'type' => 'RECURRING', ], [ 'agreementId' => 'agr_456', 'amount' => 2500, 'transactionType' => 'DIRECT_CAPTURE', 'description' => 'January charge', 'due' => '2024-01-01', 'retryDays' => 5, 'type' => 'RECURRING', ], ]; $result = $client->recurring()->createChargesAsync($charges);
Login API Examples
OAuth 2.0 / OpenID Connect Flow
use Coretrek\Vipps\Login\AuthorizationUrlBuilder; // Generate secure random values $state = AuthorizationUrlBuilder::generateState(); $nonce = AuthorizationUrlBuilder::generateNonce(); $codeVerifier = AuthorizationUrlBuilder::generateCodeVerifier(); // Build authorization URL $authUrl = $client->login() ->buildAuthorizationUrl() ->clientId('your-client-id') ->redirectUri('https://example.com/vipps/callback') ->scope(['openid', 'name', 'email', 'phoneNumber', 'address']) ->state($state) ->nonce($nonce) ->pkce($codeVerifier, 'S256') ->build(); // Store state, nonce, and code_verifier in session $_SESSION['oauth_state'] = $state; $_SESSION['oauth_nonce'] = $nonce; $_SESSION['oauth_code_verifier'] = $codeVerifier; // Redirect user to Vipps login header('Location: ' . $authUrl);
App-to-App Flow
For mobile apps, you can use the app-to-app flow to redirect users from your app to the Vipps app and back:
use Coretrek\Vipps\Login\AuthorizationUrlBuilder; // Generate secure random values $state = AuthorizationUrlBuilder::generateState(); $nonce = AuthorizationUrlBuilder::generateNonce(); $codeVerifier = AuthorizationUrlBuilder::generateCodeVerifier(); // Build authorization URL with app-to-app flow $authUrl = $client->login() ->buildAuthorizationUrl() ->clientId('your-client-id') ->redirectUri('https://example.com/vipps/callback') ->scope(['openid', 'name', 'email', 'phoneNumber']) ->state($state) ->nonce($nonce) ->pkce($codeVerifier, 'S256') ->requestedFlow('app_to_app') ->appCallbackUri('myapp://oauth/callback/vipps') ->build(); // Store state, nonce, and code_verifier securely // Then open the auth URL in the device browser or use a deep link
The requestedFlow('app_to_app') parameter tells Vipps to use the app-to-app flow, and appCallbackUri() specifies the deep link URI where the user will be redirected after authenticating in the Vipps app.
Handle OAuth Callback
// In your callback handler $code = $_GET['code']; $returnedState = $_GET['state']; // Verify state to prevent CSRF if ($returnedState !== $_SESSION['oauth_state']) { throw new Exception('Invalid state'); } // Exchange code for tokens $tokens = $client->login()->exchangeCodeForTokens( code: $code, redirectUri: 'https://example.com/vipps/callback', options: [ 'code_verifier' => $_SESSION['oauth_code_verifier'], ] ); // Get user information $userInfo = $client->login()->getUserInfo($tokens['access_token']); echo "Welcome, " . $userInfo['name']; echo "Email: " . $userInfo['email']; echo "Phone: " . $userInfo['phone_number'];
CIBA Flow (Merchant-Initiated Login)
// Check if user exists $userExists = $client->login()->checkUserExists('4712345678'); if ($userExists['exists']) { // Initiate authentication $auth = $client->login()->initiateCibaAuth( loginHint: '4712345678', options: [ 'scope' => 'openid name email', 'bindingMessage' => 'Login to Example App', 'requested_expiry' => 300, ] ); // Poll for token (with proper interval) $interval = $auth['interval']; $authReqId = $auth['auth_req_id']; while (true) { sleep($interval); try { $tokens = $client->login()->pollCibaToken($authReqId); // User has authenticated break; } catch (VippsException $e) { // Still waiting for user to approve continue; } } $userInfo = $client->login()->getUserInfo($tokens['access_token']); }
Get OpenID Configuration
// Get OpenID Connect discovery document $config = $client->login()->getOpenIdConfiguration(); echo "Issuer: " . $config['issuer']; echo "Authorization Endpoint: " . $config['authorization_endpoint']; echo "Supported Scopes: " . implode(', ', $config['scopes_supported']); // Get JWKS for token verification $jwks = $client->login()->getJwks();
Testing
Running Tests
# Run all tests composer test # Run only unit tests composer test:unit # Run only integration tests composer test:integration # Run with coverage report composer test:coverage
Code Quality
# Run static analysis with PHPStan (level 5) composer phpstan # Check code style with Laravel Pint composer pint:test # Fix code style issues automatically composer pint
Integration Tests
Integration tests require valid Vipps test environment credentials. Set these environment variables:
export VIPPS_CLIENT_ID="your-test-client-id" export VIPPS_CLIENT_SECRET="your-test-client-secret" export VIPPS_SUBSCRIPTION_KEY="your-test-subscription-key" export VIPPS_MERCHANT_SERIAL_NUMBER="your-test-msn" # Run integration tests composer test:integration
Note: Integration tests are skipped by default if environment variables are not set.
API Documentation
For detailed API documentation, visit:
Support
- Issues: GitHub Issues
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for details.
Development Setup
# Clone the repository git clone https://github.com/coretrekas/vipps.git cd vipps # Install dependencies composer install # Run tests composer test # Check code quality composer phpstan composer pint:test
License
This SDK is licensed under the MIT License. See the LICENSE file for details.
Changelog
See CHANGELOG.md for version history.
coretrekas/vipps 适用场景与选型建议
coretrekas/vipps 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 887 次下载、GitHub Stars 达 1, 最近一次更新时间为 2025 年 10 月 15 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「oauth」 「payment」 「checkout」 「login」 「sdk」 「subscription」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 coretrekas/vipps 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 coretrekas/vipps 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 coretrekas/vipps 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
WeChat OAuth SDK
Ory-Hydra OAuth 2.0 Client Provider for The PHP League OAuth2-Client
Library for ORCID web services
A lightweight and powerful OAuth 2.0 authorization and resource server library with support for all the core specification grants. This library will allow you to secure your API with OAuth and allow your applications users to approve apps that want to access their data from your API.
Adds a 'checkout' to a SilverStripe site which links to an Estimate and adds the ability to setup and pay
Rich payment solutions for Laravel framework. Paypal, payex, authorize.net, be2bill, omnipay, recurring paymens, instant notifications and many more
统计信息
- 总下载量: 887
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 1
- 点击次数: 23
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2025-10-15