О насБлогКонтакты
Бизнес и продукт20 февраля 2015 г. 5 мин 256Обновлено: 18 мая 2026 г.

Как подключить ЮKassa (Яндекс.Касса) к PHP-сайту в 2015 году

AunimedaAunimeda
📋 Содержание

Как подключить ЮKassa (Яндекс.Касса) к PHP-сайту в 2015 году

Коротко: Получите shopId и scid в личном кабинете Яндекс.Кассы, создайте HTML-форму с параметрами заказа и подписью MD5, POST на https://money.yandex.ru/wd/api/payform. Обрабатывайте callback на /payment/notify - проверяйте MD5 подпись, меняйте статус заказа.


Что такое Яндекс.Касса в 2015 году

Яндекс.Касса (ныне ЮKassa) в 2015 - агрегатор платежей: принимала Яндекс.Деньги, банковские карты, QIWI, WebMoney, Сбербанк Онлайн, терминалы. Одна интеграция - все методы оплаты. Комиссия: 2.8-3.5% в зависимости от метода и оборота.


Параметры формы оплаты

<?php
// payment/YandexKassa.php

class YandexKassa {
    private string $shopId;
    private string $scid;
    private string $shopPassword;  // Пароль 1 или пароль 2

    public function __construct(string $shopId, string $scid, string $shopPassword) {
        $this->shopId       = $shopId;
        $this->scid         = $scid;
        $this->shopPassword = $shopPassword;
    }

    /**
     * Генерировать HTML-форму для перенаправления на оплату
     */
    public function createForm(array $order): string {
        $params = [
            'shopId'           => $this->shopId,
            'scid'             => $this->scid,
            'sum'              => number_format($order['total'], 2, '.', ''),
            'customerNumber'   => $order['user_id'],
            'orderNumber'      => $order['id'],
            'shopSuccessURL'   => 'https://myshop.ru/order/' . $order['id'] . '/success',
            'shopFailURL'      => 'https://myshop.ru/order/' . $order['id'] . '/fail',
            'shopSendEmailNotification' => 0,
            'cps_email'        => $order['email'] ?? '',  // Предзаполнить email покупателя
            'paymentType'      => '',                     // Пусто = показать все методы
        ];

        $html = '<form method="POST" action="https://money.yandex.ru/wd/api/payform" id="ykassa-form">' . "\n";
        foreach ($params as $name => $value) {
            $html .= '<input type="hidden" name="' . htmlspecialchars($name) . '" value="' . htmlspecialchars($value) . '">' . "\n";
        }
        $html .= '<button type="submit">Оплатить</button>' . "\n";
        $html .= '</form>' . "\n";

        // Автосабмит если форма показывается на промежуточной странице
        // $html .= '<script>document.getElementById("ykassa-form").submit();</script>';

        return $html;
    }

    /**
     * Проверить подпись уведомления от Яндекс.Кассы
     * Вызывается на URL /payment/notify
     */
    public function verifyNotification(array $post): bool {
        $required = ['action', 'orderSumAmount', 'orderSumCurrencyPaycash',
                     'orderSumBankPaycash', 'shopId', 'invoiceId', 'customerNumber'];

        foreach ($required as $field) {
            if (!isset($post[$field])) return false;
        }

        // Подпись: MD5 от конкатенации полей + пароль
        $signStr = implode(';', [
            $post['action'],
            $post['orderSumAmount'],
            $post['orderSumCurrencyPaycash'],
            $post['orderSumBankPaycash'],
            $post['shopId'],
            $post['invoiceId'],
            $post['customerNumber'],
            $this->shopPassword,
        ]);

        $expectedMd5 = strtoupper(md5($signStr));
        $receivedMd5 = strtoupper($post['md5'] ?? '');

        return hash_equals($expectedMd5, $receivedMd5);
    }
}

Обработчик уведомлений (Callback)

<?php
// public/payment/notify.php
// Яндекс.Касса вызывает этот URL после каждого платежа

require_once '../../bootstrap.php';

$ykassa  = new YandexKassa(YKASSA_SHOP_ID, YKASSA_SCID, YKASSA_PASSWORD);
$post    = $_POST;
$action  = $post['action'] ?? '';

// Яндекс.Касса посылает два типа уведомлений:
// checkOrder   - перед платежом (проверить что заказ существует)
// paymentAviso - после успешного платежа

if ($action === 'checkOrder') {
    $orderId = (int)($post['orderNumber'] ?? 0);
    $order   = OrderRepository::find($orderId);

    if (!$order) {
        echo buildResponse(100, $post['invoiceId'] ?? '', 'Order not found');
        exit;
    }

    // Проверяем что сумма совпадает
    $expectedAmount = number_format($order['total'], 2, '.', '');
    $receivedAmount = $post['orderSumAmount'] ?? '0';

    if (abs((float)$expectedAmount - (float)$receivedAmount) > 0.01) {
        echo buildResponse(100, $post['invoiceId'] ?? '', 'Amount mismatch');
        exit;
    }

    echo buildResponse(0, $post['invoiceId'] ?? '', 'OK');
    exit;
}

if ($action === 'paymentAviso') {
    // Проверяем подпись
    if (!$ykassa->verifyNotification($post)) {
        echo buildResponse(1, $post['invoiceId'] ?? '', 'Bad signature');
        exit;
    }

    $orderId = (int)($post['orderNumber'] ?? 0);
    $order   = OrderRepository::find($orderId);

    if ($order && $order['status'] === 'pending_payment') {
        OrderRepository::updateStatus($orderId, 'paid');
        PaymentRepository::record([
            'order_id'       => $orderId,
            'method'         => 'yandex_kassa',
            'transaction_id' => $post['invoiceId'],
            'amount'         => $post['orderSumAmount'],
            'payment_type'   => $post['paymentType'] ?? 'unknown',
            'paid_at'        => date('Y-m-d H:i:s'),
        ]);
        // Отправить письмо клиенту
        Mailer::sendOrderPaid($order);
    }

    echo buildResponse(0, $post['invoiceId'] ?? '', 'OK');
    exit;
}

echo buildResponse(200, '', 'Unknown action');

function buildResponse(int $code, string $invoiceId, string $message): string {
    // Яндекс.Касса ожидает XML-ответ строго в этом формате
    return '<?xml version="1.0" encoding="UTF-8"?>' . "\n"
         . '<' . ($_POST['action'] ?? 'checkOrder') . 'Response '
         . 'performedDatetime="' . date('c') . '" '
         . 'code="' . $code . '" '
         . 'invoiceId="' . htmlspecialchars($invoiceId) . '" '
         . 'message="' . htmlspecialchars($message) . '" '
         . 'shopId="' . YKASSA_SHOP_ID . '"/>';
}

Таблица платежей

CREATE TABLE payments (
    id             INT AUTO_INCREMENT PRIMARY KEY,
    order_id       INT NOT NULL,
    method         VARCHAR(50) DEFAULT 'yandex_kassa',
    payment_type   VARCHAR(50),  -- 'PC' = Яндекс.Деньги, 'AC' = банковская карта
    transaction_id VARCHAR(100), -- invoiceId из Яндекс.Кассы
    amount         DECIMAL(10,2) NOT NULL,
    currency       CHAR(3) DEFAULT 'RUB',
    status         ENUM('pending','completed','failed','refunded') DEFAULT 'pending',
    raw_request    TEXT,         -- Сохраняем POST данные от ЯК
    paid_at        DATETIME,
    created_at     DATETIME DEFAULT NOW(),
    
    INDEX idx_order_id (order_id),
    INDEX idx_transaction (transaction_id)
) ENGINE=InnoDB;

Типы оплаты (paymentType) в 2015

Код Метод
PC Яндекс.Деньги
AC Банковская карта
MC Мобильный банк
WM WebMoney
SB Сбербанк Онлайн
MP Мобильный платёж (SMS)
AB Альфа-Клик
MA MasterPass
PB Промсвязьбанк
QW QIWI
GP Терминалы (Contact/Svyaznoy)

Тестирование без реальных денег

// В личном кабинете Яндекс.Кассы включить тестовый режим
// Тестовые данные карты:
// Номер: 5555555555554444
// Срок: любой будущий
// CVV: 123
// 3D Secure пароль: 12345678

// Логировать все уведомления в тестовом режиме
file_put_contents(
    '/var/log/ykassa_callbacks.log',
    date('Y-m-d H:i:s') . ' ACTION:' . ($_POST['action'] ?? '') . 
    ' ORDER:' . ($_POST['orderNumber'] ?? '') .
    ' AMOUNT:' . ($_POST['orderSumAmount'] ?? '') . "\n" .
    print_r($_POST, true) . "\n---\n",
    FILE_APPEND
);

Частые ошибки интеграции

Код 1 (bad signature): Убедитесь что используете пароль магазина (не пароль от личного кабинета). В настройках: «Мои магазины» → «Показать пароли».

Яндекс.Касса не вызывает callback: URL должен быть доступен извне. На localhost не работает - используйте ngrok для локального тестирования.

Код 100 (order not found): orderNumber в уведомлении - строка, а в базе - INT. Всегда приводить к нужному типу при сравнении.

Читайте также

Apple Pay и Google Pay в веб-приложении: что было реально в 2016 году (Россия)aunimeda
Бизнес и продукт

Apple Pay и Google Pay в веб-приложении: что было реально в 2016 году (Россия)

Apple Pay Web появился в октябре 2016 (Safari 10, macOS Sierra). Google Pay Web (Android Pay) - в 2016 работал через Payment Request API. Оба в России требовали поддержки со стороны банка-эквайера. Реальный статус поддержки, код интеграции Payment Request API, что реально работало.

Первый SaaS из Самары в 2013 году: от идеи до первого платящего клиента за 4 месяцаaunimeda
Бизнес и продукт

Первый SaaS из Самары в 2013 году: от идеи до первого платящего клиента за 4 месяца

2013 год. SaaS как бизнес-модель в России только начинал формироваться. Мы решили сделать сервис для управления клиентскими заявками для небольших сервисных компаний. Вот хронология: PHP 5.4, CodeIgniter, Bootstrap 2 и как мы нашли первого платящего клиента буквально на улице.

IT аутсорсинг в Кыргызстан для российских компаний: как это работает в 2026aunimeda
Веб-разработка

IT аутсорсинг в Кыргызстан для российских компаний: как это работает в 2026

Почему российские компании всё чаще заказывают разработку у кыргызских студий: ценовое преимущество, общий язык, часовой пояс. Как организовать работу с командой из Бишкека.

Нужна IT-разработка для вашего бизнеса?

Разрабатываем сайты, мобильные приложения и AI-решения для бизнеса в России. Бесплатная консультация.

Получить консультацию Все статьи