sonnenglas/yoco-php-sdk

Framework-agnostic PHP SDK for the Yoco Online (Checkout) Payment API with Standard Webhooks signature verification.

Maintainers

Package info

github.com/sonnenglas/yoco-php-sdk

Documentation

pkg:composer/sonnenglas/yoco-php-sdk

Statistics

Installs: 7

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.1 2026-05-22 08:43 UTC

This package is auto-updated.

Last update: 2026-05-22 08:44:18 UTC


README

Framework-agnostic PHP SDK for the Yoco Online (Checkout) Payment API, with first-class support for Standard Webhooks signature verification.

Packagist Version Packagist Downloads PHP Version License PHPStan

Features

  • Framework-agnostic. Plain PSR-18 (HTTP client), PSR-17 (factories), and PSR-7 (messages). No Laravel, no Symfony, no global state — drop it into any modern PHP project.
  • Type-safe DTOs. Readonly request/response objects with strict shape validation. No untyped arrays leaking through your code.
  • Idempotency built in. Checkouts::create() and ::refund() auto-generate a UUID v4 Idempotency-Key per call, or accept your own for deterministic retries.
  • Webhook signature verification. Standard Webhooks compliant. Constant-time HMAC comparison, replay-window enforcement, multi-version signature parsing for graceful key rotation.
  • Hardened by default. 1 MiB response and webhook body caps, JSON depth limit of 64, defensive header sanitization, and PSR-18 transport error wrapping that scrubs the Authorization: Bearer header from leaked messages.
  • Comprehensive HTTP error mapping. 400, 403, 409, 422, 429 each map to a distinct exception subclass — no need to grep on status codes.
  • Auto-detected test mode. CheckoutResponse::$processingMode and WebhookSubscription::$mode surface Yoco's live / test distinction so the same code path works in both environments.
  • PHPStan level 9. Zero static-analysis errors. Strict types throughout.
  • 114 tests. PHPUnit 10 with realistic Yoco payload fixtures.

Why this SDK?

Yoco does not publish an official PHP SDK at the time of writing, and the third-party packages we could find were either Laravel-specific, abandoned, or predated the current Checkout API.

sonnenglas/yoco-php-sdk is built against the current public Yoco Online Payments API, targets modern PHP (8.2+), and stays out of your dependency injection container by design.

Requirements

  • PHP 8.2 or newer.
  • A PSR-18 HTTP client. Guzzle 7 is the recommended default and is automatically discovered via php-http/discovery.

Installation

composer require sonnenglas/yoco-php-sdk

If you do not already have a PSR-18 client installed:

composer require guzzlehttp/guzzle

php-http/discovery will auto-wire any installed PSR-18 client and PSR-17 factories — you only need to inject them manually if you want to override defaults (timeouts, retry middleware, mock client in tests, etc.).

Quick start

1. Create a hosted checkout

use Sonnenglas\Yoco\Client;
use Sonnenglas\Yoco\Dto\CreateCheckoutRequest;

$client = new Client(secretKey: getenv('YOCO_SECRET_KEY'));

$checkout = $client->checkouts()->create(new CreateCheckoutRequest(
    amount:     10000,                          // 100.00 ZAR — Yoco amounts are in cents
    currency:   'ZAR',
    successUrl: 'https://example.com/success',
    cancelUrl:  'https://example.com/cancel',
    metadata:   ['orderNumber' => 'ORD-100'],
));

header('Location: '.$checkout->redirectUrl);   // send the customer to Yoco

2. Verify an incoming webhook

use Sonnenglas\Yoco\Webhook\SignatureVerifier;
use Sonnenglas\Yoco\Exceptions\SignatureVerificationException;

$verifier = new SignatureVerifier(getenv('YOCO_WEBHOOK_SECRET')); // whsec_...

try {
    $event = $verifier->verify(file_get_contents('php://input'), getallheaders());
} catch (SignatureVerificationException $e) {
    http_response_code(401);
    exit;
}

// $event->type === 'payment.succeeded' | 'payment.failed'
// $event->payload['metadata']['orderNumber']

3. Refund a checkout

$refund = $client->checkouts()->refund(
    checkoutId: 'ch_9LVKD8GnAj7f39DFbn4F16bE',
    amount:     2500,         // partial refund of 25.00 ZAR; omit for full refund
);

echo $refund->status;         // 'created' | 'succeeded' | ...

Documentation

Supported features

In scope Out of scope
POST /api/checkouts — create checkout GET /v1/payments/{id} (main Yoco API)
POST /api/checkouts/{id}/refund — full + partial refund Payouts (/v1/payouts)
POST /api/webhooks — register subscription Locations (/v1/locations)
GET /api/webhooks — list subscriptions OAuth applications
DELETE /api/webhooks/{id} — delete subscription Yoco Card Machine / POS APIs
Standard Webhooks signature verification (v1) In-person card-present payments
Standard Webhooks key rotation (multi-signature headers)
Test-mode detection (processingMode, mode fields)
Idempotency-Key auto-generation (UUID v4)
Retry-After parsing on rate-limit responses

The SDK targets the Yoco Online Payments API at https://payments.yoco.com/api. The broader api.yoco.com/v1 surface (payments, payouts, locations) is intentionally not implemented — open an issue if you need it.

Exceptions

All SDK exceptions extend Sonnenglas\Yoco\Exceptions\YocoException.

HTTP Exception When
400 ValidationException Invalid request body or parameters.
401 AuthenticationException Defensive — Checkout API uses 403, but proxies may return 401.
403 AuthenticationException Missing or invalid API key.
409 IdempotencyConflictException Another request with the same Idempotency-Key is in flight.
422 IdempotencyMismatchException Re-used Idempotency-Key with a different request body.
429 RateLimitException ($retryAfter) Rate limit reached (defensive; surfaces Retry-After if present).
other ApiException Anything else (5xx, unmapped 4xx, malformed JSON, etc.).
SignatureVerificationException Webhook signature failed verification.

Security

  • Constant-time signature comparison via hash_equals — no early-exit timing side channel.
  • WebhookSubscription redaction__debugInfo() masks the webhook secret so it never surfaces in var_dump, print_r, or Symfony VarDumper output.
  • Transport-error sanitization — PSR-18 client exceptions are wrapped without propagating the inner message, which on some clients embeds the full outgoing request (including the Authorization: Bearer <secret> header). The original exception is still available via getPrevious().
  • Defensive size limits — 1 MiB on response bodies, 1 MiB on webhook bodies, JSON depth capped at 64. Malformed or oversized payloads fail loudly rather than blow up memory.
  • Replay protection — webhook timestamps are checked against a configurable tolerance window (default 180 s, max 3600 s).

See SECURITY.md for the full security policy and reporting process.

Development

composer install
composer test         # PHPUnit
composer phpstan      # PHPStan level 9
composer cs-fix       # PHP-CS-Fixer
composer check        # phpstan + test together

Versioning

This project follows Semantic Versioning 2.0.0.

Contributing

Pull requests, issues, and discussions are welcome. Please read CONTRIBUTING.md before opening a PR — it covers the dev setup, coding standards, and the test-first workflow used throughout the codebase.

For bug reports and feature requests, please use the GitHub issue templates.

License

Released under the MIT License.

Credits

Built and maintained by SONNENGLAS.

If this SDK saves you time, please consider starring the GitHub repository.