Skip to content

Instantly share code, notes, and snippets.

@swapnilshrikhande
Last active May 19, 2020 15:53
Show Gist options
  • Save swapnilshrikhande/25af606b06ef6cf07dc2d84fa0aa9b85 to your computer and use it in GitHub Desktop.
Save swapnilshrikhande/25af606b06ef6cf07dc2d84fa0aa9b85 to your computer and use it in GitHub Desktop.
Twilio Salesforce Simple Chat Bot Integration WARNING : Hardcoded data & No Error Handling
without sharing public class CaseCreationService {
//Queries
//How do we autoselect the product ? chat ?
//If no contact found should we create one ?
//How do we link the product
private final Case caseRecord = new Case();
private String contactName;
public static final String MESSAGE_BODY='Body';
public static final String MESSAGE_FROM='From';
public static final String MESSAGE_TO='To';
public CaseCreationService defaultFields(){
caseRecord.Subject = 'Faulty Part Replacement Request';
caseRecord.Reason = 'Faulty Part';
caseRecord.Origin = 'Message';
return this;
}
public static String getPersonName(RestRequest req){
return new CaseCreationService().populateFromBio(extractNumber(
req.params.get(MESSAGE_FROM)
)).getContactName();
}
public CaseCreationService populateFromBio(String phoneNumb){
Id contactId;
//fetch contactid and account id from the phone number
phoneNumb = phoneNumb.trim();
System.debug('Searching for phone='+phoneNumb);
List<Contact> contactLst = [
SELECT AccountId
, Id
, Name
From Contact
where Phone = :phoneNumb
OR MobilePhone = :phoneNumb
limit 1
];
System.debug('Searching for phone='+contactLst);
if( !contactLst.isEmpty() ) {
caseRecord.ContactId = contactLst[0].Id;
caseRecord.AccountId = contactLst[0].AccountId;
contactName = contactLst[0].Name;
}
return this;
}
public CaseCreationService save(){
upsert caseRecord;
return this;
}
//Request Parsing : can be another service
public CaseCreationService parseRestRequest(RestRequest req){
this.defaultFields()
.parseToCase(req.params)
.populateFromBio(extractNumber(
req.params.get(MESSAGE_FROM)
));
return this;
}
public CaseCreationService parseToCase(Map<String,String> requestMap){
//'To'; 'whatsapp:+14155238886'
//'From'; 'whatsapp:+919860337398'
//'Body'; body text
//To
caseRecord.Service_Contact_Phone__c = extractNumber(
requestMap.get(MESSAGE_TO)
);
//From
caseRecord.SuppliedPhone = extractNumber(
requestMap.get(MESSAGE_FROM)
);
caseRecord.Description = requestMap.get(MESSAGE_BODY);
return this;
}
public CaseCreationService setProductId(String productId){
caseRecord.ProductId = productId;
return this;
}
//getters
public String getCaseNumber(){
if( caseRecord.Id == null )
return null;
String caseNumber = [
Select CaseNumber
From Case
Where Id = :caseRecord.Id
limit 1].CaseNumber;
return caseNumber;
}
public Case getCase(){
return caseRecord;
}
public String getContactName(){
return String.isEmpty(contactName) ? 'User' : contactName;
}
//utils
public static String extractNumber(String phoneString){
return phoneString.split(':')[1].trim();
}
public static String getSelectedProduct(RestRequest req){
String phoneNumb = extractNumber(req.params.get(MESSAGE_FROM));
List<Case> caseList = [
Select ProductId
from Case
Where SuppliedPhone =:phoneNumb
AND Reason = 'Faulty Part'
AND Origin = 'Message'
ORDER BY CreatedDate desc
limit 1
];
if(!caseList.isEmpty())
return caseList[0].ProductId;
return '';
}
}
/*Webhook to receive message inside Salesforce*/
@RestResource(urlMapping='/pushmessage/*')
global with sharing class IncomingWhatsAppService {
//@TODO Move to custom label
public static final Map<String,String> productMap = new Map<String,String>{
'1' => 'Engine Mount - EM-08921',
'2' => 'Front Forks - FF-8462',
'3' => 'Hose - H-05946',
'4' => 'Hydromounts - H-6742',
'5' => 'Slip Joints - SJ-6751',
'6' => 'All Products'
};
public static final Map<String,String> productIdMap = new Map<String,String>{
'1' => '01t5w000008POqPAAW',
'2' => '01t5w000008POqVAAW',
'3' => '01t5w000008POqUAAW',
'4' => '01t5w000008POqeAAG',
'5' => '01t5w000008POqZAAW',
'6' => 'All Products'
};
public static final String YES = 'yes';
public static final String NO = 'no';
public static final String ERROR = 'Error:';
public static final String CASELOG_SUCCESSMSG = ' A new Case *{0}* has been logged for your request.\n'
+'We will place an order for the part *{1}* to be replaced on your behalf once you confirm.\n'
+'Please respond with a *yes* or *no* to continue placing the order.';
public static final String ORDER_PLACED_YESMSG = 'Your order *{0}* has been successfully placed and submitted for approval.\n'
+'Once approved, you will be notified of the invoice and shipment details of the part shortly.';
public static final String ORDER_PLACED_NOMSG = 'Thank you for your response. You will need to place an order through our dealer portal *https://sforce.co/36fti5x* to request a replacement for the part under consideration.';
public static final Map<String,String> orderConfirmMap = new Map<String,String>{
YES => ORDER_PLACED_YESMSG,
NO => ORDER_PLACED_NOMSG
};
public static final String ServiceMessageResponseTemplate =
'Hi {0}, Please select a product from below list to place a replacement order.\n'
+' 1. Engine Mount - EM-08921\n'
+' 2. Front Forks - FF-8462\n'
+' 3. Hose - H-05946\n'
+' 4. Hydromounts - H-6742\n'
+' 5. Slip Joints - SJ-6751\n'
+' 6. All Products\n'
+'Reply with the product option number.';
@HttpGet
global static String doGet() {
return 'hello world';
}
@HttpPost
global static void doPost() {
RestRequest req = RestContext.request;
RestResponse res = Restcontext.response;
Utils.debugMap('Header',req.headers);
Utils.debugMap('Param',req.params);
//Routing
//Get Message Body
String messageBody = req.params.get(CaseCreationService.MESSAGE_BODY);
messageBody = Utils.sanitize(messageBody);
Boolean isProductSelectResponse = productMap.containsKey(messageBody);
Boolean isOrderConfirmResponse = orderConfirmMap.containsKey(messageBody);
//If cookie is blank then first request
//Send Product Options
if( isProductSelectResponse == false && isOrderConfirmResponse == false ) {
sendProductOptions(req,res);
} else if( isProductSelectResponse ) {
sendOrderConfirmation(req,res,messageBody);
} else if( isOrderConfirmResponse ) {
sendOrderDetails(req,res, messageBody );
}
return;
}
private static void sendProductOptions(RestRequest req,RestResponse res){
String contactName = CaseCreationService.getPersonName(req);
//generating response
res.addHeader('Content-Type', 'text/plain');
res.headers.put('content-type','text/plain');
String responseStr = String.format(ServiceMessageResponseTemplate, new List<String>{contactName});
res.responseBody = Blob.valueOf(responseStr);
}
private static void sendOrderConfirmation(RestRequest req,RestResponse res,String msg){
String caseNumber = createCase(req,res,msg);
res.addHeader('Content-Type', 'text/plain');
String responseStr = String.format(
CASELOG_SUCCESSMSG
, new List<String>{ caseNumber, productMap.get(msg) } );
res.responseBody = Blob.valueOf(responseStr);
}
private static String createCase(RestRequest req,RestResponse res,String msg){
//create case record
CaseCreationService caseCreator = new CaseCreationService()
.parseRestRequest(req)
.setProductId( productIdMap.get(msg) )
.save();
return caseCreator.getCaseNumber();
}
private static void sendOrderDetails(RestRequest req,RestResponse res,String message){
//get the product Id from last case created for the incoming number
String productId = CaseCreationService.getSelectedProduct(req);
res.addHeader('Content-Type', 'text/plain');
if( message.equalsIgnoreCase(YES) ) {
String orderName = OrderCreationService.createOrder( productId );
String response;
if( orderName.startsWith(ERROR) ){
response = orderName;
} else {
response = String.format(orderConfirmMap.get(message), new List<String>{orderName});
}
res.responseBody = Blob.valueOf( response );
} else {
res.responseBody = Blob.valueOf( orderConfirmMap.get(message) );
}
}
}
without sharing public class OrderCreationService {
public static String createOrder(String productId){
PriceBook2 pb2Standard = [select Id from Pricebook2 where isStandard=true];
Id standardPriceBookId = pb2Standard.Id;
//Create Order Record
//Hardcoded value for Demo purpose
Order orderRecord = new Order();
orderRecord.AccountId = '0015w00002Br136AAB';
orderRecord.ContractId = '8005w000001Eb3GAAS';
orderRecord.Status = 'Draft';
orderRecord.Type= 'Replacement';
orderRecord.EffectiveDate = Date.today();
orderRecord.OpportunityId = '0065w000023hOsYAAU';
orderRecord.Pricebook2Id = standardPriceBookId;
insert orderRecord;
List<PricebookEntry> pbeLst = [
Select Id
from PricebookEntry
Where Pricebook2Id = :standardPriceBookId
and Product2Id =:productId
limit 1
];
if( pbeLst.isEmpty() ){
return 'Error: Invalid Product. Contact Admin!';
}
OrderItem orderItem = new OrderItem();
orderItem.OrderId = orderRecord.Id;
orderItem.Quantity = 1;
orderItem.UnitPrice = 1500;
orderItem.PricebookEntryId = pbeLst[0].Id;
orderItem.Product2Id = productId;
insert orderItem;
List<Order> orderList = [Select OrderNumber from Order Where Id= :orderRecord.Id limit 1];
// Create an approval request for the account
Approval.ProcessSubmitRequest approvalReq =
new Approval.ProcessSubmitRequest();
approvalReq.setComments('Submitting order for approval.');
approvalReq.setObjectId(orderRecord.Id);
Approval.ProcessResult result = Approval.process(approvalReq);
if( !orderList.isEmpty() ) {
return orderList[0].OrderNumber;
}
return 'Error: Order is not placed, Contact Admin!';
}
}
public class SendWhatsappNotification {
@future(callout=true)
public static void sendMessage(String fromNumber, String toNumber, String message){
Twillio_Config__c config = Twillio_Config__c.getInstance();
HttpRequest req = new HttpRequest();
req.setEndpoint(config.Endpoint__c);
req.setMethod('POST');
String username = config.Account_SID__c;
String password = config.Auth_Token__c;
Blob headerValue = Blob.valueOf(username + ':' + password);
String authorizationHeader = 'Basic ' +
EncodingUtil.base64Encode(headerValue);
req.setHeader('Authorization', authorizationHeader);
req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
if( !toNumber.startsWith('+'))
toNumber = '+'+toNumber;
if( !fromNumber.startsWith('+'))
fromNumber = '+'+fromNumber;
toNumber = EncodingUtil.urlEncode('whatsapp:'+toNumber, 'UTF-8');
fromNumber = EncodingUtil.urlEncode('whatsapp:'+fromNumber, 'UTF-8');
message = EncodingUtil.urlEncode(message, 'UTF-8');
String requestBody = String.format(
'To={0}&From={1}&Body={2}'
, new List<String>{toNumber, fromNumber, message }
);
req.setBody(requestBody);
// Create a new http object to send the request object
// A response object is generated as a result of the request
Http http = new Http();
HTTPResponse res = http.send(req);
System.debug(res.getBody());
//@TODO Error Handling
}
}
public class Utils {
public static void debugMap(Map<String,Object> mapObj){
debugMap('',mapObj);
}
public static void debugMap(String prefix,Map<String,Object> mapObj){
for(String key : mapObj.keySet() ) {
System.debug(prefix+ ' : '+ key + ' => ' + String.valueOf( mapObj.get(key) ) );
}
}
public static String sanitize(String msg){
return String.isBlank(msg) ? '' : msg.trim().toLowerCase();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment