1. Go to this page and download the library: Download nesthus/vipps-php library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use Nesthus\Vipps\Environment;
use Nesthus\Vipps\SystemInfo;
use Nesthus\Vipps\Vipps;
use Nesthus\Vipps\VippsConfig;
$config = new VippsConfig(
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
subscriptionKey: 'your-subscription-key', // Ocp-Apim-Subscription-Key
merchantSerialNumber: '123456',
environment: Environment::Test, // Environment::Production when live
system: new SystemInfo('acme-webshop', '2.4.1'), // your system's name + version, sent as Vipps-System-* headers
);
$httpFactory = new HttpFactory(); // PSR-17 request + stream factory
$vipps = new Vipps(
$config,
new Client(['timeout' => 15, 'connect_timeout' => 5]),
$httpFactory,
$httpFactory,
);
use Nesthus\Vipps\Amount;
use Nesthus\Vipps\Recurring\AgreementStatus;
use Nesthus\Vipps\Recurring\ChargeTransactionType;
use Nesthus\Vipps\Recurring\Interval;
use Nesthus\Vipps\Recurring\NewAgreement;
use Nesthus\Vipps\Recurring\NewCharge;
use Nesthus\Vipps\Recurring\Pricing;
// 1. Create the agreement. Mint the idempotency key yourself and persist it
// BEFORE the request — a key you cannot replay protects nothing.
$created = $vipps->recurring()->createAgreement(new NewAgreement(
pricing: Pricing::legacy(Amount::fromMajor(49)), // 49.00 NOK per charge
interval: Interval::months(1),
productName: 'Premium',
merchantRedirectUrl: 'https://shop.example/vipps/return', // where the user lands afterwards
merchantAgreementUrl: 'https://shop.example/account/subscription', // where they can manage/cancel (r(49),
transactionType: ChargeTransactionType::DirectCapture,
description: 'Premium — September',
due: new DateTimeImmutable('+30 days'), // a plain date; Vipps collects some time that day
retryDays: 5, // days Vipps retries a failed collection (0–14)
), $chargeIdempotencyKey);
}
use Nesthus\Vipps\Amount;
use Nesthus\Vipps\Epayment\CreatePayment;
use Nesthus\Vipps\Epayment\PaymentState;
$created = $vipps->epayment()->createPayment(new CreatePayment(
amount: Amount::fromMajor(249, 50), // 249.50 NOK
reference: 'order-2026-000123', // your permanent id: 8–64 chars of [a-zA-Z0-9-]
returnUrl: 'https://shop.example/checkout/return',
), $idempotencyKey);
header('Location: ' . $created->redirectUrl); // null for flows without a browser hop (e.g. PUSH_MESSAGE)
// Back on returnUrl — same rule as Recurring, the redirect proves nothing:
$payment = $vipps->epayment()->getPayment('order-2026-000123');
if ($payment->state === PaymentState::Authorized) {
// Capture when you deliver — in full or in parts (ship half, capture half).
// An authorization nobody captures expires on its own; cancel() releases it early.
$result = $vipps->epayment()->capture('order-2026-000123', Amount::fromMajor(249, 50), $captureKey);
// capture()/cancel()/refund() return the adjusted payment. Vipps says to
// verify the capture response before shipping — read the aggregates:
$result->capturedAmount?->minorUnits; // 24950 when the full capture landed
}
// Money already captured goes back with refund():
$vipps->epayment()->refund('order-2026-000123', Amount::fromMajor(50), $refundKey);
use Nesthus\Vipps\Login\AuthorizationRequest;
// 1. Generate state (the CSRF binding) and a PKCE verifier, and store BOTH in
// the session — they must survive until the redirect returns.
$state = bin2hex(random_bytes(16));
$codeVerifier = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');
$url = $vipps->login()->buildAuthorizationUrl(new AuthorizationRequest(
redirectUri: 'https://shop.example/auth/vipps/callback', // must exactly match one registered in the portal
state: $state,
codeVerifier: $codeVerifier, // SDK derives the S256 challenge; verifier never leaves you
));
// 2. On the callback: REFUSE unless the returned `state` matches the session's
// (the SDK cannot do this — it has no session), then exchange the code with
// the byte-identical redirectUri and the SAME verifier.
$tokens = $vipps->login()->exchangeCode(
$_GET['code'],
'https://shop.example/auth/vipps/callback',
$codeVerifier,
);
$claims = $tokens->idTokenClaims(); // sub, and whatever the granted scopes surface
$profile = $vipps->login()->userinfo($tokens->accessToken()); // authorized by the USER's token, not the merchant's
$hook = $vipps->webhooks()->register(
'https://shop.example/hooks/vipps',
['epayments.payment.authorized.v1', 'epayments.payment.captured.v1'],
$idempotencyKey,
);
// ⚠️ $hook->secret() is shown EXACTLY ONCE — Vipps never re-reveals it, and
// all() returns id/url/events only. Persist it (encrypted, next to $hook->id)
// before doing anything else. If storage fails, delete() and register() again.
// The secret is a method, not a property: dumping the object (print_r,
// var_export, var_dump) shows ***redacted*** instead of signing material.
use Nesthus\Vipps\Webhooks\SignatureValidator;
use Nesthus\Vipps\Webhooks\WebhookRequest;
$result = (new SignatureValidator())->validate(
WebhookRequest::fromPsr7($serverRequest), // PSR-7 ServerRequestInterface
$secret, // the one you persisted at registration
);
if (! $result->valid) {
// $result->reason is a stable snake_case slug ("signature_mismatch",
// "stale_timestamp", …) that never contains signing material — safe to log verbatim.
http_response_code(401);
exit;
}
use Nesthus\Vipps\Auth\Psr16TokenCache;
$vipps = new Vipps(
$config,
$client,
$httpFactory,
$httpFactory,
tokenCache: new Psr16TokenCache($yourPsr16Cache), // Redis, APCu, your framework's store
);
use Nesthus\Vipps\Exceptions\VippsApiException;
use Nesthus\Vipps\Exceptions\VippsConfigException;
use Nesthus\Vipps\Exceptions\VippsException;
try {
$vipps->epayment()->capture($reference, $amount, $key);
} catch (VippsApiException $e) {
$e->status; // HTTP status; 0 when the transport itself failed (DNS, TLS, timeout)
$e->details; // Vipps' decoded error body (problem+json when available)
$e->traceId; // quote this in a Vipps support case
} catch (VippsConfigException $e) {
// a value YOUR code built is invalid — bad reference format, negative
// amount, empty credential — thrown before any request goes out
}
use GuzzleHttp\Psr7\HttpFactory;
use Nesthus\Vipps\Amount;
use Nesthus\Vipps\Epayment\CreatePayment;
use Nesthus\Vipps\Epayment\EpaymentApi;
use Nesthus\Vipps\Http\ApiTransport;
use Nesthus\Vipps\Tests\Support\FakeHttpClient;
use Nesthus\Vipps\VippsConfig;
$http = new FakeHttpClient();
$factory = new HttpFactory();
$api = new EpaymentApi(new ApiTransport(
$http,
$factory,
$factory,
new VippsConfig('client-id', 'client-secret', 'subscription-key', '123456'),
));
$http->queueJson(201, ['reference' => 'order-2026-000123', 'redirectUrl' => 'https://landing.vipps.no?token=abc']);
$created = $api->createPayment(new CreatePayment(
amount: Amount::fromMajor(49),
reference: 'order-2026-000123',
returnUrl: 'https://shop.example/return',
), 'idem-create-1');
$request = $http->lastRequest(); // full PSR-7 request — assert method, URI, Idempotency-Key, body
bash
composer
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.