Skip to content

Integrate with the PHP Checkout SDK

Learn how to integrate ICEPAY payments in a PHP application using the official PHP Checkout SDK.

The PHP Checkout SDK provides a PHP interface around the ICEPAY Checkout API. It handles HTTP communication, maps API responses to PHP models, provides consistent exception handling, and includes a webhook handler for verifying payment status notifications.

This guide walks through a complete integration, from installing the SDK and creating a payment to handling webhooks, retrieving payments, issuing refunds, and forwarding funds.

The SDK runs in your backend application and communicates with the ICEPAY Checkout API for you.

  1. Create a checkout request

    Build the payment using the SDK’s request and amount models.

  2. Send the payment to ICEPAY

    Call createCheckout() on the CheckoutClient.

  3. Redirect the customer

    ICEPAY returns a checkout URL through the response model. Redirect the customer to that URL.

  4. Handle payment updates

    Use the SDK’s PostbackHandler to verify incoming ICEPAY webhooks and parse the updated payment.

  5. Retrieve or manage the payment when needed

    Use the same client to retrieve a payment, issue a refund, forward funds, or retrieve available payment methods.

The PHP Checkout SDK requires:

  • PHP 8.1 or newer;
  • Composer;
  • compatible PSR HTTP client, message, and factory implementations;
  • your ICEPAY Merchant ID and Merchant Secret.

The SDK does not force one specific HTTP client implementation. This means it can work with different HTTP stacks depending on your application.

Install the SDK with Composer:

Terminal window
composer require icepay/checkout-sdk

The SDK requires implementations for the PSR HTTP interfaces it uses. If your project does not already provide compatible implementations, you can install the SDK together with Nyholm PSR-7 and Symfony HTTP Client:

Terminal window
composer require icepay/checkout-sdk nyholm/psr7 symfony/http-client

Keep your credentials outside your source code, for example as environment variables:

.env
ICEPAY_MERCHANT_ID=your_merchant_id
ICEPAY_MERCHANT_SECRET=your_merchant_secret

Create the CheckoutClient and configure the credentials:

<?php
require_once 'vendor/autoload.php';
use ICEPAY\Checkout\CheckoutClient;
$merchantId = getenv('ICEPAY_MERCHANT_ID');
$merchantSecret = getenv('ICEPAY_MERCHANT_SECRET');
if (
!is_string($merchantId) || $merchantId === '' ||
!is_string($merchantSecret) || $merchantSecret === ''
) {
throw new RuntimeException('Missing ICEPAY credentials.');
}
$checkoutClient = (new CheckoutClient())->withAuthorization(
merchantId: $merchantId,
merchantSecret: $merchantSecret,
);

The client uses these credentials when communicating with the ICEPAY Checkout API.

A payment starts with a Checkout request model.

<?php
use ICEPAY\Checkout\Models\Amount;
use ICEPAY\Checkout\Models\Request\Checkout as CheckoutRequest;
$checkoutRequest = new CheckoutRequest(
reference: 'ORD-16307',
description: 'Ice cream dipped in sprinkles',
amount: new Amount(
value: 299,
currency: Amount::CURRENCY_EUR,
),
redirectUrl: 'https://merchant.example.com/payment-complete',
webhookUrl: 'https://merchant.example.com/payment-webhook',
);
$response = $checkoutClient->createCheckout($checkoutRequest);

The response is mapped to an SDK response model, so you can access the returned values as PHP properties instead of manually decoding JSON.

For example:

$paymentKey = $response->key;
$checkoutUrl = $response->links->checkout;
$status = $response->status->toString();

After creating the payment, redirect the customer to the returned checkout URL:

header('Location: ' . $response->links->checkout, true, 303);
exit;

The customer selects a method on the ICEPAY Checkout Page, then completes the payment on the ICEPAY Payment Page or the payment provider’s page.

The Amount model takes the amount as an integer.

For currencies with two decimal places:

Customer-facing amount SDK value
€0.01 1
€2.99 299
€10.00 1000
€149.95 14995

For example:

$amount = new Amount(
value: 299,
currency: Amount::CURRENCY_EUR,
);

The SDK’s CheckoutRequest model supports the following properties:

Property Type Purpose
reference string Your payment or order reference.
description string Description of the payment.
amount Amount Payment amount and currency.
redirectUrl string URL to which the customer returns after checkout.
webhookUrl string URL to which ICEPAY sends payment status updates.
paymentMethod PaymentMethod or string Optionally preselect a payment method.
metadata Metadata Additional metadata sent with the payment.
expireAfter ?int Optional payment expiry period in minutes.

You can supply these values in the constructor or use the helper methods provided by the request model.

For example:

$checkoutRequest = new CheckoutRequest(
reference: 'ORD-16307',
description: 'Ice cream dipped in sprinkles',
amount: new Amount(299, Amount::CURRENCY_EUR),
expireAfter: 60,
);
$checkoutRequest
->withRedirectUrl('https://merchant.example.com/payment-complete')
->withWebhookUrl('https://merchant.example.com/payment-webhook')
->withCustomerEmail('johndoe@example.com');

expireAfter: 60 keeps the payment open for 60 minutes (1 hour). If omitted, the API default is 240 minutes (4 hours). The maximum is 44,640 minutes (31 days).

For a customer email address, use:

$checkoutRequest->withCustomerEmail('johndoe@example.com');

You can also provide customer information as an array:

$checkoutRequest->withCustomer([
'email' => 'johndoe@example.com',
'firstName' => 'John',
'lastName' => 'Doe',
]);

The SDK stores this information in the request metadata before serialising the payment request.

If you do not set paymentMethod, redirect the customer to:

$response->links->checkout

The hosted Checkout Page can then present the available payment methods to the customer.

If the customer already selected a payment method in your own checkout, include it in the request.

You can pass the payment method as a string:

$checkoutRequest = new CheckoutRequest(
reference: 'ORD-16307',
description: 'Ice cream dipped in sprinkles',
amount: new Amount(299, Amount::CURRENCY_EUR),
paymentMethod: 'ideal',
);

Or use the SDK’s PaymentMethod model:

use ICEPAY\Checkout\Models\PaymentMethod;
$checkoutRequest = new CheckoutRequest(
reference: 'ORD-16307',
description: 'Ice cream dipped in sprinkles',
amount: new Amount(299, Amount::CURRENCY_EUR),
paymentMethod: new PaymentMethod(
type: PaymentMethod::TYPE_IDEAL,
),
);

When a direct payment-method URL is returned, it is available through:

$response->links->direct

Because the direct link is optional, check that it is available before using it:

if ($response->links->direct !== null) {
header('Location: ' . $response->links->direct, true, 303);
exit;
}

Use getPaymentMethods() to retrieve the payment methods available to the authenticated merchant:

$paymentMethods = $checkoutClient->getPaymentMethods();
foreach ($paymentMethods as $paymentMethod) {
echo $paymentMethod->id;
echo $paymentMethod->description;
}

Each item is returned as an SDK response model with properties such as:

$paymentMethod->id;
$paymentMethod->description;
$paymentMethod->logo;

After a payment status changes, ICEPAY sends a request to the webhookUrl supplied when you created the payment.

The PHP SDK uses the name postback for a webhook. Its class name remains PostbackHandler; both terms refer to the same payment status notification.

The SDK includes PostbackHandler, which:

  • verifies the ICEPAY-Signature;
  • uses your Merchant Secret for signature validation;
  • parses the JSON body;
  • returns the payment as an SDK Checkout response model.

This means you do not have to implement the HMAC verification yourself.

<?php
use ICEPAY\Checkout\Exceptions\InvalidSignature;
use ICEPAY\Checkout\PostbackHandler;
$merchantSecret = getenv('ICEPAY_MERCHANT_SECRET');
if (!is_string($merchantSecret) || $merchantSecret === '') {
throw new RuntimeException('Missing ICEPAY Merchant Secret.');
}
$handler = new PostbackHandler(
merchantSecret: $merchantSecret,
);
$body = file_get_contents('php://input');
$signature = $_SERVER['HTTP_ICEPAY_SIGNATURE'] ?? '';
if ($body === false) {
http_response_code(400);
return;
}
try {
$payment = $handler->handle($body, $signature);
} catch (InvalidSignature) {
http_response_code(400);
return;
}
$paymentKey = $payment->key;
$status = $payment->status->toString();
$financialStatus = $payment->financialStatus->toString();
// Find the order using $payment->key.
// Check that the payment matches the stored order details.
// Ignore duplicate or older updates.
// Update the payment and queue any additional work safely.

Validate the payment before updating your order

Section titled “Validate the payment before updating your order”

After verifying the webhook signature, make sure the payment belongs to the order you expect.

Use the payment key that you stored when the payment was created. Before updating or fulfilling the order, check important values such as:

  • the merchant;
  • the processing mode (Test mode or Live mode);
  • the amount; and
  • the currency.

Reject unknown payment keys or values that do not match your stored payment information.

Your endpoint may receive the same payment update more than once.

Make sure processing the same update again does not repeat actions such as:

  • fulfilling an order;
  • sending a confirmation email;
  • updating inventory; or
  • performing another action that should only happen once.

Store the payment state and completed actions in your database. When updating an order, use a database transaction or equivalent protection so two webhook requests arriving at the same time cannot perform the same action twice.

If additional work is processed asynchronously, make sure those background jobs are also safe to run more than once.

Webhook updates may arrive late or in a different order than expected.

Do not allow an older update to overwrite a newer payment state. If you cannot reliably determine which update is newer, retrieve the current payment through the ICEPAY Checkout API before updating your order.

Refunds and forwarded payments can change independently from the main payment status, so track them separately.

Return HTTP 200 only after the verified update has been processed or safely stored for later processing.

If storing or processing the update fails, return an unsuccessful response so the webhook can be retried.

A duplicate update that was already processed successfully can return HTTP 200.

See Process webhooks for more information about validating and processing webhook updates.

If you only need to check the authenticity of a webhook:

if (!$handler->verify($body, $signature)) {
http_response_code(400);
return;
}

If your framework already gives you a PSR-7 MessageInterface, the handler can read the body and signature header directly:

use ICEPAY\Checkout\Exceptions\InvalidSignature;
use Psr\Http\Message\MessageInterface;
/** @var MessageInterface $request */
try {
$payment = $handler->handleRequest($request);
} catch (InvalidSignature) {
http_response_code(400);
return;
}

You can also use:

$handler->verifyRequest($request);

when you only want to verify the request.

Store the ICEPAY payment key returned when the payment is created:

$paymentKey = $response->key;

You can retrieve the current payment later with:

$payment = $checkoutClient->getCheckout($paymentKey);

The returned model exposes the payment state:

$payment->key;
$payment->reference;
$payment->amount;
$payment->paymentMethod;
$payment->links;
$payment->status->toString();
$payment->financialStatus->toString();

The SDK maps the payment status to the Status enum.

Possible values handled by the SDK are:

started
pending
completed
expired
cancelled
unknown

The separate financial status is represented by the FinancialStatus enum:

uncleared
cleared
unknown

Create a refund request using ICEPAY\Checkout\Models\Request\Refund.

use ICEPAY\Checkout\Models\Request\Refund as RefundRequest;
$refundRequest = new RefundRequest(
reference: 'RFD-00069',
amount: 299,
description: 'Received chocolate dip instead of sprinkles',
);
$refund = $checkoutClient->refund(
refund: $refundRequest,
checkoutId: $paymentKey,
);

The refund amount can be supplied as either an integer or an Amount object.

When an integer is supplied, the SDK serialises it as the request’s amount.value:

$refundRequest = new RefundRequest(
reference: 'RFD-00069',
amount: 299,
description: null,
);

The SDK exposes payment forwarding through the Forward request model.

use ICEPAY\Checkout\Models\Request\Forward as ForwardRequest;
$forwardRequest = new ForwardRequest(
reference: 'FWD-00042',
amount: 299,
recipient: '1001',
description: 'Forwarding to merchant 1001',
);
$forward = $checkoutClient->forward(
forward: $forwardRequest,
checkoutId: $paymentKey,
);

The SDK accepts either a recipient ID string or a Recipient model.

When a string is supplied:

recipient: '1001'

the SDK serialises it as:

{
"recipient": {
"id": "1001"
}
}

All CheckoutClient operations use the same base exception:

ICEPAY\Checkout\Exceptions\ApiException

This includes:

  • API errors returned by ICEPAY;
  • typed API exceptions;
  • transport failures such as timeouts, DNS errors, or refused connections.

A general error handler can therefore catch ApiException:

use ICEPAY\Checkout\Exceptions\ApiException;
try {
$response = $checkoutClient->createCheckout($checkoutRequest);
} catch (ApiException $exception) {
$message = $exception->getMessage();
$httpStatus = $exception->getCode();
$type = $exception->type;
$errors = $exception->errors;
// Log or handle the failure.
}

For transport failures, the status code can be 0.

The SDK also provides more specific exception classes for particular API problems. You can catch those when your application needs different behaviour for a specific error, while keeping ApiException as the general fallback.

For example:

use ICEPAY\Checkout\Exceptions\ApiException;
use ICEPAY\Checkout\Exceptions\Payment\NotFound;
try {
$payment = $checkoutClient->getCheckout($paymentKey);
} catch (NotFound $exception) {
// Handle a payment that does not exist.
} catch (ApiException $exception) {
// Handle any other ICEPAY or connection error.
}

For platform integrations or reusable modules, the checkout request can include integration information:

$checkoutRequest->withIntegrationInformation(
type: 'custom',
version: '1.0.0',
developer: 'Example Developer',
);

The SDK stores this information in the payment metadata.

This can be useful when the same ICEPAY Account receives payments from different integration types or versions.

For most integrations, automatic PSR implementation discovery is enough.

Advanced integrations can provide their own PSR-18 client by constructing the SDK’s HttpClient:

use ICEPAY\Checkout\CheckoutClient;
use ICEPAY\Checkout\HttpClient;
use Psr\Http\Client\ClientInterface;
/** @var ClientInterface $psrHttpClient */
$httpClient = new HttpClient(
client: $psrHttpClient,
);
$checkoutClient = (new CheckoutClient($httpClient))->withAuthorization(
merchantId: $merchantId,
merchantSecret: $merchantSecret,
);

This is useful when your application or framework already has its own HTTP transport layer.

The main CheckoutClient operations are:

SDK method Purpose
createCheckout($checkoutRequest) Create a payment.
getCheckout($paymentKey) Retrieve a payment.
getPaymentMethods() Retrieve available payment methods.
refund($refundRequest, $paymentKey) Refund a payment.
forward($forwardRequest, $paymentKey) Forward payment funds.

These methods correspond to the Checkout API operations while keeping request creation, response mapping, and exception handling inside the SDK.

For a normal PHP checkout integration:

  1. Install and configure the SDK

    Configure the CheckoutClient with your Merchant ID and Merchant Secret.

  2. Create the payment

    Build a CheckoutRequest with your reference, amount, redirect URL, and webhook URL.

  3. Store the payment key

    Save $response->key together with your own order or payment record.

  4. Redirect the customer

    Send the customer to $response->links->checkout, or use the direct link when you deliberately preselect a supported payment method.

  5. Process webhooks

    Use PostbackHandler::handle() to verify and parse status updates.

  6. Update your own payment record

    Store the returned status and financialStatus separately.

  7. Use additional operations when needed

    Retrieve the payment, refund it, forward funds, or retrieve available payment methods through the same CheckoutClient.

Contact the ICEPAY team

Contact the ICEPAY team with any questions about your SDK integration.