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.