<< 19 June 2017 | Home | 21 June 2017 >>

Currency conversion in Apex

While waiting for my flight in the lounge tonight I was playing around with currencies in Salesforce because - why not... Conversion between configured currencies are supported in SOQL and Salesforce but only between the configured corporate currency and the users personal currency. But what if you want to convert between an opportunity amount in one currency and into another currency using the configured conversion rates in Salesforce? Well there is no support for this. So as an Apex / SOQL self-assignment I wrote the below class to do that. Basically it lazily reads in configured currencies and allows you to convert between any currency, from a supplied currency to the corporate currency or from a supplied currency to the users own currency. For extra credits it follows the decimal places configured in the Salesforce Setup.

Please note code is provided as-is without any warrenties or guarantees. As a friend always writes --- YMMV....

public class CurrencyConverter {
    private Map conversions = null;
    private String corporateIso = null;
    private String userIso = null;
    
    /**
     * Initialize corporate currencies setup in Setup.
     */
    private void initCorpCurrencies() {
        // build once only
        if (null != this.conversions) return;
        
        // build map
        this.conversions = new Map();
        final List currencies = [select Id, IsCorporate, IsoCode, ConversionRate, DecimalPlaces from CurrencyType where IsActive=true];
        for (CurrencyType cur : currencies) {
            this.conversions.put(cur.IsoCode, cur);
            if (cur.IsCorporate) this.corporateIso = cur.IsoCode;
        }
    }
    
    /**
     * Read user currency from users preferences.
     */
    private void initUserCurrency() {
        // load only once
        if (null != this.userIso) return;
        
        // get user currency ISO and store it
        List users = [SELECT DefaultCurrencyIsoCode FROM User WHERE Id =: UserInfo.getUserId()];
        if (null == users || users.size() != 1) {
           throw new UnknownUserException('Could not find user record for active user <' + UserInfo.getUserId() + '>');
        }
        this.userIso = users[0].DefaultCurrencyIsoCode;
    }
    
    /**
     * Get corporate currency.
     */
    public String getCorporateISO() {
        this.initCorpCurrencies();
        return this.corporateIso;
    }
    
    /**
     * Get user currency.
     */
    public String getUserISO() {
        this.initUserCurrency();
        return this.userIso;
    }
    
    /**
     * Convert from supplied currency to corpotate currency.
     */
    public Decimal convertToCorporateCurrency(Decimal value, String fromIso) {
        return this.convert(value, fromIso, this.getCorporateIso());
    }
    
    /**
     * Convert from supplied currency to users currency.
     */
    public Decimal convertToUserCurrency(Decimal value, String fromIso) {
        return this.convert(value, fromIso, this.getUserISO());
    }
    
    /**
     * Convert between two known currencies.
     */
    public Decimal convert(Decimal value, String fromIso, String toIso) {
        if (String.isEmpty(fromIso) || String.isEmpty(toIso)) {
            return value;
        }
        this.initCorpCurrencies();
        
        // ensure valid to/from ISO
        if (!this.conversions.containsKey(fromIso)) {
           throw new UnknownCurrencyException('Unable to find active from ISO currency <' + fromISO + '>');
        }
        if (!this.conversions.containsKey(toIso)) {
           throw new UnknownCurrencyException('Unable to find active to ISO currency <' + toISO + '>');
        }
        
        // if same currencies we simply round
        if (fromIso.equalsIgnoreCase(toIso)) {
            return value.setScale(this.conversions.get(fromIso.toUpperCase()).DecimalPlaces, System.RoundingMode.HALF_UP);
        }
        
        // get values and then rate
        final CurrencyType fromCur = this.conversions.get(fromIso.toUpperCase());
        final Decimal fromRate = fromCur.ConversionRate;
        final CurrencyType toCur = this.conversions.get(toIso.toUpperCase());
        final Decimal toRate = toCur.ConversionRate;
        final Decimal rate = toRate/fromRate;
        
        // calc
        final Decimal result = value * rate;
        final Decimal resultRounded = result.setScale(toCur.DecimalPlaces, System.RoundingMode.HALF_UP);
        
        // return
        return resultRounded;
    }
    
    public class UnknownUserException extends Exception {
        
    }
    
    public class UnknownCurrencyException extends Exception {
        
    }
}