From 752cf00858e557d862b27cb709ee77eba4719029 Mon Sep 17 00:00:00 2001 From: John Doe Date: Thu, 23 Jun 2022 17:46:19 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D1=8B=D0=B9=20=D1=80=D0=B5?= =?UTF-8?q?=D0=BB=D0=B8=D0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 42 ++++++++++++++ billMetadata.go | 124 ++++++++++++++++++++++++++++++++++++++++ createOplataURL.go | 70 +++++++++++++++++++++++ go.mod | 17 ++++++ go.sum | 48 ++++++++++++++++ qiwip2p.go | 137 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 438 insertions(+) create mode 100644 README.md create mode 100644 billMetadata.go create mode 100644 createOplataURL.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 qiwip2p.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..7865d9f --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# Qiwi P2P-счета + +Библиотека для работы с Qiwi P2P-счетами. Поддерживает создание ссылок для создания счёта на стороне клиента и непосредственную работу с API P2P-счетов. + +Документация P2P-счетов: https://developer.qiwi.com/ru/p2p-payments/ + +Документация этой библиотеки: https://pkg.go.dev/github.com/nickname76/qiwip2p + +## Пример использования + +```Go +package main + +import ( + "log" + "time" + + "github.com/nickname76lib/qiwip2p" +) + +func main() { + api := qiwip2p.NewAPI("PUBLIC_KEY", "SECRET_KEY") + bill, err := api.CreateBill("test12345", &qiwip2p.BillMetadata{ + Comment: "Test Comment", + Amount: &qiwip2p.BillMetadataAmount{ + Value: "99.99", + Currency: qiwip2p.CurrencyRUB, + }, + CustomFields: map[string]string{ + qiwip2p.CustomFieldOptionThemeCode: "THEME_CODE", + "test_custom_field": "123456", + }, + ExpirationDateTime: qiwip2p.FormatBillMetadataDateTime(time.Now().Add(time.Hour)), + }) + if err != nil { + log.Fatalf("Ошибка: %w", err) + } + + log.Panicln(bill) +} + +``` \ No newline at end of file diff --git a/billMetadata.go b/billMetadata.go new file mode 100644 index 0000000..69628f5 --- /dev/null +++ b/billMetadata.go @@ -0,0 +1,124 @@ +package qiwip2p + +import "time" + +// Идентификатор счёта +type BillID string + +// Идентификатор в сервисе приёма платежей +type SiteID string + +// Данные о счёте +type BillMetadata struct { + // Ваш идентификатор в сервисе приема платежей для физических лиц p2p.qiwi.com. + // НЕ ИСПОЛЬЗОВАТЬ С CreateBill(). + SiteID SiteID `json:"siteId,omitempty"` + // Идентификатор выставляемого счёта в вашей системе. Может быть длиной <=200. + // Он должен быть уникальным, и генерироваться на вашей стороне любым способом. + // Идентификатором может быть любая уникальная последовательность букв или цифр. + // Также разрешено использование символа подчеркивания (_) и дефиса (-). + // НЕ ИСПОЛЬЗОВАТЬ С CreateBill(). + BillID BillID `json:"billId,omitempty"` + // Ссылка на созданную форму. Перенаправьте пользователя + // по этой ссылке для оплаты счета или используйте + // библиотеку Popup, чтобы открыть форму во всплывающем окне. + // https://developer.qiwi.com/ru/p2p-payments/#popup + // НЕ ИСПОЛЬЗОВАТЬ С CreateBill(). + PayURL string `json:"payUrl,omitempty"` + // Идентификаторы пользователя. + Customer *CustomerIdentificators `json:"customer,omitempty"` + // Комментарий к счету. + Comment string `json:"comment,omitempty"` + // Информация о сумме счета. + Amount *BillMetadataAmount `json:"amount,omitempty"` + // Информация о статусе счета. + // НЕ ИСПОЛЬЗОВАТЬ С CreateBill(). + Status *BillMetadataStatus `json:"status,omitempty"` + // Объект строковых дополнительных параметров. Возможные элементы: paySourcesFilter, themeCode + // Может также включать поля (см. константы этого модуля): + // CustomFieldOptionPaySourcesFilter (с возможными значениями: PaySourceQiwi, PaySourceCard, PaySourceQiwiAndCard) + // и CustomFieldOptionThemeCode. + CustomFields map[string]string `json:"customFields,omitempty"` + // Системная дата создания счета. Формат даты: `ГГГГ-ММ-ДДTчч:мм:сс±чч:мм`. + // НЕ ИСПОЛЬЗОВАТЬ С CreateBill(). + CreationDateTime string `json:"creationDateTime,omitempty"` + // Срок действия созданной формы для перевода. Формат даты: `ГГГГ-ММ-ДДTчч:мм:сс±чч:мм`. + ExpirationDateTime string `json:"expirationDateTime,omitempty"` +} + +// Идентификаторы пользователя +type CustomerIdentificators struct { + // Номер телефона пользователя (в международном формате). + Phone string `json:"phone,omitempty"` + // E-mail пользователя. + Email string `json:"email,omitempty"` + // Идентификатор пользователя в вашей системе. + Account string `json:"account,omitempty"` +} + +// Валюта счёта +type Currency string + +const ( + // Российский рубль + CurrencyRUB Currency = "RUB" + // Казахстанский тенге + CurrencyKZT Currency = "KZT" +) + +// Сумма счёта +type BillMetadataAmount struct { + // Сумма счета, округленная до 2 знаков после запятой в меньшую сторону. + Value string `json:"value,omitempty"` + // Валюта суммы счета (Alpha-3 ISO 4217 код). Доступные значения: CurrencyRUB, CurrencyKZT (см. константы модуля) + Currency Currency `json:"currency,omitempty"` +} + +// Статус оплаты счета. +type BillMetadataStatusValue string + +const ( + // Счет выставлен, ожидает оплаты. + BillMetadataStatusValueWaiting BillMetadataStatusValue = "WAITING" + // Счет оплачен. Финальный. + BillMetadataStatusValuePaid BillMetadataStatusValue = "PAID" + // Время жизни счета истекло. Счет не оплачен. Финальный. + BillMetadataStatusValueExpired BillMetadataStatusValue = "EXPIRED" + // Счет отклонен. Финальный. + BillMetadataStatusValueRejected BillMetadataStatusValue = "REJECTED" +) + +type BillMetadataStatus struct { + // Текущий статус счета. + Value BillMetadataStatusValue `json:"value,omitempty"` + // Дата обновления статуса. Формат даты: `ГГГГ-ММ-ДДTчч:мм:сс±чч:мм`. + ChangedDateTime string `json:"changedDateTime,omitempty"` +} + +const ( + // При открытии формы будут отображаться только указанные + // способы перевода, если они доступны. Возможные значения см. ниже. + CustomFieldOptionPaySourcesFilter = "paySourcesFilter" + // QIWI Кошелек. + PaySourceQiwi = "qw" + // Банковская карта. + PaySourceCard = "card" + // QIWI Кошелек и банковская карта. + PaySourceQiwiAndCard = PaySourceQiwi + "," + PaySourceCard +) + +const ( + // Код персонализации вашей формы. Может быть длиной <=255. + // https://developer.qiwi.com/ru/p2p-payments/#custom + CustomFieldOptionThemeCode = "themeCode" +) + +// Форматирует time.Time в значение формата для полей с datetime в BillMetadata +func FormatBillMetadataDateTime(t time.Time) string { + return t.Format(time.RFC3339Nano) +} + +// Парсит значение поля с datetime из BillMetedata +func ParseBillMetadataDateTime(changedDateTime string) (time.Time, error) { + return time.Parse(time.RFC3339Nano, changedDateTime) +} diff --git a/createOplataURL.go b/createOplataURL.go new file mode 100644 index 0000000..c76ba6a --- /dev/null +++ b/createOplataURL.go @@ -0,0 +1,70 @@ +package qiwip2p + +import ( + "time" + + "github.com/google/go-querystring/query" +) + +// Необязательные параметры для выставления сч1та. +type OplataCreateOptions struct { + // Идентификатор выставляемого сч1та в вашей системе. Может быть длиной <=200. + // Он должен быть уникальным, и генерироваться на вашей стороне любым способом. + // Идентификатором может быть любая уникальная последовательность букв или цифр. + // Также разрешено использование символа подчеркивания (_) и дефиса (-). + BillID BillID `url:"billId,omitempty"` + // Сумма, на которую выставляется счёт, округленная в меньшую сторону до 2 десятичных знаков. + // Должна быть не больше 6 знаков до запятой и не больше 2 знаков после запятой. + // Пример: `Amount: "123456.78"` + Amount string `url:"amount,omitempty"` + // Номер телефона пользователя (в международном формате). + Phone string `url:"phone,omitempty"` + // E-mail пользователя. + Email string `url:"email,omitempty"` + // Идентификатор пользователя в вашей системе. + Account string `url:"account,omitempty"` + // Комментарий к счету. Может быть длиной <=255. + Comment string `url:"comment,omitempty"` + // Дополнительные данные счета. Может быть длиной суммарно <=255. + // Может также включать поля (см. константы этого модуля): + // CustomFieldOptionPaySourcesFilter (с возможными значениями: PaySourceQiwi, PaySourceCard, PaySourceQiwiAndCard) + // и CustomFieldOptionThemeCode + CustomFields map[string]string `url:"customFields,omitempty"` + // Дата и время по МСК (UTC+3), до которого счёт будет актуален. Формат - ГГГГ-ММ-ДДTччмм, см. ConvertTimeToLifetimeValue(). + // Если перевод по счету не будет произведен до этой даты, + // ему присваивается финальный статус `EXPIRED` и последующий перевод станет невозможен. + // **Внимание! По истечении 45 суток от даты выставления счёт автоматически будет переведен в финальный статус** + Lifetime string `url:"lifetime,omitempty"` + // URL для переадресации на ваш сайт в случае успешного перевода + SuccessURL string `url:"successUrl,omitempty"` +} + +// Создаёт ссылку при переходе по которой отображается форма с выбором способа перевода. +// publicKey - публичный ключ Qiwi P2p API. Не может быть пустым. +// При использовании этого способа нельзя гарантировать, что +// все счета выставлены вами, в отличие от выставления счёта по API. +// +// https://developer.qiwi.com/ru/p2p-payments/#http-invoice +func (api *API) CreateOplataURL(options *OplataCreateOptions) string { + // Ошибка не может возникать в этом вызове + v, err := query.Values(options) + if err != nil { + panic(err) + } + + v.Add("publicKey", api.PublicKey) + + queryStr := v.Encode() + + return "https://oplata.qiwi.com/create?" + queryStr +} + +// Форматирует time.Time в значение для `OplataCreateOptions.Lifetime` +func FormatTimeToLifetime(t time.Time) string { + return t.Format("2006-01-02T150405") +} + +// Парсит значение `OplataCreateOptions.Lifetime` в time.Time +func ParseLifetime(str string) (time.Time, error) { + return time.Parse("2006-01-02T150405", str) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2f5beb0 --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module github.com/nickname76lib/qiwip2p + +go 1.18 + +require ( + github.com/google/go-querystring v1.1.0 + github.com/json-iterator/go v1.1.12 + github.com/valyala/fasthttp v1.37.0 +) + +require ( + github.com/andybalholm/brotli v1.0.4 // indirect + github.com/klauspost/compress v1.15.6 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ec096d2 --- /dev/null +++ b/go.sum @@ -0,0 +1,48 @@ +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A= +github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY= +github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.35.0 h1:wwkR8mZn2NbigFsaw2Zj5r+xkmzjbrA/lyTmiSlal/Y= +github.com/valyala/fasthttp v1.35.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= +github.com/valyala/fasthttp v1.37.0 h1:7WHCyI7EAkQMVmrfBhWTCOaeROb1aCBiTopx63LkMbE= +github.com/valyala/fasthttp v1.37.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/qiwip2p.go b/qiwip2p.go new file mode 100644 index 0000000..28dbc3f --- /dev/null +++ b/qiwip2p.go @@ -0,0 +1,137 @@ +// https://developer.qiwi.com/ru/p2p-payments/ +package qiwip2p + +import ( + "encoding/json" + "errors" + "fmt" + + jsoniter "github.com/json-iterator/go" + "github.com/valyala/fasthttp" +) + +// Интерфейс для взаимодействия с API P2P-счетов по стандарту OAuth 2.0. +type API struct { + // Публичный ключ для авторизации при выставлении счетов через форму. + PublicKey string + // Секретный ключ для авторизации запросов к API + SecretKey string + // Функция, которая используется для http запросов к Qiwi P2P API + HttpDoRequest func(method string, url string, headers map[string]string, body []byte) (respStatusCode int, respBody []byte, err error) +} + +// Создаёт новый интерфейс для взаимодействия с API P2P-счетов Qiwi. +// Если вы хотите кастомизировать выполнение HTTP запросов к API, создавайте объект API самостоятельно, +// в поле HttpDoRequest можно указать свою функцию для выполнения HTTP запросов. +func NewAPI(publicKey string, secretKey string) *API { + return &API{ + PublicKey: publicKey, + SecretKey: secretKey, + HttpDoRequest: DefaultHttpDoRequest, + } +} + +var defaultFasthttpClient = &fasthttp.Client{ + NoDefaultUserAgentHeader: true, + DisableHeaderNamesNormalizing: true, + DisablePathNormalizing: true, +} + +func DefaultHttpDoRequest(method string, url string, headers map[string]string, body []byte) (respStatusCode int, respBody []byte, err error) { + req := &fasthttp.Request{} + req.Header.SetMethod(method) + req.SetRequestURI(url) + + for k, v := range headers { + req.Header.Set(k, v) + } + + req.SetBody(body) + + resp := &fasthttp.Response{} + + err = defaultFasthttpClient.Do(req, resp) + if err != nil { + return 0, nil, fmt.Errorf("DefaultHttpDoRequest: %w", err) + } + + respStatusCode = resp.StatusCode() + respBody = resp.Body() + + return +} + +func (api *API) makeAPICall(httpMethod string, pathCompound string, billMeta *BillMetadata) (*BillMetadata, error) { + requestURL := "https://api.qiwi.com/partner/bill/v1/bills/" + pathCompound + + var body []byte + + if billMeta != nil { + jsoniterCfg := jsoniter.Config{ + EscapeHTML: false, + OnlyTaggedField: true, + ObjectFieldMustBeSimpleString: true, + CaseSensitive: true, + }.Froze() + + var err error + body, err = jsoniterCfg.Marshal(billMeta) + if err != nil { + return nil, fmt.Errorf("makeAPICall: %w", err) + } + } + + respStatusCode, respBody, err := api.HttpDoRequest(httpMethod, requestURL, map[string]string{ + "Authorization": "Bearer " + api.SecretKey, + "Accept": "application/json", + "Content-Type": "application/json", + }, body) + if err != nil { + return nil, fmt.Errorf("makeAPICall: %w", err) + } + + if respStatusCode < 200 || respStatusCode >= 300 { + return nil, fmt.Errorf("makeAPICall: qiwi p2p api error (status code %v): %w", respStatusCode, errors.New(string(respBody))) + } + + responseData := &BillMetadata{} + err = json.Unmarshal(respBody, responseData) + if err != nil { + return nil, fmt.Errorf("makeAPICall: %w", err) + } + + return responseData, nil +} + +// Выставление счета +// +// https://developer.qiwi.com/ru/p2p-payments/#create +func (api *API) CreateBill(billID BillID, billMeta *BillMetadata) (*BillMetadata, error) { + respBillMeta, err := api.makeAPICall("PUT", string(billID), billMeta) + if err != nil { + return nil, fmt.Errorf("CreateBill: %w", err) + } + return respBillMeta, nil +} + +// Проверка статуса перевода по счету +// +// https://developer.qiwi.com/ru/p2p-payments/#invoice-status +func (api *API) GetBill(billID BillID) (*BillMetadata, error) { + respBillMeta, err := api.makeAPICall("GET", string(billID), nil) + if err != nil { + return nil, fmt.Errorf("GetBill: %w", err) + } + return respBillMeta, nil +} + +// Отмена неоплаченного счета +// +// https://developer.qiwi.com/ru/p2p-payments/#cancel +func (api *API) CancelBill(billID BillID) (*BillMetadata, error) { + respBillMeta, err := api.makeAPICall("POST", string(billID)+"/reject", nil) + if err != nil { + return nil, fmt.Errorf("CancelBill: %w", err) + } + return respBillMeta, nil +}