top of page
Search

Bitcoin Calculator

  • abhyash
  • Oct 31, 2022
  • 5 min read

Updated: Nov 5, 2022


V1 Prepared By: Abhyash Timsina - 31 October 2022


Motivation: Showcase a demo of Lightning Web Component communicating with APEX to do a REST callout to Bitpay.com, get all currency codes from JSON as well as doing real time calculation – specify a value and currency code and get the currency value. Note - This is a personal project and not intended for financial gain.



My Technical Components






Screenshots of LWC







Source Code for bitcoinLWC (with annotation starting with //)


Html


<!--
 - Created by Abhyash Timsina on 31/10/2022.
 -->

<template>
    <lightning-card title="Bitcoin Value Comparison">
        <div style="text-align: right; padding-right: 20px"> Rate: {rateValue} </div>
        <div class="slds-p-around_medium lgc-bg">
        // Lightning combobox dropdown that displays variables from Javascript for currency codes
            <lightning-combobox
                    name="comparisonCurrencyCodeValue"
                    label="Comparison Currency Code"
                    value={comparisonCurrencyCodeValue}
                    placeholder="Select Currency Code"
                    options={countryCodeOptions}
                    onchange={handleComparisonCurrencyChange}
            ></lightning-combobox>
        </div>

        <div class="slds-p-around_medium lgc-bg">
        
        // Lightning Input that allows user to input the Bitcoin Value
            <lightning-input type="number" name="bitcoinValue" label="Bitcoin Value" value={bitcoinValue} step="0.01" onchange={handleBitcoinValueChange}></lightning-input>
        </div>
        <div class="slds-p-around_medium lgc-bg">
        
        // Lightning Input that allows user to input the Currency Value
            <lightning-input type="number" name="currencyValue" label="Currency Value" value={currencyValue} formatter="currency" step="0.01" onchange={handleCurrencyValueChange}></lightning-input>
        </div>
        
        // Create Record button that creates a record on Bitcoin Request object of above 3 values
        <div style="text-align: right; padding-right: 20px"> <lightning-button variant="brand" label="Create Record" title="Create Record" onclick={createRecord} class="slds-m-left_x-small"></lightning-button> </div>

    </lightning-card>

</template>


JavaScript


/**
 * Created by Abhyash Timsina on 31/10/2022.
 */

// uses track and wire decorators for this example
import {LightningElement, track, wire} from 'lwc';
// imports both methods from apex
import getRateForCurrencyCode from'@salesforce/apex/BitcoinService.getRateForCurrencyCode';
import getCountryCodes from'@salesforce/apex/BitcoinService.getCountryCodes';
// uses createRecord method to create record from lwc
import { createRecord } from 'lightning/uiRecordApi';

let i=0;

export default class BitcoinLwc extends LightningElement {

// set tracking variables. default currency code is Australian Dollars. Some variables are lists
    @track comparisonCurrencyCodeValue = 'AUD';
    @track bitcoinValue = 1.00;
    @track currencyValue;
    @track countryCodeList = [];

    @track rateValue;
    @track error;

    @track countryCodeOptions = [];

// Lifecycle hook that starts when this component is inserted into DOM. In simplest terms - when page is refreshed and loaded
    connectedCallback() {
    // uses Apex method to get the latest rate and store it in rateValue
        getRateForCurrencyCode({comparisonCurrencyCodeValue: this.comparisonCurrencyCodeValue})
            .then(result => {
                this.rateValue = result;
                this.currencyValue = result;
            })
            .catch(error => {
                this.error = error;
            });
            // apex method to retrieve all country codes and store into countryCodeOptions
        getCountryCodes()
            .then(result => {
                this.countryCodeList = result;
                for(i=0; i<result.length; i++)  {
                    this.countryCodeOptions = [...this.countryCodeOptions ,{value: result[i] , label: result[i]} ];
                }
            })
            .catch(error => {
                this.error = error;
            });
    }

// function to create a record when the button is pressed
    createRecord(){
        // Creating mapping of fields of Bitcoin Request with values
        var fields = {'Bitcoin_Value__c' : this.bitcoinValue, 'Rate__c' : this.rateValue, 'Currency_Code__c' : this.comparisonCurrencyCodeValue, 'Currency_Value__c' : this.currencyValue};
        // Record details to pass to create method with api name of Bitcoin Request Object.
        var objRecordInput = {'apiName' : 'Bitcoin_Request__c', fields};
        // Lightning Data Service method to create record
        createRecord(objRecordInput).then(response => {
        // Present alert to user
            alert('Bitcoin Request Record created with Id: ' +response.id);
        }).catch(error => {
            alert('Error: ' +JSON.stringify(error));
        });
    }

// Event handler that tracks when a currency code is changed
    handleComparisonCurrencyChange(event) {
        this.comparisonCurrencyCodeValue = event.detail.value;
        // Each time a new currency code is selected, it uses APEX method to gather the newest rate
        getRateForCurrencyCode({comparisonCurrencyCodeValue: event.detail.value})
        // Then store the result values into the respective fields in the JS & HTML
            .then(result => {
                this.rateValue = result;
                this.bitcoinValue = (this.currencyValue / result);
                this.currencyValue = (result * this.bitcoinValue);
            })
            .catch(error => {
                this.error = error;
            });

    }

// Event handler to handle user input changes in Bitcoin value. Does real time calculation
    handleBitcoinValueChange(event) {
        this.currencyValue = (this.rateValue * event.detail.value);
    }
// Event handler to handle user input changes in Currency Value. Does real time calculation
    handleCurrencyValueChange(event) {
        this.bitcoinValue = (event.detail.value / this.rateValue);
    }

}


Metadata


<?xml version="1.0" encoding="UTF-8"?>

<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>56.0</apiVersion>
    <description>Bitcoin Lwc</description>
    <isExposed>true</isExposed>
    <masterLabel>Bitcoin Lwc</masterLabel>
    
    // Allows LWC to be visible in AppPage, RecordPage and HomePage
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
    </targets>

</LightningComponentBundle>



Source Code for BitcoinService (with annotation starting with //)


/**
 * Created by Abhyash Timsina on 31/10/2022.
 */

public class BitcoinService {

// Wrapper class to store data for Currency Code, Currency Name and Currency Rate - the variable names are used to match the API JSON
    public class BitpayList
    {
        public String code;
        public String name;
        public Decimal rate;
    }

// Method to get country codes. AuraEnabled is required for LWC to communicate with APEX
    @AuraEnabled(Cacheable=true)
    public static List<String> getCountryCodes() {

//Create an empty list of country codes 
        List<String> countryCodesList = new List<String>();
        
  //Create an empty list of wrapper class BitpayList       
        List<BitpayList> response = new List<BitpayList>();

// Do a REST callout to bitpay website using GET
        try {
            HttpRequest req = new HttpRequest();
            HttpResponse res = new HttpResponse();
            Http http = new Http();
            req.setHeader('Content-Type', 'application/json');
            req.setHeader('X-Accept-Version', '2.0.0');
            req.setEndpoint('https://bitpay.com/api/rates/');
            req.setMethod('GET');


            res = http.send(req);

// Capture the response values into the wrapper class values and deserialize the values
            response = (List<BitpayList>) JSON.deserialize(res.getBody(), List<BitpayList>.class);

            System.debug(response);
            System.debug('Str:' + res.getBody());

// For all the returned values, map it with each country code
            for (BitpayList bpl : response) {
                countryCodesList.add(bpl.code);

            }


        } catch (Exception e) {
            System.debug('Error:' + e.getMessage() + 'LN:' + e.getLineNumber());
        }

//Return the value
        return countryCodesList;

    }

// Method to get rate for the provided currency code
    @AuraEnabled(Cacheable=true)
    public static Decimal getRateForCurrencyCode( String comparisonCurrencyCodeValue ) {

        Decimal rateValue;

// The same REST callout as above
        List<BitpayList> response = new List<BitpayList>();

        try {
            HttpRequest req = new HttpRequest();
            HttpResponse res = new HttpResponse();
            Http http = new Http();
            req.setHeader('Content-Type', 'application/json');
            req.setHeader('X-Accept-Version', '2.0.0');
            req.setEndpoint('https://bitpay.com/api/rates/');
            req.setMethod('GET');


            res = http.send(req);


            response = (List<BitpayList>) JSON.deserialize(res.getBody(), List<BitpayList>.class);

            System.debug(response);
            System.debug('Str:' + res.getBody());

// Has a IF statement to pass the rate to the selected country code (remember this method is called upon event change)
            for (BitpayList bpl : response) {
                if (bpl.code == comparisonCurrencyCodeValue) {
                    rateValue = bpl.rate;
                }

            }


        } catch (Exception e) {
            System.debug('Error:' + e.getMessage() + 'LN:' + e.getLineNumber());
        }

//Return the value
        return rateValue;

    }



}



Source Code for BitcoinServiceTest (with annotation starting with //)


/**
 * Created by Abhyash Timsina on 31/10/2022.
 */

@IsTest
private class BitcoinServiceTest {

    @IsTest static void getBitcoinRateTest() {
    
    // Set headers so it imitates a real REST callout
        System.Test.startTest();
        Map<String,String> headers = new Map<String, String>();
        headers.put('Content-Type','application/json');
        headers.put('X-Accept-Version','2.0.0');
        
        // Uses mock class to set expected values from the mock response
        System.Test.setMock(HttpCalloutMock.class, new BitcoinServiceMock(200,'Success','[{"code":"AUD","name":"Australian Dollar","rate":83743.75},{"code":"CNY","name":"Chinese Yuan","rate":396272.55},{"code":"CHF","name":"Swiss Franc","rate":56491.49},{"code":"SEK","name":"Swedish Krona","rate":544288.77},{"code":"NZD","name":"New Zealand Dollar","rate":87039.69}]',headers));
        Decimal rate = 
        
        // Call the BitcoinService apex class for the test with country code AUD as parameter
        BitcoinService.getRateForCurrencyCode('AUD');
        System.Test.stopTest();
        
        // Assert that the rate is accurate
        System.assertEquals(83743.75, rate);
    }
    @IsTest static void getCountryCodesTest() {
        System.Test.startTest();
        Map<String,String> headers = new Map<String, String>();
        headers.put('Content-Type','application/json');
        headers.put('X-Accept-Version','2.0.0');
        
        // Same as above, set mock expected returns
        System.Test.setMock(HttpCalloutMock.class, new BitcoinServiceMock(200,'Success','[{"code":"AUD","name":"Australian Dollar","rate":83743.75},{"code":"CNY","name":"Chinese Yuan","rate":396272.55},{"code":"CHF","name":"Swiss Franc","rate":56491.49},{"code":"SEK","name":"Swedish Krona","rate":544288.77},{"code":"NZD","name":"New Zealand Dollar","rate":87039.69}]',headers));
        List <String> countryCodeList = BitcoinService.getCountryCodes();
        System.Test.stopTest();
        // Assert that the response back is a list of 5 as per the mock
        System.assert(countryCodeList.size()==5);
    }

}



Source Code for BitcoinServiceMock (with annotation starting with //)


/**
 * Created by Abhyash Timsina on 31/10/2022.
 */

// Note that his implements HttpCalloutMock - cannot be used as a standard apex class
 
public with sharing class BitcoinServiceMock implements HttpCalloutMock{

// Same variable values from Bitpay REST JSON
    protected Integer code;
    protected String status;
    protected String body;
    // Variable to store the header data from REST
    protected Map<String , String> responseHeaders;

// Mock Method that accepts passed from APEX header variables 
    public BitcoinServiceMock(Integer code, String status, String body, Map<String,String> responseHeaders) {
        this.code = code;
        this.status = status;
        this.body = body;
        this.responseHeaders = responseHeaders;
    }

    public HttpResponse respond(HttpRequest req) {
        // Create Fake Response
        HttpResponse res = new HttpResponse();
        for (String key : this.responseHeaders.keySet()) {
            res.setHeader(key, this.responseHeaders.get(key));
        }
        Pass the header values to each of the variables back to TEST class
        res.setBody(this.body);
        res.setStatusCode(this.code);
        res.setStatus(this.status);
        return res;
    }

}


Package Link:


If you need help in installing and getting this working – please contact me on LinkedIn





 
 
 

Comments


bottom of page