Go
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).
Обработка ошибок
Все методы могут возвращать следующие ошибки:
| Ошибка | Когда |
|---|---|
*APIError | API вернул 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 # Интеграционный тест