PHP code example of nesthus / vipps-php

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/ */

    

nesthus / vipps-php example snippets


> $client = new \GuzzleHttp\Client([
>     'timeout' => 15,          // whole request, seconds
>     'connect_timeout' => 5,   // TCP/TLS handshake, seconds
> ]);
> 

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