Cost+Docs

PHP

Official PHP SDK for the Cost+ payment gateway

Official PHP SDK for the Cost+ payment gateway. Simplifies the HPP (Hosted Payment Page) redirect flow, HMAC payload signing, and webhook verification.

Features

  • Zero external dependencies — uses only built-in PHP extensions (curl, json, hash)
  • PHP 8.1+ with readonly properties, named arguments, and strict types
  • HMAC-SHA256 signature generation and constant-time verification via hash_equals
  • Automatic snake_case/camelCase mapping between the API and the SDK
  • Webhook parsing + API-based order verification
  • Tested across PHP 8.1, 8.2, and 8.3

Requirements

  • PHP 8.1 or later
  • Extensions: curl, json (both included in standard PHP)
  • A Cost+ merchant account — dashboard.costplus.io

Installation

composer require nopayn/sdk

Quick Start

1. Initialise the Client

use NoPayn\NoPaynClient;

$nopayn = new NoPaynClient([
    'apiKey'     => 'your-api-key',
    'merchantId' => 'your-project',
]);

2. Create a Payment and Redirect to the HPP

$result = $nopayn->generatePaymentUrl([
    'amount'           => 1295,            // €12.95 in cents
    'currency'         => 'EUR',
    'merchantOrderId'  => 'ORDER-001',
    'description'      => 'Premium Widget',
    'returnUrl'        => 'https://shop.example.com/success',
    'failureUrl'       => 'https://shop.example.com/failure',
    'webhookUrl'       => 'https://shop.example.com/webhook',
    'locale'           => 'en-GB',
    'expirationPeriod' => 'PT30M',
]);

// Redirect the customer
// $result['orderUrl']   → HPP (customer picks payment method)
// $result['paymentUrl'] → direct link to the first transaction's payment method
// $result['signature']  → HMAC-SHA256 for verification
// $result['orderId']    → NoPayn order UUID
header('Location: ' . ($result['paymentUrl'] ?? $result['orderUrl']));

3. Handle the Webhook

$rawBody  = file_get_contents('php://input');
$verified = $nopayn->verifyWebhook($rawBody);

echo $verified['order']['status']; // 'completed', 'cancelled', etc.
echo $verified['isFinal'];        // true when the order won't change

if ($verified['order']['status'] === 'completed') {
    // Fulfil the order
}

http_response_code(200);

API Reference

new NoPaynClient(array $config)

ParameterTypeRequiredDefault
apiKeystringYes
merchantIdstringYes
baseUrlstringNohttps://api.nopayn.co.uk

$client->createOrder(array $params): array

Creates an order via POST /v1/orders/.

ParameterTypeRequiredDescription
amountintYesAmount in smallest currency unit (cents)
currencystringYesISO 4217 code (EUR, GBP, USD, NOK, SEK)
merchantOrderIdstringNoYour internal order reference
descriptionstringNoOrder description
returnUrlstringNoRedirect after successful payment
failureUrlstringNoRedirect on cancel/expiry/error
webhookUrlstringNoAsync status-change notifications
localestringNoHPP language (en-GB, de-DE, nl-NL, etc.)
paymentMethodsstring[]NoFilter HPP methods
expirationPeriodstringNoISO 8601 duration (PT30M)

Available payment methods: credit-card, apple-pay, google-pay, vipps-mobilepay

$client->getOrder(string $orderId): array

Fetches order details via GET /v1/orders/{id}/.

$client->createRefund(string $orderId, int $amount, ?string $description = null): array

Issues a full or partial refund via POST /v1/orders/{id}/refunds/.

$client->generatePaymentUrl(array $params): array

Convenience method that creates an order and returns:

[
    'orderId'    => string,   // NoPayn order UUID
    'orderUrl'   => string,   // HPP URL
    'paymentUrl' => ?string,  // Direct payment URL (first transaction)
    'signature'  => string,   // HMAC-SHA256 of amount:currency:orderId
    'order'      => array,    // Full order object
]

$client->generateSignature(int $amount, string $currency, string $orderId): string

Generates an HMAC-SHA256 hex signature.

$client->verifySignature(int $amount, string $currency, string $orderId, string $signature): bool

Constant-time verification of an HMAC-SHA256 signature.

$client->verifyWebhook(string $rawBody): array

Parses the webhook body, then calls GET /v1/orders/{id}/ to verify the actual status.

Standalone HMAC Utilities

use NoPayn\Signature;

$sig = Signature::generate('your-api-key', 1295, 'EUR', 'order-uuid');
$ok  = Signature::verify('your-api-key', 1295, 'EUR', 'order-uuid', $sig);

Error Handling

use NoPayn\Exceptions\ApiException;
use NoPayn\Exceptions\NoPaynException;
use NoPayn\Exceptions\WebhookException;

try {
    $nopayn->createOrder(['amount' => 100, 'currency' => 'EUR']);
} catch (ApiException $e) {
    echo $e->getStatusCode(); // 401, 400, etc.
    print_r($e->getErrorBody()); // Raw API error response
} catch (NoPaynException $e) {
    echo $e->getMessage(); // Network or parsing error
}

Order Statuses

StatusFinal?Description
newNoOrder created
processingNoPayment in progress
completedYesPayment successful — deliver the goods
cancelledYesPayment cancelled by customer
expiredYesPayment link timed out
errorYesTechnical failure

Webhook Best Practices

  1. Always verify via the API — the webhook payload only contains the order ID, never the status. The SDK's verifyWebhook() does this automatically.
  2. Return HTTP 200 to acknowledge receipt. Any other code triggers up to 10 retries (2 minutes apart).
  3. Implement a backup poller — for orders older than 10 minutes that haven't reached a final status, poll getOrder() as a safety net.
  4. Be idempotent — you may receive the same webhook multiple times.

Test Cards

Use these cards in Cost+ test mode (sandbox website):

CardNumberNotes
Visa (success)4111 1111 1111 1111Any CVV
Mastercard (success)5544 3300 0003 7Any CVV
Visa (declined)4111 1111 1111 1105Do Not Honor
Visa (insufficient funds)4111 1111 1111 1151Insufficient Funds

Use any future expiry date and any 3-digit CVC.

Demo App

A Docker-based demo app is included in the GitHub repository for testing the full payment flow.

Support

Need help? Reach out to our support team at support@costplus.io.

On this page