Create checkout sessions
Collecting a one-off payment, for up to ten billing periods or your own priced lines, with the SUQO PHP SDK.
$suqo->checkoutSessions opens a session that collects one payment for up to ten of your own billing periods, or for lines you price yourself. It is the route to a payment that is not a subscription. A subscription's first payment comes from subscriptions->create(), and its next from subscriptions->renew() — both of which hand back a checkout URL of their own.
Opening a session
use Suqo\Params\CheckoutItem;
use Suqo\Params\CreateCheckoutSessionParams;
$session = $suqo->checkoutSessions->create(new CreateCheckoutSessionParams(
items: [CheckoutItem::billingPeriod('pbp_3n9k2x')],
returnUrl: 'https://partner.example.com/orders/1234',
));
echo $session->checkoutUrl, PHP_EOL; // send this to the buyer
echo $session->expiresAt, PHP_EOL;Returns Suqo\Model\CheckoutSession — publicId (cks_…), expiresAt, checkoutUrl.
Throws ValidationError on 400 (an item, the customer or the return URL was rejected), RateLimitError on 429, AuthenticationError on 401, KycRequiredError / PermissionDeniedError on 403.
The two item shapes
An item is either an existing billing period or an inline, partner-priced line — never a mixture. The API rejects any inline field sent beside a pbp_id, because the plan behind a billing period sets the price, the cadence and the discount. CheckoutItem has one named constructor per shape, so the invalid combination cannot be expressed:
use Suqo\IntervalType;
CheckoutItem::billingPeriod('pbp_3n9k2x');
CheckoutItem::inline(
name: 'Setup fee',
amount: '2500.00', // a decimal string, never a float
intervalType: IntervalType::OneTime,
intervalCount: 0, // must be 0 for one_time
discountAmount: '500.00', // optional
);intervalCount is bounded per unit: 1–1095 for day, 1–156 for week, 1–36 for month, 1–3 for year, and exactly 0 for one_time. intervalType accepts an IntervalType case or a raw string.
Each item's price is snapshotted when the session opens, so a plan edited afterwards cannot change what the buyer already saw. If two items resolve to the same product — through different pbp_ids, say — only the last one is kept.
The customer is optional
customerId | What happens |
|---|---|
omitted or null | The buyer identifies themselves by OTP at checkout. |
cus_… | Must already exist on your account — see Customers. |
The SDK sends customer_id only when you set it.
This is the one rate-limited endpoint
20 sessions a minute per account by default. Over that, the API answers 429 and the SDK raises RateLimitError with retryAfter set from the response header:
use Suqo\Exception\RateLimitError;
try {
$session = $suqo->checkoutSessions->create($params);
} catch (RateLimitError $e) {
sleep((int) ceil($e->retryAfter ?? 1.0));
}It is not retried for you. Creating a session is a write, and writes are never retried while there is no idempotency key — a blind resend would open a second session. Back off and resend yourself, deliberately.
Reading a session back
$detail = $suqo->checkoutSessions->read($session->publicId);
echo $detail->sellerDetails?->businessName ?? '-', PHP_EOL;
echo $detail->isExpired ? 'expired' : 'open', PHP_EOL;Returns Suqo\Model\CheckoutSessionDetail — publicId, lineItems (kept exactly as it arrived — openapi describes no members), customerId (null when the buyer identified by OTP), sellerDetails (?CheckoutSeller: id, phone, businessLogo, businessName), returnUrl, expiresAt, completedAt (null while open), isExpired (?bool — openapi types it as a string; both spellings decode).
Only an open session is served. Once it is paid, or once it has expired, reading it answers 404 exactly as an unknown id does — see Handle errors for recovering the buyer's return_url from that 404.
See also
- Manage subscriptions —
subscriptions->renew()opens a session of its own, for a payment on an existing subscription. - Handle errors — the rate limit and the 404-on-expiry in full.