Skip to content

PHP

Early access

shotpay/php — not published to Packagist yet. Install from a checkout of the repository or a git dependency in the meantime. Requires PHP 8.2 or newer.

use ShotPay\Sdk\ShotPayClient;

$client = new ShotPayClient(
    clientId: config('shotpay.client_id'),
    apiKey: config('shotpay.api_key'),
    // $baseUrl defaults to the sandbox — set it to the live host in production.
);

Every method

Method API reference Scope
checkoutSessions->create Open a checkout session checkout:write
checkoutSessions->retrieve Verify a session from your server checkout:read
contracts->list List contracts contracts:read
contracts->retrieve Retrieve a contract contracts:read
contracts->update Update a contract contracts:write
contracts->cancel Cancel a contract contracts:write
WebhookSignatureVerifier::verify — none

Checkout sessions

checkoutSessions->create

Uses the Open a checkout session API.

Opens a session and returns it, including the checkoutUrl to redirect to.

create(array $params, ?string $idempotencyKey = null): CheckoutSession

Pass $idempotencyKey to make a retry safe — replaying the same key with the same params returns the original session rather than opening a second one.

$session = $client->checkoutSessions->create([
    'order_reference' => $order->number,
    'total_amount' => $order->total_cents, // integer cents — 49999 is $499.99
    'return_url' => route('checkout.complete'),
    'cancel_url' => route('cart'),
], idempotencyKey: $order->shotpay_idempotency_key);

return redirect()->away($session->checkoutUrl);

checkoutSessions->retrieve

Uses the Verify a session from your server API.

Reads a session your server opened, without waiting on a webhook.

retrieve(string $id): CheckoutSession

This is the call that settles an order. Put it in one function and call that function from both of the places that learn a checkout resolved:

function fulfil(string $sessionId): void
{
    $session = $client->checkoutSessions->retrieve($sessionId);

    if ($session->status !== 'completed') {
        return;
    }

    $order = Order::where('number', $session->orderReference)->firstOrFail();

    // The webhook and the returning customer race routinely.
    if (! $order->isPaid()) {
        $order->markPaid();
    }
}

Contracts

The agreements a completed checkout becomes, addressed by the reference webhooks carry — never a row id.

contracts->list

Uses the List contracts API.

One page of your agreements, newest first.

list(array $params = []): ContractPage
Parameter Notes
status Optional. active, completed, cancelled or defaulted.
page Optional. Defaults to the first page.

Page size is fixed by the API — there is no per_page. The nextPageUrl is absolute and this client will not follow it for you, so step page instead:

$page = $client->contracts->list(['status' => 'active']);

while ($page->hasMorePages()) {
    foreach ($page->data as $contract) {
        // $contract->reference, $contract->remainingAmount, $contract->installments
    }

    $page = $client->contracts->list([
        'status' => 'active',
        'page' => $page->currentPage + 1,
    ]);
}

ContractPage carries $data, $currentPage, $lastPage, $perPage, $total, $nextPageUrl and $previousPageUrl, plus hasMorePages().

contracts->retrieve

Uses the Retrieve a contract API.

One agreement with its full installment schedule.

retrieve(string $reference): Contract
$contract = $client->contracts->retrieve('LAY-XK2M9QPR7V');

$contract->remainingAmount; // integer cents still to collect
$contract->installments;    // Installment[]: sequence, amount, dueDate, status, paidAt

A reference belonging to another merchant answers 404 rather than 403, and so does one of your own from the other mode.

contracts->update

Uses the Update a contract API.

Corrects bookkeeping. Money, status and the customer's identity are not writable — anything not named below never reaches the record.

update(string $reference, array $params): Contract
Parameter Notes
order_reference Optional. Your own identifier, up to 100 characters.
metadata Optional. String keys and values. Replaces the stored map rather than merging; null clears it. Max 50 keys, keys 40 chars, values 500.
$client->contracts->update('LAY-XK2M9QPR7V', [
    'order_reference' => 'SO-2026-0042',
    'metadata' => ['crm_id' => '77'],
]);

contracts->cancel

Uses the Cancel a contract API.

Ends an active agreement. No further installments are collected; paid installments stay paid.

cancel(string $reference): Contract

Cancelling an already-cancelled agreement returns it as it stands, so a retry is safe. One that already completed or defaulted raises a 422. A contract.cancelled webhook goes to your subscribed endpoints.

$client->contracts->cancel('LAY-XK2M9QPR7V');

Webhooks

WebhookSignatureVerifier::verify

Confirms a delivery came from ShotPay and has not been altered.

public static function verify(
    string $payload,
    string $secret,
    string $header,
    int $toleranceSeconds = 300,
): bool
use ShotPay\Sdk\WebhookSignatureVerifier;

// The raw bytes, not re-serialised JSON — see Signatures.
$verified = WebhookSignatureVerifier::verify(
    $request->getContent(),
    $endpointSecret,
    $request->header('ShotPay-Signature'),
);

abort_unless($verified, 400);

The scheme itself is on Signatures.

Errors

Every non-2xx raises ShotPayApiException, carrying the HTTP status and the decoded response body. Which statuses are worth retrying is on Error Handling.

use ShotPay\Sdk\Exceptions\ShotPayApiException;

try {
    $client->contracts->cancel($reference);
} catch (ShotPayApiException $exception) {
    if ($exception->status === 422) {
        // Already completed or defaulted — nothing left to cancel.
    }
}

Deprecated

$client->createCheckoutSession() and $client->retrieveCheckoutSession() still work and call the same endpoints, but are removed in 0.3.0. Use checkoutSessions->create and checkoutSessions->retrieve.