AboutBlogContact
Web DevelopmentApril 6, 2026 6 min read 1306Updated: May 18, 2026

YooKassa (ЮKassa) Integration Guide for Node.js and TypeScript (2026)

AunimedaAunimeda
📋 Table of Contents

YooKassa (ЮKassa) Integration Guide for Node.js and TypeScript (2026)

YooKassa (formerly Yandex.Kassa) is the leading payment aggregator in Russia. One contract gives you: bank cards, SBP (Fast Payment System), SberPay, T-Pay, and cash terminals. This guide covers the full integration in Node.js/TypeScript, including 54-FZ fiscal receipt generation.


What YooKassa Covers

Payment Method Notes
Visa / Mastercard / Mir Russian-issued cards work without restrictions
SBP (Система быстрых платежей) Mandatory for most businesses since 2024
SberPay High conversion for Sber customers
T-Pay (Тинькофф) Popular among younger users
YooMoney wallet Digital wallet
Cash via terminals QIWI, Euroset, etc.

Setup

npm install yookassa
# or use the REST API directly - no SDK required

Get your credentials from the YooKassa merchant portal:

  • shopId - your merchant ID
  • secretKey - API secret key

Test environment: set shopId to 100500 and secretKey to test_<anything> for sandbox.


Basic Payment Flow

1. Your backend creates a payment → YooKassa returns payment object with confirmationUrl
2. Redirect user to confirmationUrl (or use embedded widget)
3. User pays
4. YooKassa sends webhook to your server
5. Your server verifies payment → fulfills order

Step 1: Create a Payment

// payment.service.ts
import axios from 'axios';
import { v4 as uuidv4 } from 'uuid';

const YOOKASSA_SHOP_ID = process.env.YOOKASSA_SHOP_ID!;
const YOOKASSA_SECRET_KEY = process.env.YOOKASSA_SECRET_KEY!;
const BASE_URL = 'https://api.yookassa.ru/v3';

const yooKassaClient = axios.create({
  baseURL: BASE_URL,
  auth: {
    username: YOOKASSA_SHOP_ID,
    password: YOOKASSA_SECRET_KEY,
  },
});

interface CreatePaymentOptions {
  amount: number;          // In rubles (e.g. 1500 = 1500 ₽)
  orderId: string;
  description: string;
  returnUrl: string;
  customerEmail?: string;
  customerPhone?: string;
  metadata?: Record<string, string>;
}

async function createPayment(options: CreatePaymentOptions) {
  const idempotenceKey = uuidv4(); // Unique per request - prevents duplicates on retry

  const response = await yooKassaClient.post(
    '/payments',
    {
      amount: {
        value: options.amount.toFixed(2),
        currency: 'RUB',
      },
      confirmation: {
        type: 'redirect',
        return_url: options.returnUrl,
      },
      capture: true, // Auto-capture payment (false = two-step authorization)
      description: options.description,
      metadata: {
        order_id: options.orderId,
        ...options.metadata,
      },
      receipt: options.customerEmail || options.customerPhone
        ? buildReceipt(options) // Required for 54-FZ
        : undefined,
    },
    {
      headers: { 'Idempotence-Key': idempotenceKey },
    }
  );

  return response.data as YooKassaPayment;
}

54-FZ Fiscal Receipts (Required for B2C)

If you accept payments from individuals (not companies), Russian law (54-FZ) requires sending a fiscal receipt. YooKassa can send it automatically:

function buildReceipt(options: CreatePaymentOptions) {
  return {
    customer: {
      email: options.customerEmail,
      phone: options.customerPhone, // Format: +79001234567
    },
    items: [
      {
        description: options.description,
        quantity: '1.00',
        amount: {
          value: options.amount.toFixed(2),
          currency: 'RUB',
        },
        vat_code: 1,          // 1 = НДС не применяется (most common for services)
        payment_mode: 'full_payment',
        payment_subject: 'service', // or 'commodity' for physical goods
      },
    ],
    tax_system_code: 2,       // 1=ОСН, 2=УСН доходы, 3=УСН доходы-расходы, etc.
  };
}

VAT codes:

  • 1 - НДС не облагается (most services, simplified tax system)
  • 2 - НДС 0%
  • 5 - НДС 20%

Step 2: Webhook Handler

// webhook.controller.ts
import { Request, Response } from 'express';
import crypto from 'crypto';

export async function handleYooKassaWebhook(req: Request, res: Response) {
  const event = req.body as YooKassaWebhookEvent;

  // YooKassa recommends IP whitelisting instead of signature verification
  // Allowed IPs: 185.71.76.0/27, 185.71.77.0/27, 77.75.153.0/25, etc.
  // Full list: https://yookassa.ru/developers/using-api/webhooks

  switch (event.event) {
    case 'payment.succeeded':
      await handlePaymentSucceeded(event.object);
      break;

    case 'payment.canceled':
      await handlePaymentCanceled(event.object);
      break;

    case 'refund.succeeded':
      await handleRefundSucceeded(event.object);
      break;
  }

  // Always return 200 - otherwise YooKassa retries for 24 hours
  res.status(200).json({ ok: true });
}

async function handlePaymentSucceeded(payment: YooKassaPayment) {
  const orderId = payment.metadata.order_id;

  await db.orders.update({
    where: { id: orderId },
    data: {
      status: 'paid',
      paidAt: new Date(),
      yookassaPaymentId: payment.id,
      paymentMethod: payment.payment_method.type,
    },
  });

  await fulfillOrder(orderId);
}

Verify Payment on Return URL

async function getPaymentStatus(paymentId: string) {
  const response = await yooKassaClient.get(`/payments/${paymentId}`);
  return response.data as YooKassaPayment;
}

// Return URL handler
app.get('/payment/return', async (req, res) => {
  const paymentId = req.query.paymentId as string;

  if (!paymentId) return res.redirect('/cart');

  const payment = await getPaymentStatus(paymentId);

  if (payment.status === 'succeeded') {
    res.render('success');
  } else if (payment.status === 'pending') {
    // Payment processing - show waiting screen, poll or wait for webhook
    res.render('processing', { paymentId });
  } else {
    res.render('failed');
  }
});

Refunds

async function createRefund(paymentId: string, amount?: number) {
  const payment = await getPaymentStatus(paymentId);

  const response = await yooKassaClient.post(
    '/refunds',
    {
      payment_id: paymentId,
      amount: {
        // Full refund if amount not specified
        value: amount ? amount.toFixed(2) : payment.amount.value,
        currency: 'RUB',
      },
    },
    {
      headers: { 'Idempotence-Key': uuidv4() },
    }
  );

  return response.data;
}

Split Payments (for Marketplaces)

Split payments let you receive a platform fee while routing the rest to the seller. Requires connecting sellers to your platform via YooKassa self-employed / marketplace functionality.

async function createSplitPayment(options: CreatePaymentOptions & { sellerId: string }) {
  const platformFee = options.amount * 0.10; // 10% platform commission
  const sellerAmount = options.amount - platformFee;

  const response = await yooKassaClient.post('/payments', {
    amount: { value: options.amount.toFixed(2), currency: 'RUB' },
    confirmation: { type: 'redirect', return_url: options.returnUrl },
    capture: true,
    description: options.description,
    transfers: [
      {
        account_id: options.sellerId,    // Seller's YooKassa account ID
        amount: {
          value: sellerAmount.toFixed(2),
          currency: 'RUB',
        },
        platform_fee_amount: {
          value: platformFee.toFixed(2),
          currency: 'RUB',
        },
      },
    ],
  }, {
    headers: { 'Idempotence-Key': uuidv4() },
  });

  return response.data;
}

TypeScript Types

interface YooKassaPayment {
  id: string;
  status: 'pending' | 'waiting_for_capture' | 'succeeded' | 'canceled';
  amount: { value: string; currency: string };
  description: string;
  payment_method: { type: 'bank_card' | 'sbp' | 'sberbank' | 'tinkoff_bank' };
  metadata: Record<string, string>;
  created_at: string;
  captured_at?: string;
  test: boolean;
}

interface YooKassaWebhookEvent {
  type: 'notification';
  event: 'payment.succeeded' | 'payment.canceled' | 'refund.succeeded';
  object: YooKassaPayment;
}

SBP (Fast Payment System) Specific Flow

SBP shows a QR code that users scan with their banking app:

async function createSBPPayment(options: CreatePaymentOptions) {
  return yooKassaClient.post('/payments', {
    amount: { value: options.amount.toFixed(2), currency: 'RUB' },
    payment_method_data: { type: 'sbp' },   // Force SBP method
    confirmation: { type: 'qr' },           // Get QR code
    capture: true,
    description: options.description,
  }, {
    headers: { 'Idempotence-Key': uuidv4() },
  });
  // response.data.confirmation.confirmation_url - QR code image URL
}

Common Issues

Issue Solution
Idempotence-Key already used Generate new UUID per retry
Webhook not received Check IP whitelist, respond 200 always
Receipt rejected Verify vat_code matches your tax system
SBP not showing Enable SBP in merchant portal settings
Payment stuck in pending Normal for SBP - wait up to 24h or cancel

Need help with payment integration? →


Aunimeda develops websites and web applications for businesses - corporate sites, e-commerce, portals, and custom platforms.

Contact us to discuss your web project. See also: Web Development, E-commerce Development

Read Also

How to Build an MVP in 2026: A Founder's Guide to Scope, Cost, and Speedaunimeda
Web Development

How to Build an MVP in 2026: A Founder's Guide to Scope, Cost, and Speed

What an MVP actually is (and isn't), how to scope it correctly, how much it costs in 2026, and how to choose the right development approach. Practical advice for founders and product teams.

Outsource Software Development to Kyrgyzstan: A Practical Guide for 2026aunimeda
Web Development

Outsource Software Development to Kyrgyzstan: A Practical Guide for 2026

Why businesses outsource software development to Kyrgyzstan in 2026, how to evaluate and hire a development team in Bishkek, and what the engagement process actually looks like.

SaaS Web App Development Guide 2026: Architecture, Stack, and Pricingaunimeda
Web Development

SaaS Web App Development Guide 2026: Architecture, Stack, and Pricing

How to build a SaaS web application in 2026: architecture decisions, technology stack, multi-tenancy, subscription billing, and realistic development costs. Practical guide from a development studio.

Need IT development for your business?

We build websites, mobile apps and AI solutions. Free consultation.

Web Development

Get Consultation All articles