PHPackages                             setono/quickpay-php-sdk - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. setono/quickpay-php-sdk

ActiveLibrary[API Development](/categories/api)

setono/quickpay-php-sdk
=======================

Consume the Quickpay API with this PHP SDK

1.x-dev(today)039↑2669.2%MITPHPPHP &gt;=8.1CI passing

Since Jun 30Pushed todayCompare

[ Source](https://github.com/Setono/quickpay-php-sdk)[ Packagist](https://packagist.org/packages/setono/quickpay-php-sdk)[ RSS](/packages/setono-quickpay-php-sdk/feed)WikiDiscussions 1.x Synced today

READMEChangelogDependencies (21)Versions (2)Used By (0)

Quickpay PHP SDK
================

[](#quickpay-php-sdk)

[![Latest Stable Version](https://camo.githubusercontent.com/328d41f58bc6bff606b865fec39ea70c45fb603190a72c6ee500a82190b430aa/68747470733a2f2f706f7365722e707567782e6f72672f7365746f6e6f2f717569636b7061792d7068702d73646b2f762f737461626c65)](https://packagist.org/packages/setono/quickpay-php-sdk)[![License](https://camo.githubusercontent.com/7bb981d3ece87e7535e656de3c171a8fe02887069e2ce7201ad8e3036ee2d432/68747470733a2f2f706f7365722e707567782e6f72672f7365746f6e6f2f717569636b7061792d7068702d73646b2f6c6963656e7365)](https://packagist.org/packages/setono/quickpay-php-sdk)[![Build Status](https://github.com/Setono/quickpay-php-sdk/actions/workflows/build.yaml/badge.svg)](https://github.com/Setono/quickpay-php-sdk/actions)[![Code Coverage](https://camo.githubusercontent.com/ae8a81391e2fdd4073dca65328c6acbd515605c7cdc41a27ec5b7f0aace63a19/68747470733a2f2f636f6465636f762e696f2f67682f5365746f6e6f2f717569636b7061792d7068702d73646b2f6272616e63682f312e782f67726170682f62616467652e737667)](https://codecov.io/gh/Setono/quickpay-php-sdk)[![Mutation testing badge](https://camo.githubusercontent.com/c56553ac6d3db0d0f4d8ef9c4f5ca1650ee541d1ccbf76fa0d6262555f66e3d0/68747470733a2f2f696d672e736869656c64732e696f2f656e64706f696e743f7374796c653d666c61742675726c3d687474707325334125324625324662616467652d6170692e737472796b65722d6d757461746f722e696f2532466769746875622e636f6d2532465365746f6e6f253246717569636b7061792d7068702d73646b253246312e78)](https://dashboard.stryker-mutator.io/reports/github.com/Setono/quickpay-php-sdk/1.x)

Consume the [Quickpay API](https://learn.quickpay.net/tech-talk/api/) in PHP. A small, strongly-typed SDK focused on the `payments` resource, the `/ping` health check, the payment-window link flow, and callback (webhook) verification.

Built on PSR-18 (HTTP client), PSR-17 (factories) and PSR-7 (messages), discovered automatically via [`php-http/discovery`](https://github.com/php-http/discovery), so it works with any compliant HTTP client.

Installation
------------

[](#installation)

```
composer require setono/quickpay-php-sdk
```

You also need a PSR-18 client and a PSR-17 factory if your project doesn't already provide them, e.g.:

```
composer require kriswallsmith/buzz nyholm/psr7
```

Usage
-----

[](#usage)

Authenticate with your Quickpay **API key** (Quickpay manager → Settings → API user). The SDK uses the key as the HTTP Basic password with an empty username, exactly as Quickpay expects. There is no separate sandbox host or test key — a payment becomes a *test* payment (`test_mode: true`) when it's paid with a [test card](https://learn.quickpay.net/tech-talk/appendixes/test/).

```
use Setono\Quickpay\Client\Client;
use Setono\Quickpay\Request\Payment\CreatePaymentRequest;

$client = new Client('YOUR_API_KEY');

// Health check
$client->ping(); // true, or throws on a non-2xx response

// Create a payment
$payment = $client->payments()->create(new CreatePaymentRequest(
    orderId: 'order-0001',
    currency: 'DKK',
));

echo $payment->id;        // 1234
echo $payment->state;     // "initial"
echo $payment->state()?->name; // PaymentState enum (or null for an unknown value)
```

### Payment link flow (redirect the customer to the payment window)

[](#payment-link-flow-redirect-the-customer-to-the-payment-window)

The recommended way to take a payment is to create the payment, create a link for it, then redirect the customer to the returned URL. See [the Quickpay docs](https://learn.quickpay.net/tech-talk/payments/link/).

```
use Setono\Quickpay\Request\Payment\CreateLinkRequest;

$payment = $client->payments()->create(new CreatePaymentRequest(orderId: 'order-0001', currency: 'DKK'));

$link = $client->payments()->createLink($payment->id, new CreateLinkRequest(
    amount: 1000, // 10.00 DKK — amounts are integers in the smallest currency unit
    continueUrl: 'https://shop.example/continue',
    cancelUrl: 'https://shop.example/cancel',
    callbackUrl: 'https://shop.example/callback',
));

header('Location: ' . $link->url);
```

`continueUrl` / `cancelUrl` are where the customer is sent after a successful / cancelled payment; `callbackUrl` is the server-to-server URL Quickpay POSTs the result to (see [Callbacks](#callbacks)).

### Capturing, refunding, cancelling

[](#capturing-refunding-cancelling)

```
use Setono\Quickpay\Request\Payment\CaptureRequest;
use Setono\Quickpay\Request\Payment\RefundRequest;

$client->payments()->capture($payment->id, new CaptureRequest(1000));
$client->payments()->refund($payment->id, new RefundRequest(250));
$client->payments()->cancel($payment->id);
```

Quickpay processes these operations asynchronously by default — the returned payment may still have a pending operation. Pass `synchronized: true` to wait for and receive the completed transaction:

```
$payment = $client->payments()->capture($payment->id, new CaptureRequest(1000), synchronized: true);
```

### Updating a payment

[](#updating-a-payment)

Before a payment is authorized you can update some of its fields (`PATCH /payments/{id}`). Note the API does not allow changing `order_id` or `basket` after creation:

```
use Setono\Quickpay\Request\Payment\UpdatePaymentRequest;

$client->payments()->updatePayment($payment->id, new UpdatePaymentRequest(
    variables: ['internal_ref' => 'abc-123'],
));
```

> Authorizing directly via the API — `$client->payments()->authorize($id, new AuthorizePaymentRequest(...))` — requires you to handle card data and puts you in PCI scope. Most integrations authorize through the payment window instead (see the link flow above).

### Reading and listing payments

[](#reading-and-listing-payments)

```
$payment = $client->payments()->getById(1234);

// One page
$page = $client->payments()->getPage(); // Collection
foreach ($page as $payment) {
    echo $payment->orderId;
}

// All pages (lazily). Quickpay sends no total-count metadata, so pagination stops when a page comes
// back with fewer items than the requested page size.
use Setono\Quickpay\Request\CollectionRequestOptions;

foreach ($client->payments()->paginate(new CollectionRequestOptions(pageSize: 50)) as $payment) {
    // ...
}
```

### Callbacks

[](#callbacks)

Quickpay notifies your `callbackUrl` by POSTing the payment object and signing it with a `QuickPay-Checksum-Sha256` header — `hash_hmac('sha256', rawBody, privateKey)`. The **private key**(Quickpay manager → Settings → Integration) is different from the API key.

Always verify the checksum against the **raw, byte-for-byte request body** — do not decode and re-encode the JSON first, or the checksum won't match. The SDK uses `hash_equals()` for a timing-safe comparison.

A callback isn't always a payment — Quickpay also sends them for subscriptions — so `handle()`returns a verified `Callback` carrying the resource type (from the `QuickPay-Resource-Type` header). That header is required and must be a known value (`Payment` or `Subscription`); an unexpected or missing one is rejected. Only deserialize to a `Payment` once you know it is one:

```
use Setono\Quickpay\Callback\CallbackHandler;
use Setono\Quickpay\Enum\ResourceType;

$handler = new CallbackHandler('YOUR_PRIVATE_KEY');

try {
    // $request is your incoming PSR-7 server request (Symfony/Laravel/PSR-15 all give you one).
    // Verifies the checksum and validates the resource type — does NOT assume it's a payment.
    $callback = $handler->handle($request);
} catch (\Setono\Quickpay\Exception\InvalidChecksumException $e) {
    http_response_code(403); // not authentic
    exit;
} catch (\Setono\Quickpay\Exception\InvalidCallbackException $e) {
    http_response_code(400); // unknown resource type (or a malformed body)
    exit;
}

if ($callback->isPayment()) {
    $payment = $callback->payment();        // typed Payment
    // ... handle $payment->state(), $payment->accepted, $payment->operations, $payment->raw ...
} elseif (ResourceType::Subscription === $callback->type) {
    // a subscription — inspect $callback->toArray()
}

// Respond 2xx so Quickpay marks the callback as delivered.
http_response_code(200);
```

No PSR-7 request handy? Use `handleRaw($rawBody, $checksum, $resourceType)` with the raw body and header values — e.g. `file_get_contents('php://input')`, `$_SERVER['HTTP_QUICKPAY_CHECKSUM_SHA256']`, `$_SERVER['HTTP_QUICKPAY_RESOURCE_TYPE']`. This is also the one to use if your framework already consumed the request body. To only verify (without wrapping), use `CallbackValidator`.

### Accessing fields the SDK doesn't model

[](#accessing-fields-the-sdk-doesnt-model)

The SDK types the most commonly used fields; every response object also exposes the full decoded payload (with the original snake\_case keys from the Quickpay docs) via `$raw`:

```
$payment = $client->payments()->getById(1234);
$payment->raw['text_on_statement'];
$payment->raw['acquirer'];
```

### Error handling

[](#error-handling)

Every non-2xx response throws a typed exception; all of them implement `Setono\Quickpay\Exception\QuickpayException`:

```
use Setono\Quickpay\Exception\QuickpayException;
use Setono\Quickpay\Exception\ValidationException;

try {
    $client->payments()->create(new CreatePaymentRequest(orderId: 'dup', currency: 'DKK'));
} catch (ValidationException $e) {
    $e->getMessageText();       // Quickpay's "message"
    $e->getErrorCode();         // Quickpay's "error_code"
    $e->getValidationErrors();  // Quickpay's "errors" map (field => messages)
} catch (QuickpayException $e) {
    // any other SDK error (UnauthorizedException, NotFoundException, ConflictException,
    // TooManyRequestsException, InternalServerErrorException, MalformedResponseException, ...)
}
```

Production usage
----------------

[](#production-usage)

Valinor's mapping/normalization is fast but benefits from a cache in production. Wrap your own builders with the SDK's configuration and pass them to the client:

```
use CuyZ\Valinor\Cache\FileSystemCache;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\NormalizerBuilder;
use Setono\Quickpay\Client\Client;

$cache = new FileSystemCache(__DIR__ . '/var/cache/valinor');

$client = new Client(
    'YOUR_API_KEY',
    mapperBuilder: Client::configureMapperBuilder((new MapperBuilder())->withCache($cache)),
    normalizerBuilder: Client::registerNormalizerTransformers((new NormalizerBuilder())->withCache($cache)),
);
```

Contributing
------------

[](#contributing)

```
composer install
composer phpunit              # tests
composer analyse             # PHPStan (level max)
composer check-style         # ECS
composer fix-style           # ECS, auto-fixing
composer rector -- --dry-run # Rector modernization (CI runs --dry-run)
vendor/bin/infection         # mutation testing (min covered MSI 70%)
composer e2e:smoke           # real-API smoke test (needs QUICKPAY_API_KEY — see examples/e2e/)
```

Live API tests are skipped unless `QUICKPAY_LIVE=1` and `QUICKPAY_API_KEY` are set. (Quickpay has no separate test key — you use your real API key, and a payment is a *test* payment when paid with a [test card](https://learn.quickpay.net/tech-talk/appendixes/test/).)

End-to-end testing
------------------

[](#end-to-end-testing)

Unit tests fake the HTTP layer; to verify the *whole* flow — create a payment, complete it in Quickpay's hosted window with a test card, and receive and verify the signed asynchronous callback — use the harness under [`examples/e2e/`](examples/e2e/README.md). It runs a local callback listener, tunnels it to a public HTTPS URL with [Expose](https://expose.dev) (a small client Dockerfile is included) so Quickpay can reach it, and gives you CLI scripts to create payments and drive capture/refund/cancel:

```
composer e2e:listen     # terminal A: php -S 0.0.0.0:8000 listener that verifies callbacks
# terminal B: run the Expose tunnel (see examples/e2e/README.md)
QUICKPAY_CALLBACK_BASE=https://.sharedwithexpose.com composer e2e:create -- 1000 DKK
composer e2e:operate -- capture  1000
```

See [`examples/e2e/README.md`](examples/e2e/README.md) for the full runbook, the test-card table, and macOS/Docker networking notes.

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance100

Actively maintained with recent releases

Popularity11

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity28

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

0d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/2412177?v=4)[Joachim Løvgaard](/maintainers/loevgaard)[@loevgaard](https://github.com/loevgaard)

---

Top Contributors

[![loevgaard](https://avatars.githubusercontent.com/u/2412177?v=4)](https://github.com/loevgaard "loevgaard (15 commits)")

---

Tags

phpquickpayquickpay-api

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Rector

Type Coverage Yes

### Embed Badge

![Health badge](/badges/setono-quickpay-php-sdk/health.svg)

```
[![Health](https://phpackages.com/badges/setono-quickpay-php-sdk/health.svg)](https://phpackages.com/packages/setono-quickpay-php-sdk)
```

###  Alternatives

[telnyx/telnyx-php

Official Telnyx PHP SDK — APIs for Voice, SMS, MMS, WhatsApp, Fax, SIP Trunking, Wireless IoT, Call Control, and more. Build global communications on Telnyx's private carrier-grade network.

35729.6k2](/packages/telnyx-telnyx-php)[n1ebieski/ksef-php-client

PHP API client that allows you to interact with the API Krajowego Systemu e-Faktur

8754.6k](/packages/n1ebieski-ksef-php-client)[openai-php/client

OpenAI PHP is a supercharged PHP API client that allows you to interact with the Open AI API

5.8k26.1M295](/packages/openai-php-client)[phpro/http-tools

HTTP tools for developing more consistent HTTP implementations.

28146.3k](/packages/phpro-http-tools)[getbrevo/brevo-php

Official Brevo provided RESTFul API V3 php library

1003.6M46](/packages/getbrevo-brevo-php)[trycourier/courier

Courier PHP SDK

15654.8k](/packages/trycourier-courier)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
