cancel
Showing results for 
Search instead for 
Did you mean: 

CTI Adapter related Q&A for Cloud for Service

pushkar_ranjan
Employee
Employee
0 Kudos

Q - In the prerequisites I noticed that we need to use Microsoft® Visual Studio to edit the CTI Connector project, this is extra tool  or is included in C4C / CTI Adapter solution?

A —> Visual Studio license is not included. Customer or partner who develops integration to C4C using this plugin needs to have valid Visual Studio licenses for development. Modifications to the project is required only to support outbound calls. If the call center is only receiving calls from customers, there is no need to edit this project.

Q - Also I read that all parameters are included in the following extension spot, available in the SDK: ES_COD_CTI_CALL_HANDLING.

SDK is a prerequisite?

A —> SDK is not a pre-requisite. The enhancement spot can be used if additional search parameters need to be used to search tickets based on call data that is captured. E.g. If the IVR captures a mileage card number, and this should be used to search customers and tickets in C4C, the exit can be used.

Q - I assume that the CTI Adapter part is SAP responsibility  and not partner or customer responsibility, SAP will  build DLL´s 

A —> We deliver a CTI Client Adapter that can be uses as-is. This component is our responsibility. Any CTI vendor can integrate to C4C CTI Client adapter by just passing events, with no modifications. But, if a partner wants to make changes to the client adapter, they can, and then the maintenance of this customized version becomes the customer’s responsibility.

Q - When you said  some development / customization on the Avaya soft phone, what do we need to discuss with Avaya in order to figure out those developments

A —> I believe partners should be able to extend it as well. I am not familiar with this part. The soft phone is a piece of software that Avaya ships, and I assume it will have customization capabilities to be able to trigger additional events to our CTI adapter.

Accepted Solutions (0)

Answers (6)

Answers (6)

Former Member
0 Kudos

Dear colleagues,
We now try to integrate C4C (via CTI adapter) with Asterisk (ip-tel.).
In integration guide we found information about needed DLL (p.6-7) to provide outboard calls...
Can you please provide us by example of code of dial-out logic?


Maybe your customers did this type of integration before?..

Do you know where we can find examples of dial-out logic to dll (in CTI adapter)?


rds88
Explorer
0 Kudos

For outbound calls, here's a sample implementation that requires .NET Framework 4.6 or later (I provide no guarantees whatsoever).

Note that you do not need Visual Studio to build and run the solution, you can instead just install a current version of a .NET SDK (see dotnet.microsoft.com/en-us/download), use your favorite editor to edit the sources, and run the build/publish commands from command line. In order for that to work, you need to port the provided project file into the new Project SDK format. I'll also provide an example project file at the end of the post.

I bundle and ship a configuration file called "SAPCODCTIConnector.exe.config" in order to adapt to changing endpoint URLs without having to change the code.

Context Information

Context information can be obtained using the optional parameter "context" of method "dialOut". Thanks to https://blogs.sap.com/2018/11/28/cti-integration-enabling-outbound-calls-in-rui/ for providing that info, which cannot be found in the official docs.

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
using System.Net.Http;
using System.Net.Http.Headers;

using Newtonsoft.Json;
using System.Web;
using System.Net;
using System.Threading.Tasks;
using System.Threading;

namespace SAP.COD.CTI.Connector
{
    /// <summary>
    /// Handles outbound calls of the SAP Fiori Client using the SAP Service Cloud CTI Client Adapter.
    /// </summary>
    /// <see href="https://help.sap.com/docs/SAP_CLOUD_FOR_CUSTOMER/5f35ee8b31e44f2786d7c2696defa2f6/281b7d35a520464f942b3b34692c045d.html"/>
    public class OutboundCallHandler
    {
        /// <summary>
        /// Name of the config key for the retrieval of the endpoint base URL to the staging/UAT system.
        /// </summary>
        const string ConfigKey_EndpointBaseUrlStaging = "UserAcceptanceTestEndpoint";

        /// <summary>
        /// Name of the config key for the retrieval of the endpoint base URL to the production system.
        /// </summary>
        const string ConfigKey_EndpointBaseUrlProduction = "ProductionEndpoint";

        /// <summary>
        /// Name of the config key for the retrieval of the run mode.
        /// </summary>
        const string ConfigKey_UserAcceptanceTestMode = "UserAcceptanceTestMode";

        /// <summary>
        /// The endpoint base URL to the staging/UAT system.
        /// </summary>
        Uri uriStaging;

        /// <summary>
        /// The endpoint base URL to the production system.
        /// </summary>
        Uri uriProduction;

        /// <summary>
        /// Assume we run in production until otherwise noted.
        /// </summary>
        bool inProduction = true;

        /// <summary>
        /// An instance of the logger.
        /// </summary>
        readonly Logger logUtil = new Logger();

        /// <summary>
        /// An instance of the HTTP client used to make the call to the endpoint.
        /// </summary>
        readonly HttpClient httpClient = new HttpClient();

        //Add constructor to initiate logging
        public OutboundCallHandler()
        {
            try
            {
                ReadConfiguration();
                ConfigureHttpClient();
            }
            catch (ConfigurationErrorsException ex)
            {
                logUtil.writeLog(ex.ToString());
            }
            catch (Exception ex)
            {
                logUtil.writeLog(ex.ToString());
            }
        }

        //Destructor
        ~OutboundCallHandler()
        {
            httpClient?.Dispose();
            logUtil.Close();
        }

        private void ReadConfiguration()
        {
            var configFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
            var configKeys = ConfigurationManager.AppSettings.AllKeys;

            if (!configKeys.Contains(ConfigKey_UserAcceptanceTestMode))
            {
                ThrowMissingConfigKeyException(configFile, ConfigKey_UserAcceptanceTestMode);
            }

            var mode = ConfigurationManager.AppSettings[ConfigKey_UserAcceptanceTestMode];

            if (bool.TryParse(mode, out bool inUserAcceptanceMode))
            {
                inProduction = !inUserAcceptanceMode;
            }

            if (inProduction && !configKeys.Contains(ConfigKey_EndpointBaseUrlProduction))
            {
                ThrowMissingConfigKeyException(configFile, ConfigKey_EndpointBaseUrlProduction);
            }

            uriProduction = new Uri(ConfigurationManager.AppSettings[ConfigKey_EndpointBaseUrlProduction]);

            if (!inProduction && !configKeys.Contains(ConfigKey_UserAcceptanceTestMode))
            {
                ThrowMissingConfigKeyException(configFile, ConfigKey_EndpointBaseUrlStaging);
            }

            uriStaging = new Uri(ConfigurationManager.AppSettings[ConfigKey_EndpointBaseUrlStaging]);

            logUtil.writeLog($"Mode: {(inProduction ? "production" : "user acceptance test")}");
            logUtil.writeLog($"Endpoint: {(inProduction ? uriProduction.ToString() : uriStaging.ToString())}");
        }

        private void ConfigureHttpClient()
        {
            // Must end with trailing slash
            httpClient.BaseAddress = new Uri((inProduction ? uriProduction : uriStaging).AbsoluteUri);

            httpClient.DefaultRequestHeaders.Accept.Clear();
            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            // Optional headers
            httpClient.DefaultRequestHeaders.Add("User-Agent", "sap_c4c-cti_connector");

            httpClient.Timeout = TimeSpan.FromSeconds(15);
        }

        private void ThrowMissingConfigKeyException(string configFile, string configKey)
        {
            throw new ConfigurationErrorsException($"The required configuration key '{configKey}' could not be found in configuration file '{configFile}'.");
        }

        public bool dialOut(string dialData, [Optional] string context)
        {
            try
            {
                DialData data = new DialData();

                if (!string.IsNullOrWhiteSpace(context))
                {
                    var contextData = JsonConvert.DeserializeObject<ContextData>(context);
                    data.ActivityUUID = contextData.ActivityUUID;
                    data.LoggedInUserID = contextData.LoggedInUserID;
                }

                data.PhoneNumber = dialData.Trim().Replace(" ", "");

                bool result = ProcessOutboundCallAsync(data).GetAwaiter().GetResult();

                return result;
            }
            catch (HttpRequestException ex)
            {
                logUtil.writeLog(ex.ToString());
            }
            catch (Exception ex)
            {
                logUtil.writeLog(ex.ToString());
            }

            return false;
        }

        private async Task<bool> ProcessOutboundCallAsync(DialData data, CancellationToken token = default)
        {
            var builder = new UriBuilder(httpClient.BaseAddress);
            builder.Path = $"{builder.Path}/makeCall/{data.LoggedInUserID}";

            var query = HttpUtility.ParseQueryString(builder.Query);
            query["callee"] = data.PhoneNumber;
            builder.Query = query.ToString();

            var response = await httpClient.GetAsync(builder.Uri, token);

            if (response.IsSuccessStatusCode)
            {
                if (response.StatusCode == HttpStatusCode.OK)
                {
                    var content = await response.Content.ReadAsStringAsync();
                    var json = JsonConvert.DeserializeObject<DialDataResponse>(content);
                    var jsonReformatted = JsonConvert.SerializeObject(json);
                    logUtil.writeLog($"HTTP {response.StatusCode}: Call successful. Response: {jsonReformatted}");
                }

                return true;
            }

            switch (response.StatusCode)
            {
                case HttpStatusCode.BadRequest:
                    logUtil.writeLog($"HTTP {response.StatusCode}: Invalid phone number provided.");
                    break;
                case HttpStatusCode.NotFound:
                    logUtil.writeLog($"HTTP {response.StatusCode}: No agent found or multiple agents found for the logged-in user, or campaign could not be determinated.");
                    break;
                case HttpStatusCode.InternalServerError:
                    logUtil.writeLog($"HTTP {response.StatusCode}: An error occurred while making the call.");
                    break;
            }

            return false;
        }
    }

    class DialData
    {
        public string PhoneNumber { get; set; }
        public string LoggedInUserID { get; set; }
        public string ActivityUUID { get; set; }
    }

    class DialDataResponse
    {
        [JsonProperty("status")]
        public string Status { get; set; }

        [JsonProperty("agent")]
        public string Agent { get; set; }

        [JsonProperty("campaign")]
        public string Campaign { get; set; }

        [JsonProperty("callee")]
        public string Callee { get; set; }
    }
}
rds88
Explorer
0 Kudos

The project file:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net46</TargetFramework>
    <Company>Your Company</Company>
    <Copyright>Copyright (c) 2023 Your Company</Copyright>
    <Description>SAP C4C to Some Other Company Computer Telephony Integration Connector</Description>
    <Product>SAPCODCTIConnector</Product>
    <AssemblyTitle>SAPCODCTIConnector</AssemblyTitle>
    <AssemblyVersion>1.0.0</AssemblyVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
  </ItemGroup>

  <ItemGroup>
    <Reference Include="System.Configuration" />
    <Reference Include="System.Net" />
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Web" />
  </ItemGroup>

  <ItemGroup>
    <None Update="SAPCODCTIConnector.exe.config">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>

</Project>
The configuration file:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <!-- Wether to run in UAT mode or not -->
    <add key="UserAcceptanceTestMode" value="true"/>
    
    <!-- Endpoint for User Acceptance Tests -->
    <add key="UserAcceptanceTestEndpoint" value="https://..."/>
    
    <!-- Endpoint for Production -->
    <add key="ProductionEndpoint" value="https://..."/>
  </appSettings>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
  </startup>
</configuration>
Hope this helps.
nikhilwalsetwar
Contributor
0 Kudos

Hi Pushkar,

Nice post!!


In the last question you have mentioned that partners will also be able to extend the avaya system.

Could you please point me where can I get detail steps need to carry out on avaya to integrate it with C4C CTI adaptor for incoming calls?

Thanks in Advance,

Nikhil W

Former Member
0 Kudos

Hi all. Our product "Voice CTI" supports Voice CTI (incoming/outgoing) on all PBX and UC solutions (Avaya, Lync/S4b, Cisco and so forth). PBX must provide call control on Workstation.

Christian

Former Member
0 Kudos

Hi Pushkar,

Excellent blog!

You made a statement: "But, if a partner wants to make changes to the client adapter, they can, and then the maintenance of this customized version becomes the customer’s responsibility"

In a case of "employee support", a client has IVRS which would be used to recognize the incoming caller based on employee ID. Needless to say, the adapter needs to be 'modified' to query on employee ID & not phone numbers present in BO.

Is there any document available which talks about how the adopter can be modified by partners and still be used in the Live Activity Pane.

Regards,

Rahul

havder
Explorer
0 Kudos

Hi,

Any ideas if it's possible to integrate CTI on Terminal Server?

See my original post/question here:

Any help is appreciated.

BR

Sam

Former Member
0 Kudos

Could you tell me where I could find any information/documentation about Cloud for Customer CTI Interface? We would like to improve our unicersal CTI Interface that is already existing for SAP Business By Design and is supporting today all PBX Solutions.

Please provide me cloud for customer CTI interface description to Christian.sohn@Frings-informatic.de

JaninaW
Advisor
Advisor
0 Kudos

Hello Christian,

normally this guide should help: http://service.sap.com/cloud4customer > 9a. SAP Cloud for Customer CTI Guide or directly: https://websmp107.sap-ag.de/~sapidb/011000358700000142552014E/

Best regards, Janina

0 Kudos

Hi Janina,

Is this file removed or moved from the marketplace? I can not find it at the C4C-1605 list of documents.

It would be great if you can provide another link.

Best Regards,

Paresh Khanna

Former Member
0 Kudos

Good one. Very Useful.