На прошлой неделе я анонсировал AtomicPay .NET SDK. Если вы пропустили это, AtomicPay — это поставщик крипто-платежей, не связанный с тюремным заключением, и я вношу свой вклад в проект. В этой серии из трех сообщений я покажу вам, как обрабатывать веб-перехватчик службы (который информирует о входящих платежах по счетам) с помощью Azure.

Если вы пропустили анонс SDK, нажмите здесь, чтобы прочитать его сейчас.

Что такое вебхук?

Вебхуки набирают популярность, и многие сервисы и сайты предоставляют их для более удобного взаимодействия с другими сайтами и сервисами. Чаще всего веб-перехватчики запускаются событием и отправляют запрос подписчикам веб-перехватчика. AtomicPay предоставляет такой вебхук для счетов и состояния их оплаты.

Почему Азур?

Есть несколько причин, и Azure — это мой личный выбор облачного сервиса. Кроме того, Azure предоставляет две дополнительные функции, которые мне нравятся, а именно Azure KeyVault для обработки учетных данных и Azure Notificationhub для отправки уведомлений на широкий спектр платформ. Эта новая серия будет разделена на три части:

  1. Обработка входящего уведомления с помощью функции Azure (этот пост)
  2. Проверьте состояние счета в функции Azure, но сохраните учетные данные API наиболее безопасным способом (второй пост).
  3. Отправка push-уведомления через Azure Notificationhub на все устройства, к которым привязана учетная запись продавца (третий пост)

Начиная

Сначала войдите в свою учетную запись Azure (или создайте новую). В меню слева выберите Создать ресурс и найдите Бессерверное функциональное приложение:

На следующем шаге вы назначите имя для своей функции, создадите новую группу ресурсов и назначите или создадите учетную запись хранения:

После завершения развертывания нажмите кнопку «Перейти к ресурсу» в уведомлении портала:

Следующим шагом будет добавление кода для нашей функции. Нажмите на знак «+» рядом с «Функции»:

Теперь вам нужно решить, как вы хотите двигаться дальше в развитии. Я выбрал Visual Studio, потому что нам понадобятся некоторые пакеты Nuget позже, и мне нравится использовать Intellisense при написании кода. Просто следуйте инструкциям на экране, чтобы запустить проект.

Убедитесь, что вы выбрали версию проекта v1 (.NET Framework). Это необходимо, потому что мы хотим подключиться к Notificationhub позже (в третьем посте), а на данный момент v2 не поддерживает это (согласно документации).

Щелкнув «ОК», Visual Studio создаст для вас новый проект.

Увидел какой-то код… наконец!

Если вы посмотрите на только что сгенерированный код в сгенерированном классе Function, вам сразу может что-то напомнить. Вы правы, поскольку функции Azure основаны на ASP.NET, поэтому есть некоторые сходства. Давайте посмотрим на параметры метода Run (вы можете изменить имя метода, так как атрибут FunctionName уже объявляет метод как таковой).

Первый параметр — это HttpTriggerAttribute, который определяет уровень авторизации, разрешенные методы и необязательный маршрут. Единственное изменение, которое я внес в параметры атрибута по умолчанию, — это удаление метода «get», потому что Webhook AtomicPay отправляет запрос POST.

Второй параметр — это HttpRequestMessage, который содержит запрос, отправленный от AtomicPay. Если вы уже работали с API или другими веб-запросами, это должно быть вам знакомо. Мы будем работать с этой частью по мере продолжения примера.

И последнее, но не менее важное: у нас есть поставщик журналов, который позволяет нам писать в журнал функции Azure (что может быть очень полезно, если вы ищете ошибку). Мы также будем использовать это, поскольку пример продолжается.

Полезная нагрузка веб-перехватчика AtomicPay

Чтобы иметь возможность обрабатывать полезную нагрузку от AtomicPay, полезно иметь образец полезной нагрузки. К счастью, AtomicPay предоставляет подробную документацию для вебхука (как и для API). Полезная нагрузка выглядит следующим образом:

{
"invoice_id":"DBVZzHMxjjfdRZYeignEZC",
"order_id":"1235425",
"fiat_price":"1.00",
"fiat_currency":"USD",
"payment_currency":"BTC",
"payment_rate":"4,192.00",
"payment_address":"bc1qmtyax97phenvvs3sdg5r45kdcphd",
"payment_total":"0.00023855",
"payment_paid":"0.00023855",
"payment_due":"0.00000000",
"payment_txid":"14edecbf114f2b2e73cf7470916122286506de5",
"payment_confirmation":"3",
"status":"confirmed",
"statusException":"null"
}

В зависимости от криптовалюты счета ваша функция будет запускаться несколько раз — каждый раз при обновлении статуса. Поскольку вы, вероятно, хотите иметь разные действия для разных состояний, имеет смысл создать класс на основе JSON для десериализации и дальнейшей работы с:

public class WebhookInvoiceInfo
    {
        [JsonProperty("invoice_id")]
        public string InvoiceId { get; set; }

        [JsonProperty("order_id")]
        public string OrderId { get; set; }

        [JsonProperty("fiat_price")]
        [JsonConverter(typeof(StringToDecimalConverter))]
        public decimal FiatPrice { get; set; }

        [JsonProperty("fiat_currency")]
        public string FiatCurrency { get; set; }

        [JsonProperty("payment_currency")]
        public string PaymentCurrency { get; set; }

        [JsonProperty("payment_rate")]
        public string PaymentRate { get; set; }

        [JsonProperty("payment_address")]
        public string PaymentAddress { get; set; }

        [JsonProperty("payment_total")]
        [JsonConverter(typeof(StringToDecimalConverter))]
        public decimal PaymentTotal { get; set; }

        [JsonProperty("payment_paid")]
        [JsonConverter(typeof(StringToDecimalConverter))]
        public decimal PaymentPaid { get; set; }

        [JsonProperty("payment_due")]
        [JsonConverter(typeof(StringToDecimalConverter))]
        public decimal PaymentDue { get; set; }

        [JsonProperty("payment_txid")]
        public string PaymentTxid { get; set; }

        [JsonProperty("payment_confirmation")]
        [JsonConverter(typeof(StringToLongConverter))]
        public long PaymentConfirmation { get; set; }

        [JsonProperty("status")]
        [JsonConverter(typeof(StringToInvoiceStatusConverter))]
        public InvoiceStatus Status { get; set; }

        [JsonProperty("statusException")]
        [JsonConverter(typeof(StringToInvoiceStatusExceptionConverter))]
        public InvoiceStatusException StatusException { get; set; }
    }

Возможно, вы заметили, что у меня есть преобразователи, прикрепленные к некоторым свойствам выше. Они необходимы для преобразования значений в их правильные типы, поскольку API обычно отправляет только строки. Этот класс и его преобразователи являются частью AtomicPay .NET SDK (найдите его на Nuget или Github), который мы будем использовать также во второй части этой серии блогов. Добавьте библиотеку в проект удобным для вас способом.

Работа с полезной нагрузкой вебхука

В этой первой части серии мы просто собираемся писать записи журнала на основе статуса входящего веб-перехватчика. Просто добавьте эти строки в метод Run вашей функции:

[FunctionName("Function1")]
        public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequestMessage req, TraceWriter log)
        {
            log.Info($"arrived at function trigger for AtomicPay webhook.");
            log.Info("trying to get payload object from request...");
            WebhookInvoiceInfo result = null;
            _jsonSerializerSettings = new JsonSerializerSettings()
            {
                MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
                DateParseHandling = DateParseHandling.None,
                Converters ={

                        new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal },
                        StringToLongConverter.Instance,
                        StringToDecimalConverter.Instance,
                        StringToInvoiceStatusConverter.Instance,
                        StringToInvoiceStatusExceptionConverter.Instance
                }
            };

            _jsonSerializer = JsonSerializer.Create(_jsonSerializerSettings);

            using (var stream = await req.Content.ReadAsStreamAsync())
            {
                using (var reader = new StreamReader(stream))
                {
                    using (var jsonReader = new JsonTextReader(reader))
                    {
                        result = _jsonSerializer.Deserialize<WebhookInvoiceInfo>(jsonReader);
                    }
                }
            }

            if (result != null)
                log.Info($"received payload for invoice id: {result.InvoiceId} with status {result.Status.ToString()}");
            else
                log.Info("there was an error getting the payload from the request body");

            return result == null ?
                req.CreateResponse(HttpStatusCode.BadRequest, "there was an error getting the payload from the request body") :
                req.CreateResponse(HttpStatusCode.OK, "OK");
        }

Мы читаем содержимое ответа из HttpRequestMessage и десериализуем его. Если полезной нагрузки нет или что-то пошло не так, наш результат будет нулевым. Мы пишем запись в журнале, которая указывает, получили ли мы полезную нагрузку и статус счета в ней. В итоге мы возвращаем соответствующий ответ на POST-запрос.

Локальное тестирование функции

Еще одним большим преимуществом использования Visual Studio для написания функции Azure является возможность локальной отладки. Если вы этого еще не сделали, вам потребуется установить
инструменты Azure Functions Core (CLI) — Visual Studio предложит вам сделать это. Как только это будет сделано, мы сможем отлаживать локально:

Теперь, когда наш локальный сервис запущен, давайте протестируем функцию с Почтальоном (это один из моих тестовых счетов):

Как мы видим, все прошло гладко, и наша функция работает, как и ожидалось:

Теперь, когда мы убедились, что наша функция может работать, мы готовы ее опубликовать.

Публикация функции в Azure

Чтобы начать процесс, щелкните правой кнопкой мыши имя проекта и выберите «Опубликовать». Поскольку мы уже определили наш проект на портале Azure, мы используем параметр «Выбрать существующий» на следующем шаге:

Я не выбираю здесь опцию «запустить из файла пакета». Нажмите «Опубликовать», чтобы продолжить. Вам снова будет предложено окно выбора (возможно, вам придется сначала войти в свою учетную запись). Выберите созданный проект:

Подтвердите свой выбор кнопкой «ОК». После выполнения некоторых дополнительных шагов подготовки Visual Studio позволит вам нажать кнопку «Опубликовать»:

Опубликовав свою функцию, снова посетите портал Azure. Выберите опубликованную функцию. Портал покажет вам файл декларации function.json. В верхней части веб-сайта вы найдете параметры «Сохранить», «Выполнить» и «‹/›Получить URL-адрес функции». . Нажмите на последний, чтобы просмотреть URL вашей функции:

Я почти уверен, что вы заметили параметр кода в URL-адресе. Этот параметр требуется при каждом вызове вашей функции Azure в качестве параметра авторизации. Чтобы протестировать свою функцию в Azure, скопируйте URL-адрес и вставьте его в Postman (заменив URL-адрес локального хоста, который мы использовали для локального тестирования). В Postman вы должны получить такой же результат — оптимально OK-ответ.

Вывод

Если вы последовали этому примеру и ваша функция запущена и работает — поздравляем! Вы только что создали базовую «бессерверную» (она же работающая на чужом сервере) функцию. В следующем посте этой серии мы правильно инициализируем AtomicPay SDK после того, как добавим наши учетные данные API в другую функцию, работающую в Azure — KeyVault. Как всегда, я надеюсь, что этот пост будет полезен для некоторых из вас.

До следующего поста, всем удачного кодирования!

Изображение в заголовке

Первоначально опубликовано на bit.ly 18 января 2019 г.