We recently had a requirement to load SAP S/4 HANA Exchange Rate tables from OANDA, one of the leaders in foreign currency exchange arena – https://www.oanda.com/
In SAP, exchange rates are stored in the TCURR table and you can use transaction code OB08 to show the maintenance view to the table:
So now, our requirement is to update this table from the OANDA interface API call.
The approach we took here was to implement the ALE distribution model to allow for outbound and inbound integration for exchange rates. The outbound integration can be used to send the exchange rates from SAP to other SAP systems or other downstream systems. It also is a way to generate the IDoc to view how it needs to appear for the equivalent inbound process instead of coming up with all of the fields that need to be mapped from scratch.
So let’s generate an EXCHANGE_RATE IDoc so we can take a look at the IDoc segments and fields:
Here is the call to OANDA:
https://www1.oanda.com/rates/api/v2/rates/candle.xml?api_key=your_api_key_here&date_time=2021-08-15&base=USD"e=CAD&fields=averages
Here is the response:
<?xml version="1.0" encoding="utf-8"?>
<response>
<meta>
<effective_params>
<data_set>OANDA</data_set>
<base_currencies>
<currency>USD</currency>
</base_currencies>
<quote_currencies>
<currency>CAD</currency>
</quote_currencies>
<date_time>2021-08-15T00:00:00+00:00</date_time>
<fields>
<field>averages</field>
</fields>
</effective_params>
<endpoint>candle</endpoint>
<request_time>2021-10-29T04:17:36+00:00</request_time>
<skipped_currency_pairs></skipped_currency_pairs>
</meta>
<quotes>
<quote>
<base_currency>USD</base_currency>
<quote_currency>CAD</quote_currency>
<start_time>2021-08-15T00:00:00+00:00</start_time>
<open_time>2021-08-15T00:00:00+00:00</open_time>
<close_time>2021-08-15T23:59:59+00:00</close_time>
<average_bid>1.25114</average_bid>
<average_ask>1.25180</average_ask>
<average_midpoint>1.25147</average_midpoint>
</quote>
</quotes>
</response>
Here is the IFlow:
For now, we just have a timer that executes when we deploy the flow. This can be scheduled at regular intervals as needed.
Here is the Set Dates Groovy script to set the date interval of the OANDA API call:
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
import java.util.Calendar;
import java.util.Date;
def Message processData(Message message) {
// def yesterday = new Date() - 1
// message.setProperty("yesterday", yesterday.format("yyyy-MM-dd"))
// return message
// Get instance of the calender
Calendar calendar = Calendar.getInstance();
// Let's go to last month
calendar.add(Calendar.MONTH, -1);
// Get the last date of that month
int max = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
// Set the calendar to the end of that month
calendar.set(Calendar.DAY_OF_MONTH, max);
// Get the date of the this last day of the month
def endOfLastMonth = calendar.getTime();
// Set this property
message.setProperty("endOfLastMonth", endOfLastMonth.format("yyyy-MM-dd"))
// Let's go to month before last month
calendar.add(Calendar.MONTH, -1);
// Get the last date of that month
max = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
// Set the calendar to the end of that month
calendar.set(Calendar.DAY_OF_MONTH, max);
// Get the date of the this last day of the month
def endOfLastLastMonth = calendar.getTime();
// Set this property
message.setProperty("endOfLastLastMonth", endOfLastMonth.format("yyyy-MM-dd"))
// Read in the externalized parameter
def map = message.getProperties();
def mode = map.get("mode");
def strDateInput;
if (mode.equals("candle")) {
strInput = "date_time=" + endOfLastMonth.format("yyyy-MM-dd");
message.setProperty("dateInput", strInput);
} else if (mode.equals("aggregated")) {
strInput = "start_time=" + endOfLastLastMonth.format("yyyy-MM-dd") + "&end_time=" + endOfLastMonth.format("yyyy-MM-dd");
message.setProperty("dateInput",strInput);
} else {
strInput = "Logic=NotWorking";
message.setProperty("dateInput", strInput);
}
return message;
}
Here is the HTTP Connection to OANDA:
Here is the Groovy script to log the OANDA response:
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
def Message processData(Message message) {
def body = message.getBody(java.lang.String) as String;
def messageLog = messageLogFactory.getMessageLog(message);
//Properties
def properties = message.getProperties();
String sBody = "OANDA Response XML";
if(messageLog != null) {
messageLog.setStringProperty(sBody, body);
messageLog.addAttachmentAsString(sBody, body, 'text/xml');
}
return message;
}
Here is the simple graphical mapping of the OANDA response XML to the Exchange Rate IDoc:
Here is the Groovy script to log the mapped Exchange rate IDoc:
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
def Message processData(Message message) {
def body = message.getBody(java.lang.String) as String;
def messageLog = messageLogFactory.getMessageLog(message);
//Properties
def properties = message.getProperties();
String sBody = "ExchangeRate IDoc for S4 HANA";
if(messageLog != null) {
messageLog.setStringProperty(sBody, body);
messageLog.addAttachmentAsString(sBody, body, 'text/xml');
}
return message;
}
So here is the execution of the IFlow with the OANDA response and mapped IDoc captured as attachments:
Here is the OANDA response XML:
<?xml version="1.0" encoding="utf-8"?>
<response>
<meta>
<effective_params>
<data_set>OANDA</data_set>
<base_currencies>
<currency>CAD</currency>
<currency>EUR</currency>
<currency>USD</currency>
<currency>USD</currency>
</base_currencies>
<quote_currencies>
<currency>CAD</currency>
<currency>EUR</currency>
<currency>GBP</currency>
<currency>USD</currency>
</quote_currencies>
<date_time>2021-09-30T00:00:00+00:00</date_time>
<fields>
<field>averages</field>
</fields>
</effective_params>
<endpoint>candle</endpoint>
<request_time>2021-10-15T17:44:43+00:00</request_time>
<skipped_currency_pairs>
</skipped_currency_pairs>
</meta>
<quotes>
<quote>
<base_currency>CAD</base_currency>
<quote_currency>CAD</quote_currency>
<start_time>2021-09-30T00:00:00+00:00</start_time>
<open_time>2021-09-30T00:00:00+00:00</open_time>
<close_time>2021-09-30T23:59:59+00:00</close_time>
<average_bid>1.00000</average_bid>
<average_ask>1.00000</average_ask>
<average_midpoint>1.00000</average_midpoint>
</quote>
<quote>
<base_currency>CAD</base_currency>
<quote_currency>EUR</quote_currency>
<start_time>2021-09-30T00:00:00+00:00</start_time>
<open_time>2021-09-30T00:00:00+00:00</open_time>
<close_time>2021-09-30T23:59:59+00:00</close_time>
<average_bid>0.678736</average_bid>
<average_ask>0.678941</average_ask>
<average_midpoint>0.678838</average_midpoint>
</quote>
<quote>
<base_currency>CAD</base_currency>
<quote_currency>GBP</quote_currency>
<start_time>2021-09-30T00:00:00+00:00</start_time>
<open_time>2021-09-30T00:00:00+00:00</open_time>
<close_time>2021-09-30T23:59:59+00:00</close_time>
<average_bid>0.584443</average_bid>
<average_ask>0.584625</average_ask>
<average_midpoint>0.584534</average_midpoint>
</quote>
<quote>
<base_currency>CAD</base_currency>
<quote_currency>USD</quote_currency>
<start_time>2021-09-30T00:00:00+00:00</start_time>
<open_time>2021-09-30T00:00:00+00:00</open_time>
<close_time>2021-09-30T23:59:59+00:00</close_time>
<average_bid>0.786678</average_bid>
<average_ask>0.786811</average_ask>
<average_midpoint>0.786744</average_midpoint>
</quote>
<quote>
<base_currency>EUR</base_currency>
<quote_currency>CAD</quote_currency>
<start_time>2021-09-30T00:00:00+00:00</start_time>
<open_time>2021-09-30T00:00:00+00:00</open_time>
<close_time>2021-09-30T23:59:59+00:00</close_time>
<average_bid>1.47288</average_bid>
<average_ask>1.47333</average_ask>
<average_midpoint>1.47310</average_midpoint>
</quote>
<quote>
<base_currency>EUR</base_currency>
<quote_currency>EUR</quote_currency>
<start_time>2021-09-30T00:00:00+00:00</start_time>
<open_time>2021-09-30T00:00:00+00:00</open_time>
<close_time>2021-09-30T23:59:59+00:00</close_time>
<average_bid>1.00000</average_bid>
<average_ask>1.00000</average_ask>
<average_midpoint>1.00000</average_midpoint>
</quote>
<quote>
<base_currency>EUR</base_currency>
<quote_currency>GBP</quote_currency>
<start_time>2021-09-30T00:00:00+00:00</start_time>
<open_time>2021-09-30T00:00:00+00:00</open_time>
<close_time>2021-09-30T23:59:59+00:00</close_time>
<average_bid>0.860986</average_bid>
<average_ask>0.861170</average_ask>
<average_midpoint>0.861078</average_midpoint>
</quote>
<quote>
<base_currency>EUR</base_currency>
<quote_currency>USD</quote_currency>
<start_time>2021-09-30T00:00:00+00:00</start_time>
<open_time>2021-09-30T00:00:00+00:00</open_time>
<close_time>2021-09-30T23:59:59+00:00</close_time>
<average_bid>1.15888</average_bid>
<average_ask>1.15903</average_ask>
<average_midpoint>1.15896</average_midpoint>
</quote>
<quote>
<base_currency>USD</base_currency>
<quote_currency>CAD</quote_currency>
<start_time>2021-09-30T00:00:00+00:00</start_time>
<open_time>2021-09-30T00:00:00+00:00</open_time>
<close_time>2021-09-30T23:59:59+00:00</close_time>
<average_bid>1.27095</average_bid>
<average_ask>1.27117</average_ask>
<average_midpoint>1.27106</average_midpoint>
</quote>
<quote>
<base_currency>USD</base_currency>
<quote_currency>CAD</quote_currency>
<start_time>2021-09-30T00:00:00+00:00</start_time>
<open_time>2021-09-30T00:00:00+00:00</open_time>
<close_time>2021-09-30T23:59:59+00:00</close_time>
<average_bid>1.27095</average_bid>
<average_ask>1.27117</average_ask>
<average_midpoint>1.27106</average_midpoint>
</quote>
<quote>
<base_currency>USD</base_currency>
<quote_currency>EUR</quote_currency>
<start_time>2021-09-30T00:00:00+00:00</start_time>
<open_time>2021-09-30T00:00:00+00:00</open_time>
<close_time>2021-09-30T23:59:59+00:00</close_time>
<average_bid>0.862788</average_bid>
<average_ask>0.862903</average_ask>
<average_midpoint>0.862846</average_midpoint>
</quote>
<quote>
<base_currency>USD</base_currency>
<quote_currency>EUR</quote_currency>
<start_time>2021-09-30T00:00:00+00:00</start_time>
<open_time>2021-09-30T00:00:00+00:00</open_time>
<close_time>2021-09-30T23:59:59+00:00</close_time>
<average_bid>0.862788</average_bid>
<average_ask>0.862903</average_ask>
<average_midpoint>0.862846</average_midpoint>
</quote>
<quote>
<base_currency>USD</base_currency>
<quote_currency>GBP</quote_currency>
<start_time>2021-09-30T00:00:00+00:00</start_time>
<open_time>2021-09-30T00:00:00+00:00</open_time>
<close_time>2021-09-30T23:59:59+00:00</close_time>
<average_bid>0.742926</average_bid>
<average_ask>0.743031</average_ask>
<average_midpoint>0.742978</average_midpoint>
</quote>
<quote>
<base_currency>USD</base_currency>
<quote_currency>GBP</quote_currency>
<start_time>2021-09-30T00:00:00+00:00</start_time>
<open_time>2021-09-30T00:00:00+00:00</open_time>
<close_time>2021-09-30T23:59:59+00:00</close_time>
<average_bid>0.742926</average_bid>
<average_ask>0.743031</average_ask>
<average_midpoint>0.742978</average_midpoint>
</quote>
<quote>
<base_currency>USD</base_currency>
<quote_currency>USD</quote_currency>
<start_time>2021-09-30T00:00:00+00:00</start_time>
<open_time>2021-09-30T00:00:00+00:00</open_time>
<close_time>2021-09-30T23:59:59+00:00</close_time>
<average_bid>1.00000</average_bid>
<average_ask>1.00000</average_ask>
<average_midpoint>1.00000</average_midpoint>
</quote>
<quote>
<base_currency>USD</base_currency>
<quote_currency>USD</quote_currency>
<start_time>2021-09-30T00:00:00+00:00</start_time>
<open_time>2021-09-30T00:00:00+00:00</open_time>
<close_time>2021-09-30T23:59:59+00:00</close_time>
<average_bid>1.00000</average_bid>
<average_ask>1.00000</average_ask>
<average_midpoint>1.00000</average_midpoint>
</quote>
</quotes>
</response>
Here is the EXCHANGE_RATE IDoc:
<?xml version="1.0" encoding="UTF-8"?>
<EXCHANGE_RATE01>
<IDOC BEGIN="1">
<EDI_DC40 SEGMENT="1">
<TABNAM>EDI_DC40</TABNAM>
<DIRECT>1</DIRECT>
<IDOCTYP>EXCHANGE_RATE01</IDOCTYP>
<MESTYP>EXCHANGE_RATE</MESTYP>
<SNDPOR>CPIDEV</SNDPOR>
<SNDPRT>LS</SNDPRT>
<SNDPRN>CPIDEV</SNDPRN>
<RCVPOR>SAPSS4</RCVPOR>
<RCVPRT>LS</RCVPRT>
<RCVPRN>SS4CLNT055</RCVPRN>
</EDI_DC40>
<E1EXCHANGE_RATE SEGMENT="1">
<LOG_SYSTEM>CPIDEV</LOG_SYSTEM>
<UPD_ALLOW>X</UPD_ALLOW>
<DEV_ALLOW>000</DEV_ALLOW>
<E1BP1093_0 SEGMENT="1">
<RATE_TYPE>M</RATE_TYPE>
<FROM_CURR>CAD</FROM_CURR>
<TO_CURRNCY>CAD</TO_CURRNCY>
<VALID_FROM>20210816</VALID_FROM>
<EXCH_RATE>1.00000</EXCH_RATE>
<FROM_FACTOR>1</FROM_FACTOR>
<TO_FACTOR>1</TO_FACTOR>
</E1BP1093_0>
<E1BP1093_0 SEGMENT="1">
<RATE_TYPE>M</RATE_TYPE>
<FROM_CURR>CAD</FROM_CURR>
<TO_CURRNCY>EUR</TO_CURRNCY>
<VALID_FROM>20210816</VALID_FROM>
<EXCH_RATE>0.678838</EXCH_RATE>
<FROM_FACTOR>1</FROM_FACTOR>
<TO_FACTOR>1</TO_FACTOR>
</E1BP1093_0>
<E1BP1093_0 SEGMENT="1">
<RATE_TYPE>M</RATE_TYPE>
<FROM_CURR>CAD</FROM_CURR>
<TO_CURRNCY>GBP</TO_CURRNCY>
<VALID_FROM>20210816</VALID_FROM>
<EXCH_RATE>0.584534</EXCH_RATE>
<FROM_FACTOR>1</FROM_FACTOR>
<TO_FACTOR>1</TO_FACTOR>
</E1BP1093_0>
<E1BP1093_0 SEGMENT="1">
<RATE_TYPE>M</RATE_TYPE>
<FROM_CURR>CAD</FROM_CURR>
<TO_CURRNCY>USD</TO_CURRNCY>
<VALID_FROM>20210816</VALID_FROM>
<EXCH_RATE>0.786744</EXCH_RATE>
<FROM_FACTOR>1</FROM_FACTOR>
<TO_FACTOR>1</TO_FACTOR>
</E1BP1093_0>
<E1BP1093_0 SEGMENT="1">
<RATE_TYPE>M</RATE_TYPE>
<FROM_CURR>EUR</FROM_CURR>
<TO_CURRNCY>CAD</TO_CURRNCY>
<VALID_FROM>20210816</VALID_FROM>
<EXCH_RATE>1.47310</EXCH_RATE>
<FROM_FACTOR>1</FROM_FACTOR>
<TO_FACTOR>1</TO_FACTOR>
</E1BP1093_0>
<E1BP1093_0 SEGMENT="1">
<RATE_TYPE>M</RATE_TYPE>
<FROM_CURR>EUR</FROM_CURR>
<TO_CURRNCY>EUR</TO_CURRNCY>
<VALID_FROM>20210816</VALID_FROM>
<EXCH_RATE>1.00000</EXCH_RATE>
<FROM_FACTOR>1</FROM_FACTOR>
<TO_FACTOR>1</TO_FACTOR>
</E1BP1093_0>
<E1BP1093_0 SEGMENT="1">
<RATE_TYPE>M</RATE_TYPE>
<FROM_CURR>EUR</FROM_CURR>
<TO_CURRNCY>GBP</TO_CURRNCY>
<VALID_FROM>20210816</VALID_FROM>
<EXCH_RATE>0.861078</EXCH_RATE>
<FROM_FACTOR>1</FROM_FACTOR>
<TO_FACTOR>1</TO_FACTOR>
</E1BP1093_0>
<E1BP1093_0 SEGMENT="1">
<RATE_TYPE>M</RATE_TYPE>
<FROM_CURR>EUR</FROM_CURR>
<TO_CURRNCY>USD</TO_CURRNCY>
<VALID_FROM>20210816</VALID_FROM>
<EXCH_RATE>1.15896</EXCH_RATE>
<FROM_FACTOR>1</FROM_FACTOR>
<TO_FACTOR>1</TO_FACTOR>
</E1BP1093_0>
<E1BP1093_0 SEGMENT="1">
<RATE_TYPE>M</RATE_TYPE>
<FROM_CURR>USD</FROM_CURR>
<TO_CURRNCY>CAD</TO_CURRNCY>
<VALID_FROM>20210816</VALID_FROM>
<EXCH_RATE>1.27106</EXCH_RATE>
<FROM_FACTOR>1</FROM_FACTOR>
<TO_FACTOR>1</TO_FACTOR>
</E1BP1093_0>
<E1BP1093_0 SEGMENT="1">
<RATE_TYPE>M</RATE_TYPE>
<FROM_CURR>USD</FROM_CURR>
<TO_CURRNCY>CAD</TO_CURRNCY>
<VALID_FROM>20210816</VALID_FROM>
<EXCH_RATE>1.27106</EXCH_RATE>
<FROM_FACTOR>1</FROM_FACTOR>
<TO_FACTOR>1</TO_FACTOR>
</E1BP1093_0>
<E1BP1093_0 SEGMENT="1">
<RATE_TYPE>M</RATE_TYPE>
<FROM_CURR>USD</FROM_CURR>
<TO_CURRNCY>EUR</TO_CURRNCY>
<VALID_FROM>20210816</VALID_FROM>
<EXCH_RATE>0.862846</EXCH_RATE>
<FROM_FACTOR>1</FROM_FACTOR>
<TO_FACTOR>1</TO_FACTOR>
</E1BP1093_0>
<E1BP1093_0 SEGMENT="1">
<RATE_TYPE>M</RATE_TYPE>
<FROM_CURR>USD</FROM_CURR>
<TO_CURRNCY>EUR</TO_CURRNCY>
<VALID_FROM>20210816</VALID_FROM>
<EXCH_RATE>0.862846</EXCH_RATE>
<FROM_FACTOR>1</FROM_FACTOR>
<TO_FACTOR>1</TO_FACTOR>
</E1BP1093_0>
<E1BP1093_0 SEGMENT="1">
<RATE_TYPE>M</RATE_TYPE>
<FROM_CURR>USD</FROM_CURR>
<TO_CURRNCY>GBP</TO_CURRNCY>
<VALID_FROM>20210816</VALID_FROM>
<EXCH_RATE>0.742978</EXCH_RATE>
<FROM_FACTOR>1</FROM_FACTOR>
<TO_FACTOR>1</TO_FACTOR>
</E1BP1093_0>
<E1BP1093_0 SEGMENT="1">
<RATE_TYPE>M</RATE_TYPE>
<FROM_CURR>USD</FROM_CURR>
<TO_CURRNCY>GBP</TO_CURRNCY>
<VALID_FROM>20210816</VALID_FROM>
<EXCH_RATE>0.742978</EXCH_RATE>
<FROM_FACTOR>1</FROM_FACTOR>
<TO_FACTOR>1</TO_FACTOR>
</E1BP1093_0>
<E1BP1093_0 SEGMENT="1">
<RATE_TYPE>M</RATE_TYPE>
<FROM_CURR>USD</FROM_CURR>
<TO_CURRNCY>USD</TO_CURRNCY>
<VALID_FROM>20210816</VALID_FROM>
<EXCH_RATE>1.00000</EXCH_RATE>
<FROM_FACTOR>1</FROM_FACTOR>
<TO_FACTOR>1</TO_FACTOR>
</E1BP1093_0>
<E1BP1093_0 SEGMENT="1">
<RATE_TYPE>M</RATE_TYPE>
<FROM_CURR>USD</FROM_CURR>
<TO_CURRNCY>USD</TO_CURRNCY>
<VALID_FROM>20210816</VALID_FROM>
<EXCH_RATE>1.00000</EXCH_RATE>
<FROM_FACTOR>1</FROM_FACTOR>
<TO_FACTOR>1</TO_FACTOR>
</E1BP1093_0>
</E1EXCHANGE_RATE>
</IDOC>
</EXCHANGE_RATE01>
So to summarize, in this example CPI triggers the OANDA exchange rate API call and maps the response data to the Exchange rate IDoc and send this to SAP to update the TCURR table.
I hope that comes in handy to you if you are trying to implement a CPI interface that calls an external foreign exchange rate API but not necessarily OANDA, and map this back to SAP format and send this to SAP S/4 HANA system. We also investigated the SOAP approach but we only see the outbound SOAP async ExchangeRateMasterDataReplicationBundleRequest_Out and not an equivalent inbound SOAP async message.
Here you can see the outbound exchange rate
However, there does not appear to be an inbound asynchronous service exposed through the API Hub at this point but hopefully that will be added soon. But the IDoc approach works really well.