Skip to content

Setono/quickpay-php-sdk

Repository files navigation

Quickpay PHP SDK

Latest Stable Version License Build Status

Consume the Quickpay 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, so it works with any compliant HTTP client.

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

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 — use a test API key to run in test mode.

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)

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.

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).

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

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

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

// One page
$page = $client->payments()->getPage(); // Collection<Payment>
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

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.

use Setono\Quickpay\Callback\CallbackHandler;

$handler = new CallbackHandler('YOUR_PRIVATE_KEY');

$rawBody  = file_get_contents('php://input');
$checksum = $_SERVER['HTTP_QUICKPAY_CHECKSUM_SHA256'] ?? '';

try {
    // Verifies the checksum AND deserializes the body into a Payment in one step.
    $payment = $handler->handle($rawBody, $checksum);
} catch (\Setono\Quickpay\Exception\InvalidChecksumException $e) {
    http_response_code(403);
    exit;
}

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

If you have a PSR-7 server request, handleRequest($request) reads the raw body and the checksum header for you. To only verify (without deserializing), use CallbackValidator.

Accessing fields the SDK doesn't 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

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

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

composer install
composer phpunit       # tests
composer analyse       # PHPStan (level max)
composer check-style   # ECS
composer fix-style     # ECS, auto-fixing

Live API tests are skipped unless QUICKPAY_LIVE=1 and QUICKPAY_API_KEY (a test key) are set.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages