Библиотека .Net Core для внедрения единого платежного протокола эквайринга и QIWI Кошелька.
Установка с помощью Nuget:
nuget install Qiwi.BillPaymentsПошаговое руководство по работе с SDK (для физических лиц): https://developer.qiwi.com/ru/p2p-sdk-guide/#integration-sdk
API P2P-счетов (для физических лиц): https://developer.qiwi.com/ru/p2p-payments
API QIWI Кассы (для юридических лиц): https://developer.qiwi.com/ru/bill-payments
Для использования SDK требуется secretKey, подробности в документации — для физ.лиц, для юр.лиц.
var client = BillPaymentClientFactory.Create(
secretKey: "eyJ2ZXJzaW9uIjoicmVzdF92MyIsImRhdGEiOnsibWVyY2hhbnRfaWQiOjUyNjgxMiwiYXBpX3VzZXJfaWQiOjcxNjI2MTk3LCJzZWNyZXQiOiJmZjBiZmJiM2UxYzc0MjY3YjIyZDIzOGYzMDBkNDhlYjhiNTnONPININONPN090MTg5Z**********************"
);По умолчанию пользователю доступно несколько способов оплаты. В платежной форме параметры счета передаются в открытом виде в ссылке. Далее клиенту отображается форма с выбором способа оплаты. При использовании этого способа нельзя гарантировать, что все счета выставлены мерчантом, в отличие от выставления по API. Через API все параметры передаются в закрытом виде, так же в API поддерживаются операции выставления и отмены счетов, возврата средств по оплаченным счетам (только для юр. лиц), а также проверки статуса выполнения операций.
Простой способ для интеграции. При открытии формы клиенту автоматически выставляется счет. Параметры счета передаются в открытом виде в ссылке. Далее клиенту отображается платежная форма с выбором способа оплаты. При использовании этого способа нельзя гарантировать, что все счета выставлены мерчантом, в отличие от выставления по API.
Метод CreatePaymentForm создает платежную форму. В параметрах нужно указать:
- данные для создания платежной формы
paymentInfo, включая:- ключ идентификации провайдера
PublicKey, полученный в личном кабинете QIWI; - идентификатор счета
BillIdвнутри вашей системы; - сумму
Amount; - не обязательный адрес перехода после успешной оплаты
SuccessUrl;
- ключ идентификации провайдера
- не обязательные дополнительные данные
customFields, включая:- персонализация платежной формы
ThemeCode.
- персонализация платежной формы
В результате будет получена ссылка на форму оплаты, которую можно передать клиенту. Подробнее о доступных параметрах в документации — для физ. лиц, для юр. лиц.
client.CreatePaymentForm(
paymentInfo: new PaymentInfo
{
PublicKey = "2tbp1WQvsgQeziGY9vTLe9vDZNg7tmCymb4Lh6STQokqKrpCC6qrUUKEDZAJ7mvFnzr1yTebUiQaBLDnebLMMxL8nc6FF5zfmGQnypdXCbQJqHEJW5RJmKfj8nvgc",
Amount = new MoneyAmount
{
ValueDecimal = 499.9m,
CurrencyEnum = CurrencyEnum.Rub
},
BillId = Guid.NewGuid().ToString(),
SuccessUrl = "https://merchant.com/payment/success?billId=893794793973"
},
customFields: new CustomFields
{
ThemeCode = "кодСтиля"
}
);В результате:
new Uri(
"https://oplata.qiwi.com/create?amount=499.90&customFields%5BapiClient%5D=dotnet_sdk&customFields%5BapiClientVersion%5D=0.1.0&customFields%5BthemeCode%5D=%D0%BA%D0%BE%D0%B4%D0%A1%D1%82%D0%B8%D0%BB%D1%8F&publicKey=2tbp1WQvsgQeziGY9vTLe9vDZNg7tmCymb4Lh6STQokqKrpCC6qrUUKEDZAJ11HeiD1GQX8jTnjMxLpMcSZuGZP7xbocwJicsoBAG1HyiPDJ8A8ecBKCKWu6FP5oa&billId=920dc584-ed30-4683-8251-486426768160&successUrl=https%3A%2F%2Fmerchant.com%2Fpayment%2Fsuccess%3FbillId%3D893794793973"
);Возможные исключения: UrlEncodingException.
Надежный способ для интеграции.
Параметры передаются server2server с использованием авторизации.
Метод позволяет выставить счет, при успешном выполнении запроса в ответе вернется параметр PayUrl - ссылка для редиректа пользователя на платежную форму.
Метод CreateBill выставляет новый счет.
В параметрах нужно указать:
- данные для выставления стеча
info, включая:- идентификатор счета
BillIdвнутри вашей системы; - сумма счета
Amount; - не обязательный комментарий
Comment; - срок оплаты счета
ExpirationDateTime; - информация о пользователе
Customer; - не обязательный адрес перехода после успешной оплаты
SuccessUrl;
- идентификатор счета
- не обязательные дополнительные данные
customFields, включая:- персонализация платежной формы
ThemeCode.
- персонализация платежной формы
В результате будет получен ответ с данными о выставленном счете. Подробное описание параметров для выставления счёта представлено в руководстве по работе с SDK, а так же в документации для физ.лиц и для юр. лиц
client.CreateBill(
info: new CreateBillInfo
{
BillId = Guid.NewGuid().ToString(),
Amount = new MoneyAmount
{
ValueDecimal = 199.9m,
CurrencyEnum = CurrencyEnum.Rub
},
Comment = "comment",
ExpirationDateTime = DateTime.Now.AddDays(45),
Customer = new Customer
{
Email = "example@mail.org",
Account = Guid.NewGuid().ToString(),
Phone = "79123456789"
},
SuccessUrl = new Uri("http://merchant.ru/success")
},
customFields: new CustomFields
{
ThemeCode = "кодСтиля"
}
);Ответ:
new BillResponse
{
SiteId = "270304",
BillId = "81150938-dde5-45b8-ba22-df12cc6cee27",
Amount = new MoneyAmount
{
ValueString = "199.90",
CurrencyString = "RUB"
},
Status = new ResponseStatus
{
ValueString: "WAITING",
ChangedDateTime: BillPaymentsUtils.ParseDate("2018-11-03T15:43:51+03:00")
},
Comment = "comment",
Customer = new Customer
{
Email = "example@mail.org",
Account = "040c3bb8-b207-4ecc-9ff9-90168d3bc34f",
Phone = "79123456789"
},
CreationDateTime = BillPaymentsUtils.ParseDate("2018-11-03T15:43:51+03:00"),
ExpirationDateTime = BillPaymentsUtils.ParseDate("2018-12-18T15:43:50+03:00"),
PayUrl = new Uri("https://oplata.qiwi.com/form/?invoice_uid=c77a9051-1467-416b-991e-c25f06c61168&successUrl=http%3A%2F%2Fmerchant.ru%2Fsuccess"),
CustomFields = new CustomFields
{
ApiClient = "dotnet_sdk",
ApiClientVersion = "0.1.0",
ThemeCode = "кодСтиля"
}
};Метод может быть вызван ассинхронно через CreateBillAsync.
Возможные исключения: SerializationException, HttpClientException, BadResponseException, BillPaymentsServiceException.
Метод GetBillInfo возвращает информацию о счете.
В параметрах нужно указать идентификатор счета billId внутри вашей системы, в результате будет получен ответ со статусом счета.
Подробнее в документации — для физ.лиц, для юр.лиц.
client.GetBillInfo(
billId: "fcb40a23-6733-4cf3-bacf-8e425fd1fc71"
);Ответ:
new BillResponse
{
SiteId = "270304",
BillId = "fcb40a23-6733-4cf3-bacf-8e425fd1fc71",
Amount = new MoneyAmount
{
ValueString = "199.90",
CurrencyString = "RUB"
},
Status = new ResponseStatus
{
ValueString: "WAITING",
ChangedDateTime: BillPaymentsUtils.ParseDate("2018-11-03T16:03:09+03:00")
},
Comment = "test",
Customer = new Customer
{
Email = "example@mail.org",
Account = "349d5978-bccc-4e10-be7e-3ca0808237b7",
Phone = "79123456789"
},
CreationDateTime = BillPaymentsUtils.ParseDate("2018-11-03T16:03:09+03:00"),
ExpirationDateTime = BillPaymentsUtils.ParseDate("2018-12-18T16:03:08+03:00"),
PayUrl = new Uri("https://oplata.qiwi.com/form/?invoice_uid=b77618b4-746c-485f-8bb8-fff43ddef114"),
CustomFields = new CustomFields
{
ApiClient = "dotnet_sdk",
ApiClientVersion = "0.1.0"
}
};Метод может быть вызван ассинхронно через GetBillInfoAsync.
Возможные исключения: SerializationException, HttpClientException, BadResponseException, BillPaymentsServiceException.
Метод CancelBill отменяет неоплаченный счет.
В параметрах нужно указать идентификатор счета billId внутри вашей системы, в результате будет получен ответ с информацией о счете.
Подробнее в документации — для физ.лиц, для юр.лиц.
client.CancelBill(
billId: "fcb40a23-6733-4cf3-bacf-8e425fd1fc71"
);Ответ:
new BillResponse
{
SiteId = "270304",
BillId = "fcb40a23-6733-4cf3-bacf-8e425fd1fc71",
Amount = new MoneyAmount
{
ValueString = "199.90",
CurrencyString = "RUB"
},
Status = new ResponseStatus
{
ValueString: "REJECTED",
ChangedDateTime: BillPaymentsUtils.ParseDate("2018-11-03T16:03:09+03:00")
},
Comment = "test",
Customer = new Customer
{
Email = "example@mail.org",
Account = "349d5978-bccc-4e10-be7e-3ca0808237b7",
Phone = "79123456789"
},
CreationDateTime = BillPaymentsUtils.ParseDate("2018-11-03T16:03:09+03:00"),
ExpirationDateTime = BillPaymentsUtils.ParseDate("2018-12-18T16:03:08+03:00"),
PayUrl = new Uri("https://oplata.qiwi.com/form/?invoice_uid=b77618b4-746c-485f-8bb8-fff43ddef114"),
CustomFields = new CustomFields
{
ApiClient = "dotnet_sdk",
ApiClientVersion = "0.1.0"
}
};Метод может быть вызван ассинхронно через CancelBillAsync.
Возможные исключения: SerializationException, HttpClientException, BadResponseException, BillPaymentsServiceException.
Метод RefundBill производит возврат средств.
В параметрах нужно указать:
- идентификатор счета
billId; - идентификатор возврата
refundIdвнутри вашей системы; - сумму возврата
amount.
Подробнее в документации.
client.RefundBill(
billId: "fcb40a23-6733-4cf3-bacf-8e425fd1fc71",
refundId: Guid.NewGuid().ToString(),
amount: new MoneyAmount
{
ValueDecimal = 104.9m,
CurrencyEnum = CurrencyEnum.Rub
}
);В результате будет получен ответ c информацией о возврате:
new RefundResponse
{
new MoneyAmount
{
ValueString = "104.90",
CurrencyString = "RUB"
},
DateTime = BillPaymentsUtils.ParseDate("2018-11-03T16:11:57+03:00")
RefundId = "3444e8ca-cf68-4dbd-92ee-f68c4bf8f29b",
StatusString = "PARTIAL"
};Метод может быть вызван ассинхронно через RefundBillAsync.
Возможные исключения: SerializationException, HttpClientException, BadResponseException, BillPaymentsServiceException.
Метод GetRefundInfo запрашивает статус возврата, в параметрах нужно указать:
- идентификатор счета
billId; - идентификатор возврата
refundIdвнутри вашей системы.
Подробнее в документации.
client.GetRefundInfo(
billId: "fcb40a23-6733-4cf3-bacf-8e425fd1fc71",
refundId: "3444e8ca-cf68-4dbd-92ee-f68c4bf8f29b"
);В результате будет получен ответ c информацией о возврате:
new RefundResponse
{
Amount = new MoneyAmount
{
ValueString = "104.90",
CurrencyString = "RUB"
},
DateTime = BillPaymentsUtils.ParseDate("2018-11-03T16:11:57+03:00")
RefundId = "3444e8ca-cf68-4dbd-92ee-f68c4bf8f29b",
StatusString = "PARTIAL"
};Метод может быть вызван ассинхронно через GetRefundInfoAsync.
Возможные исключения: SerializationException, HttpClientException, BadResponseException, BillPaymentsServiceException.
Метод CheckNotificationSignature осуществляет проверку подписи при нотификации о новом счете от сервера уведомлений QIWI.
Принимает на вход подпись из входящего запроса, объект - тело запроса и secret ключ, с помощью которого должна осуществляться подпись:
Assert.IsTrue(
condition: BillPaymentsUtils.CheckNotificationSignature(
validSignature: "07e0ebb10916d97760c196034105d010607a6c6b7d72bfa1c3451448ac484a3b",
notification: new Notification
{
Bill = new Bill
{
SiteId = "test",
BillId = "test_bill",
Amount = new MoneyAmount
{
ValueDecimal = 1m,
CurrencyEnum = CurrencyEnum.Rub
},
Status = new BillStatus
{
ValueEnum = BillStatusEnum.Paid
}
),
Version = "1"
},
secretKey: "eyJ2ZXJzaW9uIjoicmVzdF92MyIsImRhdGEiOnsibWVyY2hhbnRfaWQiOjUyNjgxMiwiYXBpX3VzZXJfaWQiOjcxNjI2MTk3LCJzZWNyZXQiOiJmZjBiZmJiM2UxYzc0MjY3YjIyZDIzOGYzMDBkNDhlYjhiNTnONPININONPN090MTg5Z**********************"
)
);Возможные исключения: EncryptionException.
По умолчанию для работы с JSON используется System.Runtime.Serialization.Json.DataContractJsonSerializer, но можно воспользоваться любым другим, реализовав интерфейс IObjectMapper.
public interface IClient {
string WriteValue(object entityOpt);
T ReadValue<T>(string body);
}Примером реализации является ContractObjectMapper, принимающий System.Runtime.Serialization.Json.DataContractJsonSerializerSettings для конфигурации совместимости формата дат.
BillPaymentClientFactory.Create(
secretKey: "eyJ2ZXJzaW9uIjoicmVzdF92MyIsImRhdGEiOnsibWVyY2hhbnRfaWQiOjUyNjgxMiwiYXBpX3VzZXJfaWQiOjcxNjI2MTk3LCJzZWNyZXQiOiJmZjBiZmJiM2UxYzc0MjY3YjIyZDIzOGYzMDBkNDhlYjhiNTnONPININONPN090MTg5Z**********************",
client: null,
objectMapper: ObjectMapperFactory.Create<ContractObjectMapper>(
settings: new DataContractJsonSerializerSettings
{
DateTimeFormat = new DateTimeFormat(BillPaymentsClient.DateTimeFormat)
}
)
);Проект Qiwi.BillPayments.Json.Newtonsoft предоставляет обработчик JSON на освнове пакета Newtonsoft.Json.
По умолчанию для отправки HTTP-запросов используется System.Net.Http.HttpClient, но можно воспользоваться любым другим, реализовав интерфейс IClient.
public interface IClient {
ResponseData Request(
string method,
string url,
IReadOnlyDictionary<string, string> headers,
string entityOpt = null
);
Task<ResponseData> RequestAsync(
string method,
string url,
IReadOnlyDictionary<string, string> headers,
string entityOpt = null
);
}Примером реализации является NetClient, принимающий System.Net.Http.HttpClient произвольной конфигурации.
BillPaymentClientFactory.Create(
secretKey: "eyJ2ZXJzaW9uIjoicmVzdF92MyIsImRhdGEiOnsibWVyY2hhbnRfaWQiOjUyNjgxMiwiYXBpX3VzZXJfaWQiOjcxNjI2MTk3LCJzZWNyZXQiOiJmZjBiZmJiM2UxYzc0MjY3YjIyZDIzOGYzMDBkNDhlYjhiNTnONPININONPN090MTg5Z**********************",
client: ClientFactory.Create<NetClient>(
httpClient: new HttpClient(
handler: new HttpClientHandler()
{
Proxy: new WebProxy("http://proxyserver:80/", true)
}
)
)
);Данная библиотека не берет на себя обработку исключительных ситуаций, при ее использовании следует определить и перехватывать возможные исключительные ситуации самостоятельно.
При использовании SDK в нештатных ситуациях могут возникать следующие исключения из пространства имен Qiwi.BillPayments.Exception:
Возникает, если ответ сервера получен, но не содержит корректный JSON.
Обьект сообщения типа ResponseData можно получить из поля Response.
Код ответа HTTP можно получить из поля HttpStatus.
Возникает, если от API получено сообщение об ошибке.
Обьект сообщения типа ErrorResponse можно получить из поля Response.
Код ответа HTTP можно получить из поля HttpStatus.
Возникает, если невозможно получить хэш строки, алгоритм не поддерживается или не задан секретный ключ. Содержит сообщение и строит стек вызовов от оригинальной ошибки.
Возникает, если невозможно выполнить запрос к API, ошибка HTTP клиента. Содержит сообщение и строит стек вызовов от оригинальной ошибки.
Возникает, если передаваемые в API данные не могут быть корректно сериализованы в JSON. Содержит сообщение и строит стек вызовов от оригинальной ошибки.
Возникает, если невозможно сформировать корректный URL. Содержит сообщение и строит стек вызовов от оригинальной ошибки.
Запросы выполняются в отдельном потоке, даже если используется синхронная форма вызова метода.
Таким образом, перехват исключений должен использовать класс AggregateException для обьекта ошибки.
try
{
client.CreateBill(billInfo);
}
catch (AggregateException aggregateException)
{
var exception = exception.GetBaseException();
// exception - исходная ошибка, которую следует обработать
}- .Net Standart 2.0 или .Net Core 2.0 или .Net Framework 4.5