Paylow Docs ← К документации

Быстрый старт интеграции

Подписывайте запросы ed25519 своим ключом. Ниже готовые примеры на шести языках.

Проверка без ключей. Откройте документацию, выберите сервер «Песочница» и нажмите Test Request. Запросы подписываются ключом мерчанта на сервере, ключи вводить не нужно; в ответе возвращаются действующие nspkID и ссылки оплаты. Ниже описана подпись запросов своим ключом для продакшена.

Аутентификация и подпись

Каждый непубличный запрос подписывается ключом ed25519. Заголовок Authorization содержит JSON-объект:

{
  "partnerID": "<ваш partnerID>",
  "key": "ssh-ed25519 AAAA... (ваш публичный ключ)",
  "sign": "<base64 ed25519-подписи>"
}

Ваши учётные данные

ПараметрЧто это
PARTNER_IDВаш partnerID (например 4payXXXX-....).
PUBLIC_KEYСтрока ssh-ed25519 AAAA..., которую вы передали Paylow.
SEED_B64Ваш приватный ключ как base64 32-байтового seed ed25519.
Замените значения-плейсхолдеры на свои. Если приватный ключ в формате файла OpenSSH (-----BEGIN OPENSSH PRIVATE KEY-----), извлеките из него 32-байтовый seed. Готовый разбор есть в локальном тест-клиенте examples/test_client.py (он принимает и base64-seed, и файл OpenSSH).

Примеры: создание QR (POST /qr)

/*
 * Paylow SBP QR & Card API. Подпись запроса на Node.js.
 * Установка: npm i tweetnacl
 * Подпись ed25519; для POST подписывается тело; результат в base64.
 */
const https = require("https");
const nacl = require("tweetnacl");

// --- Ваши учётные данные ---
const PARTNER_ID = "<ВАШ partnerID>";
const PUBLIC_KEY = "ssh-ed25519 AAAA... (ВАШ публичный ключ)";
const SEED_B64   = "<ВАШ приватный ключ: base64 32-байтового seed>";

const seed = Uint8Array.from(Buffer.from(SEED_B64, "base64")); // 32 байта
const kp = nacl.sign.keyPair.fromSeed(seed);

// Тело обязано содержать partnerID; для /qr также partnerqrID === partnerID.
const body = JSON.stringify({
  amount: 100, currency: "RUB", qrType: "QRDynamic",
  qrDescription: "QR для заказа 126", order: "order-126",
  callbackUrl: "https://partner.example/callback",
  partnerID: PARTNER_ID, partnerqrID: PARTNER_ID,
});

const sign = Buffer.from(nacl.sign.detached(Buffer.from(body, "utf8"), kp.secretKey)).toString("base64");
const authorization = JSON.stringify({ partnerID: PARTNER_ID, key: PUBLIC_KEY, sign });

const req = https.request({
  method: "POST", host: "qr.paylow.online", path: "/api/qr",
  headers: { "Authorization": authorization, "Content-Type": "application/json" },
}, (res) => { let d = ""; res.on("data", c => d += c); res.on("end", () => console.log("HTTP", res.statusCode, d)); });
req.write(body); req.end();
"""
Paylow SBP QR & Card API. Подпись запроса на Python.
Установка: pip install cryptography requests
"""
import base64, json, requests
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey

# --- Ваши учётные данные ---
PARTNER_ID = "<ВАШ partnerID>"
PUBLIC_KEY = "ssh-ed25519 AAAA... (ВАШ публичный ключ)"
SEED_B64   = "<ВАШ приватный ключ: base64 32-байтового seed>"

key = Ed25519PrivateKey.from_private_bytes(base64.b64decode(SEED_B64))  # 32 байта

# Тело обязано содержать partnerID; для /qr также partnerqrID == partnerID.
body = json.dumps({
    "amount": 100, "currency": "RUB", "qrType": "QRDynamic",
    "qrDescription": "QR для заказа 126", "order": "order-126",
    "callbackUrl": "https://partner.example/callback",
    "partnerID": PARTNER_ID, "partnerqrID": PARTNER_ID,
}, separators=(",", ":"))

sign = base64.b64encode(key.sign(body.encode("utf-8"))).decode("ascii")
authorization = json.dumps({"partnerID": PARTNER_ID, "key": PUBLIC_KEY, "sign": sign})

resp = requests.post("https://qr.paylow.online/api/qr",
                     data=body.encode("utf-8"),
                     headers={"Authorization": authorization, "Content-Type": "application/json"},
                     timeout=20)
print("HTTP", resp.status_code, resp.text)
// Paylow SBP QR & Card API. Подпись запроса на Go (только stdlib).
package main

import (
	"crypto/ed25519"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
)

// --- Ваши учётные данные ---
const partnerID = "<ВАШ partnerID>"
const publicKey = "ssh-ed25519 AAAA... (ВАШ публичный ключ)"
const seedB64 = "<ВАШ приватный ключ: base64 32-байтового seed>"

func main() {
	seed, _ := base64.StdEncoding.DecodeString(seedB64) // 32 байта
	priv := ed25519.NewKeyFromSeed(seed)

	// Тело обязано содержать partnerID; для /qr также partnerqrID == partnerID.
	body := `{"amount":100,"currency":"RUB","qrType":"QRDynamic","qrDescription":"QR для заказа 126","order":"order-126","callbackUrl":"https://partner.example/callback","partnerID":"` + partnerID + `","partnerqrID":"` + partnerID + `"}`

	sign := base64.StdEncoding.EncodeToString(ed25519.Sign(priv, []byte(body)))
	auth, _ := json.Marshal(map[string]string{"partnerID": partnerID, "key": publicKey, "sign": sign})

	req, _ := http.NewRequest("POST", "https://qr.paylow.online/api/qr", strings.NewReader(body))
	req.Header.Set("Authorization", string(auth))
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil { fmt.Println("Ошибка:", err); return }
	defer resp.Body.Close()
	b, _ := io.ReadAll(resp.Body)
	fmt.Println("HTTP", resp.StatusCode, string(b))
}
// Paylow SBP QR & Card API. Подпись запроса на Rust.
// Cargo.toml:
//   ed25519-dalek = "2"
//   base64 = "0.22"
//   reqwest = { version = "0.12", features = ["blocking"] }
use base64::Engine;
use ed25519_dalek::{Signer, SigningKey};

// --- Ваши учётные данные ---
const PARTNER_ID: &str = "<ВАШ partnerID>";
const PUBLIC_KEY: &str = "ssh-ed25519 AAAA... (ВАШ публичный ключ)";
const SEED_B64: &str   = "<ВАШ приватный ключ: base64 32-байтового seed>";

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let seed_vec = base64::engine::general_purpose::STANDARD.decode(SEED_B64)?;
    let seed: [u8; 32] = seed_vec.as_slice().try_into()?;
    let signing = SigningKey::from_bytes(&seed);

    // Тело обязано содержать partnerID; для /qr также partnerqrID == partnerID.
    let body = format!(
        r#"{{"amount":100,"currency":"RUB","qrType":"QRDynamic","qrDescription":"QR для заказа 126","order":"order-126","callbackUrl":"https://partner.example/callback","partnerID":"{p}","partnerqrID":"{p}"}}"#,
        p = PARTNER_ID
    );

    let sig = signing.sign(body.as_bytes());
    let sign = base64::engine::general_purpose::STANDARD.encode(sig.to_bytes());
    let authorization = format!(r#"{{"partnerID":"{}","key":"{}","sign":"{}"}}"#, PARTNER_ID, PUBLIC_KEY, sign);

    let client = reqwest::blocking::Client::new();
    let resp = client.post("https://qr.paylow.online/api/qr")
        .header("Authorization", authorization)
        .header("Content-Type", "application/json")
        .body(body).send()?;
    println!("HTTP {} {}", resp.status(), resp.text()?);
    Ok(())
}
// Paylow SBP QR & Card API. Подпись запроса на Java (11+).
// Зависимость: BouncyCastle (org.bouncycastle:bcprov-jdk18on:1.78+).
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;

public class SignQr {
    // --- Ваши учётные данные ---
    static final String PARTNER_ID = "<ВАШ partnerID>";
    static final String PUBLIC_KEY = "ssh-ed25519 AAAA... (ВАШ публичный ключ)";
    static final String SEED_B64   = "<ВАШ приватный ключ: base64 32-байтового seed>";

    public static void main(String[] args) throws Exception {
        byte[] seed = Base64.getDecoder().decode(SEED_B64); // 32 байта
        Ed25519PrivateKeyParameters priv = new Ed25519PrivateKeyParameters(seed, 0);

        // Тело обязано содержать partnerID; для /qr также partnerqrID == partnerID.
        String body = String.format(
            "{\"amount\":100,\"currency\":\"RUB\",\"qrType\":\"QRDynamic\","
          + "\"qrDescription\":\"QR для заказа 126\",\"order\":\"order-126\","
          + "\"callbackUrl\":\"https://partner.example/callback\","
          + "\"partnerID\":\"%s\",\"partnerqrID\":\"%s\"}", PARTNER_ID, PARTNER_ID);
        byte[] msg = body.getBytes(StandardCharsets.UTF_8);

        Ed25519Signer signer = new Ed25519Signer();
        signer.init(true, priv);
        signer.update(msg, 0, msg.length);
        String sign = Base64.getEncoder().encodeToString(signer.generateSignature());

        String authorization = String.format(
            "{\"partnerID\":\"%s\",\"key\":\"%s\",\"sign\":\"%s\"}", PARTNER_ID, PUBLIC_KEY, sign);

        HttpRequest req = HttpRequest.newBuilder()
            .uri(URI.create("https://qr.paylow.online/api/qr"))
            .header("Authorization", authorization)
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body)).build();
        HttpResponse<String> resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
        System.out.println("HTTP " + resp.statusCode() + " " + resp.body());
    }
}
// Paylow SBP QR & Card API. Подпись запроса на C# (.NET 6+).
// NuGet: dotnet add package BouncyCastle.Cryptography
using System;
using System.Net.Http;
using System.Text;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;

class SignQr
{
    // --- Ваши учётные данные ---
    const string PartnerId = "<ВАШ partnerID>";
    const string PublicKey = "ssh-ed25519 AAAA... (ВАШ публичный ключ)";
    const string SeedB64   = "<ВАШ приватный ключ: base64 32-байтового seed>";

    static async System.Threading.Tasks.Task Main()
    {
        byte[] seed = Convert.FromBase64String(SeedB64); // 32 байта
        var priv = new Ed25519PrivateKeyParameters(seed, 0);

        // Тело обязано содержать partnerID; для /qr также partnerqrID == partnerID.
        string body = $"{{\"amount\":100,\"currency\":\"RUB\",\"qrType\":\"QRDynamic\","
            + "\"qrDescription\":\"QR для заказа 126\",\"order\":\"order-126\","
            + "\"callbackUrl\":\"https://partner.example/callback\","
            + $"\"partnerID\":\"{PartnerId}\",\"partnerqrID\":\"{PartnerId}\"}}";
        byte[] msg = Encoding.UTF8.GetBytes(body);

        var signer = new Ed25519Signer();
        signer.Init(true, priv);
        signer.BlockUpdate(msg, 0, msg.Length);
        string sign = Convert.ToBase64String(signer.GenerateSignature());

        string authorization =
            $"{{\"partnerID\":\"{PartnerId}\",\"key\":\"{PublicKey}\",\"sign\":\"{sign}\"}}";

        using var client = new HttpClient();
        var content = new StringContent(body, Encoding.UTF8, "application/json");
        client.DefaultRequestHeaders.Add("Authorization", authorization);
        var resp = await client.PostAsync("https://qr.paylow.online/api/qr", content);
        Console.WriteLine($"HTTP {(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}");
    }
}

Проверка статуса и другие запросы (GET)

Для GET/DELETE подпись считается по полному URL в нижнем регистре. Пример (Python): статус динамического QR по nspkID.

url = f"https://qr.paylow.online/api/qr/dynamic/{PARTNER_ID}/{nspkID}"
sign = base64.b64encode(key.sign(url.lower().encode())).decode()
auth = json.dumps({"partnerID": PARTNER_ID, "key": PUBLIC_KEY, "sign": sign})
requests.get(url, headers={"Authorization": auth})

Подписки и списания по привязанной карте

Для первой оплаты по подписке создайте карточный платёж через POST /card с полем binding: true. После успешной оплаты сохраните bindingID из ответа, статуса платежа или webhook.

{
  "amount": 100,
  "currency": "RUB",
  "orderID": "sub-first-001",
  "description": "Первый платёж по подписке",
  "callbackUrl": "https://partner.example/card-callback",
  "returnUrl": "https://partner.example/return",
  "binding": true,
  "partnerID": "<ваш partnerID>"
}

Для следующего регулярного списания создайте новый платёж тем же методом POST /card, но передайте сохранённый bindingID и новый orderID.

{
  "amount": 100,
  "currency": "RUB",
  "orderID": "sub-renewal-002",
  "description": "Регулярное списание по подписке",
  "callbackUrl": "https://partner.example/card-callback",
  "binding": true,
  "bindingID": "<bindingID сохранённой карты>",
  "partnerID": "<ваш partnerID>"
}

Локальный тест-клиент

Проверить интеграцию можно консольным клиентом examples/test_client.py. Он подписывает запросы вашим ключом локально (ключ никуда не отправляется) и обращается к реальному API:

# переменные окружения (или vn.config.json рядом со скриптом)
set VN_PARTNER_ID=<ВАШ partnerID>
set VN_PUBLIC_KEY=ssh-ed25519 AAAA...
set VN_PRIVATE_SEED=<base64 seed>

python examples/test_client.py partner
python examples/test_client.py create-qr --amount 100 --callback https://partner.example/cb
python examples/test_client.py qr-status <nspkID>
python examples/test_client.py create-card --amount 100 --callback https://partner.example/cb
python examples/test_client.py create-card --amount 100 --callback https://partner.example/cb --binding
python examples/test_client.py create-card --amount 100 --callback https://partner.example/cb --binding-id <bindingID>

← Вернуться к документации  ·  Просмотрщик webhook →