Click here to Skip to main content
15,885,278 members
Articles / Mobile Apps
Tip/Trick

Azure Mobile Services Managed Backend–fortumo Mobile Payment Integration

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
26 Sep 2014CPOL5 min read 11.8K   1   1
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)


Written By
Software Developer ExGrip LCC
Germany Germany
Working as professional freelancer for the last 5 years. Specialized on and addicted to .NET and a huge fan of Windows Azure from the beginning. Socializing people of all areas, from CEO's to co-workers. Consider myself as a social architect.

Now the proud owner of ExGrip LLC - building mobile cloud experiences. Our latest product "Tap-O-Mizer" is shortly in Beta2. It enables you to see what really needs to be changed in your Windows Phone 8 or Windows 8 app (in real-time, if needed), to ensure customer satisfaction.

Started authorship for "Pluralsight - Hardcore Developer Training" and going to publish the first course on Windows Azure soon.

A few years ago I made a major shift from developer to "devsigner".Focusing my creativity also on great user experiences on Windows, Windows 8 and Windows Phone. Utilizing Expression Design, Expression Blend, Photoshop and Illustrator.

I started developing my first programs on a Commodore C64 (basic and assembly) at the age of fourteen years. Later on an Amiga 500 (C++). After that I made the shift to DOS and Windows from version 3.11 and up.

To me the most important part of developing new experiences is to work with creative and outstanding people and to bring new, exciting ideas to life.

I strongly believe that everyone can be a hero if he/she get's pushed and motivated and valued. Therefore, and that under any circumstances: "People first!"

Specialties:Extremely motivated and pushing people to create results.

Comments and Discussions

 
Bug[My vote of 2] Your code is too heavy and have unnecessary complication Pin
x4572696B4-Nov-15 2:02
x4572696B4-Nov-15 2:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.