Robokassa Payment System Integration
Robokassa is payment aggregator, working in Russian market since 2003. Supports bank cards, SBP, e-wallets (QIWI, YooMoney), terminals and installments. Often chosen for low entry threshold and simple connection without lengthy negotiations.
How It Works
Robokassa works by classic redirect scheme: site forms link with signature and sends user to Robokassa payment page. After payment Robokassa notifies server (ResultURL) and redirects customer (SuccessURL/FailURL).
Connection parameters: MrchLogin (store login), Password1 (for signing requests), Password2 (for verifying Robokassa notifications).
Building Payment Link
function buildRobokassaUrl(int $orderId, float $amount, string $description): string
{
$login = env('ROBOKASSA_LOGIN');
$pass1 = env('ROBOKASSA_PASS1');
$isTest = env('ROBOKASSA_TEST', false) ? 1 : 0;
// Signature: MD5(Login:OutSum:InvId:Password1)
$signature = md5("{$login}:{$amount}:{$orderId}:{$pass1}");
$params = http_build_query([
'MrchLogin' => $login,
'OutSum' => number_format($amount, 2, '.', ''),
'InvId' => $orderId,
'Desc' => $description,
'SignatureValue' => $signature,
'IsTest' => $isTest,
'Culture' => 'ru',
'Encoding' => 'utf-8',
'Email' => '', // customer email if known
]);
return 'https://auth.robokassa.ru/Merchant/Index.aspx?' . $params;
}
If using fiscalization, signature includes Receipt: MD5(Login:OutSum:InvId:Receipt:Password1) — order matters.
ResultURL Processing
ResultURL — server callback, not accessible to user. Status change happens here:
public function result(Request $request): Response
{
$outSum = $request->input('OutSum');
$invId = $request->input('InvId');
$received = strtolower($request->input('SignatureValue'));
// Check signature with Password2
$expected = strtolower(md5("{$outSum}:{$invId}:" . env('ROBOKASSA_PASS2')));
if (!hash_equals($expected, $received)) {
return response('bad sign', 400);
}
$order = Order::findOrFail($invId);
// Additionally check amount
if (abs((float)$outSum - $order->total) > 0.01) {
return response('amount mismatch', 400);
}
$order->update(['status' => 'paid']);
// Robokassa expects response strictly as "OK{InvId}"
return response("OK{$invId}");
}
If Robokassa doesn't get OK{InvId}, notification repeats several times. This why handler must be idempotent.
SuccessURL and FailURL
User lands on SuccessURL after successful payment. Can't change order status here — only display result. Status already set via ResultURL. Can additionally verify:
public function success(Request $request): View
{
$invId = $request->input('InvId');
$outSum = $request->input('OutSum');
$received = strtolower($request->input('SignatureValue'));
$expected = strtolower(md5("{$outSum}:{$invId}:" . env('ROBOKASSA_PASS1')));
// On SuccessURL signature calculated with Password1, not Password2
if (!hash_equals($expected, $received)) {
abort(403);
}
$order = Order::findOrFail($invId);
return view('payment.success', compact('order'));
}
Fiscalization
Receipt data passed in Receipt parameter (JSON, then URL-encode):
$receipt = json_encode([
'sno' => 'usn_income',
'items' => [
[
'name' => 'Product 1',
'quantity' => 1,
'sum' => 1500.00,
'tax' => 'none',
'payment_method' => 'full_payment',
'payment_object' => 'commodity',
],
],
]);
// In signature if Receipt present:
// MD5(Login:OutSum:InvId:urlencode(Receipt):Password1)
$signature = md5("{$login}:{$amount}:{$orderId}:" . urlencode($receipt) . ":{$pass1}");
Test Mode
In test mode (IsTest=1) payment passes without actual debit. Test cards listed in Robokassa docs. Switch to production — remove IsTest and replace keys.
New store activation takes up to 2 business days. For marketplaces and aggregators separate contract required.







