Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile

Azure Mobile Services Managed Backend–fortumo Mobile Payment Integration

0.00/5 (No votes)
26 Sep 2014CPOL5 min read 12K  
Azure Mobile Services Managed Backend–fortumo Mobile Payment Integration

Introduction

World-wide there are users who have not credit card and still would love to buy your apps or game-credits for example. If you think about this, you will realize very fast that you are missing out users on emerging markets like for example China, Argentinia, Brazil and many more countries. Fortumo is partnering with Microsoft to fill that gap. You can use fortumo to offer In-App purchasing on many platforms like Windows and Windows Phone (for universal apps), Android and for example for Unity. You can read more about fortumo on their homepage.

Browsing their documentation, I missed the implementation of the backend part or “Receipt Verification Request” based on .NET. All the samples for that implementation are written in PHP. Because I am working on a Windows Store app currently, I wanted fortumo to work with my existing AMS .NET backend.

Background

If you are new to Mobile Services development using the .NET backend I recommend reading my article-series on how to master Azure Mobile Services using the .NET backend:

The Code

Browsing their documentation, I missed the implementation of the backend part or “Receipt Verification Request” based on .NET. All the samples for that implementation are written in PHP. Because I am working on a Windows Store app currently, I wanted fortumo to work with my existing AMS .NET backend.

The verification process is not very hard to implement as it is documented very well in a short PHP snippet. The best fit to implement the verification is a custom controller. So I added a new payment-controller and started the implementation. I had no real problems doing that. The only two problems (not really) to overcome where to tell the controller not to allow unencrypted http-requests and to deliver an MD5 string that is PHP compatible from a .NET backend:

Here is the code for the Transaction-controller:

C#
[AuthorizeLevel(AuthorizationLevel.Anonymous)]
    [RoutePrefix("api/transactions")]
    [RequireHttpsWebApi]
    public class TransactionController : ApiController {

        public ApiServices Services {
            get;
            set;
        }

        [Route("fortumo")]
        public async Task<bool> Get() {

            return await Task.Run<bool>(()=> {

                var ip = Request.GetOwinContext().Request.RemoteIpAddress;

                var queryParams = Request.GetOwinContext().Request.Query;

                IPAddress callerAddress;

                var parseOk = IPAddress.TryParse(ip, out callerAddress);

                if (parseOk) {

                    var ipValid = AuthorizationRepositoryPayment.GetAuthorizedIPs().Contains(callerAddress.MapToIPv4().ToString());
                    var validQueryKeys = AuthorizationRepositoryPayment.AllowedParameters();

                    if(ipValid) {

                        //ip seems to be ok, now check for valid parameters.
                        var queryKeys = Request.GetOwinContext().Request.Query.Select(q => q.Key).AsQueryable();

                        var isValid = false;

                        foreach(var key in queryKeys) {
                            if(validQueryKeys.Contains(key)) {
                                isValid = true;
                            }

                            else {
                                isValid = false;
                            }
                        }

                        if(isValid) {

                            return CheckSignature(Request.GetOwinContext().Request.Query);

                        }

                        else {
                            Services.Log.Error(string.Format("Request parameters not valid. Payment {0}", ip));
                            return false;
                        }

                    }

                    else {
                        Services.Log.Error(string.Format("Caller IP not valid. Payment {0}",ip));
                        return false;
                    }
                }

                else {
                    Services.Log.Error(string.Format("Caller IP could not be parsed. Payment {0}", ip));
                    return false;
                }


            });
        }

        private bool CheckSignature(IReadableStringCollection queryStrings) {

            //Sort the keys
            var sorted = queryStrings.OrderBy(k => k.Key);

            var signatureString = default(string);

            foreach(var keyValue in sorted) {
                if(!keyValue.Key.Equals("sig")) {
                    signatureString += string.Format("{0}={1}", keyValue.Key, keyValue.Value[0]);
                }
            }

            //Choose the right product

            var product = sorted.FirstOrDefault(p => p.Key.Equals("credit_name")).Value;

            string secret = default(string);

            //To be replaced by tables
            switch(product[0]) {
                case "[PRODUCTTYPE1]":
                    secret = Services.Settings["[SERVICENAME1]"];
                    break;

                case "[PRODUCTTYPE2]":
                    secret = Services.Settings["[SERVICENAME2]"];
                    break;

                case "[PRODUCTTYPE3]":
                    secret = Services.Settings["[SERVICENAME3]"];
                    break;
            }

            signatureString += secret;

            var md5 = PhpCompatible.Md5Hash(signatureString);

            var sig = sorted.FirstOrDefault(k => k.Key.Equals("sig")).Value;

            return sig[0].Equals(md5);

        }
    }

This controller is used for “Receipt Verfication”. Here is an excerpt taken from the fortumo developer documentation:

When someone completes a payment, Fortumo will inform service providers’ servers by making a HTTP GET request to the URL that was specified in the service configuration (for example http://yourdomain.com/in-app-payment.php). The payment processor does not need to be written in PHP any other server side technology (.NET, Java, Ruby on Rails) will work as well. This response is considered successful and notification delivered if your server responds with code 200, otherwise the request will be repeated (up to 10 times). The body of your response will not be processed or forwarded.

Receipt verification is a convenient way for integrating in-app purchases with users online profile so that in-app purchases can be kept track of and shared between different devices and platforms.

Receipt verification can also be used to gather live statistics about purchases made and integrate data with your live dashboard or accounting systems.

First of all you can see that the controller is a custom controller, because it inherits from the “ApiController” class and not from the “TableController” class. It is therefore a standard Web API controller. Four additional attributes are used:

  • AuthorizeLevel – This attribute is used to control the access-level (who can use the controller and execute the actions) – In this case we need fortumo to access it (a third party) and therefore we need anonymous access. 
  • RoutePrefix – This is the route that will be used to access the controller (it defines the root level), the last stage (access to the action itself, Get()) is defined using the Route attribute. In this case the full access URL would be: https://[YOURMOBILESERVICE].azure-mobile.net/api/transactions/fortumo 
  • RequireHttpsWebApi – This is the custom attribute that allows access to the method only using HTTPS and not HTTP

The custom attribute derives from:

C#
<a :="" a="" actioncontext.request.requesturi.scheme="" actioncontext.response="new" authorizationfilterattribute="" color="#0066cc" else="" font="" href=""http://msdn.microsoft.com/en-us/library/system.web.http.filters.authorizati" if="" override="" public="" reasonphrase="HTTPS Required" requirehttpswebapiattribute="" system.web.http.controllers.httpactioncontext="" void="">It hooks into the Web API authorization pipeline and is executed during the authorization process. <a href="http://bitoftech.net/2013/12/03/enforce-https-asp-net-web-api-basic-authentication/" target="_blank">Here is another implementation sample, that is using basic-authentication</a>.

Here is how the Get-Method works:

  • First it checks for a valid caller IP, and tries to parse it
  • Then it maps the IPV6 IP to a IPV4 address and check it against the authorized IP’s that are hardcoded (provided by fortumo) within the AuthorizationRepositoryPayment-Class
  • If all of that is ok, it will check, if only valid GET parameters have been submitted using again the AuthorizationRepositoryPayment-Class and a bit of LINQ
  • If that is ok, it will check the signature using the CheckSignature method
  • If all of that is ok, it will return true otherwise it will log errors using the Services member and return false

Here is the implementation of the AuthorizationRepositoryPayment-Class

C#
public class AuthorizationRepositoryPayment {

        public static IQueryable<string> GetAuthorizedIPs() {

            var ips = new List<string>();

            ips.Add("54.72.6.126");
            ips.Add("54.72.6.27 ");
            ips.Add("54.72.6.17");
            ips.Add("54.72.6.23");
            ips.Add("79.125.125.1");
            ips.Add("79.125.5.205");
            ips.Add("79.125.5.95");

            return ips.AsQueryable();
        }

        public static IQueryable<string> AllowedParameters() {

            var validGetParameters = new List<string>();

            validGetParameters.Add("serviceid");
            validGetParameters.Add("cuid");
            validGetParameters.Add("credit_name");
            validGetParameters.Add("price");
            validGetParameters.Add("currency");
            validGetParameters.Add("country_code");
            validGetParameters.Add("amount");
            validGetParameters.Add("display_type");
            validGetParameters.Add("tc_id");
            validGetParameters.Add("tc_amount");
            validGetParameters.Add("msisdn");

            validGetParameters.Add("service_id");
            validGetParameters.Add("price_wo_vat");
            validGetParameters.Add("revenue");
            validGetParameters.Add("sender");
            validGetParameters.Add("sig");
            validGetParameters.Add("operator");
            validGetParameters.Add("payment_id");
            validGetParameters.Add("status");
            validGetParameters.Add("user_share");

            validGetParameters.Add("test");

            validGetParameters.Sort();

            return validGetParameters.AsQueryable();

        }

    }

The CheckSignature-Method (within the controller-source) is the implementation of the PHP-Method that can be found within the fortumo documentation. Very simple:

  • Get all the GET-parameters
  • Sort the GET-Parameters
  • Concatenate the GET parameters like “[PARAMETER]=[VALUE]” (except for the “sig” parameter) and as last step, add the service-secret at the end of that string
  • Then calculate the MD5 of the string and compare it against the “sig” parameter

I have multiple services so I am passing a string, indicated by [PRODUCTTYPE1-3]. The service secrets are added using Web.config. You add them using the “appSettings” section. The key is your service-name, and the value the secret. Using Services.Settings[“”[KEYNAME]”] will get you the setting from Web.config. This parameter is passed as extra parameter called “credit_name” which is a fortumo standard-definition.

The PHP-MD5 like method source looks like this:

C#
//Taken from:
   //http://www.codeproject.com/Articles/10986/An-MD-Function-Compatible-with-PHP
   public static class PhpCompatible {
       public static string Md5Hash(string pass) {

           if(string.IsNullOrEmpty(pass) || string.IsNullOrWhiteSpace(pass)) {
               throw new ArgumentException("Parmeter cannot be null or empty.", "pass");
           }

           MD5 md5 = MD5CryptoServiceProvider.Create();
           byte[] dataMd5 = md5.ComputeHash(Encoding.Default.GetBytes(pass));
           StringBuilder sb = new StringBuilder();

           for (int i = 0; i < dataMd5.Length; i++)
               sb.AppendFormat("{0:x2}", dataMd5[i]);

           return sb.ToString();
       }
   }

That’s it. I hope it helps. Thanks for reading!

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)