Click here to Skip to main content
15,885,366 members
Articles / Programming Languages / Javascript
Article

Currencies in Practice - OutSystems Wrapper for the Fixer.io API

17 Nov 2020CPOL15 min read 4.8K   1   1
In this article I’m going to propose a list of possibilities using Fixer’s API in order to support a set of scenarios to support multi-currency.

This article is in the Product Showcase section for our sponsors at CodeProject. These articles are intended to provide you with information on products and services that we consider useful and of value to developers.

Hello everyone! I’m excited to share awesome integration with the OutSystems platform. For this demonstration, we’ll be integrating Fixer, a simple and lightweight API for current and historical foreign exchange (forex) rates.

So, currencies in practice, huh? If you’re wondering, the answer is yes: the title of this article was inspired in a book that I personally enjoy named Software Architecture in Practice. It’s a good book, you should take a look. It’s not a regular "read from cover to cover" but has very interesting topics about quality attributes and tactics to achieve those same attributes. If you’re interested in the software architecture subject, I think it’s worth a look.

Let’s get back to the subject. As you might know by now, OutSystems supports multi-language and multi-tenancy. But… what about multi-currency? Yes, OutSystems doesn’t support multi-currency, at least directly. And, to be clear, we are not going to create a one-size-fits-all solution for your multi-currency needs. I’m going to propose a list of possibilities using Fixer’s API in order to support a set of scenarios.

And now, the almighty question: which scenarios?

Before going into the scenario list, I would like to make a quick introduction to the subject and clarify a set of terms that will be very, very common from now on.

So, what is a currency?

A currency (from Middle English: curraunt, "in circulation", from Latin: currens, -entis), in the most specific use of the word, refers to money in any form when in actual use or circulation as a medium of exchange, especially circulating banknotes and coins. A more general definition is that a currency is a system of money (monetary units) in common use, especially in a nation. Under this definition, US dollars, British pounds, Australian dollars, European euros and Russian ruble are examples of currency. These various currencies are recognized stores of value and are traded between nations in foreign exchange markets, which determine the relative values of the different currencies. Currencies in this sense are defined by governments, and each type has limited boundaries of acceptance.

The excerpt above was taken from Wikipedia, the free encyclopedia. But, most of us are pretty aware of what a currency is — since we use it pretty much everyday — , right? As such, the relevant question — and that the vast majority might not know — is: why does a currency value change?

According to Investopedia, there are six major factors that influence the value of a currency. And, again, the value of the currency is not an absolute value but always a relative value against a different currency. As of Aug. 21st, 2018, one (1) EURO is worth one United States Dollar and fifteen cents (1.15). The absolute value will always be 1 — one EURO will always be one EURO.

Those major factors are:

  • Differentials in Inflation
  • Differentials in Interest Rates
  • Current Account Deficits
  • Public Debt
  • Terms of Trade
  • Political Stability and Economical Performance

A comprehensive description of these factors along with more information can be seen in the following article: 6 factors that influence exchange rates, from Investopedia.

Now that we’ve seen the tip of the iceberg in terms of the factors that influence currency values, let’s discuss some terms that will be wildly used along the remaining of the article.

  • Symbol: symbol is the code of a given currency. EUR is the symbol for EURO while USD is the symbol for United States Dollar.
  • Base: The base currency/symbol to which the value is calculated against. If the USD value with an EUR base is 1.20, means that €1 will get you $1.2.
  • EOD: end-of-day. In the context of this article, the EOD value will be the reference value for a given day.

Remember the almighty question about the scenarios? So: we are going to retrieve, on an hourly basis, the current value of a set of symbols against a base. By default our base will be EUR. If you need another base — let’s say that your business runs on US Dollars — , we can either:

a) retrieve the new value from the API

b) calculate the value using a common base.

In terms of the possible options, one has potential costs (API restriction), the other… not so much. Not only this, but we’ll support both by providing a set of functions to retrieve a value during a given point in time — which is a good thing if you deal with multi-currency on your financial transactions — and to convert from Currency A to B using a Base C — mostly known as cross-currency rate.

I’m not going to discuss how to get an API key since, well… it’s pretty straightforward on Fixer’s website. Once you have your API key, you can start integrating the available endpoints.

Now, the technicalities.

Fixer provides, by "default", six different endpoints: one for supported symbols and five for the currency values themselves. On these five currency-related endpoints, we have one for the latest values, one for historical values, one for currency conversion, one for currency fluctuation between two dates and one for time-series values.

While the first three seem very clear to their purpose, let me give a brief explanation on the last two endpoints. Consider, just for the sake of this example, two dates: January 1st and January 5th. When you invoke the fluctuation endpoint, you’ll retrieve the difference between the EOD value of Jan 5th and Jan 1st — so, one result only. If you invoke the time-series value for these two dates, you’ll receive the daily EOD value of the given symbols between these five days — so, five results. Once we get to the implementation and the response itself, this will be easier to understand.

Regarding the available endpoints, I would like to make a very important statement here: not all endpoints are included in the free tier. Only Symbols, Latest and Historical data are included in the free tier — which is not that problematic, since we can use the free endpoints to obtain the data related to the paid ones, as long as we do it within the limits of the free tier itself. That is the reason why, when you take a look at the REST integrations, you’ll see two "different" entries: one named FixerFree and the other named FixerPaid. Each of them contains the respective endpoints while the BaseURL property is the same.

The integration

As discussed previously, we’ll implement all the six endpoints available. The implementation logic will be pretty much similar for all the endpoints: we get the full request and the response, we paste into Service Studio and then it just creates the integrations with all the structures. We’ll support this integration with a data model to store all the information. Simple, right?

Well… not so much. And let me explain why, using the Symbols endpoint. Let’s do a GET request to endpoint: http://data.fixer.io/api/symbols?access_key=<API KEY>. This is what you’ll see as a result:

JavaScript
{
    "success": true,
    "symbols": {
        "AED": "United Arab Emirates Dirham",
        "AFN": "Afghan Afghani",
        "ALL": "Albanian Lek",
        "AMD": "Armenian Dram",
        "ANG": "Netherlands Antillean Guilder",
        "AOA": "Angolan Kwanza",
        "ARS": "Argentine Peso",
        "AUD": "Australian Dollar",
        "AWG": "Aruban Florin",
        "AZN": "Azerbaijani Manat",
        "BAM": "Bosnia-Herzegovina Convertible Mark",
        "BBD": "Barbadian Dollar",
        "BDT": "Bangladeshi Taka",
        "BGN": "Bulgarian Lev",
        "BHD": "Bahraini Dinar",
        "BIF": "Burundian Franc",
        "BMD": "Bermudan Dollar",
        "BND": "Brunei Dollar",
        "BOB": "Bolivian Boliviano",
        "BRL": "Brazilian Real",
        "BSD": "Bahamian Dollar",
        "BTC": "Bitcoin",
        "BTN": "Bhutanese Ngultrum",
        "BWP": "Botswanan Pula",
        "BYN": "New Belarusian Ruble",
        "BYR": "Belarusian Ruble",
        "BZD": "Belize Dollar",
        "CAD": "Canadian Dollar",
        "CDF": "Congolese Franc",
        "CHF": "Swiss Franc",
        "CLF": "Chilean Unit of Account (UF)",
        "CLP": "Chilean Peso",
        "CNY": "Chinese Yuan",
        "COP": "Colombian Peso",
        "CRC": "Costa Rican Colón",
        "CUC": "Cuban Convertible Peso",
        "CUP": "Cuban Peso",
        "CVE": "Cape Verdean Escudo",
        "CZK": "Czech Republic Koruna",
        "DJF": "Djiboutian Franc",
        "DKK": "Danish Krone",
        "DOP": "Dominican Peso",
        "DZD": "Algerian Dinar",
        "EGP": "Egyptian Pound",
        "ERN": "Eritrean Nakfa",
        "ETB": "Ethiopian Birr",
        "EUR": "Euro",
        "FJD": "Fijian Dollar",
        "FKP": "Falkland Islands Pound",
        "GBP": "British Pound Sterling",
        "GEL": "Georgian Lari",
        "GGP": "Guernsey Pound",
        "GHS": "Ghanaian Cedi",
        "GIP": "Gibraltar Pound",
        "GMD": "Gambian Dalasi",
        "GNF": "Guinean Franc",
        "GTQ": "Guatemalan Quetzal",
        "GYD": "Guyanaese Dollar",
        "HKD": "Hong Kong Dollar",
        "HNL": "Honduran Lempira",
        "HRK": "Croatian Kuna",
        "HTG": "Haitian Gourde",
        "HUF": "Hungarian Forint",
        "IDR": "Indonesian Rupiah",
        "ILS": "Israeli New Sheqel",
        "IMP": "Manx pound",
        "INR": "Indian Rupee",
        "IQD": "Iraqi Dinar",
        "IRR": "Iranian Rial",
        "ISK": "Icelandic Króna",
        "JEP": "Jersey Pound",
        "JMD": "Jamaican Dollar",
        "JOD": "Jordanian Dinar",
        "JPY": "Japanese Yen",
        "KES": "Kenyan Shilling",
        "KGS": "Kyrgystani Som",
        "KHR": "Cambodian Riel",
        "KMF": "Comorian Franc",
        "KPW": "North Korean Won",
        "KRW": "South Korean Won",
        "KWD": "Kuwaiti Dinar",
        "KYD": "Cayman Islands Dollar",
        "KZT": "Kazakhstani Tenge",
        "LAK": "Laotian Kip",
        "LBP": "Lebanese Pound",
        "LKR": "Sri Lankan Rupee",
        "LRD": "Liberian Dollar",
        "LSL": "Lesotho Loti",
        "LTL": "Lithuanian Litas",
        "LVL": "Latvian Lats",
        "LYD": "Libyan Dinar",
        "MAD": "Moroccan Dirham",
        "MDL": "Moldovan Leu",
        "MGA": "Malagasy Ariary",
        "MKD": "Macedonian Denar",
        "MMK": "Myanma Kyat",
        "MNT": "Mongolian Tugrik",
        "MOP": "Macanese Pataca",
        "MRO": "Mauritanian Ouguiya",
        "MUR": "Mauritian Rupee",
        "MVR": "Maldivian Rufiyaa",
        "MWK": "Malawian Kwacha",
        "MXN": "Mexican Peso",
        "MYR": "Malaysian Ringgit",
        "MZN": "Mozambican Metical",
        "NAD": "Namibian Dollar",
        "NGN": "Nigerian Naira",
        "NIO": "Nicaraguan Córdoba",
        "NOK": "Norwegian Krone",
        "NPR": "Nepalese Rupee",
        "NZD": "New Zealand Dollar",
        "OMR": "Omani Rial",
        "PAB": "Panamanian Balboa",
        "PEN": "Peruvian Nuevo Sol",
        "PGK": "Papua New Guinean Kina",
        "PHP": "Philippine Peso",
        "PKR": "Pakistani Rupee",
        "PLN": "Polish Zloty",
        "PYG": "Paraguayan Guarani",
        "QAR": "Qatari Rial",
        "RON": "Romanian Leu",
        "RSD": "Serbian Dinar",
        "RUB": "Russian Ruble",
        "RWF": "Rwandan Franc",
        "SAR": "Saudi Riyal",
        "SBD": "Solomon Islands Dollar",
        "SCR": "Seychellois Rupee",
        "SDG": "Sudanese Pound",
        "SEK": "Swedish Krona",
        "SGD": "Singapore Dollar",
        "SHP": "Saint Helena Pound",
        "SLL": "Sierra Leonean Leone",
        "SOS": "Somali Shilling",
        "SRD": "Surinamese Dollar",
        "STD": "São Tomé and Príncipe Dobra",
        "SVC": "Salvadoran Colón",
        "SYP": "Syrian Pound",
        "SZL": "Swazi Lilangeni",
        "THB": "Thai Baht",
        "TJS": "Tajikistani Somoni",
        "TMT": "Turkmenistani Manat",
        "TND": "Tunisian Dinar",
        "TOP": "Tongan Paʻanga",
        "TRY": "Turkish Lira",
        "TTD": "Trinidad and Tobago Dollar",
        "TWD": "New Taiwan Dollar",
        "TZS": "Tanzanian Shilling",
        "UAH": "Ukrainian Hryvnia",
        "UGX": "Ugandan Shilling",
        "USD": "United States Dollar",
        "UYU": "Uruguayan Peso",
        "UZS": "Uzbekistan Som",
        "VEF": "Venezuelan Bolívar Fuerte",
        "VND": "Vietnamese Dong",
        "VUV": "Vanuatu Vatu",
        "WST": "Samoan Tala",
        "XAF": "CFA Franc BEAC",
        "XAG": "Silver (troy ounce)",
        "XAU": "Gold (troy ounce)",
        "XCD": "East Caribbean Dollar",
        "XDR": "Special Drawing Rights",
        "XOF": "CFA Franc BCEAO",
        "XPF": "CFP Franc",
        "YER": "Yemeni Rial",
        "ZAR": "South African Rand",
        "ZMK": "Zambian Kwacha (pre-2013)",
        "ZMW": "Zambian Kwacha",
        "ZWL": "Zimbabwean Dollar"
    }
}
Figure 1 — Symbol endpoint output

Image 1

Figure 2 — Generated structures.

Seems like a simple output, right? WRONG! When you use OutSystems default capabilities to import REST method / endpoint, you’ll get what’s depicted on Figure 2. Like, 168 attributes on a structure. As Jesse Ventura would say, I ain’t got time to go over 168 attributes on a structure that I would have to, well, iterate manually**. Also, before anyone hits me with something like "probably ardoJSON could do the trick", I’m going to say this once AND ONLY ONCE:

Image 2

Figure 3 — But it’s a different love, mate!

** Yes, I’m 100% sure Jesse Ventura said this.

So, what did I do? Well, the output of every endpoint except one — Convert — will be plain text. I’ll then use a cool — and custom — C# extension to parse the output and provide OutSystems with a "scalable" version of the response. Despite seeing all the benefits of the low-code platforms, I still like to "hit some lines of code" — inspired by Carlos Carvalhal’s "Put all the meat on the barbecue". And probably that’s why I got some new and old reading materials ready for the next days — despite none of these books being "recommended" readings by OutSystems people.

Image 3

Figure 4 — Reading materials. And yes, those are ball cans. Regarding the last one, my Data Structures and Algorithms teacher would be proud, I’m sure.

Since I don’t want to make this article unnecessarily long, let me quickly round up the development process:

  • We’ll retrieve the full JSON output from Fixer
  • We’ll use a tool to generate a C# class from the JSON output. (Funny part, believe me)
  • We’ll use Integration Studio to implement the methods so we can receive our data in OutSystems.

I’m not going to delve into "how to import REST endpoints into OutSystems". There are lots of resources on the internet and, in fact, it’s so easy that you should try first, all by yourself.

So far we have our endpoints integrated in the Service Studio. As such, let’s move to the funny part: processing the response on the OutSystems extension. As stated above, I’ve used a tool to generate a C# class from a JSON output. If you Google json to c# class, the first hit you’ll see is, probably, http://json2csharp.com. And while this is totally fine, there’s an option on the bottom called Quicktype. I strongly recommend using Quicktype. Why?

I’m going to guide you through my journey and I’ll generate a C# class from a JSON object using json2csharp. For the Symbols endpoint, we get this:

JavaScript
public class Symbols
{
    public string AED { get; set; }
    public string AFN { get; set; }
    public string ALL { get; set; }
    public string AMD { get; set; }
    public string ANG { get; set; }
    public string AOA { get; set; }
    public string ARS { get; set; }
    public string AUD { get; set; }
    public string AWG { get; set; }
    public string AZN { get; set; }
    public string BAM { get; set; }
    public string BBD { get; set; }
    public string BDT { get; set; }
    public string BGN { get; set; }
    public string BHD { get; set; }
    public string BIF { get; set; }
    public string BMD { get; set; }
    public string BND { get; set; }
    public string BOB { get; set; }
    public string BRL { get; set; }
    public string BSD { get; set; }
    public string BTC { get; set; }
    public string BTN { get; set; }
    public string BWP { get; set; }
    public string BYN { get; set; }
    public string BYR { get; set; }
    public string BZD { get; set; }
    public string CAD { get; set; }
    public string CDF { get; set; }
    public string CHF { get; set; }
    public string CLF { get; set; }
    public string CLP { get; set; }
    public string CNY { get; set; }
    public string COP { get; set; }
    public string CRC { get; set; }
    public string CUC { get; set; }
    public string CUP { get; set; }
    public string CVE { get; set; }
    public string CZK { get; set; }
    public string DJF { get; set; }
    public string DKK { get; set; }
    public string DOP { get; set; }
    public string DZD { get; set; }
    public string EGP { get; set; }
    public string ERN { get; set; }
    public string ETB { get; set; }
    public string EUR { get; set; }
    public string FJD { get; set; }
    public string FKP { get; set; }
    public string GBP { get; set; }
    public string GEL { get; set; }
    public string GGP { get; set; }
    public string GHS { get; set; }
    public string GIP { get; set; }
    public string GMD { get; set; }
    public string GNF { get; set; }
    public string GTQ { get; set; }
    public string GYD { get; set; }
    public string HKD { get; set; }
    public string HNL { get; set; }
    public string HRK { get; set; }
    public string HTG { get; set; }
    public string HUF { get; set; }
    public string IDR { get; set; }
    public string ILS { get; set; }
    public string IMP { get; set; }
    public string INR { get; set; }
    public string IQD { get; set; }
    public string IRR { get; set; }
    public string ISK { get; set; }
    public string JEP { get; set; }
    public string JMD { get; set; }
    public string JOD { get; set; }
    public string JPY { get; set; }
    public string KES { get; set; }
    public string KGS { get; set; }
    public string KHR { get; set; }
    public string KMF { get; set; }
    public string KPW { get; set; }
    public string KRW { get; set; }
    public string KWD { get; set; }
    public string KYD { get; set; }
    public string KZT { get; set; }
    public string LAK { get; set; }
    public string LBP { get; set; }
    public string LKR { get; set; }
    public string LRD { get; set; }
    public string LSL { get; set; }
    public string LTL { get; set; }
    public string LVL { get; set; }
    public string LYD { get; set; }
    public string MAD { get; set; }
    public string MDL { get; set; }
    public string MGA { get; set; }
    public string MKD { get; set; }
    public string MMK { get; set; }
    public string MNT { get; set; }
    public string MOP { get; set; }
    public string MRO { get; set; }
    public string MUR { get; set; }
    public string MVR { get; set; }
    public string MWK { get; set; }
    public string MXN { get; set; }
    public string MYR { get; set; }
    public string MZN { get; set; }
    public string NAD { get; set; }
    public string NGN { get; set; }
    public string NIO { get; set; }
    public string NOK { get; set; }
    public string NPR { get; set; }
    public string NZD { get; set; }
    public string OMR { get; set; }
    public string PAB { get; set; }
    public string PEN { get; set; }
    public string PGK { get; set; }
    public string PHP { get; set; }
    public string PKR { get; set; }
    public string PLN { get; set; }
    public string PYG { get; set; }
    public string QAR { get; set; }
    public string RON { get; set; }
    public string RSD { get; set; }
    public string RUB { get; set; }
    public string RWF { get; set; }
    public string SAR { get; set; }
    public string SBD { get; set; }
    public string SCR { get; set; }
    public string SDG { get; set; }
    public string SEK { get; set; }
    public string SGD { get; set; }
    public string SHP { get; set; }
    public string SLL { get; set; }
    public string SOS { get; set; }
    public string SRD { get; set; }
    public string STD { get; set; }
    public string SVC { get; set; }
    public string SYP { get; set; }
    public string SZL { get; set; }
    public string THB { get; set; }
    public string TJS { get; set; }
    public string TMT { get; set; }
    public string TND { get; set; }
    public string TOP { get; set; }
    public string TRY { get; set; }
    public string TTD { get; set; }
    public string TWD { get; set; }
    public string TZS { get; set; }
    public string UAH { get; set; }
    public string UGX { get; set; }
    public string USD { get; set; }
    public string UYU { get; set; }
    public string UZS { get; set; }
    public string VEF { get; set; }
    public string VND { get; set; }
    public string VUV { get; set; }
    public string WST { get; set; }
    public string XAF { get; set; }
    public string XAG { get; set; }
    public string XAU { get; set; }
    public string XCD { get; set; }
    public string XDR { get; set; }
    public string XOF { get; set; }
    public string XPF { get; set; }
    public string YER { get; set; }
    public string ZAR { get; set; }
    public string ZMK { get; set; }
    public string ZMW { get; set; }
    public string ZWL { get; set; }
}

public class RootObject
{
    public bool success { get; set; }
    public Symbols symbols { get; set; }
}
Figure 5 — json2csharp output.

See the result? I’m not a C# expert at all but I wondered: how would I convert this set of elements into a Key-Pair list, knowing the Key is the name of the field itself?

Hello, haaaaave you met Reflection? For those who don’t know, Reflection is for programmers what the Boogeyman is for kids. I might be exaggerating a bit here — unless you try to do meta-programming with CLOS: in that case, the Boogeyman is as evil as the Cookie Monster.

Reflection is awesome, but it is also problematic. The performance cost of using reflection is non-negligible. Luckily, using Quicktype, I got this neat version:

JavaScript
// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do:
//
//    using OutSystems.NssFixerParser.Integrations;
//
//    var getSymbols = GetSymbols.FromJson(jsonString);

namespace OutSystems.NssFixerParser.Integrations
{
    using System;
    using System.Collections.Generic;

    using System.Globalization;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Converters;

    public partial class GetSymbols
    {
        [JsonProperty("success")]
        public bool Success { get; set; }

        [JsonProperty("symbols")]
        public Dictionary<string, string> Symbols { get; set; }
    }

    public partial class GetSymbols
    {
        public static GetSymbols FromJson(string json)
        {
            return JsonConvert.DeserializeObject<GetSymbols>(json, OutSystems.NssFixerParser.Integrations.Converter.Settings);
        }
    }

    public static class Serialize
    {
        public static string ToJson(this GetSymbols self)
        {
            return JsonConvert.SerializeObject(self, OutSystems.NssFixerParser.Integrations.Converter.Settings);
        }
    }

    internal static class Converter
    {
        public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
        {
            MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
            DateParseHandling = DateParseHandling.None,
            Converters = {
                new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
            },
        };
    }
}
Figure 6 — Quicktype output

Relevant note: when using Quicktype to generate C# classes from JSON objects for OutSystems use, make sure the C# version you select is V5. By default, V6 is selected and that won’t compile. Note: on a chat with other fellow MVP’s, it could be related to the MSBuild. I’ll take a further look and update the post accordingly.

Somewhat-relevant note: if you use multiple classes generated by Quicktype that have elements in common — like an Error structure — , just move those classes into separate files. You can then remove them from the generated code and, as long as you have everything referenced, no errors will appear.

Image 4

Figure 7— Extension structure.

Based on the previous note, I’ve moved the internal static class Converter, public static class Serialize and the Error class — which is not shown on figure 6 — into separate files, with the end result being something like Figure 7. Doing this, we’ll have four classes for five endpoints — Convert endpoint is not considered for this approach — , the Converter, the Serialize and the Error classes (generics). If you’re good with math, you’re wondering: wait, what? Four classes for five endpoints?

Yes, and that’s because Historical and Latest rates have the same output structure. So, what can we do? That’s right, we can use the same code for it. Saving work already.

Image 5

Figure 8 — We’re all good with math. Source: Amazon.ca.

Let’s now move into the action itself. On the Integration Studio, we’ll create an extension with the configurations shown on Figure 9.

Image 6

Figure 9 — Extension configuration.

Note: before anyone asks, a Java version is not available at the moment. It might happen in the future, although very unlikely.

Then we’ll define an action and the following parameters — as shown on Figure 10:

  • JSONString (Input)
  • Success (Output)
  • Symbols — The key-pair list (Output)
  • Error — generic error structure (Output)

Image 7

Figure 10— Integration Studio with Parse_GetSymbols open.

Now that we have defined our action and updated our extension code, it’s time to use our generated classes:

JavaScript
/// <summary>
/// 
/// </summary>
/// <param name="ssJSONString"></param>
/// <param name="ssSuccess"></param>
/// <param name="ssSymbols"></param>
/// <param name="ssError"></param>
public void MssParse_GetSymbols(string ssJSONString, out bool ssSuccess, 
                                out RLKeyPairStructureRecordList ssSymbols, 
                                out RCErrorRecord ssError)
{
        ssSuccess = false;
        ssSymbols = new RLKeyPairStructureRecordList();
        ssError = new RCErrorRecord(null);

        GetSymbols getSymbols = GetSymbols.FromJson(ssJSONString);
        if (getSymbols.Success)
        {
                foreach (String ISOCode in getSymbols.Symbols.Keys)
                {
                        RCKeyPairStructureRecord rec = new RCKeyPairStructureRecord();
                        rec.ssSTKeyPairStructure.ssKey = ISOCode;
                        rec.ssSTKeyPairStructure.ssValue = getSymbols.Symbols[ISOCode];
                        ssSymbols.Append(rec);
                }
                ssSuccess = true;
        }
        else
        {
                ssSuccess = false;
                ssError.ssSTError.ssCode = getSymbols.Error.Code.ToString();
                ssError.ssSTError.ssInfo = getSymbols.Error.Info;
                ssError.ssSTError.ssType = getSymbols.Error.Type;
        }
} // MssParse_GetSymbols
Figure 11 — Parse_GetSymbols method body.

Above you can see what we are doing. This is a common procedure across all actions. What differs from one to others, is the complexity and/or number of fields. If you want, feel free to take a look at the extension’s source code and let me know if you have any question, concern or suggestions. Once again, sharing is caring.

So, besides the regular initialization of variables, we deserialize our JSON string into a GetSymbols object (line 16) and we validate if the request was successful. If so, we iterate over our dictionary and create a KeyPairStructureRecord — I wonder why the full name is RC KeyPairStructure Record since best practices tell us to start a record name with RC… — that we’ll append to the list.

If the request was not successful, we fill our error structure. Simple.

I’m going to add a personal comment here: when I started working for this component, I was eager to write an article all about Reflection with C#, so you would think I was a SUPER DUPER ÜBER C# Programmer. But this is the cool thing about software development: the things you think, they change so quickly and easily (#Agile). And that, my friends, is the beauty of our job — if I ever change career to a TV News anchor, that will be my closing line.

But… how do we use it?

It’s worth nothing creating a component and write an article about it if you don’t show it working. So, if you recall from a previous article, we already have a Control Panel that would be a great candidate for this component.

So, summing up — and creating a scenario — , we already have a control panel that shows us the latest payments made through our gateway. Those latest payments can have different currencies but our business only runs on EUROS.

On the Figure 17 of the referenced article (Figure 12 on this one), you have the following image:

Image 8

Figure 12 — Adyen Control Panel

On the top right corner, we have a CardSimple with the Top Payments in the last 30 days. It’s hard to see, but we have payments in EURO, YEN and others. Let’s now change this CardSimple and make sure we show the conversion value for the day that the transaction took place. The original value will be converted against EURO. This has a "licensing" reason, that I’ll explain later.

For the sake of simplicity, I’m going to use one of the available functions — CalculateConversion. For each item on the ListRecords widget, I’ll invoke the function and the result will be computed. The function behavior is the shown on Figure 13.

Image 9

Figure 13 — CalculateConversion function.

This function receives the following input parameters: BaseCurrency (Text), DestinationCurrency (Text), Amount (Currency), Date (Date) and one output parameter: Value (Currency). Now, step by step:

  • We check if a currency value for the given date is already available (GetConversion aggregate). If it exists, we return the value.
  • If doesn’t exist, we try to do a cross-currency conversion. If both values are available for the mentioned date, we return the computed value. The calculation is done the following way — elements shortened on purpose.

Amount / (GetSourceCurrency.Value / GetDestinationCurrency.Value)

  • If the values are not available, we execute a request to the API that, if successful, will return the Rate. Since we’re just requesting one value, we can immediately map to the output.

Then, on the ListRecords present on the Card widget, we’ll add this to our expression — as shown on Figure 14. It’s a bit "bloated" due to the usage of the FormatCurrency function to the converted amount. Also, since we’re using EURO as a base, we don’t need to do any calculation of decimal places. Refer to the previous article in order to understand this concept.

Image 10

Figure 14 — ListRecords expression.

We had the first FormatCurrency and now we added the text part " (EUR: XXX)", in which the XXX will be computed using the CalculateConversion function. Just one important note: see that I didn’t use the DestinationCurrency parameter when invoking the CalculateConversion function? This is because we’re mapping the DestinationCurrency parameter to the Base parameter on the service invocation. Since we’re using the free tier, we can’t use the base parameter on the API calls and, by default, it will be EUR. If you have, at least, the basic membership, you can use it.

And here’s the final result — which is a perfect match with Google’s value at the corresponding day:

Image 11

Figure 15 — End result on the UI.

And that’s all, folks! The component is already available at OutSystems Forge at https://www.outsystems.com/forge/component/4122/fixer-io-api/.

Before finishing, I’m going to try something new. I’m looking into the possibility of publish sponsored articles. If you (or your company) have some money to spare and would like to sponsor any article or about a specific subject, please drop me a note through hello@armandogom.es . And, just to be clear on this: I really enjoy writing and creating new contents so I won’t keep the money for myself.

As such, all the profits of any sponsored article will be donated to charity.

For more details on this, ping me here, through the email above, Twitter, or LinkedIn.

End with - And that’s all, folks! The component is already available at OutSystems Forge at https://www.outsystems.com/forge/component/4122/fixer-io-api/.

License

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


Written By
Founder
Portugal Portugal
Founder of Volitional Software, OutSystems Expert and MVP.

Comments and Discussions

 
QuestionThanks For Sharing Pin
Hyland Computer Systems17-Nov-20 10:12
Hyland Computer Systems17-Nov-20 10:12 
Hello,

I'm a Developer of Financial Apps and this looks very intriguing and helpful.

Again, thanks for sharing.
"Try?! Try, Not! Do... Or DO NOT!!" - Master Yoda
"Learn, Know. Know, Learn... " - Robert Hyland

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.