未加星标

Handling Payment Webhooks with Azure Functions

字体大小 | |
[系统(windows) 所属分类 系统(windows) | 发布者 店小二04 | 时间 2016 | 作者 红领巾 ] 0人收藏点击收藏

A few years back I created Skype Voice Changer Pro which I sold online, using Paddle as my payment provider. Whenever I make a sale (which isn’t too often these days thanks to an issue with recent versions of Skype), I get notified via a webhook. On receiving that webhook, I need to generate a license file and email it to the customer.

Azure Functions are perfect for this scenario. I can quickly create a secure webhook to handle the callback from Paddle, post a message onto a queue to trigger license generation, and then another queue to trigger sending an email.

Let’s see how we can set this up.

First of all, I need to create a new Azure Function, which I’ll create as a generic C# webhook:


Handling Payment Webhooks with Azure Functions

The first thing I needed to do for my webhook, was edit the function.json file to remove the “ webHookType ” setting from the input httpTrigger . By default this will be set to “ genericJson ”, but that means we can only accept webhooks with JSON in their body. Paddle’s webhook comes in as x-www-form-urlencoded content, so removing the webHookType setting allows us to receive the HTTP request.


Handling Payment Webhooks with Azure Functions

Now in our run.csx file we can use ReadAsFormDataAsync to get access to the form parameters.

Next, we need to validate the order. Azure Functions has got built-in webhook validation for GitHub and Slack, but not for Paddle, so we must do this ourselves. This is done using a shared secret which we can set in the App Service configuration and access through the ConfigurationManager in the same way you would with a regular web app.

If the order is valid, for now let’s just respond saying thank you. Paddle will include this text in their confirmation email to the customer.

public static async Task<object> Run(HttpRequestMessage req, TraceWriter log)
{
var formData = await req.Content.ReadAsFormDataAsync();
var orderId = formData["p_order_id"];
var customerEmail = formData["customer_email"];
var messageId = formData["message_id"];
var customerName = formData["customer_name"];
log.Info($"Received {orderId}");
var sharedSecret = ConfigurationManager.ConnectionStrings["PaddleSharedSecret"].ConnectionString;
if (!ValidateOrder(sharedSecret, customerEmail, messageId, log))
{
log.Info($"Failed to Validate!");
return req.CreateResponse(HttpStatusCode.Forbidden, new {error = $"Invalid message id"
});
}
return req.CreateResponse(HttpStatusCode.OK, new {
greeting = $"Thank you for order {orderId}!"
});
}

And here’s the C# code to validate a Paddle webhook:

public static bool ValidateOrder(string sharedSecret, string customerEmail, string messageId, TraceWriter log)
{
if (customerEmail == null || messageId == null)
{
log.Warning("Missing email or message id");
return false;
}
var input = HttpUtility.UrlEncode(customerEmail + sharedSecret);
var md5 = System.Security.Cryptography.MD5.Create();
byte[] inputBytes = Encoding.ASCII.GetBytes(input);
byte[] hash = md5.ComputeHash(inputBytes);
var sb = new StringBuilder();
foreach (byte t in hash)
{
sb.Append(t.ToString("x2"));
}
var expectedId = sb.ToString();
var success = (expectedId == messageId);
if (!success)
{
log.Warning($"Expected {expectedId}, got {messageId}");
}
return success;
}

Now the only thing left to do is to trigger the license generation and email, which we’ll do by posting a message to a queue. This is preferable to doing everything there and then in the webhook as queues allow our webhook to respond quickly and give us retrying if the email service is temporarily down. Breaking the process down into three small loosely coupled pieces will also give us maintainability and testability benefits.

We support send a message to a queue by going to the “Integrate” section in the portal and adding a new output binding of type Azure Storage Queue. This is the easiest to set up as there’s already a storage account associated with your function app you can use called “AzureWebJobsStorage” (although arguably you should create your own to keep your application data separate from the Azure Functions runtime’s data which resides in that storage account).


Handling Payment Webhooks with Azure Functions

I’ll call my queue “orders”, and Azure Functions will automatically create it for me.


Handling Payment Webhooks with Azure Functions

To send the message to the queue there are a number of options but I chose to create a strongly typed class “ OrderInfo ” and use a IAsyncCollector<T> parameter type for binding. This has the advantage of working with async functions (which mine is), but also supports sending 0 or more messages to the queue. We won’t be generating a license if the webhook is invalid so this is handy.

Here’s the key bits of the updated function:

public class OrderInfo
{
public string OrderId { get; set; }
public string CustomerEmail { get; set; }
public string CustomerName { get; set; }
public string LicenseDownloadCode { get; set; }
}
public static async Task<object> Run(HttpRequestMessage req, IAsyncCollector<OrderInfo> outputQueueItem, TraceWriter log)
{
// ... order validation here
// send on to the queue to generate license
var orderInfo = new OrderInfo {
OrderId = orderId,
CustomerEmail = customerEmail,
CustomerName = customerName,
LicenseDownloadCode = licenceDownloadCode,
};
await outputQueueItem.AddAsync(orderInfo);
return req.CreateResponse(HttpStatusCode.OK, new {
greeting = $"Thank you for order {orderId}!"
});
}

As you can see it’s super easy to send the message, just call AddAsync on the collector.

Finally, we need to handle messages in the queue. There’s a super feature in the portal where if you go to the Integrate tab for your function and select the queue output binding, there’s a button to set up a new function that is triggered by messages on that queue:


Handling Payment Webhooks with Azure Functions

By clicking this, it will auto-fill in the bindings for my new function, giving me a new function all set up to read off the queue and log each message received:


Handling Payment Webhooks with Azure Functions
Handling Payment Webhooks with Azure Functions

Now it’s just a case of putting my license generation code into this function, as well as posting to another queue to trigger a third function which sends out the license email. Azure functions includes a built-in SendGrid binding which makes sending emails very easy (although I’m currently using a different service).

We can easily test our function using Postman (can’t use the portal in this case as it only sends JSON), and sure enough the webhook function is successful, and we can see in the logs for the license generation function that a message was indeed posted to the queue.


Handling Payment Webhooks with Azure Functions
Handling Payment Webhooks with Azure Functions

Using Azure Functions to handle webhooks is a big improvement from the quick and dirty code I originally created which simply did everything synchronously in a hidden API sat on my website. It meant my order webhook code was now coupled to the web server, which got in the way of me doing things like switching the website to use WordPress. With Azure functions I can move this webhook (and several others for things like letting users report errors from the app) out of my website into small loosely coupled functions.

本文系统(windows)相关术语:三级网络技术 计算机三级网络技术 网络技术基础 计算机网络技术

主题: C#GitSlackGitHubSkypeWord
分页:12
转载请注明
本文标题:Handling Payment Webhooks with Azure Functions
本站链接:http://www.codesec.net/view/484575.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 系统(windows) | 评论(0) | 阅读(37)