AboutBlogContact
DevelopmentApril 6, 2026 6 min read 139

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? →

Read Also

Web Vitals & Lighthouse 100: Practical Optimization Guide 2026aunimeda
Development

Web Vitals & Lighthouse 100: Practical Optimization Guide 2026

Achieving Lighthouse 100 on a real-world production Next.js app — not a blank page. Covers LCP, INP (replaced FID in 2024), CLS, TTFB, font optimization, image optimization, JS bundle analysis, and CSS critical path — with specific code changes.

Node.js vs Bun vs Deno in 2026: Runtime Comparison with Real Benchmarksaunimeda
Development

Node.js vs Bun vs Deno in 2026: Runtime Comparison with Real Benchmarks

Bun 1.x is production-stable. Deno 2.0 supports npm packages. Node.js 22 has native TypeScript. The runtime landscape changed. Here's what the numbers actually show and when each runtime makes sense for real projects.

Clean Architecture in Node.js: A Practical Guide Without the Academic Fluffaunimeda
Development

Clean Architecture in Node.js: A Practical Guide Without the Academic Fluff

Clean Architecture sounds great in theory. In practice, most implementations add complexity without benefit. This guide shows the pattern that actually works in Node.js — dependency inversion, use cases, and repository pattern with real, runnable code.

Need IT development for your business?

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

Get Consultation All articles