Skip links

Exposing the Customer Mapping API to CCXai

The requirement is to create a GET endpoint (/api/customer-mapping) to retrieve customer details for populating a dropdown in the Agent Widget. Below is a professional approach to designing and implementing this API:

API Design
Query Parameters
Implementation in C#

Assuming a .NET Core/ASP.NET Core backend, the API can be implemented as follows:


using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace TenantApp.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class CustomerMappingController : ControllerBase
    {
        private readonly ICustomerService _customerService;

        public CustomerMappingController(ICustomerService customerService)
        {
            _customerService = customerService;
        }

        [HttpGet]
        public async Task<ActionResult<List<DropdownBO>>> GetCustomerMapping([FromQuery] string ApplicationCode, [FromQuery] string CustomerType)
        {
            if (string.IsNullOrEmpty(ApplicationCode) || string.IsNullOrEmpty(CustomerType))
            {
                return BadRequest("ApplicationCode and CustomerType are required.");
            }

            try
            {
                var customers = await _customerService.GetCustomerMappingsAsync(ApplicationCode, CustomerType);
                return Ok(customers);
            }
            catch (Exception ex)
            {
                // Log the exception (use a logging framework like Serilog or NLog)
                return StatusCode(500, "An error occurred while retrieving customer mappings.");
            }
        }
    }

    public class DropdownBO
    {
        public string CustomerId { get; set; }
        public string CustomerName { get; set; }
    }

    public interface ICustomerService
    {
        Task<List<DropdownBO>> GetCustomerMappingsAsync(string applicationCode, string customerType);
    }

    public class CustomerService : ICustomerService
    {
        public async Task<List<DropdownBO>> GetCustomerMappingsAsync(string applicationCode, string customerType)
        {
            // Simulate database call or external service integration
            // Replace with actual logic to fetch data based on ApplicationCode and CustomerType
            return new List<DropdownBO>
            {
                new DropdownBO { CustomerId = "12345", CustomerName = "John Doe" },
                new DropdownBO { CustomerId = "67890", CustomerName = "Jane Smith" }
            };
        }
    }
}
Best Practices
Security

Authentication: Secure the endpoint with an authentication mechanism (e.g., JWT tokens via OAuth 2.0 or API keys) to ensure only authorized CCX clients can access it. For example, integrate with Auth0 or Azure AD for tenant-based authentication

Authorization: Restrict access based on tenant-specific permissions, ensuring the ApplicationCode corresponds to the authenticated tenant

Data Isolation: Ensure tenant data is isolated by filtering results based on the ApplicationCode to prevent cross-tenant data leakage.

Error Handling: Return meaningful error messages and HTTP status codes. Log exceptions for debugging and monitoring.

Scalability: Cache frequently accessed customer mappings (e.g., using Redis or in-memory caching) to reduce database load for high-traffic scenarios.

Documentation: Use OpenAPI/Swagger to document the endpoint, including parameter descriptions, response models, and example responses.
Example Usage

API Request: Get Customer Mapping

Request:


GET /api/customer-mapping?ApplicationCode=CFSS_GGL2&CustomerType=Client

Expected Response (HTTP 200 OK):


[
  {
    "CustomerId": "12345",
    "CustomerName": "John Doe"
  },
  {
    "CustomerId": "67890",
    "CustomerName": "Jane Smith"
  }
]

Error Responses:

400 Bad Request (Missing Parameters):


{
  "error": "ApplicationCode and CustomerType are required."
}

500 Internal Server Error:


{
  "error": "An error occurred while retrieving customer mappings."
}
Response:

[
  {
    "CustomerId": "12345",
    "CustomerName": "John Doe"
  },
  {
    "CustomerId": "67890",
    "CustomerName": "Jane Smith"
  }
]
Internal APIs (If Required)

For internal use within the tenant application, you may need APIs to fetch the current user’s details (e.g., UserId, FullName, UserType, AppCode) to pass to the Agent and Customer Widgets. These APIs should not be exposed publicly and should be secured for internal use only.

Internal API Design
Implementation

[Route("api/internal/[controller]")]
[ApiController]
[Authorize] // Requires authentication (e.g., JWT)
public class UserDetailsController : ControllerBase
{
    private readonly IUserService _userService;
    public UserDetailsController(IUserService userService)
    {
        _userService = userService;
    }
    [HttpGet]
    public async Task> GetUserDetails()
    {
        try
        {
            var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
            if (string.IsNullOrEmpty(userId))
            {
                return Unauthorized("User not authenticated.");
            }
            var userDetails = await _userService.GetUserDetailsAsync(userId);
            return Ok(userDetails);
        }
        catch (Exception ex)
        {
            // Log exception
            return StatusCode(500, "An error occurred while retrieving user details.");
        }
    }
}
public class UserDetailsBO
{
    public string UserId { get; set; }
    public string FullName { get; set; }
    public string UserType { get; set; }
    public string AppCode { get; set; }
    public Dictionary AdditionalDetails { get; set; }
}
public interface IUserService
{
    Task GetUserDetailsAsync(string userId);
}
public class UserService : IUserService
{
    public async Task GetUserDetailsAsync(string userId)
    {
        // Simulate fetching user details from database or identity provider
        return new UserDetailsBO
        {
            UserId = userId,
            FullName = "John Doe",
            UserType = "EMPLOYEE",
            AppCode = "CFSS_ABC",
            AdditionalDetails = new Dictionary
            {
                { "phone", "+1-234-567-890" },
                { "email", "johndoe@example.com" }
            }
        };
    }
}
Best Practices
Tenant App UI Development (Angular)

Project Setup

To integrate the Agent and Customer Widgets in an Angular application, follow these steps to set up the project and ensure compatibility with the provided requirements.
Prerequisites
Install the required dependency for widget interactions:

npm install interactjs
This library enables drag, resize, and move functionality for the widgets.

ng new tenant-app --routing --style=css
Folder structure as specified:

src/
├── app/
│   ├── components/
│   │   ├── header/ # Shared UI controls
│   │   ├── agent-widget/ # Agent interaction component
│   │   ├── customer-widget/ # Customer interaction component
│   ├── global/ # Global settings and shared logic
│   ├── service/
│   │   ├── iframe.ts # Iframe communication service
│   │   ├── widgetreset.service.ts # Widget state reset service
│   ├── environment.service.ts # Environment-specific variables
│   ├── app.settings.ts # App-wide configuration
│   ├── app.settings.model.ts # Type model for settings
Configuration Files

export interface AppSettings {
  appColor: string;
  appCode: string;
  apiBaseUrl: string;
  // Add other settings as needed
}

import { AppSettings } from './app.settings.model';
export const APP_SETTINGS: AppSettings = {
  appColor: '#00786a',
  appCode: 'CFSS_ABC',
  apiBaseUrl: 'https://api.tenant-app.com'
};

import { Injectable } from '@angular/core';
import { APP_SETTINGS } from './app.settings';
@Injectable({ providedIn: 'root' })
export class EnvironmentService {
  getSettings() {
    return APP_SETTINGS;
  }
}
Widget Integration
The Agent and Customer Widgets are embedded via iframes, and data is passed using URL query parameters. Below is the implementation for both widgets.
Customer Widget
The Customer Widget requires passing AppColor, CustomerAppCode, CustomerType, CustomerId, CustomerName, and CustomerDetails.
Component (customer-widget.component.ts)

import { Component, OnInit } from '@angular/core';
import { EnvironmentService } from '../environment.service';
import { GlobalService } from '../global/global.service';
@Component({
  selector: 'app-customer-widget',
  templateUrl: './customer-widget.component.html'
})
export class CustomerWidgetComponent implements OnInit {
  iframeUrl: string;
  constructor(
    private envService: EnvironmentService,
    private globalService: GlobalService
  ) {}
  ngOnInit() {
    const settings = this.envService.getSettings();
    const customerDetails = {
      phone: '+1-234-567-890',
      email: 'johndoe@example.com',
      address: '123 Main St, Houston, TX'
    };
    const params = new URLSearchParams();
    params.append('AppColor', settings.appColor);
    params.append('CustomerAppCode', `CFSS_${this.globalService.cfssOnboardAgencyCode}`);
    params.append('CustomerType', 'CLIENT');
    params.append('CustomerId', this.globalService.cfssLoginEntityId);
    params.append('CustomerName', this.globalService.fullName);
    params.append('CustomerDetails', JSON.stringify(customerDetails));
    this.iframeUrl = `${settings.apiBaseUrl}/customer-widget?${params.toString()}`;
  }
}
Template (customer-widget.component.html)

<iframe [src]="iframeUrl | safe: 'url'" width="100%" height="400px" frameborder="0"></iframe>
Safe Pipe (safe.pipe.ts)

To sanitize the iframe URL:


import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
@Pipe({ name: 'safe' })
export class SafePipe implements PipeTransform {
  constructor(private sanitizer: DomSanitizer) {}
  transform(url: string, type: string): SafeUrl {
    return this.sanitizer.bypassSecurityTrustUrl(url);
  }
}
Agent Widget

The Agent Widget follows a similar approach, passing AppColor, AgentAppCode, AgentType, AgentID, AgentName, and AgentDetails.

Component (agent-widget.component.ts)

import { Component, OnInit } from '@angular/core';
import { EnvironmentService } from '../environment.service';
import { GlobalService } from '../global/global.service';
@Component({
  selector: 'app-agent-widget',
  templateUrl: './agent-widget.component.html'
})
export class AgentWidgetComponent implements OnInit {
  iframeUrl: string;
  constructor(
    private envService: EnvironmentService,
    private globalService: GlobalService
  ) {}
  ngOnInit() {
    const settings = this.envService.getSettings();
    const agentDetails = {
      phone: '+1-234-567-890',
      email: 'johndoe@example.com',
      address: '123 Main St, Houston, TX'
    };
    const params = new URLSearchParams();
    params.append('AppColor', settings.appColor);
    params.append('AgentAppCode', `CFSS_${this.globalService.agencyCode}`);
    params.append('AgentType', 'EMPLOYEE');
    params.append('AgentID', this.globalService.userID);
    params.append('AgentName', this.globalService.fullName);
    params.append('AgentDetails', JSON.stringify(agentDetails));
    this.iframeUrl = `${settings.apiBaseUrl}/agent-widget?${params.toString()}`;
  }
}

Template (agent-widget.component.html)


<iframe [src]="iframeUrl | safe: 'url'" width="100%" height="400px" frameborder="0"></iframe>
Iframe Communication Service (iframe.ts)

To enable communication between the iframe and parent application:


import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class IframeService {
  private messageSubject = new Subject();
  constructor() {
    window.addEventListener('message', (event) => {
      if (event.origin === 'https://api.tenant-app.com') { // Validate origin
        this.messageSubject.next(event.data);
      }
    });
  }
  sendMessage(message: any) {
    const iframe = document.querySelector('iframe');
    iframe?.contentWindow?.postMessage(message, 'https://api.tenant-app.com');
  }
  getMessages() {
    return this.messageSubject.asObservable();
  }
}
Widget Reset Service (widgetreset.service.ts)

To reset widget state:


import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class WidgetResetService {
  resetWidget(iframeId: string) {
    const iframe = document.getElementById(iframeId) as HTMLIFrameElement;
    if (iframe) {
      iframe.src = iframe.src; // Reload iframe to reset state
    }
  }
}
Global Service (global/global.service.ts)

To manage global variables like cfssLoginEntityId, fullName, etc.:


import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class GlobalService {
  cfssLoginEntityId = '30051';
  fullName = 'John Doe';
  cfssOnboardAgencyCode = 'ABC';
  agencyCode = 'ABC';
  userID = 'ab7c094c-2ecd-4f68-b70e-6c1728fa9eb4';
}

Widget Interactivity with Interact.js
To enable drag, resize, and move functionality for widgets:


import interact from 'interactjs';
export class WidgetComponent implements OnInit {
  ngOnInit() {
    interact('.widget')
      .draggable({
        listeners: {
          move: (event) => {
            const target = event.target;
            const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
            const y = (parseFloat(target.getAttribute('data-y')) || 0		

Multi-Tenancy Considerations

For multi-tenant support in the Angular UI:

  • Tenant Identification:
    • Use the ApplicationCode (e.g., CFSS_ABC) to identify the tenant, extracted from the URL or a configuration file.
    • Configure the environment.prod.ts to support tenant-specific URLs

export const environment = {
  production: true,
  application: {
    baseUrl: 'https://{0}.mydomain.com/'
  }
};
  • Tenant Switching: Implement a tenant-switching component in the UI to allow users to switch between tenants if needed. Hide the switching component if using a tenant-specific URL with the {0} placeholder.
  • Data Isolation: Ensure the CustomerAppCode and AgentAppCode are used in API calls to filter data by tenant, preventing cross-tenant data access.

Best Practices for UI Development

  • Modularity: Keep components modular (e.g., separate agent-widget and customer-widget) for reusability and maintainability.
  • Security:
    • Sanitize iframe URLs to prevent XSS attacks using the SafePipe.
    • Validate postMessage origins to ensure secure communication between the iframe and parent.
  • Performance: Use Angular’s ChangeDetectionStrategy.OnPush for widget components to optimize rendering performance.
  • Error Handling: Implement error boundaries in components to handle API or iframe failures gracefully.
  • Testing: Write unit tests for services (e.g., IframeService, WidgetResetService) and components using Jasmine/Karma, and consider end-to-end tests with Cypress for widget interactions.

Additional Considerations

  1. Scalability:
    • For high-traffic applications, consider load balancing the API backend and caching customer mappings.
    • Use lazy loading for Angular modules to improve frontend performance.

      Monitoring and Logging:
      • Implement logging for API requests and iframe communications to troubleshoot issues.
      • Use tools like Application Insights or ELK Stack for monitoring.

        Documentation:
        • Document the API using Swagger and provide a developer guide for widget integration.
        • Include examples for single-tenant and multi-tenant scenarios.
  2. Extensibility:
    • Design the CustomerDetails and AgentDetails JSON structures to be flexible for future additions.
    • Allow configuration of additional widget parameters via app.settings.ts.

Sample Integration Flow

  1. User Logs In:
    • The application authenticates the user (e.g., via Auth0 or Azure AD) and retrieves UserId, FullName, UserType, and AppCode from the internal API or identity provider.
  2. Widget Initialization:
    • The Angular app constructs the iframe URLs for the Agent and Customer Widgets, appending the required parameters.
    • The IframeService handles two-way communication for data sharing.
  3. Customer Mapping:
    • The Agent Widget calls the /api/customer-mapping endpoint with the appropriate ApplicationCode and CustomerType to populate the dropdown.
  4. Interactivity:
    • Users can drag, resize, or move widgets using Interact.js.
    • The WidgetResetService resets widget state when needed.

This implementation provides a robust, secure, and scalable solution for integrating the tenant application with the Agent and Customer Widgets while adhering to multi-tenancy best practices.

Prerequisites
WordPress Environment: Ensure your site runs WordPress 6.0+ (recommended for security and compatibility, though not explicitly stated). Test on a staging site first.

CCXAI Account: Register at https://ccxai.com and obtain your Company Name and Identifier Code from the CCXAI dashboard. These are essential for authentication.

Server Requirements: PHP 7.4+ (common for modern WP plugins), with cURL enabled for iframe communication. No additional libraries are needed.

Development Tools: Access to FTP/SFTP for manual uploads, or use the WP dashboard. For customization, you’ll need knowledge of PHP, CSS, and WordPress hooks/filters (though the plugin doesn’t expose many).

Installation

Follow these steps to install the plugin:
Via WordPress Dashboard

Manual Upload
Verification:
Configuration

The plugin provides a simple admin panel for setup:
Access Settings

Enter Credentials:

Widget Placement
User Consent and Data Handling:
Customization

Customization is limited but feasible through CSS and potential code modifications:

Styling with Custom CSS


.ccxai-chat-widget {
    --primary-color: #00786a; /* Replace with your brand color */
    background-color: var(--primary-color);
    border-radius: 10px;
}
.ccxai-chat-widget button {
    color: white;
    background-color: var(--primary-color);
}
Future updates may add built-in styling options in the admin panel.
Positioning and Visibility:

add_action('wp_enqueue_scripts', 'custom_ccxai_widget_visibility');
function custom_ccxai_widget_visibility() {
    if (is_page('contact')) { // Show only on 'Contact' page
        wp_enqueue_style('ccxai-hide', get_stylesheet_directory_uri() . '/ccxai-hide.css');
    }
}
In ccxai-hide.css: .ccxai-chat-widget { display: none; } (reverse for showing).
Extending Functionality:

// In your theme's JS file
window.addEventListener('message', function(event) {
    if (event.origin === 'https://ccxai.com') {
        // Handle messages from iframe
    }
});
// Send data to iframe
const iframe = document.querySelector('iframe[src*="ccxai.com"]');
iframe.contentWindow.postMessage({ userName: 'display_name; ?>' }, 'https://ccxai.com');
Enqueue this script via functions.php.
Integration with Other Systems
Analytics Tracking: Add Google Analytics events for chat interactions:

document.querySelector('.ccxai-chat-widget').addEventListener('click', function() {
    gtag('event', 'chat_open', { 'event_category': 'engagement' });
});
Custom Development: Since it's open-source, fork the GitHub repo (if available via CCXAI) or modify locally. Add features like agent-side widgets by integrating with CCXAI's tenant app guidelines.
Testing and Debugging
Maintenance and Updates
This website uses cookies to improve your web experience.
Explore
Drag