Skip to content

Instantly share code, notes, and snippets.

@amitastreait
Created July 6, 2023 07:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save amitastreait/dd606a72584c366bde137fa12308e23d to your computer and use it in GitHub Desktop.
Save amitastreait/dd606a72584c366bde137fa12308e23d to your computer and use it in GitHub Desktop.
.btnIconOverlay {
position: absolute;
z-index: 1;
margin: 0% 0px 0px 88%;
}
<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>
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);
});
}
}
}
public class WhatsAppLWCService {
@AuraEnabled
public static sObject getCustomerPhone(String Query){
return Database.query(Query);
}
@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 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;
public String caption;
}
/* Class for the Document Message */
public class document{
public String mime_type;
public String filename;
public String sha256;
public String id;
}
/* Check if the user has clicked on any button */
public class button {
public String payload;
public String text;
}
}
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 static WAMessage__c sendFlightBookingConfirmationMessage(String toPhone, String name, String source, String destination){
String body = '{'+
' "messaging_product": "whatsapp",'+
' "recipient_type": "individual",'+
' "to": "'+toPhone+'",'+
' "type": "template",'+
' "template": {'+
' "name": "flight_booking_confirm_template",'+
' "language": {'+
' "code": "en"'+
' },'+
' "components": ['+
' {'+
' "type": "header",'+
' "parameters": ['+
' {'+
' "type": "image",'+
' "image": {'+
' "link": "https://bit.ly/3AWkh2p"'+
' }'+
' }'+
' ]'+
' },'+
' {'+
' "type": "body",'+
' "parameters": ['+
' {'+
' "type": "text",'+
' "text": "'+Name+'"'+
' },'+
' {'+
' "type": "text",'+
' "text": "'+source+'"'+
' },'+
' {'+
' "type": "text",'+
' "text": "'+destination+'"'+
' },'+
' {'+
' "type": "date_time",'+
' "date_time" : {'+
' "fallback_value": "October 25, 2023 22:34 PM",'+
' "day_of_week": 6,'+
' "day_of_month": 25,'+
' "year": 2023,'+
' "month": 10,'+
' "hour": 12,'+
' "minute": 34'+
' }'+
' },'+
' {'+
' "type": "text",'+
' "text": "999-999-9999"'+
' }'+
' ]'+
' },'+
' {'+
' "type": "button",'+
' "sub_type": "quick_reply",'+
' "index": "0",'+
' "parameters": ['+
' {'+
' "type": "payload",'+
' "payload": "Contact to Support"'+
' }'+
' ]'+
' },'+
' {'+
' "type": "button",'+
' "sub_type": "quick_reply",'+
' "index": "1",'+
' "parameters": ['+
' {'+
' "type": "payload",'+
' "payload": "Check PNR Status"'+
' }'+
' ]'+
' }'+
' ]'+
' }'+
'}';
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);
httpReq.setBody(body);
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 = body;
salesforceMessage.CustomerPhone__c = toPhone;
salesforceMessage.MessageID__c = responseFromWA.messages.get(0).id;
salesforceMessage.MessageType__c = 'template';
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 static WAMessage__c sendMediaMessages(String toPhone, String url){
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);
if(String.isBlank(url)){
url = '';
}
String body = '{'+
' "messaging_product": "whatsapp",'+
' "recipient_type": "individual",'+
' "to": "'+toPhone+'",'+
' "type": "document",'+
' "document": {'+
' "link": "'+url+'",'+
' "caption" : "Field Service Lightning Topics.pdf",'+
' "filename" : "Field Service Lightning Topics.pdf"'+
' }'+
'}';
httpReq.setBody(body);
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 = body;
salesforceMessage.CustomerPhone__c = toPhone;
salesforceMessage.MessageID__c = responseFromWA.messages.get(0).id;
salesforceMessage.MessageType__c = 'media';
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 message is Document */
/* 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