Created
October 19, 2023 07:59
-
-
Save amitastreait/646d6de0829ded8b294a5132936192ef to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<lightning-card variant="Narrow" > | |
<lightning-button if:true={messageList} variant="border-filled" | |
label="Chat with another customer" onclick={handleAnOtherChat} slot="actions"> | |
</lightning-button> | |
<lightning-spinner alternative-text="Loading" size="small" if:true={isSpinner}></lightning-spinner> | |
<div class="slds-p-horizontal_small"> | |
<section role="log" class="slds-chat" if:false={messageList}> | |
<lightning-input type="tel" | |
message-when-value-missing="Please provide a valid contact no to chat with" | |
required variant="label-hidden" name="Phone" | |
onchange={handlePhoneChange} | |
placeholder="Enter Customer Phone Number and click next"> | |
</lightning-input> | |
<br/> | |
<lightning-button variant="brand" label="Chat with Customer" title="Chat with Customer" onclick={handlelistAllMessageByCustomer}></lightning-button> | |
</section> | |
<section role="log" class="messageContent slds-chat slds-scrollable" if:true={messageList} style="height: 350px;"> | |
<ul class="slds-chat-list"> | |
<template if:true={messageList} for:each={messageList} for:item="message" for:index="index"> | |
<!-- Message From Customer --> | |
<li class="slds-chat-listitem slds-chat-listitem_inbound" key={message.Id} if:false={message.Outgoing__c}> | |
<div class="slds-chat-message" if:true={message.MessageContent__c}> | |
<span aria-hidden="true" class="slds-avatar slds-avatar_circle slds-chat-avatar"> | |
<abbr class="slds-avatar__initials slds-avatar__initials_inverse" title={message.CustomerName__c}>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={message.areaLabel}>{message.CustomerName__c} • <lightning-formatted-date-time value={message.CreatedDate} hour="2-digit"></lightning-formatted-date-time></div> | |
</div> | |
</div> | |
</li> | |
<!-- Message From Salesforce --> | |
<li class="slds-chat-listitem slds-chat-listitem_outbound" key={message.Id} if:true={message.Outgoing__c}> | |
<div class="slds-chat-message" if:true={message.MessageContent__c}> | |
<!-- <span aria-hidden="true" class="slds-avatar slds-avatar_circle slds-chat-avatar"> | |
<abbr class="slds-avatar__initials slds-avatar__initials_inverse" title={message.AgentName__c}>AM</abbr> | |
</span> --> | |
<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={message.areaLabel}>{message.AgentName__c} • <lightning-formatted-date-time value={message.CreatedDate} hour="2-digit"></lightning-formatted-date-time></div> | |
</div> | |
</div> | |
</li> | |
</template> | |
</ul> | |
</section> | |
<div if:true={chatEnabled}> | |
<span class="slds-icon_container slds-icon-utility-end_chat slds-chat-icon"> | |
<svg class="slds-icon slds-icon_x-small slds-icon-text-default" aria-hidden="true"> | |
<use xlink:href="/apexpages/slds/latest/assets/icons/utility-sprite/svg/symbols.svg#chat"></use> | |
</svg> | |
</span> | |
<p> | |
<lightning-button-icon onclick={handleSendMessage} class="btnIconOverlay" icon-name="utility:send"> | |
</lightning-button-icon> | |
<lightning-input message-when-value-missing="Please type a valid messgae before sending it" | |
required value={chatMessage} type="text" class="chat-input" | |
variant="label-hidden" onchange={handleChatChange} name="Message" | |
placeholder="type here..."> | |
</lightning-input> | |
</p> | |
</div> | |
</div> | |
<div slot="footer"> | |
Powered by <a href="https://www.pantherschools.com/" target="_blank" aria-label="Panther Schools">@PantherSchools.com</a> | |
</div> | |
</lightning-card> | |
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { api, LightningElement, track } from 'lwc'; | |
import listAllMessageByCustomer from '@salesforce/apex/WhatsAppLWCService.listAllMessageByCustomer'; | |
import sendTextMessage from '@salesforce/apex/WhatsAppLWCService.sendTextMessage'; | |
import getSingleMessage from '@salesforce/apex/WhatsAppLWCService.getSingleMessage'; | |
import { subscribe, onError } from 'lightning/empApi'; | |
export default class WhatsAppChatComponent extends LightningElement { | |
@api recordId; | |
@track messageList; | |
@track errors; | |
channelName = '/event/WAMessageEvent__e'; | |
subscription = {}; | |
chatMessage | |
customerNo; | |
isSpinner = false; | |
chatEnabled = false; | |
handleChatChange(event){ | |
event.preventDefault(); | |
this.chatMessage = event.target.value; | |
} | |
connectedCallback(){ | |
this.registerErrorListener(); | |
this.handleSubscribe(); | |
} | |
handlelistAllMessageByCustomer(event){ | |
event.preventDefault(); | |
if(!this.handleValidate()){ | |
return; | |
} | |
this.isSpinner = true; | |
listAllMessageByCustomer({ | |
customerPhone : this.customerNo | |
}) | |
.then(result => { | |
this.messageList = result.map(item => { | |
return { | |
...item, | |
areaLabel: item.Outgoing__c ? `${item.AgentName__c} said at ${item.CreatedDate}` : `${item.CustomerName__c} said at ${item.CreatedDate}` | |
} | |
}); | |
this.chatEnabled = true; | |
}) | |
.catch(error => { | |
console.error('Error:', error); | |
this.errors = error; | |
}) | |
.finally(()=>{ | |
this.chatEnabled = true; | |
console.log('turing off the spinner'); | |
const chatArea = this.template.querySelector('.messageContent'); | |
if(chatArea){ | |
chatArea.scrollTop = chatArea.scrollHeight | |
} | |
this.isSpinner = false; | |
this.setUpChatMessage(); | |
}) | |
} | |
setUpChatMessage(){ | |
let chatInput = this.template.querySelector(".chat-input"); | |
if(chatInput){ | |
chatInput.addEventListener("keydown", (event) => { | |
console.log(`Enent handler added`) | |
if (event.key === "Enter") { | |
this.handleSendMessage(); | |
} | |
}); | |
} | |
} | |
handleSendMessage(){ | |
console.log('Enter Clicked ', this.chatMessage ); | |
if(!this.handleValidate()){ | |
return; | |
} | |
this.isSpinner = true; | |
console.log(this.customerNo); | |
sendTextMessage({ | |
messageContent : this.chatMessage, | |
toPhone : this.customerNo | |
}) | |
.then(result => { | |
console.log(' message list ', result ); | |
this.chatMessage = ''; | |
this.messageList = [...this.messageList, result]; | |
console.log(' Updated Message from ', this.messageList ); | |
}) | |
.catch(error => { | |
// TODO Error handling | |
}) | |
.finally(()=>{ | |
console.log('turing off the spinner'); | |
this.isSpinner = false; | |
}) | |
} | |
handlePhoneChange(event){ | |
this.customerNo = event.target.value; | |
} | |
handleAnOtherChat(event){ | |
event.preventDefault(); | |
this.messageList = undefined; | |
this.chatEnabled = false; | |
} | |
handleValidate(){ | |
const allValid = [ | |
...this.template.querySelectorAll('lightning-input'), | |
].reduce((validSoFar, inputCmp) => { | |
inputCmp.reportValidity(); | |
return validSoFar && inputCmp.checkValidity(); | |
}, true); | |
return allValid; | |
} | |
registerErrorListener() { | |
// Invoke onError empApi method | |
onError((error) => { | |
console.log('Received error from server: ', JSON.stringify(error)); | |
// Error contains the server-side error | |
}); | |
} | |
handleSubscribe() { | |
// Callback invoked whenever a new event message is received | |
const messageCallback = this.handleEventResponse.bind(this); | |
// Invoke subscribe method of empApi. Pass reference to messageCallback | |
subscribe(this.channelName, -1, messageCallback).then((response) => { | |
this.subscription = response; | |
}); | |
} | |
handleEventResponse(response) { | |
console.log('New message received: ', JSON.stringify(response)); | |
let payload = response.data.payload; | |
let MessageId = payload.MessageId__c; | |
let CustomerPhone = payload.CustomerPhone__c; | |
if(this.customerNo === CustomerPhone){ | |
getSingleMessage({ | |
recordId : MessageId, | |
customerPhone : CustomerPhone | |
}) | |
.then(result => { | |
this.messageList = [...this.messageList, result]; | |
const chatArea = this.template.querySelector('.messageContent'); | |
if(chatArea){ | |
chatArea.scrollTop = chatArea.scrollHeight | |
} | |
}) | |
.catch(error => { | |
// TODO Error handling | |
console.error(error); | |
}); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"> | |
<apiVersion>55.0</apiVersion> | |
<isExposed>true</isExposed> | |
<masterLabel>WhatsApp Chat Component</masterLabel> | |
<targets> | |
<target>lightning__RecordPage</target> | |
<target>lightning__AppPage</target> | |
<target>lightning__HomePage</target> | |
<target>lightning__UtilityBar</target> | |
<target>lightning__FlowScreen</target> | |
</targets> | |
</LightningComponentBundle> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 CustomerName__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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class WhatsAppMessage { | |
public entry[] entry; | |
public class entry { | |
public String id; | |
public changes[] changes; | |
} | |
public class changes { | |
public value value; | |
public String field; | |
} | |
public class value { | |
public String messaging_product; | |
public metadata metadata; | |
public contacts[] contacts; | |
public messages[] messages; | |
} | |
public class metadata { | |
public String display_phone_number; | |
public String phone_number_id; | |
} | |
public class contacts { | |
public profile profile; | |
public String wa_id; | |
} | |
public class profile { | |
public String name; | |
} | |
public class messages { | |
public context context; | |
public String fromx; | |
public String id; | |
public String timestamp; | |
public text text; | |
public String typex; | |
public reaction reaction; | |
public image image; | |
public image video; | |
} | |
public class context { | |
public String fromx; | |
public String id; | |
} | |
public class text { | |
public String body; | |
} | |
public class reaction{ | |
public String emoji; | |
public String message_id; | |
} | |
public class image{ | |
public String mime_type; | |
public String id; | |
public String sha256; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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 */ | |
WAMessageEvent__e platformEvent = new WAMessageEvent__e(); | |
platformEvent.MessageId__c = salesforceMessage.Id; | |
platformEvent.CustomerPhone__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