Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Sam Gooch 24 posts 116 karma points
    Oct 09, 2017 @ 13:23
    Sam Gooch
    0

    Currency Conversion Example

    Hi All

    I am developing a site where people can checkout in various currencies. Does anybody have an example of converting the price of an item using a conversion table or api?

    Basically every time I request a product I want to hook into the Merchello event (if there is one) and calculate the price based on the "merchCurrencyCode" stored in the Customer Context.

    Is this possible?

    Thanks, Sam

  • Simon Dingley 1470 posts 3427 karma points c-trib
    Oct 09, 2017 @ 15:32
    Simon Dingley
    1

    I have a solution that stores currency rates in a config file. I use Hangfire for triggering the recurring task on a schedule and serializing the results to a config file. I am currently getting exchange rates from fixer.io on a daily basis because the customer doesn't want to pay a subscription fee right now.

    In my AddToBasket method I do the following;

    var customerCurrency = CurrencyHelper.GetCustomerCurrency();
    
    var variant = product.GetProductVariantDisplayWithAttributes(model.OptionChoices);
    
    variant.Price = CurrencyHelper.ConvertToCurrency(variant.Price, customerCurrency);
    variant.SalePrice = CurrencyHelper.ConvertToCurrency(variant.SalePrice, customerCurrency);
    
    var extendedData = new ExtendedDataCollection();
    extendedData.SetValue(Merchello.Core.Constants.ExtendedDataKeys.CurrencyCode, customerCurrency.CurrencyCode);
    
    this.Basket.AddItem(variant, variant.Name, 1, extendedData);
    

    My ConvertToCurrency method is what I think you are interested in and that currently looks like this:

    /// <summary>
    /// Converts the value to the specified currency based on the converstion rate from GBP.
    /// </summary>
    /// <param name="amount">The amount to convert.</param>
    /// <param name="targetCurrency">The target currency.</param>
    /// <returns>The converted amount based on the converstion rate from GBP.</returns>
    /// <exception cref="System.Exception">Missing currency conversion rate</exception>
    public static decimal ConvertToCurrency(decimal amount, ICurrency targetCurrency)
    {
        // If we are alredy dealing with EUR then we can return the amount as it is
        if (string.Equals(targetCurrency.CurrencyCode, "EUR", StringComparison.CurrentCultureIgnoreCase)) return amount;
    
        var rate = GetExchangeRate("EUR", targetCurrency.CurrencyCode);
    
        if (rate != null) return amount * (decimal)rate.Rate;
    
        throw new Exception("Missing currency conversion rate for " + targetCurrency.CurrencyCode);
    }
    

    And the method that retrieves the exchange rates from the config file as follows;

    /// <summary>
    /// Gets the exchange rate required to convert from one currency to another.
    /// </summary>
    /// <param name="from">The currency that is to be converted from.</param>
    /// <param name="to">The currency that is to be converted to.</param>
    /// <returns>The current exchange rate</returns>
    private static CurrencyExchangeRate GetExchangeRate(string from, string to)
    {
        var serializer = new XmlSerialization();
    
        var rates = serializer.DeSerializeObject<ExchangeRateCollection>(ExchangeRateConfigFilePath);
    
        return rates.FirstOrDefault(r => r.From == from && r.To == to);
    }
    

    One other thing I think I had to do was to add my own Task in the CheckoutManagerInvoiceCreate task chain in Merchello.config. I think it may have replaced the out of the box one. My task looks like this;

    /// <summary>
    /// Performs the task of asserting everything is billed in a common currency.
    /// </summary>
    /// <param name="value">
    /// The value.
    /// </param>
    /// <returns>
    /// The <see cref="Umbraco.Core.Attempt"/>.
    /// </returns>
    public override Attempt<IInvoice> PerformTask(IInvoice value)
    {
        var unTagged = value.Items.Where(x => !x.ExtendedData.ContainsKey(Merchello.Core.Constants.ExtendedDataKeys.CurrencyCode)).ToArray();
        var targetCurrency = CurrencyHelper.GetCustomerCurrency();
    
        if (unTagged.Any())
        {
            foreach (var item in unTagged)
            {
                item.ExtendedData.SetValue(Merchello.Core.Constants.ExtendedDataKeys.CurrencyCode, targetCurrency.CurrencyCode);
            }
        }
    
        var otherCurrencyItems =
            value.Items.Where(
                x => x.ExtendedData[Merchello.Core.Constants.ExtendedDataKeys.CurrencyCode] != targetCurrency.CurrencyCode)
                .ToArray();
    
        if (otherCurrencyItems.Any())
        {
            foreach (var item in otherCurrencyItems)
            {
                item.Price = CurrencyHelper.ConvertToCurrency(item.Price, targetCurrency);
                item.ExtendedData.SetValue(Merchello.Core.Constants.ExtendedDataKeys.CurrencyCode, targetCurrency.CurrencyCode);
            }
        }
    
        var allCurrencyCodes =
            value.Items.Select(x => x.ExtendedData.GetValue(Merchello.Core.Constants.ExtendedDataKeys.CurrencyCode)).Distinct().ToArray();
    
        // Assign the currency code on the invoice
        if (allCurrencyCodes.Length == 1) value.CurrencyCode = allCurrencyCodes.First();
    
        return 1 == allCurrencyCodes.Length
                   ? Attempt<IInvoice>.Succeed(value)
                   : Attempt<IInvoice>.Fail(new InvalidDataException("Invoice is being created with line items costed in different currencies."));
    
    }
    

    Hope that helps point you in the right direction.

  • Sam Gooch 24 posts 116 karma points
    Oct 09, 2017 @ 15:46
    Sam Gooch
    0

    Hi Simon

    That's great, thanks. Are you manually converting the price before you display it to the user? E.g. Product Detail, Collection, Search page.

    And how are you handling shipping charges?

    Thanks, Sam

  • Simon Dingley 1470 posts 3427 karma points c-trib
    Oct 10, 2017 @ 09:34
    Simon Dingley
    1

    Sorry for the delayed reply Sam. Yes, I use an extension method that formats the price and performs a currency conversion (if required).

        /// <summary>
        /// Formats a price with the Merchello's setting currency symbol.
        /// </summary>
        /// <param name="price">The price.</param>
        /// <param name="currency">The currency.</param>
        /// <param name="performCurrencyConversion">if set to <c>true</c> will convert the price to the specified currency.</param>
        /// <returns>
        /// The <see cref="string" />.
        /// </returns>
        public static string FormatPrice(decimal price, ICurrency currency, bool performCurrencyConversion)
        {
            var storeSettingService = MerchelloContext.Current.Services.StoreSettingService;
    
            // Try to get a currency format else use the pre defined one.
            var symbol = currency.Symbol;
            var format = storeSettingService.GetCurrencyFormat(currency);
    
            // If the currency is not the base currency perform a conversion based on the current system exchange rate
            var convertedPrice = performCurrencyConversion ? global::MyCustomer.Infrastructure.CurrencyHelper.ConvertToCurrency(price, currency) : price;
    
            return string.Format(format.Format, symbol, convertedPrice, currency);
        }
    

    Shipping, is handled in the process where you get the ShippingQuote so something like this:

    var shipment = basket.PackageBasket(new Address { Region = province, CountryCode = country.CountryCode }).FirstOrDefault();
    var quote = shipment.ShipmentRateQuotes(false).FirstOrDefault(x => x.ShipMethod.ShipCountryKey == country.Key);
    
    // Clear previous shipment quotes
    checkoutMgr.Shipping.ClearShipmentRateQuotes();
    
    if (quote != null)
    {
        quote.Rate = CurrencyHelper.ConvertToCurrency(quote.Rate, CurrencyHelper.GetCustomerCurrency());
        checkoutMgr.Shipping.SaveShipmentRateQuote(quote);
    }
    

    I should probably add a null check in there before attempting to use the shipment!

  • Sam Gooch 24 posts 116 karma points
    Oct 10, 2017 @ 09:59
    Sam Gooch
    0

    Hi Simon

    Thanks Simon you've been a massive help! I have two more questions if you'd be happy to oblige?

    Do you ever handle a scenario where you have shipment ranges, but the range is calculated in the store currency rather than the customer currency. Have you managed to get around that at all?

    And what did you do with coupon offers for money off? As that currency is the store currency and will need converting to use the customer's currency.

    Thanks, Sam

  • Simon Dingley 1470 posts 3427 karma points c-trib
    Oct 11, 2017 @ 08:37
    Simon Dingley
    1

    Hi Sam,

    The shipping is in my example above, it gets the quote back which will be in the base currency and then performs the conversion. The shipping provider does not need to be aware of the customer's currency.

    I'm pretty sure that the discounts are handled by the ValidateCommonCurrency task because it goes through all items in the basket and if the currency is not the base currency it performs a conversion.

    Give it a go and see how you get on.

    For the customer this was implemented for they wanted a solution with the least maintenance (read no effort required!). In an ideal world, the shipping table would hold values for each currency.

    One other thing of note is that I also had to refactor the Authorize.net payment provider so that I can have one provider supporting multiple accounts (one for each currency) as they don't allow payments in multiple currencies through the same account.

    Cheers, Simon

Please Sign in or register to post replies

Write your reply to:

Draft