Keep your customers and their credits

PHP

Create a customer, top up and charge their credits, read the balance and the statement, run a flow the customer pays for, and block a customer.

7 min read

A customer is a user of your own application who pays for what Slen runs for them in credits — a unit of your own, with no currency. Your application creates the customer, tops up their credits and names the customer in each run; Slen takes the credits for the model calls of the run. The calls of this page need a key with customers:write, and the reads customers:read.

Create a customer

PHP
// The external user id is your own opaque id of the customer — not an email address or a name.
$customer = $slen->customers()->create('user_8421');

echo $customer->externalUserId, ' ', $customer->status->value, PHP_EOL;
  • The external user id is your own id of the customer. Keep it opaque — not an email address or a name: it stands in the console and in every statement.

  • customers.create() makes sure the customer exists: a second call answers the same customer. It moves no credits, and a new customer has no accounts.

  • A customer holds one account for each credit type: default unless you name another.

Top up and charge

PHP
use Slen\Sdk\Modules\Customers\Options\MovementOptions;

$account = $slen->customers()->topUp('user_8421', '100.0000', new MovementOptions(note: 'Invoice 7781', idempotencyKey: 'invoice-7781:topup'));

echo $account->balance, PHP_EOL;
  • customers.topUp() adds credits. It creates the account of the credit type, and the customer, when they do not exist yet.

  • The note is written on the entry of the statement: an invoice, a reason.

  • The idempotency key names the movement: under the same key the credits move once — Idempotency keys.

The credit type, the note and the key of a movement are named arguments of MovementOptions, the last argument of the call. The other calls of this page take their options the same way — CustomerListOptions, AccountOptions, BlockOptions, EntryListOptions, PriceListOptions — and RequestOptions where they take nothing else.

PHP
// A service of your own application, paid for in the customer's credits.
$charged = $slen->customers()->charge('user_8421', '5.0000', new MovementOptions(note: 'PDF export', idempotencyKey: 'export-991:charge'));

echo $charged->balance, PHP_EOL;
  • customers.charge() takes credits for a service of your own application that Slen does not run: an export, a report, a seat. The statement shows it as a charge of its own, not as the spend of a model call.

  • A charge the account cannot cover is refused with insufficient_credits, unless the account may go below zero. A charge creates nothing: one for a customer Slen does not know is refused with customer_not_found.

  • customers.adjust() corrects a balance by hand, with a sign: a positive amount gives credits, a negative one takes them.

  • Credits are a decimal string with at most four places: '5', '5.25'. Any other value, zero, and a negative top-up or charge are refused before the request is sent, with the code invalid_request.

Read the balance and the statement

PHP
$customer = $slen->customers()->get('user_8421');

foreach ($customer->accounts as $held) {
    echo $held->creditType, ' ', $held->available, PHP_EOL;
}

customers.get() answers the customer with every account. An account has three amounts:

  • balance — what the account holds;

  • reserved — what runs under way hold on it;

  • available — balance less reserved: what a new run may take.

PHP
use Slen\Sdk\Modules\Customers\Options\EntryListOptions;

$statement = $slen->customers()->entries('user_8421', new EntryListOptions(limit: 2));

foreach ($statement->data as $entry) {
    echo $entry->entryType->value, ' ', $entry->credits, ' ', $entry->note, PHP_EOL;
}

customers.entries() reads the statement: every movement of the customer's credits, the newest first, one page at a time. It can be narrowed to one credit type and one kind of entry.

KindWritten byWhat it does
topupcustomers.topUp()Adds to the balance
chargecustomers.charge()Takes from the balance
adjustcustomers.adjust()Moves the balance either way
holdA model call of a runTakes the price from what is available, before the model is called
commitThe same call, when it succeededTakes the price from the balance
releaseThe same call, when it failedGives the price back to what is available

A page carries the cursor of the next one: pass it to the next call, and stop when it is null. customers.list() reads the customers of the project with their accounts the same way. It finds them by a part of the external user id, by status, by credit type, or by lowBalance — fewer credits available than the number you pass.

A run the customer pays for

Name the customer in the run, and the credit type when it is not default — Send a request.

  • When the project has a credit price for the operation, each model call of the run holds that price on the customer's account before the model is called. The credits are taken when the call succeeds and given back when it fails.

  • An operation without a credit price takes no credits. The prices are set in the console, and customers.prices() reads them.

  • A run the account cannot pay for is refused before the model is called, with insufficient_credits.

  • A priced run for a customer Slen does not know is refused with customer_not_found. Create the customer and top up their credits first.

PHP
use Slen\Sdk\Exceptions\InsufficientCreditsException;
use Slen\Sdk\Modules\Operations\Options\ExecuteOptions;

try {
    $slen->operations()->execute(
        'generate_email',
        ['messages' => [['role' => 'user', 'content' => 'Write a short follow-up email.']]],
        new ExecuteOptions(customer: 'user_9310', idempotencyKey: 'order-45:email'),
    );
} catch (InsufficientCreditsException $refused) {
    // The customer's account cannot pay for the run: nothing ran, nothing was held.
    echo $refused->errorCode, PHP_EOL;
}

insufficient_credits is one of the refusals of a spend, and both SDKs raise it as a type of that family — When spend is refused.

The settings of an account

customers.updateAccount() sets the two settings of one account:

  • whether the balance may go below zero — off by default;

  • the threshold under which a movement sends the webhook customer.balance_low — '0', the default, switches it off.

The call sets both at once: a setting you leave out goes back to its default. It creates the customer and the account when they are missing. A webhook of the low balance is sent once, when a movement takes the available credits below the threshold — Webhooks.

Block a customer

PHP
use Slen\Sdk\Modules\Customers\Options\BlockOptions;

// New runs for the customer are refused from now on; the balance can still be settled.
$blocked = $slen->customers()->block('user_8421', new BlockOptions(reason: 'chargeback'));

echo $blocked->status->value, ' ', $blocked->blockReason, PHP_EOL;
  • The new runs of a blocked customer are refused with customer_blocked. A run that was queued or waiting before the block is refused at its next model call that takes credits. Both SDKs raise the code as a refusal of a policy.

  • Top-ups, charges, adjustments and the statement stay open, so you can settle with the customer.

  • customers.unblock() lifts the block.

  • customers.delete() removes a customer with no history: no movement and no balance. A customer with history is refused with 409 and the code customer_has_history — block them instead.

Where to go next