Skip to content

Instantly share code, notes, and snippets.

@amitastreait
Last active February 28, 2024 11:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save amitastreait/421a15f4f4c43a5593d0a436fc018c63 to your computer and use it in GitHub Desktop.
Save amitastreait/421a15f4f4c43a5593d0a436fc018c63 to your computer and use it in GitHub Desktop.
<template>
<lightning-card>
<lightning-button if:true={showMessages} label="Chat with another Customer"
onclick={handleAnotherChat}
title="Chat with another Customer" variant="brand" slot="actions">
</lightning-button>
<lightning-spinner variant="brand" alternative-text="loading.." if:true={isSpinner}></lightning-spinner>
<div class="slds-m-around_small" if:false={showMessages}>
<p>
<lightning-input required message-when-value-missing="Please provide the phone of the customer" type="text"
placeholder="Enter Customer Phone Number and Click Next...."
label="Send Message" variant="label-hidden"
onchange={handlePhoneChange}>
</lightning-input>
</p>
<p class="slds-m-top_medium">
<lightning-button variant="brand" label="Chat with Customer" title="Chat with Customer" onclick={handleChat}></lightning-button>
</p>
</div>
<section if:true={showMessages} role="log" class="chatArea slds-chat slds-scrollable" style="height: 400px;">
<ul class="slds-chat-list">
<template for:each={messages} for:item="message" for:index="index">
<li if:false={message.Outgoing__c} key={message.Id} class="slds-chat-listitem slds-chat-listitem_outbound">
<div class="slds-chat-message">
<div class="slds-chat-message__body">
<div class="slds-chat-message__text slds-chat-message__text_outbound">
<span>
<lightning-formatted-rich-text value={message.MessageContent__c}></lightning-formatted-rich-text>
</span>
</div>
<div class="slds-chat-message__meta" aria-label="said Amber Cann at 5:23 PM">{message.CustomerName__c}</div>
</div>
</div>
</li>
<li if:true={message.Outgoing__c} key={message.Id} class="slds-chat-listitem slds-chat-listitem_inbound">
<div class="slds-chat-message">
<span aria-hidden="true" class="slds-avatar slds-avatar_circle slds-chat-avatar">
<abbr class="slds-avatar__initials slds-avatar__initials_inverse" title="Andy Martinez">AM</abbr>
</span>
<div class="slds-chat-message__body">
<div class="slds-chat-message__text slds-chat-message__text_inbound">
<span>
<lightning-formatted-rich-text value={message.MessageContent__c}></lightning-formatted-rich-text>
</span>
</div>
<div class="slds-chat-message__meta" aria-label="said Andy Martinez at 5:29 PM">{message.AgentName__c}</div>
</div>
</div>
</li>
</template>
</ul>
<div if:true={showMessages} class="slds-m-around_small">
<lightning-input class="chat-input"
type="text"
value={messageText}
placeholder="type here....."
message-when-value-missing="Please provide a valid message for the customer to be sent!"
required
label="Send Message" variant="label-hidden" onchange={handleChange}>
</lightning-input>
</div>
</section>
</lightning-card>
</template>
import { LightningElement, track, wire } from 'lwc';
import listAllMessages from '@salesforce/apex/WhatsAppLWCService.listAllMessages';
import sendTextMessage from '@salesforce/apex/WhatsAppLWCService.sendTextMessage'
import getSingleMessage from '@salesforce/apex/WhatsAppLWCService.getSingleMessage';
import { subscribe, unsubscribe, onError } from 'lightning/empApi';
export default class Chatcomponent extends LightningElement {
@track messages;
@track errorDetails;
showMessages = false;
isSpinner = false;
phone;
messageText;
eventName = '/event/WA_Message_Event__e' //PE
subscription;
connectedCallback() {
this.handleErrorRegister();
this.handleSubscribe();
}
disconnectedCallback() {
this.handleUnSubscribe();
}
handleUnSubscribe() {
//unsubscribe(this.subscription)
}
handleSubscribe() {
subscribe(this.eventName, -1, this.handleSubscribeResponse.bind(this)).then((response) => {
this.subscription = response;
console.log('Subscribed to channel ', JSON.stringify(response));
});
}
handleSubscribeResponse(response) {
console.log('Response from WhatsApp Webhook ', JSON.stringify(response));
let data = response.data.payload;
let messageId = data.Message_Id__c;
let customerPhone = data.Customer_Phone__c;
if (this.phone === customerPhone) {
// Make the Apex Class Call to get the message details
getSingleMessage({
recordId: messageId,
customerPhone: customerPhone
})
.then((response) => {
this.messages.push(response);
})
.catch((error) => {
console.error('Error While Recieving the Platform Event Message')
})
.finally(() => {
let chatArea = this.template.querySelector('.chatArea');
if (chatArea) {
chatArea.scrollTop = chatArea.scrollHeight;
}
})
}
}
handleErrorRegister() {
onError((error) => {
console.error('Received error from server: ', JSON.stringify(error));
// Error contains the server-side error
});
}
handlePhoneChange(event) {
event.preventDefault();
this.phone = event.target.value;
console.log(this.phone);
}
handleChat(event) {
event.preventDefault();
console.log(this.phone);
if (this.handleValidate()) {
// make a call to Salesforce Apex to get the list of message
this.isSpinner = true;
listAllMessages({
customerPhone: this.phone
})
.then((result) => {
this.messages = result;
this.showMessages = true;
})
.catch((errors) => {
this.errorDetails = errors;
this.showMessages = false;
})
.finally(() => {
//
let chatArea = this.template.querySelector('.chatArea');
if (chatArea) {
chatArea.scrollTop = chatArea.scrollHeight;
}
this.isSpinner = false;
this.setUpChatMessage();
})
} else {
return;
}
}
setUpChatMessage() {
let chatInput = this.template.querySelector(".chat-input");
if (chatInput) {
chatInput.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
this.handleSendMessage();
}
});
}
}
handleSendMessage() {
let allValid = this.handleValidate();
if (allValid) {
this.isSpinner = true;
sendTextMessage({
messageContent: this.messageText,
toPhone: this.phone
})
.then((result) => {
this.messages.push(result);
})
.catch((errors) => {
this.errorDetails = errors;
this.showMessages = false;
})
.finally(() => {
let chatArea = this.template.querySelector('.chatArea');
if (chatArea) {
chatArea.scrollTop = chatArea.scrollHeight;
}
this.isSpinner = false;
this.messageText = '';
})
}
}
handleChange(event) {
event.preventDefault();
this.messageText = event.target.value;
}
handleAnotherChat() {
this.messageText = '';
this.showMessages = false;
this.messages = undefined;
}
handleValidate() {
const allValid = [
...this.template.querySelectorAll('lightning-input'),
].reduce((validSoFar, inputCmp) => {
inputCmp.reportValidity();
return validSoFar && inputCmp.checkValidity();
}, true);
return allValid;
}
}
public class WhatsAppLWCService {
@AuraEnabled//(cacheable=true)
public static List<WAMessage__c> listAllMessages(String customerPhone){
List<WAMessage__c> messages = new List<WAMessage__c>();
messages = [SELECT Id, Name, MessageContent__c, MessageType__c, CustomerName__c, AgentName__c, Outgoing__c, CreatedDate
FROM WAMessage__c
WHERE CustomerPhone__c =: customerPhone
Order By CreatedDate ASC
];
return messages;
}
@AuraEnabled
public static WAMessage__c getSingleMessage(String recordId, String customerPhone){
return [SELECT Id, Name, MessageContent__c, MessageType__c, CustomerName__c, AgentName__c, Outgoing__c, CreatedDate
FROM WAMessage__c
WHERE Id =: recordId AND
CustomerPhone__c =: customerPhone
Order By CreatedDate ASC
];
}
@AuraEnabled
public static WAMessage__c sendTextMessage(String messageContent, String toPhone){
WAMessage__c message = WhatsAppUtils.sendTextMessage(messageContent, toPhone);
return [SELECT Id, Name, MessageContent__c, MessageType__c, CustomerName__c, AgentName__c, Outgoing__c, CreatedDate
FROM WAMessage__c
WHERE Id =: message.Id
Order By CreatedDate ASC
];
}
@AuraEnabled(cacheable=true)
public static List<WAMessage__c> listAllMessageByCustomer(String customerPhone){
List<WAMessage__c> messages = new List<WAMessage__c>();
messages = [SELECT Id, Name, MessageContent__c, MessageType__c, CustomerName__c, AgentName__c, Outgoing__c, CreatedDate
FROM WAMessage__c
WHERE CustomerPhone__c =: customerPhone
Order By CreatedDate ASC
];
return messages;
}
}
public class WhatsAppUtils {
public static List<WAMessage__c> listAllMessageByCustomer(String customerPhone){
List<WAMessage__c> messages = new List<WAMessage__c>();
messages = [SELECT Id, Name, MessageContent__c, MessageType__c, Outgoing__c FROM WAMessage__c WHERE CustomerPhone__c =: customerPhone];
return messages;
}
public static WAMessage__c sendTextMessage(String messageContent, String toPhone){
HttpRequest httpReq = new HttpRequest();
httpReq.setEndpoint('https://graph.facebook.com/v13.0/<YOUR_ACCOUNT_ID>/messages');
httpReq.setMethod('POST');
httpReq.setHeader('Content-Type', 'application/json');
httpReq.setHeader('Authorization', 'Bearer '+System.Label.WHATSAPPACCESSTOKEN);
String messageBody = '{'+
' "messaging_product": "whatsapp",'+
' "recipient_type": "individual",'+
' "to": "'+toPhone+'",'+
' "type": "text",'+
' "text": {'+
' "preview_url": false,'+
' "body": "'+messageContent+'"'+
' }'+
'}';
httpReq.setBody(messageBody);
Http http = new Http();
WAMessage__c salesforceMessage = new WAMessage__c();
try{
HttpResponse response = http.send(httpReq);
if( response.getStatusCode() == 200 ){
// Parse & Create Message Record
System.debug('Successful!');
WhatsAppUtils responseFromWA = (WhatsAppUtils) JSON.deserialize( response.getBody() , WhatsAppUtils.class);
salesforceMessage.MessageContent__c = messageContent;
salesforceMessage.CustomerPhone__c = toPhone;
salesforceMessage.MessageID__c = responseFromWA.messages.get(0).id;
salesforceMessage.MessageType__c = 'text';
salesforceMessage.Outgoing__c = True;
salesforceMessage.AgentName__c = UserInfo.getFirstName()+' '+ UserInfo.getLastName();
upsert salesforceMessage MessageID__c;
}
}catch(System.CalloutException ex){
System.debug(' CalloutException Executed '+ ex.getStackTraceString() );
System.debug(' CalloutException Executed '+ ex.getMessage() );
}catch(System.Exception ex){
System.debug(' System.Exception Executed '+ ex.getStackTraceString() );
}
return salesforceMessage;
}
public String messaging_product;
public contacts[] contacts;
public messages[] messages;
public class contacts {
public String input;
public String wa_id;
}
public class messages {
public String id;
}
}
@RestResource(urlMapping='/whatsapp/webhooks/v1/*')
global without sharing class WhatsAppWebhook {
private static Final String SIGNATURE_VALID_MESSAGE = 'Signature Verified';
private static Final String SIGNATURE_NOT_VALID_MESSAGE = 'Signature could not be verified';
@HttpGet // GET
global static void doGet() {
RestResponse response = RestContext.response;
RestRequest request = RestContext.request;
if(request.params.get('hub.verify_token') == 'WHATSAPPTOKEN'){
response.responseBody = Blob.valueOf( request.params.get('hub.challenge') );
}
}
@HttpPost // POST
global static void doPost() {
RestResponse response = RestContext.response;
response.addHeader('Content-type','application/json');
String responseString = RestContext.request.requestBody.toString();
Map<String, String> headers = RestContext.request.headers;
String responseValid = validateWhatsAppSignature(RestContext.request, responseString);
if(responseValid == SIGNATURE_VALID_MESSAGE){
System.debug(System.LoggingLevel.DEBUG, ' Headers Response From WhatsApp \n '+ JSON.serialize(headers) );
System.debug(System.LoggingLevel.DEBUG, ' Response From WhatsApp \n '+ responseString);
String finalResponseString = responseString.replace('type', 'typex');
WhatsAppMessage parentMessage = (WhatsAppMessage)JSON.deserialize( finalResponseString, WhatsAppMessage.class);
List<WhatsAppMessage.entry> messageEntries = parentMessage.entry;
if(messageEntries != null && messageEntries.size() > 0){
WhatsAppMessage.entry entryMessage = messageEntries.get(0);
List<WhatsAppMessage.changes> changeMessages = entryMessage.changes;
if(changeMessages != null && changeMessages.size() > 0){
WhatsAppMessage.changes changeMessage = changeMessages.get(0);
List<WhatsAppMessage.contacts> contactList = changeMessage.value.contacts;
List<WhatsAppMessage.messages> messageList = changeMessage.value.messages;
WhatsAppMessage.metadata metadata = changeMessage.value.metadata;
/* Create record into Salesforce */
WAMessage__c salesforceMessage = new WAMessage__c();
salesforceMessage.BusinessPhoneNumber__c = metadata != null ? metadata.display_phone_number : null;
if(contactList != null && contactList.size() > 0){
WhatsAppMessage.contacts contact = contactList.get(0);
salesforceMessage.CustomerPhone__c = contact.wa_id;
salesforceMessage.CustomerName__c = contact.profile.name;
}
if(messageList != null && messageList.size() > 0){
/* Simple Message */
WhatsAppMessage.messages message = messageList.get(0);
salesforceMessage.MessageID__c = message.id;
salesforceMessage.MessageType__c = message.typex;
salesforceMessage.MessageSentTime__c = System.now();
salesforceMessage.MessageContent__c = message.text != null? message.text.body : null;
/* If message is reaction */
salesforceMessage.Reaction__c = message.reaction != null ? message.reaction.emoji : null;
salesforceMessage.ParentMessageID__c = message.reaction != null ? message.reaction.message_id : null;
/* If message is Image */
salesforceMessage.ImageID__c = message.image != null ? message.image.id : null;
salesforceMessage.ImageType__c = message.image != null ? message.image.mime_type : null;
salesforceMessage.ImageSHA256__c = message.image != null ? message.image.sha256 : null;
/* If message is Video */
salesforceMessage.VideoId__c = message.video != null ? message.video.id : null;
salesforceMessage.VideoType__c = message.video != null ? message.video.mime_type : null;
salesforceMessage.VideoSHA256__c = message.video != null ? message.video.sha256 : null;
/* If the message is reply to another message */
salesforceMessage.ParentMessageID__c = message.context != null ? message.context.id : null;
upsert salesforceMessage MessageID__c;
/* Publish the Platform Event to be listened by LWC */
WA_Message_Event__e platformEvent = new WA_Message_Event__e();
platformEvent.Message_Id__c = salesforceMessage.Id;
platformEvent.Customer_Phone__c = salesforceMessage.CustomerPhone__c;
Eventbus.publish( platformEvent );
}
}
}
}else{
response.responseBody = Blob.valueOf('{success:false, event:"Unknown","message:"'+responseValid+'"}');
response.statusCode = 401;
return;
}
response.statusCode = 200;
response.responseBody = Blob.valueOf('{success:true, event:"success"}');
}
private static String validateWhatsAppSignature(RestRequest request, String responseString) {
// Validate Stripe signature Start
Map<String, String> headers = request.headers;
String whatsAppSignature = headers.get('X-Hub-Signature-256');
String whatsAppPayload = RestContext.request.requestBody.toString();
// Verify the signature using 'hmacSHA256'. I have the Webhook key stored in a Custom Label
String whatsAppSecret = System.Label.WHATSAPPSECRET; // Facebook Application Secret Key
Blob signedPayload = Crypto.generateMac('hmacSHA256', Blob.valueOf(whatsAppPayload), Blob.valueOf( whatsAppSecret ));
String encodedPayload = 'sha256='+EncodingUtil.convertToHex(signedPayload);
// Return status code based on whether signed payload matches or not
String response = (encodedPayload == whatsAppSignature)? SIGNATURE_VALID_MESSAGE : SIGNATURE_NOT_VALID_MESSAGE;
return response;
// Validate Stripe signature End
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment