dev

New Version of the Object Mapper v1.0.6 Has Been Released

A new version Object Mapper for Python has been released.object_mapper_logo

 

Get it on Pypi via

pip install object-mapper

Now it supports possibility to specify list of fields that should be excluded from the mapping, i.e.:

result = mapper.map(FromTestClass(), ToTestClass, excluded=['date'])

will map fromTestClass to ToTestClass except property date in source class.

 

You can clone it from GitHub too. Enjoy!

dev

New Version of the GPWebpayNet v1.3.3 Has Been Released

logoA new version of the GPWebpayNet v1.3.3 has been released. It contains following changes regarding to version v1.2.0.:

v1.3.0

Possibility to set X509KeyStorageFlags arguments to be able to change them while creating X509Certificate2 instance. The change is not a breaking one because arguments are optional. For more details check IClientService.PostRequestAsyncIClientService.GenerateGPWebPayRedirectUrl.

v1.3.1

This version update data type of property PaymentRequest.Amount from decimal to int. This decision was make to because specification does not require a concrete type, just an amount in cents, i.e. value $4.99 will be converted to 499. The next reason is to prevent misunderstanding of users, which happened twice.

v1.3.2

Properties PaymentRequest.Description and PaymentRequest.MD are validated to fulfill specification requirements – an ASCII string with maximal length 255 characters. If such conditions are not satisfied, an exception InvalidPaymentRequestDataException is raised in PaymentRequestTransformer.GetParametersForDigestCalculation during request object transformation.

v1.3.3

To have better overview of validation errors on GP server returned in properties PaymentResponse.PRCode and PaymentResponse.SRCode, a better error description is provided, while a PaymentResponseException is raised in ClientService.ProcessGPWebPayResponse. Exception message contains returned codes with particular description from official documentation. This feature was added because of SDK users for easier debugging.

Install it from NuGet via

Install-Package GPWebpayNet.Sdk -Version 1.3.3

Clone it on GitHub here.

Enjoy!

 

dev

Strava Upload – A Library For Synchronization of Strava Activities and Other Services (Suunto Movescount, etc.)

Overview

Strava Upload is s simple library that  synchronizes moves (activities) with Strava. In the first version it supports moves from Suunto Movescount (a generic move/activity class is planned to support other services). If the activity is found on Strava, it tries to update its description. If not, a new activity is being created.

Library Description

The library is written in .NetCore (.Net Standard 2.0). The code is available  GitHub and you can find it in NuGet too.

PM > Install-Package StravaUpload

It consists of the following projects:

StravaUpload.Lib

The main project with the following important classes.

Uploader.cs

The main class for uploading. Nothing complicated :).

StravaUpload.StravaUploadFunction

Azure function that periodically downloads data from Suunto Movescount, store them in Azure Blob Storage and synchronize them with Strava periodically.

StravaUpload.Console

Just a test project for the library created before Azure function project.

Requirements

To be able to connect to Movescount API, you have to have following keys that are required to be able to get data:

  • MovescountAppKey – App key to be able to query Movescount API.
  • MovescountUserKey – User key to be able to query Movescount API.
  • MovescountUserEmail – User email to be able to query Movescount API.
  • MovescountMemberName – Name of the member whose data we want to get.
  • CookieValue – A cookie value that is needed to export GPX, TCX and other move files. This value can be get by the following steps:
    1. Open console in your browser to se network requests.
    2. Login into Movescount.
    3. Select a request to http://www.movescount.com/api/members/private/messages.
    4. Copy value of Cookie key in request header. It should start with ASP.NET ….

Other Configuration for Strava and Azure function:

  • BackupDir – Backup directory. Used as a root directory in Azure Blob Storage.
  • StorageConnectionString – Connection string to Azure Blob Storage.
  • ContainerName – Name of the Azure Blob Storage container.
  • StravaAccessToken – Readwrite access token to Strava.
  • SendGridApiKey – ApiKey to Sendgrid for email notification (optional).
  • EmailFrom – Sender email address (optional).
  • EmailTo – Recipient email address (optional).

Deployment

Just deploy StravaUpload.StravaUploadFunction to Azure and set required configuration. Thats all :).

Contributing

Contributing is welcomed.

dev

Movescount Backup – A Library For Full Backup of Moves From Suunto Movescount

Overview

Movescount Backup is s simple library that can download move records from Suunto Movescount. I created is just because there is no official way how to create a backup of all your moves and a optional possibility yo migrate them to other service such as Garmin or Strava.

With this library you can periodically call it and download all data about move that is available on Movescount – description, details, GPS data, media files, etc. Next you can you these downloaded data and upload them to other services. In my case I’m using these data to update activities on Strava. Because move -> activity synchronization between these services does not copy all data. And manual update is boring and time consuming.

Library Description

The library is written in .NetCore (.Net Standard 2.0). The code is available  GitHub and you can find it in NuGet too.

PM > Install-Package MovescountBackup

 

It consists of the following projects:

MovescountBackup.Lib

The main project with the following important classes.

Client.cs

Client is the class responsible for downloading data from Movescount. Because there is only need to get list of moves and one particular move with details, there are no other calls, such as crate new move, update move, etc. But, it can be extended to a full Movescount API client. The only problem is that the API spec is not public, so some inspection is required (e.g., searching on GitHub).

Downloader.cs

A class that uses Client to download single or multiple moves and store them to local disk (FileSystemStorage) or Azure Blob Store (CloudStorage). A sample Downloader usage can be found on GitHub.

Requirements

To be able to connect to Movescount API, you have to have following keys that are required to be able to get data:

  • MovescountAppKey – App key to be able to query Movescount API.
  • MovescountUserKey – User key to be able to query Movescount API.
  • MovescountUserEmail – User email to be able to query Movescount API.
  • MovescountMemberName – Name of the member whose data we want to get.
  • CookieValue – A cookie value that is needed to export GPX, TCX and other move files. This value can be get by the following steps:
    1. Open console in your browser to se network requests.
    2. Login into Movescount.
    3. Select a request to http://www.movescount.com/api/members/private/messages.
    4. Copy value of Cookie key in request header. It should start with ASP.NET ….

Deployment

You can use this lib to be run manually (MovescountBackup.Console.Program.cs) or you can create, e.g., a Azure function Lambda function on AWS, that can run periodically, as described in overview. As mentioned before, it is available in NuGet.

Contributing

Contributing is welcomed. The Client implements just two calls to Movescount API because it is enough for the backup. But implementation of the full API can be useful in other cases. So if you want such ability, feel free to extend the Client class.

dev

Deploying and Hosting Static Web Sites (ReactJS, Angular) in Azure App Service

Outline

On Azure, you can host a static web multiple ways

  • As separate App Service
  • Via Blob Storage and CDN
  • As a part of App Service (that hosts, e.g., an API)

Hosting As a Part of App Service

You you want to host the site in some existing App Service that already hosts some app, e.g., Web Api, it is a very simple process – just copy the built web into wwwroot folder. There is only one problem – proper routing. Because now there are basically two sources that are provided:

  • Web site
  • Api endpoints

the simplest way how to distinguish between Api and Web App is to use different ports. But it is not possible in our case because App Service supports only port 80/443. So it must be done in a different way.

Rewrite Rules

In out case we will use rewrite rules defined in immortal web.config file. Basically, we have to distinguish between following resources:

  • Api routes (third negation in wwwroot-spa-routing-to-index rule)
  • Web App root file, e.g., index.html (rules wwwroot-static-files and wwwroot-spa-routing-to-index)
  • Static files, e.g., images, fonts, styles, etc. (content of element handlers)
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <system.webServer xdt:Transform="Insert">
        <rewrite>
            <rules>
                <rule name="wwwroot-static-files" stopProcessing="true">
                    <!-- Following extensions should be defined in <handlers /> section -->
                    <match url="([\S]+[.](html|htm|svg|js|css|png|gif|jpg|jpeg|woff|woff2|eot|ttf|map))" />
                    <action type="Rewrite" url="wwwroot/{R:1}" />
                </rule>
                <rule name="wwwroot-index" stopProcessing="true">
                    <match url="^$" />
                    <action type="Rewrite" url="wwwroot/index.html" />
                </rule>
                <!--
                    Note: root path must be set to "/" ,i.e., <base href="/" />
                    otherwise relative paths will not work
                -->
                <rule name="wwwroot-spa-routing-to-index" stopProcessing="true">
                    <match url=".*" />
                    <conditions logicalGrouping="MatchAll">
                        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
                        <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
                        <add input="{REQUEST_URI}" pattern="api/" negate="true" />
                    </conditions>
                    <action type="Rewrite" url="wwwroot/index.html" />
                </rule>
            </rules>
        </rewrite>
        <handlers>
            <add name="StaticFileModuleHtml" path="*.htm*" verb="*" modules="StaticFileModule" resourceType="File"                  requireAccess="Read" />
            <add name="StaticFileModuleSvg" path="*.svg" verb="*" modules="StaticFileModule" resourceType="File"                  requireAccess="Read" />
            <add name="StaticFileModuleJs" path="*.js" verb="*" modules="StaticFileModule" resourceType="File"                  requireAccess="Read" />
            <add name="StaticFileModuleCss" path="*.css" verb="*" modules="StaticFileModule" resourceType="File"                  requireAccess="Read" />
            <add name="StaticFileModuleJpeg" path="*.jpeg" verb="*" modules="StaticFileModule" resourceType="File"                  requireAccess="Read" />
            <add name="StaticFileModuleJpg" path="*.jpg" verb="*" modules="StaticFileModule" resourceType="File"                  requireAccess="Read" />
            <add name="StaticFileModulePng" path="*.png" verb="*" modules="StaticFileModule" resourceType="File"                  requireAccess="Read" />
            <add name="StaticFileModuleGif" path="*.gif" verb="*" modules="StaticFileModule" resourceType="File"                  requireAccess="Read" />
            <add name="StaticFileModuleWoff" path="*.woff" verb="*" modules="StaticFileModule" resourceType="File"                  requireAccess="Read" />
            <add name="StaticFileModuleWoff2" path="*.woff2" verb="*" modules="StaticFileModule" resourceType="File"                  requireAccess="Read" />
            <add name="StaticFileModuleEot" path="*.eot" verb="*" modules="StaticFileModule" resourceType="File"                  requireAccess="Read" />
            <add name="StaticFileModuleTtf" path="*.ttf" verb="*" modules="StaticFileModule" resourceType="File"                  requireAccess="Read" />
            <add name="StaticFileModuleMap" path="*.map" verb="*" modules="StaticFileModule" resourceType="File"                  requireAccess="Read" />
            <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
        </handlers>
        <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false"                     stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" />
    </system.webServer>
</configuration>

 

Thats all. Routes matching api/ pattern will be routed to your Web Api. All other routes except static files will be routed to the index.html, where the SPA’s routing will do its work.

Conclusion

There are many methods how to publish SPA web site in Azure. If you want to host together with your Web Api in the same App Service, all you need is a proper web.config setup.

dev

GPWebpayNet – A .NET Library for Communication With GP Webpay Payment Gateway

Overview

In these days we are interconnecting multiple services into our information systems. When we are deciding which service we will integrate, except the functionality, pricing, etc., an important aspect is ease of integration. The service should contain some documentation, but support such as SDK or package for as the most popular languages should be available too.

This is not exactly the case of GP Webpay. They provide documentation for their solution. But for example a documentation for HTTP API is from year 2016. There is no SDK or source code example, except sample code for evaluation that our implementation of calculation of digest (a hash calculated from request data) is returning valid value. These samples do nothing more. Another thing is that C# code sample is written .NET 3.5. Really, in 2018. But in newer version of .NET and especially with .NETCore, interfaces changed. So it is quite obsolete.

As I mentioned, document describing how the request should be created, signed, etc., is provided. There is funny sentence that implementation will take three days in average. Ok, but if they provide some SDK, it can take few hours. Next, when the GP Webpay change their protocol, they can just provide new SDK version so consumers can update their code very simply. Without any inspection of internals.

So when I was implementing communication with this gateway, I extracted the code and created a library.

Library Description

Library supports following .NET versions:

  • Full .NET Framework 4.6.1
  • .netstandart 1.6
  • .netstandart 2.0

Use Cases

You can use is together with WebAPI and SPA of with ASP MVC. It is up to you.

The base GP webpay process (simplified) is as follows:

  1. Generate payment request
  2. Send it via GET or POST go payment gateway
  3. Process GET request from GP webpay

The process with API and SPA is as follows:

  1. SPA send order request to API
  2. API generates payment request for GET (url) and sends it back to SPA
  3. SPA use url and redirects to it
  4. GP webpay send request to API
  5. API process request and redirect to SPA with corresponding result

The process with MVC is as follows:

  1. Send request to MVC
  2. MVC generates payment request for GET/POST and redirects to GP webpay
  3. GP webpay send request to MVC
  4. MVC process request and redirect to SPA with corresponding result

Project Structure

GPWebpayNet.Example

It contains examples how to use this SDK. For more details you can check GPWebpayNet.SDK.Spec too.

GPWebpayNet.SDK

The main project. The main project class is GPWebpayNet.Sdk.Services.ClientService that provides methods needed for payment process. Other classes are used by this class. Whole project is designed to be used with some IoC framework so classes are decoupled.

GPWebpayNet.Example

It contains SDK unit tests.

Usage Samples

Get Redirect URL for Payment Request

public string GetRedirectUrl()
{
  var loggerFactory = new LoggerFactory().AddConsole();

  const string url = Constants.GPWebpayUrlTest;
  const string privateCertificateFile = "certs/test.pfx";
  const string privateCertificateFilePassword = "test";
  const string publicCertificateFile = "certs/test.pfx";
  const string publicCertificateFilePassword = "test";
  var doc = new XmlDocument();
  doc.AppendChild(doc.CreateElement("Info"));

  var request = new PaymentRequest
  {
    MerchantNumber = "235235",
    OrderNumber = 2412,
    Amount = new decimal(64.6546),
    Currency = CurrencyCodeEnum.Eur,
    DepositFlag = 1,
    MerOrderNumber = "MerOrderNumber",
    Url = "https://www.example.org",
    Description = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit.",
    MD = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit.",
    PaymentMethod = PaymentMethodEnum.Mps,
    DisabledPaymentMethod = PaymentMethodEnum.Crd,
    PaymentMethods = new[] {PaymentMethodEnum.Mcm, PaymentMethodEnum.NotSet},
    Email = "user@example.org",
    ReferenceNumber = "77987",
    AddInfo = doc.DocumentElement,
    Lang = "CZ"
  };

  var encodingLogger = loggerFactory.CreateLogger();
  var clientServiceLogger = loggerFactory.CreateLogger();
  var clientService = new ClientService(new EncodingService(encodingLogger),
  new PaymentRequestTransformer(), new PaymentResponseTransformer(), clientServiceLogger);				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			>clientServiceLogger);

  return clientService.GenerateGPWebPayRedirectUrl(
    url,
    request,
    privateCertificateFile,
    privateCertificateFilePassword,
    publicCertificateFile,
    publicCertificateFilePassword);
}

Process Incomming GP webpay request

public void ProcessIncommingGPWPRequest()
{
  var loggerFactory = new LoggerFactory().AddConsole();

  // Args from Request object
  var queryArgs = new QueryCollection(new Dictionary()
  {
    {"OPERATION", new StringValues("Operation")},
    {"ORDERNUMBER", new StringValues("12332")},
    {"PRCODE", new StringValues("0")},
    {"SRCODE", new StringValues("0")},
    {"RESULTTEXT", new StringValues("ResultText")},
    {"USERPARAM1", new StringValues("UserParam1")},
    {"DIGEST", new StringValues("Digest")},
    {"DIGEST1", new StringValues("Digest1")},
  });

  const string publicCertificateFile = "certs/test.pfx";
  const string password = "test";

  var encodingLogger = loggerFactory.CreateLogger();
  var clientServiceLogger = loggerFactory.CreateLogger();
  var clientService = new ClientService(new EncodingService(encodingLogger),
  new PaymentRequestTransformer(), new PaymentResponseTransformer(), clientServiceLogger);

  // Service will creates PaymentResponse from incomming args and validate response digest with
  // public certificate provided by GPWP and then check if "PRCODE" and "SRCODE" values have correct or error values
  // ReSharper disable once UnusedVariable
  var paymentResponse = clientService.ProcessGPWebPayResponse(queryArgs, publicCertificateFile, password);
}

That’s all – two methods. Yes, you should read how the process and communication with the gateway works, but it is really not important to know how exactly the protocol, especially creation of message, is done if you really do not need it – which is exactly this case. So GP webpay, do it. You will have more satisfied customers.

Where To Get It

You can find NuGet package here. Just add it to your project:

PM> Install-Package GPWebpayNet.Sdk

Or clone in on GitHub.

You can of course support this project too :).