Payin H2H

Go

Go SDK для интеграции с Merchant Payment API.

Go

Go SDK для интеграции с Merchant Payment API.

Требования

  • Go >= 1.21

Установка

go get github.com/unitewt/sdk-payin/go@latest

Модуль расположен в подкаталоге go/ монорепозитория. Go toolchain скачает его автоматически.

Генерация ключей ed25519

Перед началом работы необходимо сгенерировать пару ключей и передать публичный ключ в систему uniteplat:

package main

import (
    "crypto/ed25519"
    "crypto/rand"
    "encoding/base64"
    "fmt"
)

func main() {
    pub, priv, _ := ed25519.GenerateKey(rand.Reader)
    fmt.Println("Public key (передать в uniteplat):", base64.StdEncoding.EncodeToString(pub))
    fmt.Println("Private key (хранить у себя):", base64.StdEncoding.EncodeToString(priv))
}

Или используйте готовый скрипт:

go run examples/generate_keys/main.go

Быстрый старт

Инициализация клиента

import (
    "encoding/base64"
    payin "github.com/unitewt/sdk-payin/go"
)

privKey, _ := base64.StdEncoding.DecodeString("ваш-приватный-ключ-в-base64")
serverPubKey, _ := base64.StdEncoding.DecodeString("публичный-ключ-сервера-в-base64")

cfg, err := payin.NewConfig(
    "https://merchant.uniteplat.org",
    "ваш-key-id",
    privKey,
    payin.WithServerPublicKey(serverPubKey), // опционально, для верификации ответов
)
if err != nil {
    log.Fatal(err)
}

client := payin.NewClient(cfg)

Доступные суммы

Запрос доступных сумм встречных предложений для заданной валюты и метода оплаты:

available, err := client.GetAvailableAmounts(&payin.AvailableAmountsRequest{
    Sum:           1000,
    Currency:      payin.CurrencyRUB,
    PaymentMethod: payin.PaymentMethodCard,
    SumDriftUp:    500, // опционально; допустимое отклонение вверх
    SumDriftDown:  100, // опционально; допустимое отклонение вниз
})

fmt.Println(available.AvailableAmounts) // []float64 доступных сумм

Создание платежа (синхронный режим)

Реквизиты возвращаются сразу в ответе:

resp, err := client.NewPayment(&payin.NewPaymentRequest{
    Sum:              100.50,
    Currency:         payin.CurrencyRUB,
    PaymentMethod:    payin.PaymentMethodCard, // или payin.PaymentMethodSBP
    CustomerAccount:  "user_123",
    StatusWebhookURL: "https://your-site.com/webhook/status", // опционально
    PayerBank:        "100000000111",                         // опционально; обязательно, если включено в настройках мерчанта
    RedirectURL:      "https://your-site.com/return",         // опционально; пользователь может не вернуться — не полагайтесь на redirect
    Email:            "payer@example.com",                    // опционально
    UserCreatedAt:    "2026-01-15T10:00:00Z",                 // опционально; RFC3339
})

fmt.Println(resp.OrderID)          // ID заказа для дальнейших операций
fmt.Println(resp.PayeeAccount)     // Номер карты/счёта для оплаты
fmt.Println(resp.PayeeAccountBank) // Название банка (может быть пустым)
fmt.Println(resp.PayeeName)        // Имя получателя
fmt.Println(resp.Sum)              // Сумма к оплате

Поле paymentMethod обязательно (card или sbp). payeeAccountBank и deeplink могут отсутствовать.

Создание платежа (асинхронный режим)

Реквизиты придут на CredentialsWebhookURL после назначения:

resp, err := client.NewPaymentAsync(&payin.NewAsyncPaymentRequest{
    Sum:                   100.50,
    Currency:              payin.CurrencyRUB,
    PaymentMethod:         payin.PaymentMethodSBP,
    CustomerAccount:       "user_123",
    CredentialsWebhookURL: "https://your-site.com/webhook/credentials", // обязательно
    StatusWebhookURL:      "https://your-site.com/webhook/status",
    Email:                 "payer@example.com",    // опционально
    UserCreatedAt:         "2026-01-15T10:00:00Z", // опционально; RFC3339
})

fmt.Println(resp.OrderID) // Показать пользователю страницу ожидания

Подтверждение оплаты

Вызывается после того, как пользователь сообщает, что совершил перевод:

resp, err := client.Confirm("abc123")

Отмена платежа

resp, err := client.Cancel("abc123")

Отменить платёж можно, только пока он ожидает подтверждения оплаты пользователем либо находится на стадии арбитража (включая receiptRequired). Финальные статусы (doneSuccess, doneFail, arbitrageCancelledByMerchant) отменить нельзя.

Получение статуса

status, err := client.GetStatus("abc123")

fmt.Println(status.Status) // "pending", "doneSuccess", etc.

if status.Deeplink != nil {
    // Ссылка-диплинк для оплаты (если настроена у мерчанта)
    fmt.Println(*status.Deeplink)
}

if status.IsSuccessful() {
    // платёж завершён успешно
}

if status.RequiresReceipt() {
    // необходимо прикрепить чек
}

if status.IsArbitrageCancelledByMerchant() {
    // платёж отменён мерчантом в ходе арбитража
}

if status.IsDone() {
    // платёж в финальном статусе (doneSuccess, doneFail или arbitrageCancelledByMerchant)
}

Прикрепление чека

Требуется при статусе receiptRequired:

resp, err := client.AttachReceipt("abc123", "/path/to/receipt.pdf")

// Несколько файлов
resp, err := client.AttachReceipt("abc123", "/path/to/receipt1.pdf", "/path/to/receipt2.jpg")

Обработка вебхуков

Credentials webhook (credentialsWebhookUrl)

import payin "github.com/unitewt/sdk-payin/go"

verifier, err := payin.NewWebhookVerifier(serverPublicKey)
if err != nil {
    log.Fatal(err)
}

// В HTTP-хендлере:
body, _ := io.ReadAll(r.Body)
signature := r.Header.Get("X-Signature")

credentials, err := verifier.VerifyCredentialsWebhook(body, signature)
if err != nil {
    w.WriteHeader(http.StatusForbidden)
    return
}

fmt.Println(credentials.OrderID)
fmt.Println(credentials.PayeeAccount)
fmt.Println(credentials.PayeeAccountBank)
fmt.Println(credentials.PayeeName)
if credentials.Deeplink != nil {
    fmt.Println(*credentials.Deeplink) // Ссылка-диплинк, если настроена у мерчанта
}

w.WriteHeader(http.StatusOK)

Status webhook (statusWebhookUrl)

body, _ := io.ReadAll(r.Body)
signature := r.Header.Get("X-Signature")

status, err := verifier.VerifyStatusWebhook(body, signature)
if err != nil {
    w.WriteHeader(http.StatusForbidden)
    return
}

switch status.Status {
case payin.PaymentStatusDoneSuccess:
    handleSuccess(status.OrderID)
case payin.PaymentStatusDoneFail:
    handleFailure(status.OrderID)
case payin.PaymentStatusReceiptRequired:
    handleReceiptRequired(status.OrderID)
case payin.PaymentStatusArbitrageCancelledByMerchant:
    handleArbitrageCancelled(status.OrderID)
}

w.WriteHeader(http.StatusOK)

StatusPayload имеет хелперы IsSuccessful(), RequiresReceipt() и IsArbitrageCancelledByMerchant(). Метод IsDone() доступен только на StatusResponse (результат GetStatus).


Обработка ошибок

Все методы могут возвращать следующие ошибки:

ОшибкаКогда
*APIErrorAPI вернул success=false или неожиданный HTTP-статус
*SignatureErrorНе прошла верификация подписи сервера или вебхука
resp, err := client.NewPayment(&payin.NewPaymentRequest{
    Sum:             100.50,
    Currency:        payin.CurrencyRUB,
    PaymentMethod:   payin.PaymentMethodCard,
    CustomerAccount: "user_123",
})

if err != nil {
    var apiErr *payin.APIError
    var sigErr *payin.SignatureError

    switch {
    case errors.As(err, &apiErr):
        // Ошибка API: apiErr.Message, apiErr.OrderID, apiErr.Code, apiErr.ErrorCode
        switch {
        case apiErr.IsRateLimited():
            // HTTP 429 — превышен лимит запросов; повторите с backoff
        case apiErr.IsForbidden():
            // HTTP 403 — мерчант недоступен (удалён/выключен/трафик off)
        case apiErr.IsNotFound():
            // HTTP 404 — платёж не найден
        }
    case errors.As(err, &sigErr):
        // Проблема с подписью
    default:
        // Другая ошибка (сеть, таймаут и т.д.)
    }
}

apiErr.ErrorCode принимает значения: badRequest (400; в т.ч. не передан обязательный payerBank), forbidden (403), notFound (404), rateLimited (429), serverError (5xx), unknown.


Структура пакета

go/
├── client.go    # Главный клиент
├── config.go    # Параметры подключения
├── signer.go    # Подпись/верификация ed25519
├── http.go      # HTTP-клиент
├── models.go    # Типы запросов/ответов и перечисления
├── errors.go    # Типы ошибок
├── webhook.go   # Верификация вебхуков
└── examples/
    ├── generate_keys/main.go     # Генерация ключей
    └── integration_test/main.go  # Интеграционный тест
Copyright © 2026