1. Go to this page and download the library: Download dominaite/merchant-sdk library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
Dominaite\DominaiteClient;
use Dominaite\Exception\CheckoutRefusedException;
use Dominaite\Exception\RateLimitException;
use Dominaite\Exception\TransportException;
$client = new DominaiteClient(getenv('DOMINAITE_KEY_ID'), getenv('DOMINAITE_SECRET'));
try {
$session = $client->createCheckoutSession([
'amount' => 2500, // minor units: 2500 = 25.00 EUR
'currency' => 'EUR',
'orderReference' => 'order-1042', // your own order id, shows up in your dashboard
'customer' => [
// Pass everything you already know - prefilled fields are hidden from the
// payer, so the checkout form stays short.
'firstName' => 'Ana',
'lastName' => 'Kirova',
'email' => '[email protected]',
],
'language' => 'bg', // widget UI language
'theme' => 'dark',
]);
} catch (CheckoutRefusedException $e) {
// Machine-readable: $e->getErrorCode() - see the exception docblock for the codes.
http_response_code(409);
exit('Payment unavailable: ' . $e->getErrorCode());
} catch (RateLimitException $e) {
// You are over the rate limit. Nothing is retried for you - back off first.
http_response_code(503);
header('Retry-After: ' . ($e->getRetryAfterSeconds() ?? 5));
exit('Payment temporarily unavailable');
} catch (TransportException $e) {
// Network blip or a 5xx - retry with $client->getLastIdempotencyKey(), never a fresh key.
http_response_code(503);
exit('Payment temporarily unavailable');
}
// Store $session['transactionId'] against your order, then render the widget:
Dominaite\DominaiteClient;
// The RAW body, before any parsing. Do not use $_POST and do not re-encode:
// the signature covers these exact bytes.
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
if (!DominaiteClient::verifyWebhook($payload, $signature, getenv('DOMINAITE_WEBHOOK_SECRET'))) {
http_response_code(400);
exit;
}
$event = json_decode($payload, true);
// Answer immediately, then do the work. Anything slow here eats into the delivery
// timeout and earns you a retry you did not need.
http_response_code(200);
if (!already_handled($event['id'])) { // dedupe on the delivery id
queue_fulfilment($event); // your own job queue
mark_handled($event['id']);
}
$session = null;
$key = null;
for ($attempt = 1; $attempt <= 3 && $session === null; $attempt++) {
if ($key !== null) {
$params['idempotencyKey'] = $key;
}
try {
$session = $client->createCheckoutSession($params);
} catch (TransportException $e) {
// Read the key here, in the catch - store it against your order before you
// sleep or hand off, because the next create() call overwrites it.
$key = $client->getLastIdempotencyKey();
if ($attempt === 3) {
throw $e;
}
sleep($attempt);
}
}
use Dominaite\Exception\ChargeException;
use Dominaite\Exception\RevokeException;
$session = $client->createCheckoutSession([
'amount' => 2500,
'currency' => 'EUR',
'orderReference' => 'sub-8817-first',
'saveCard' => true,
]);
// ... the payer completes the hosted checkout ...
$status = $client->getStatus($session['transactionId']);
$stored = $status['storedPaymentMethod'] ?? null; // absent until the payment is approved
if ($status['status'] === 'succeeded' && ($stored['status'] ?? null) === 'active') {
$db->saveCard($customerId, $stored['id']); // pm_...
}
// Later, off-session, no payer present:
try {
$charge = $client->chargePaymentMethod($paymentMethodId, [
'amount' => 2500,
'currency' => 'EUR',
'orderReference' => 'sub-8817-2026-10',
'description' => 'Monthly plan, October',
'idempotencyKey' => 'sub-8817-2026-10', // derive it from the billing period, never random per attempt
]);
switch ($charge['status']) {
case 'succeeded':
break;
case 'pending':
// Not terminal. Poll getStatus($charge['transactionId']), or wait for the webhook.
break;
case 'failed':
// HTTP 402 from the gateway, but not an exception: branch on the class, log the code.
// hard - give up on this card, ask the customer for another one
// soft_funds - insufficient funds, retry later (not in a loop)
// soft_sca_ (RevokeException $e) {
if ($e->getErrorCode() === 'MERCHANT_API_UNAVAILABLE') {
// 503: nothing changed, retry later.
} else {
// 502 UPSTREAM_CONTRACT_ERROR: the provider refused for good, nothing changed. Contact support with the id.
}
}