Comment nous avons intégré Cirrus Shield et Salesforce.com (Partie 2/2)
Dans un précédent article nous avons listé les différentes fonctionnalités que nous avons mises en place pour réaliser un portail de support avec Cirrus Shield connecté à Salesfroce.com.
Dans cet article nous allons détailler la manière dont ce portail a été réalisé d’un point de vue technique.
Afin de réaliser l’intégration entre Cirrus Shield et Salesforce.com, les éléments suivants ont été réalisés:
- Une page asp.net externe qui contient le formulaire web-to-case généré dans Salesforce.com
Cette page servira à créer des Demandes (Cases) à partir de Cirrus Shield. Ces Demandes (Cases) seront envoyées dans Salesforce.com. Nous avons également développé une logique métier dans ce formulaire web-to-case.
Cette page a été générée à l’aide de la fonctionnalité « Web to Case HTML generator » de Salesforce.com.
- Les composants suivant côté Salesforce.com :
Composant | Type | Objet Concerné | Description |
CirrusShieldWS | Site distant | N/A | Rajouter une entrée dans les paramètres de site distant avec l’url : http://ws.cirrus-shield.net
pour pouvoir appeler la REST API de Cirrus Shield. |
Enabled_In_Portal__c | Champs de type case à cocher | Compte | Indique si le compte est activé dans Cirrus Shield. Si cochée, les Contacts et Cases reliés à ce compte sont synchronisés avec Cirrus Shield. |
Enabled_In_Portal__c | Champ de type case à cocher | Contact | Indique si l’utilisateur est actif dans Cirrus Shield. |
CS_Portal_User_Id__c | Champ de type texte | Contact | Ce champ est rempli avec l’Id de l’utilisateur dans Cirrus Shield quand le Contact est activé dans le portail. |
Account_Enabled_in_Portal__c | Champ de type formule et type de retour case à cocher | Case | Indique si le compte relié au Case est activé dans Cirrus Shield. |
Synchronization_Successful__c | Champ de type case à cocher | Case | Indique si le Case est bien synchronisé avec Cirrus Shield sans erreurs. |
Synchronization_Error_Message__c | Champ de type texte | Case | Indique le message d’erreur s’il y a eu un échec de synchronisation du Case avec Cirrus Shield. Ce champ sera vide s’il n’y a pas eu d’erreurs. |
managePortalCustomer.apxc | Classe apex | Contact | Sert à créer, désactiver et réactiver un utilisateur dans Cirrus Shield et de synchroniser les Demandes (Cases) avec Cirrus Shield. Cette classe et ses méthodes sont détaillées par la suite. |
CustomerPortal_InsertUpdateCase.apxc | Classe apex | Case et Contact | Fait appel à la classe managePortalCustomer pour synchroniser les Cases avec Cirrus Shield. |
CaseAfterInsertUpdate.apxt | Trigger apex | Case | Fait appel à la classe CustomerPortal_InsertUpdateCase pour synchroniser (insérer ou mettre à jour) les Cases dont le compte est activé dans Cirrus Shield. |
Enable_in_Portal | Bouton javascript | Contact | Fait appel à la méthode enablePortalCustomer de la classe managePortalCustomer. |
Disable_in_Portal | Bouton javascript | Contact | Fait appel à la méthode disablePortalUser de la classe managePortalCustomer. |
La classe managePortalCustomer.apxc est la classe principale qui contient toutes les méthodes permettant de créer, activer et désactiver un utilisateur dans Cirrus Shield et de synchroniser les Demandes (Cases) avec Cirrus Shield. Cette classe fait appel à la REST API de Cirrus Shield.
Voici les méthodes principales de cette classe avec des exemples de code :
- webservice static string enablePortalCustomer (Id pContactId, boolean sendNotif, boolean updateContact)
Cette méthode analyse l’état du Contact dans Salesforce et fait appel à la méthode appropriée pour créer l’utilisateur dans Cirrus Shield ou bien le réactiver s’il avait été désactivé.
Elle est appelée par le bouton « Enable in Portal » sur la page du Contact dans Salesforce.
Voici un exemple de code :
webservice static string enablePortalCustomer (Id pContactId, boolean sendNotif, boolean updateContact) { string ResultToEndUser; boolean success = true; string resBody = ''; string resStatusCode = ''; //query the contact in Salesforce Contact pContact = [select Id, FirstName, LastName, Name, Email, Title, Phone, AccountId, Enabled_In_Portal__c, CS_Portal_User_Id__c from Contact where Id = :pContactId]; //Contact is already active if (!string.isBlank(pContact.CS_Portal_User_Id__c) && pContact.Enabled_In_Portal__c == true) { ResultToEndUser = 'This Contact is already enabled in portal. If they are not able to login to the portal, please reset their password in the customer portal using an admin access.'; } else { //Get the authentication token from Cirrus Shield //call CS webservice (AuthToken) to get the authentication token try { string url_request = 'https://www.cirrus-shield.net/RestApi/AuthToken?Username='; url_request += EncodingUtil.urlEncode('username@aliston.fr','UTF-8'); url_request += '&password=' + EncodingUtil.urlEncode('password','UTF-8'); Http http = new Http(); Httprequest request = new HttpRequest(); request.setEndpoint(url_request); request.setMethod('GET'); request.setTimeout(10000); //Making call to CS external REST API HttpResponse response = http.send(request); //response System.debug('response body: ' + response.getBody()); System.debug('response status:' + response.getStatusCode()); resBody = response.getBody(); resStatusCode = response.getStatusCode().format(); //token token = resBody.remove('"'); System.debug('Token: ' + token); } catch(Exception e) { System.debug('Error::'+e.getMessage()); success = false; } if(string.isBlank(resBody) || success == false || resStatusCode != '200') { ResultToEndUser = 'Salesforce was not able to connect to the Customer Portal. Please contact your administrator.'; return ResultToEndUser; } else { token = resBody.remove('"'); system.debug('Token: ' + token); //Contact was disabled and needs to be reactivated if(!string.isBlank(pContact.CS_Portal_User_Id__c) && pContact.Enabled_In_Portal__c == false) { ResultToEndUser = activatePortalUser(pContact); } else { ResultToEndUser = findAccountPosition(pContact, sendNotif, updateContact); //Contact needs to be created in Cirrus Shield } } } return ResultToEndUser; }
- webservice static string findAccountPosition (Contact pContact, boolean sendNotif, boolean updateContact)
Cette méthode est responsable de gérer la position hiérarchique de l’utilisateur dans Cirrus Shield pour lui donner la bonne visibilité aux Demandes de support.
Chaque utilisateur a accès à ses propres Demandes et les Demandes des utilisateurs ayant la même position que lui.
Une position est créée dans Cirrus Shield pour chaque compte dans Salesforce, afin de positionner les utilisateurs d’un même compte sous une même « position » qui leur permette de partager les données.
La position est créée dans Cirrus Shield avec le nom étant égal à l’Id Salesforce du compte. Ceci permet de générer une position unique pour chaque compte et l’identifier facilement.
Voici un exemple de code qui montre comment créer une position dans Cirrus Shield correspondant à un compte dans Salesforce. Ensuite, transmette la position à la méthode « InsertPortalUser » pour créer l’utilisateur dans Cirrus Shield.
webservice static string findAccountPosition (Contact pContact, boolean sendNotif, boolean updateContact) { string ResultToEndUser; string positionId; string newPositionId = ''; string resBody = ''; string resStatusCode = ''; boolean success = true; DOM.Document doc = new DOM.Document(); try { // Query the account position in Cirrus Shield string queryPosition = 'select Id from UserPosition where Name = ' + pContact.AccountId; string url_request = 'https://www.cirrus-shield.net/RestApi/Query?authToken=' + token + '&selectQuery='; url_request+= EncodingUtil.urlEncode(queryPosition, 'UTF-8'); system.debug('Query position url request: ' + url_request); Http http = new Http(); //request Httprequest request = new HttpRequest(); request.setHeader('Accept', 'application/xml'); request.setEndpoint(url_request); request.setMethod('GET'); //Making call to Cirrus Shield external REST API HttpResponse response = http.send(request); System.debug('responseBody: '+ response.getBody()); System.debug('response status:' + response.getStatusCode()); resBody = response.getBody(); resStatusCode = response.getStatusCode().format(); } catch(Exception e) { System.debug('Error:'+e.getMessage()); success = false; } //If an exception occured or no response if(string.isBlank(resBody) || success == false || resStatusCode != '200') { ResultToEndUser = 'An error occured while trying to activate this contact. Please contact your administrator.'; return ResultToEndUser; } else { //The Account Position does not exist in portal. Create it. if(resBody.countMatches('<UserPosition>') == 0) { newPositionId = createAccountPosition(pContact.AccountId); if(newPositionId == '0' || string.isBlank(newPositionId)) { ResultToEndUser = 'An error occured while trying to activate this contact. Please contact your administrator'; } else { ResultToEndUser = insertPortalUser(pContact, newPositionId, sendNotif, updateContact); } return ResultToEndUser; } else { //The Account Position exists in Cirrus Shield. try { doc.load(resBody); DOM.XMLNode root = doc.getRootElement(); xmlData nodeP = new xmlData(); nodeP.node = root; System.debug('*** findAccountPosition root element:' + nodeP.node.getName()); walkThrough(nodeP,'Id'); positionId = nodeText; System.debug('***findAccountPosition in try position id:' + positionId); } catch(System.XMLException e) { System.Debug('***find account position error:' + e.getMessage()); success = false; } if(success == false || string.isBlank(positionId)) { ResultToEndUser = 'An error occured while trying to activate this contact. Please contact your administrator'; } else { ResultToEndUser = insertPortalUser(pContact, positionId, sendNotif, updateContact); } return ResultToEndUser; } }
- webservice static string createAccountPosition (Id AccID)
Cette méthode crée une position (l’équivalent d’un Rôle Salesforce) pour le compte dans Cirrus Shield.
Toutes les positions comptes créées ont comme position parente la position « CEO » qui est la plus haute dans la hiérarchie et qui est affectée aux administrateurs du portail (Cirrus Shield) afin de leur donner la visibilité sur toutes les données.
Voici le code ci-dessous :
webservice static string createAccountPosition (Id AccID){ boolean success = true; string resBody = ''; string resStatusCode = ''; string resSuccess = ''; string newPositionId = ''; DOM.Document docPosition = new DOM.Document(); //Prepare the request xml. The parent position id (CEO position) is hardcoded here string reqBody = '<Data><UserPosition><Name>' + AccID + '</Name><ShareDataWithPeers>1</ShareDataWithPeers><ParentPositionId>1172401898448226419</ParentPositionId></UserPosition></Data>'; system.debug('***position insert request :'+ reqBody); String encodedString = EncodingUtil.urlEncode(reqBody,'UTF-8'); try { string url_request = 'https://www.cirrus-shield.net/RestApi/DataAction/UserPosition?authToken=' + token +'&action=insert&matchingFieldName=Id'; Http http = new Http(); Httprequest request = new HttpRequest(); request.setHeader('Content-Type', 'application/x-www-form-urlencoded'); request.setHeader('Accept', 'application/x-www-form-urlencoded'); request.setEndpoint(url_request); request.setBody('=' + encodedString) ; request.setMethod('POST'); //Making call to CS external REST API HttpResponse response = http.send(request); System.debug('response body: ' + response.getBody()); System.debug('response status: ' + response.getStatusCode()); resBody = response.getBody(); resStatusCode = response.getStatusCode().format(); System.debug('resBody: ' + resBody); } catch(Exception e) { System.debug('Error:'+e.getMessage()); success = false; } //if an exception occured or no response if(string.isBlank(resBody) || success == false || resStatusCode != '200') { return ('0'); } else { try { docPosition.load(resBody); DOM.XMLNode root = docPosition.getRootElement(); xmlData nodeP = new xmlData(); nodeP.node = root; walkThrough(nodeP,'Success'); resSuccess = nodeText; walkThrough(nodeP,'GUID'); newPositionId = nodeText; } catch (System.XMLException e) { System.Debug('***createAccountPosition xml error:' + e.getMessage()); success = false; } //if an exception occured if(success == false || string.isBlank(newPositionId) || resSuccess == 'False' || string.isBlank(resSuccess)) { return ('0'); } else { System.debug('****New Position Id:' + newPositionId); return newPositionId; } } }
- webservice static string insertPortalUser (Contact pContact, string positionID, boolean sendNotif, boolean updateContact)
Cette méthode crée l’utilisateur dans Cirrus Shield à partir de l’information du Contact dans Salesforce.
webservice static string insertPortalUser (Contact pContact, string positionID, boolean sendNotif, boolean updateContact) { string reqBody; string resStatusCode = ''; string resBody = ''; string resSuccess = ''; boolean success = true; string ResultToEndUser = ''; string CSUserId = ''; DOM.Document docCSResp = new DOM.Document(); DOM.Document docCSUserId = new DOM.Document(); //Query the Contact and its Account in Salesforce Contact contactToUpdate = [select Id, Enabled_In_Portal__c, CS_Portal_User_Id__c,Portal_Credentials_Sent__c from Contact where Id = :pContact.Id]; Account accToUpdate = [Select Id, Enabled_In_Portal__c from Account where Id =:pContact.AccountId]; //Generate a random password for the User string pass = generateRandomString(); //Build the request to Cirrus Shield webservice in order to insert the user //The User will have the same information of the Contact in Salesforce reqBody = '<Data><User>'; reqBody += '<FirstName>' + (string.isBlank(pContact.FirstName)?'':pContact.FirstName.escapeXml()) + '</FirstName>'; reqBody += '<LastName>' + pContact.LastName.escapeXml() + '</LastName>'; reqBody += '<Name>' + pContact.Name.escapeXml() + '</Name>'; reqBody += '<Username>' + pContact.Email + '</Username>'; reqBody += '<Password>' + pass + '</Password>'; reqBody += '<Email>' + pContact.Email + '</Email>'; reqBody += '<Phone>' + pContact.Phone.escapeXml() + '</Phone>'; reqBody += '<Language>en_US</Language>'; reqBody += '<JobTitle>' + (string.isBlank(pContact.Title)?'':pContact.Title.escapeXml()) + '</JobTitle>'; reqBody += '<ProfileId>1172401899891067039</ProfileId>'; reqBody += '<PositionId>' + positionID + '</PositionId>'; reqBody += '<IsActive>1</IsActive>'; reqBody += '<IsFirstLogin>1</IsFirstLogin>'; reqBody += '<Salesforce_Id>' + pContact.Id + '</Salesforce_Id>'; reqBody += '</User></Data>'; //Encode the request xml String encodedString = EncodingUtil.urlEncode(reqBody,'UTF-8'); //Call Cirrus Shield webservice try { string url_request = 'https://www.cirrus-shield.net/RestApi/DataAction/User?authToken=' + token +'&action=insert&matchingFieldName=Salesforce_Id'; Http http = new Http(); Httprequest request = new HttpRequest(); request.setHeader('Content-Type', 'application/x-www-form-urlencoded'); request.setHeader('Accept', 'application/x-www-form-urlencoded'); request.setEndpoint(url_request); request.setBody('=' + encodedString) ; request.setMethod('POST'); //Making call to CS external REST API HttpResponse response = http.send(request); System.debug('response body: ' + response.getBody()); System.debug('response status: ' + response.getStatusCode()); resBody = response.getBody(); resStatusCode = response.getStatusCode().format(); System.debug('resBody: ' + resBody); } catch(Exception e) { System.debug('Error:'+e.getMessage()); success = false; } //if an exception occured or no response if(string.isBlank(resBody) || success == false || resStatusCode != '200') { ResultToEndUser = 'An error occured while trying to activate this Contact.'; } else { //success //get the User Id in CS try { docCSResp.load(resBody); DOM.XMLNode root = docCSResp.getRootElement(); xmlData nodeP = new xmlData(); nodeP.node = root; walkThrough(nodeP, 'Success'); resSuccess = nodeText; System.debug('Insert user response success: ' + resSuccess); if(nodeText == 'True') { walkThrough(nodeP, 'GUID'); CSUserId = nodeText; } System.debug('****New User Id:' + CSUserId); } catch(System.XMLException e) { System.Debug('***insert user error:' + e.getMessage()); success = false; } //if an exception occured if(success == false || string.IsBlank(CSUserId) || resSuccess == 'False' || string.isBlank(resSuccess)) { ResultToEndUser = 'An error occured while trying to activate this Contact.'; } else { //success if(updateContact == true) { //Update Contact: Enabled in Portal = true and fill the Portal User Id contactToUpdate.Enabled_In_Portal__c = true; contactToUpdate.CS_Portal_User_Id__c = CSUserId; if (sendNotif == true) contactToUpdate.Portal_Credentials_Sent__c = true; //Updated Account: Enable In Portal = true if (accToUpdate.Enabled_In_Portal__c == false) accToUpdate.Enabled_In_Portal__c = true; try { update accToUpdate; update contactToUpdate; } catch(DmlException e) { System.debug('An unexpected error has occurred: ' + e.getMessage()); success = false; } if (success == true) { if(sendNotif == true) { //Send an email notification to the Contact with the credentials NotifyContact(pContact.email, pContact.firstname, pContact.email, pass); } ResultToEndUser = 'Contact activated successfully in Customer Portal.'; } else { ResultToEndUser = 'An error occured while trying to activate this Contact.'; } } else { ResultToEndUser = 'CSUserId' + CSUserId; } } } return ResultToEndUser; }
- webservice static string disablePortalUser (Id pContactId)
Cette méthode désactive l’utilisateur dans Cirrus Shield. Elle est appelée par le bouton « Disable in Portal » sur la page du Contact dans Salesforce.
Elle fait d’abord appel à la méthode « Query » de la REST API de Cirrus Shield pour obtenir les informations de l’utilisateur :
//query the User info in portal queryPortalUserInfo = 'select FirstName, LastName, Name, Username, Password, Email, Language, JobTitle, Phone, ProfileId, PositionId, IsActive, IsFirstLogin, IsActivatePerformanceLogging, IsBlocked, Salesforce_Id from User where Id =' + contactToDisable.CS_Portal_User_Id__c ; //call CS webservice (Query) to get the User info in portal try { string url_request = 'https://www.cirrus-shield.net/RestApi/Query?authToken=' + token + '&selectQuery='; url_request+= EncodingUtil.urlEncode(queryPortalUserInfo, 'UTF-8'); Http http = new Http(); Httprequest request = new HttpRequest(); request.setHeader('Accept', 'application/xml'); request.setEndpoint(url_request); request.setMethod('GET'); //Making call to CS external REST API HttpResponse response = http.send(request); System.debug('responseBody: '+ response.getBody()); System.debug('response status:' + response.getStatusCode()); resBody = response.getBody(); resStatusCode = response.getStatusCode().format(); } catch(Exception e) { System.debug('Error:'+e.getMessage()); success = false; }
Ensuite, on construit le xml avec les infos de l’utilisateur retournées par la requête ci-dessus en précisant bien « IsActive = 0 » pour faire ensuite appel à la méthode DataAction et mettre à jour l’utilisateur comme inactif dans Cirrus Shield :
//Read the response and set the query to disactivate the user in the portal try { InactivateUserQuery += '<Data><User><FirstName>'; docUserInfo.load(resBody); OM.XMLNode root = docUserInfo.getRootElement(); xmlData nodeP = new xmlData(); nodeP.node = root; System.debug('*** User Info root element:' + nodeP.node.getName()); walkThrough(nodeP,'FirstName'); InactivateUserQuery += (string.isBlank(nodeText)?'':nodeText.escapeXml()) + '</FirstName><LastName>'; walkThrough(nodeP,'LastName'); InactivateUserQuery += nodeText + '</LastName><Name>'; walkThrough(nodeP,'Name'); InactivateUserQuery += nodeText + '</Name><Username>'; walkThrough(nodeP,'Username'); InactivateUserQuery += nodeText + '</Username><Password>'; walkThrough(nodeP,'Password'); InactivateUserQuery += nodeText + '</Password><Email>'; walkThrough(nodeP,'Email'); InactivateUserQuery += nodeText + '</Email><Language>en_US</Language><JobTitle>'; walkThrough(nodeP,'JobTitle'); InactivateUserQuery += (string.isBlank(nodeText)?'':nodeText.escapeXml()) + '</JobTitle><Phone>'; walkThrough(nodeP,'Phone'); InactivateUserQuery += nodeText + '</Phone><ProfileId>1172401899891067039</ProfileId><PositionId>'; walkThrough(nodeP,'PositionId'); InactivateUserQuery += nodeText + '</PositionId><IsActive>0</IsActive><IsFirstLogin>'; walkThrough(nodeP,'IsFirstLogin'); InactivateUserQuery += nodeText + '</IsFirstLogin><Salesforce_Id>'; walkThrough(nodeP,'Salesforce_Id'); InactivateUserQuery += nodeText + '</Salesforce_Id></User></Data>'; system.debug('***Inactivate User Query:' + InactivateUserQuery); } catch (System.XMLException e) { System.Debug('***Inactivate User Query error:' + e.getMessage()); success = false; } String encodedString = EncodingUtil.urlEncode(InactivateUserQuery,'UTF-8'); //if an exception occured if(success == false || string.isBlank(InactivateUserQuery)) { ResultToEndUser = 'An error occured while disactivating this contact. Please contact your administrator.'; return ResultToEndUser; } else { //success //Call Cirrus Shield webservice to disactivate the user try { string url_request = 'https://www.cirrus-shield.net/RestApi/DataAction/User?authToken=' + token +'&action=update&matchingFieldName=Salesforce_Id'; Http http = new Http(); Httprequest request = new HttpRequest(); request.setHeader('Content-Type', 'application/x-www-form-urlencoded'); request.setHeader('Accept', 'application/x-www-form-urlencoded'); request.setEndpoint(url_request); request.setBody('=' + encodedString) ; request.setMethod('POST'); //Making call to CS external REST API HttpResponse response = http.send(request); System.debug('response body: ' + response.getBody()); System.debug('response status: ' + response.getStatusCode()); resBody = response.getBody(); resStatusCode = response.getStatusCode().format(); System.debug('resBody: ' + resBody); } catch(Exception e) { System.debug('Error:'+e.getMessage()); success = false; } }
- webservice static string activatePortalUser (Contact pContact)
Cette méthode réactive un utilisateur qui était inactif dans Cirrus Shield.
Elle fait d’abord appel à la méthode « Query » de la REST API de Cirrus Shield pour obtenir les informations de l’utilisateur:
//query the User's info in Cirrus Shield queryPortalUserInfo = 'select FirstName, LastName, Name, Username, Password, Email, Language, JobTitle, Phone, ProfileId, PositionId, IsActive, IsFirstLogin, IsActivatePerformanceLogging, IsBlocked, Salesforce_Id from User where Id =' + pContact.CS_Portal_User_Id__c; //call CS webservice (Query) to get the User info in portal try { string url_request = 'https://www.cirrus-shield.net/RestApi/Query?authToken=' + token + '&selectQuery='; url_request+= EncodingUtil.urlEncode(queryPortalUserInfo, 'UTF-8'); system.debug('User reactivate url request: ' + url_request); Http http = new Http(); Httprequest request = new HttpRequest(); request.setHeader('Accept', 'application/xml'); request.setEndpoint(url_request); request.setMethod('GET'); //Making call to CS external REST API HttpResponse response = http.send(request); System.debug('responseBody: '+ response.getBody()); System.debug('response status:' + response.getStatusCode()); resBody = response.getBody(); resStatusCode = response.getStatusCode().format(); } catch(Exception e) { System.debug('Error:'+e.getMessage()); success = false; }
Ensuite, on construit le xml avec les infos de l’utilisateur retournées par la requête ci-dessus en précisant bien « IsActive = 1» pour faire ensuite appel à la méthode DataAction et mettre à jour l’utilisateur comme actif dans Cirrus Shield :
//Read the response and set the query to reactivate the user in the portal try { ActivateUserQuery += '<Data><User><FirstName>'; docUserInfo.load(resBody); DOM.XMLNode root = docUserInfo.getRootElement(); xmlData nodeP = new xmlData(); nodeP.node = root; System.debug('*** User Info root element:' + nodeP.node.getName()); walkThrough(nodeP,'FirstName'); ActivateUserQuery += (string.isBlank(nodeText)?'':nodeText.escapeXml()) + '</FirstName><LastName>'; walkThrough(nodeP,'LastName'); ActivateUserQuery += nodeText + '</LastName><Name>'; walkThrough(nodeP,'Name'); ActivateUserQuery += nodeText + '</Name><Username>'; walkThrough(nodeP,'Username'); ActivateUserQuery += nodeText + '</Username><Password>'; walkThrough(nodeP,'Password'); ActivateUserQuery += nodeText + '</Password><Email>'; walkThrough(nodeP,'Email'); ActivateUserQuery += nodeText + '</Email><Language>en_US</Language><JobTitle>'; walkThrough(nodeP,'JobTitle'); ActivateUserQuery += (string.isBlank(nodeText)?'':nodeText.escapeXml()) + '</JobTitle><Phone>'; walkThrough(nodeP,'Phone'); ActivateUserQuery += nodeText + '</Phone><ProfileId>1172401899891067039</ProfileId><PositionId>'; walkThrough(nodeP,'PositionId'); ActivateUserQuery += nodeText + '</PositionId><IsActive>1</IsActive><IsFirstLogin>'; walkThrough(nodeP,'IsFirstLogin'); ActivateUserQuery += nodeText + '</IsFirstLogin><Salesforce_Id>'; walkThrough(nodeP,'Salesforce_Id'); ActivateUserQuery += nodeText + '</Salesforce_Id></User></Data>'; system.debug('***Activate User Query:' + ActivateUserQuery); } catch (System.XMLException e) { System.Debug('***Activate User Query error:' + e.getMessage()); success = false; } String encodedString = EncodingUtil.urlEncode(ActivateUserQuery,'UTF-8'); //if an exception occured if(success == false || string.isBlank(ActivateUserQuery)) { ResultToEndUser = 'An error occured while reactivating this contact. Please contact your administrator.'; return ResultToEndUser; } else { //success //Call CS webservice to reactivate the user try { string url_request = 'https://www.cirrus-shield.net/RestApi/DataAction/User?authToken=' + token +'&action=update&matchingFieldName=Salesforce_Id'; Http http = new Http(); Httprequest request = new HttpRequest(); request.setHeader('Content-Type', 'application/x-www-form-urlencoded'); request.setHeader('Accept', 'application/x-www-form-urlencoded'); request.setEndpoint(url_request); request.setBody('=' + encodedString) ; request.setMethod('POST'); //Making call to CS external REST API HttpResponse response = http.send(request); System.debug('response body: ' + response.getBody()); System.debug('response status: ' + response.getStatusCode()); resBody = response.getBody(); resStatusCode = response.getStatusCode().format(); System.debug('resBody: ' + resBody); } catch(Exception e) { System.debug('Error::'+e.getMessage()); success = false; } }
- webservice static void NotifyContact (string email, string firstName, string username, string pass)
Cette méthode envoie, à partir de Salesforce, un email au nouveau utilisateur avec les informations de connexion à Cirrus Shield.
Après que l’Utilisateur se soit connecté à Cirrus Shield pour la première fois, il est incité à changer son mot de passe et choisir une question de sécurité.
- webservice static string generateRandomString()
Cette méthode génère, dans Salesforce, un mot de passe aléatoire pour l’utilisateur qui sera créé dans Cirrus Shield.
Après que l’Utilisateur s’est connecté à Cirrus Shield pour la première fois, il est incité à changer son mot de passe et choisir une question de sécurité.
- webservice static void walkThrough(xmlData xdata, string Key)
Cette méthode sert à analyser les réponses retournées par la REST API de Cirrus en retournant le texte d’un nœud spécifique du document XML.
- webservice static void synchonizePortalCases (list<Id> CaseIds, string token)
Cette méthode synchronise les Demandes de support (Cases) entre Salesforce et Cirrus Shield, Salesforce étant les système maître et Cirrus Shield le système esclave.
Les utilisateurs ont accès aux Demandes en mode lecture seule dans Cirrus Shield.
Le propriétaire du ticket dans Cirrus Shield est l’utilisateur correspondant au Contact du Case dans Salesforce.
Ci-dessous des fragments de code qui montrent la synchronisation des Demandes entre Salesforce et Cirrus Shield :
//Prepare the Data XML with the Cases to upsert(insert or update) in Cirrus Shield string CaseXML = '<Data>'; for(Case C :listPortalCases) { CS_CreatedDate = string.valueOfGmt(C.CreatedDate) + ' (GMT)'; if(! string.isBlank(string.valueOfGmt(C.ClosedDate))) { CS_ClosedDate = string.valueOfGmt(C.ClosedDate) + ' (GMT)'; } else { CS_ClosedDate = ''; } CaseXML = '<Case>'; CaseXML += '<Name>'+ (string.isBlank(C.Subject)?'':C.Subject.escapeXml()) + '</Name>'; CaseXML += '<OwnerId>' + C.ContactId + '</OwnerId>'; CaseXML += '<Status>' + (string.isBlank(C.Status)?'':C.Status.escapeXml()) + '</Status>'; CaseXML += '<Escalation_Status>' + (string.isBlank(C.Escalation_Status__c)?'':C.Escalation_Status__c.escapeXml()) + '</Escalation_Status>'; CaseXML += '<Type>' + (string.isBlank(C.Type)?'':C.Type.escapeXml()) + '</Type>'; CaseXML += '<Business_Line>' + (string.isBlank(C.BusLine__c)?'':C.BusLine__c.escapeXml()) + '</Business_Line>'; CaseXML += '<Case_Number>' + C.CaseNumber + '</Case_Number>'; CaseXML += '<Product_Name>' + (string.isBlank(C.Product__c)?'':C.Product__c.escapeXml()) +'</Product_Name>'; CaseXML += '<Priority>' + (string.isBlank(C.Priority)?'':C.Priority.escapeXml()) + '</Priority>'; CaseXML += '<Platform>' + (string.isBlank(C.Platform__c)?'':C.Platform__c.escapeXml()) + '</Platform>'; CaseXML += '<Date_Time_Opened>' + CS_CreatedDate +'</Date_Time_Opened>'; CaseXML += '<Date_Time_Closed>' + CS_ClosedDate + '</Date_Time_Closed>'; CaseXML += '<Final_Customer>' + (string.isBlank(C.PortalFinalCust__c)?'':C.PortalFinalCust__c.escapeXml()) + '</Final_Customer>'; CaseXML += '<Description>' + (string.isBlank(C.Description)?'':C.Description).escapeXml() +'</Description>'; CaseXML += '<End_Customer>' + (string.isBlank(C.End_Customer__r.Name)?'':C.End_Customer__r.Name.escapeXml()) + '</End_Customer>'; CaseXML += '<Version>' + (string.isBlank(C.Version__r.Name)?'':C.Version__r.Name.escapeXml()) + '</Version>'; CaseXML += '<Salesforce_Id>' + C.Id + '</Salesforce_Id>'; CaseXML += '</Case>'; } CaseXML += '</Data>'; System.debug('CaseXML: ' + CaseXML); //Encole the request XML String encodedString = EncodingUtil.urlEncode(CaseXML,'UTF-8'); //Call Cirrus Shield REST API DataAction method in Upsert mode to insert/update Cases in Cirrus Shield try { string url_request = 'https://www.cirrus-shield.net/RestApi/DataAction/Case?authToken=' + token +'&action=upsert&matchingFieldName=Salesforce_Id'; Http http = new Http(); Httprequest request = new HttpRequest(); request.setHeader('Content-Type', 'application/x-www-form-urlencoded'); request.setHeader('Accept', 'application/x-www-form-urlencoded'); request.setEndpoint(url_request); request.setBody('=' + encodedString) ; request.setMethod('POST'); //Making call to CS external REST API HttpResponse response = http.send(request); System.debug('response body: ' + response.getBody()); System.debug('response status: ' + response.getStatusCode()); resBody = response.getBody(); resStatusCode = response.getStatusCode().format(); System.debug('resBody: ' + resBody); doc.load(response.getBody()); } catch(Exception e) { System.debug('Error::'+e.getMessage()); success = false; } //If an exception occured or no response was returned if(success == false || string.isBlank(resBody) || resStatusCode != '200') { //Indicate unsuccessul synchronization status and error for each case in Salesforce for(Case C :listPortalCases) { C.Synchronization_Successful__c = false; C.Synchronization_Error_Message__c = 'Status code: ' + resStatusCode + ' - Error message: ' + resBody; listPortalCasesToUpdate.Add(C); } } else { //no exception occured //read the response and analyse, for each case, if the synchronization was successful //Retrieve the root element for this document. Dom.XMLNode root = doc.getRootElement(); System.debug('Root node: ' + root.getName()); //Loop through the Case child elements for (Dom.XMLNode child: root.getChildElements()) { Case C = new Case(); System.debug('Child Case Id: ' + child.getChildElement('salesforce_id',null).getText()); C.Id = child.getChildElement('salesforce_id',null).getText(); //If case synchronization failed (success = false in the response) if(child.getChildElement('Success', null).getText() == 'False') { C.Synchronization_Successful__c = false; C.Synchronization_Error_Message__c = child.getChildElement('ErrorMessage',null).getText(); listPortalCasesToUpdate.Add(C); System.debug('Adding case to cases to update list'); } else { //If case synchronization succeeded (success = true in the response) C.Synchronization_Successful__c = true; C.Synchronization_Error_Message__c = ''; listPortalCasesToUpdate.Add(C); System.debug('Adding case to cases to update list'); } } } if(listPortalCasesToUpdate.size() > 0) { try { //call the checkRecursive.runOnce method in order not to run the future method again Trigger_CheckRecursive.runOnce(); ProcessorControl.inFutureContext = true; update listPortalCasesToUpdate; System.debug('Cases updated successfully in Salesforce'); } catch(DMLException e) { System.Debug('Failed to update the cases in Salesforce: ' + e.getMessage()); } }
- Les composants suivant côté Cirrus Shield
Composant | Type | Objet Concerné | Description |
Log_a_Case | Web tab | N/A | Cet onglet affiche la page externe « Log a Case » contenant le formulaire web-to-case de Salesforce.
Cette page permet de crée des Cases à partir de Cirrus Shield. Les Cases seront ensuite renvoyées dans Salesforce.com grâce à la logique du formulaire web-to-case. |
Case | Objet | Case | L’objet Case de Cirrus Shield a été revu pour correspondre à celui de Salesforce (champs similaires). |
Case | Présentation de page | Case | La présentation de page de l’objet Case a été revue pour arranger les champs dans des sections propres et rajouter la section « Commentaires » |
Salesforce_Id | Champ de type texte et défini comme External ID | Case | Ce champ est rempli avec l’Id Salesforce du Case et sert comme champ de correspondance pour la synchronisation des Cases à partir de Salesforce.com. |
Salesforce_Id | Champ de type texte et défini comme External ID | User | Ce champ est rempli avec l’Id Salesforce du Contact et sert comme champ de correspondance pour la création des utilisateurs dans Cirrus Shield à partir de Salesforce.com. |
Salesforce Attachment | Plugin | Case | Ce plugin permet aux clients de charger des petits fichiers de taille maximale de 5MO à partir du Case dans Cirrus Shield (par exemple des fichiers de log ou bien des captures d’écran montrant le problème rencontré avec le client).
Ces fichiers sont ensuite envoyés à Salesforce.com et sont automatiquement rajoutés à la section « pièces jointes » du Case dans Salesforce.com. Le propriétaire du Case dans Salesforce, et à l’aide des règles de workflow, pourra recevoir un e-mail pour lui informer que le Client vient d’envoyer un fichier concernant le Case. |
Salesforce Comment | Plugin | Case | Ce plugin permet aux clients de rajouter des commentaires sur le Case dans Cirrus Shield.
Ces commentaires sont ensuite envoyés à Salesforce.com et sont automatiquement rajoutés à la section « Commentaires sur la requête » du Case dans Salesforce.com. Le propriétaire du Case dans Salesforce, et à l’aide des règles de workflow, pourra recevoir un e-mail pour lui informer que le Client vient de rajouter un commentaire sur le Case. |
Case_Comment | Objet | Case_Comment | Cet objet est utilisé par le plugin « Salesforce Comment » pour enregistrer les commentaires rajoutés par le client. Les commentaires sont affichés dans la section « Commentaires » du Case dans Cirrus Shield. |
Comme vous pouvez le constater, Cirrus Shield peut être facilement intégré avec Salesforce.com grâce à son API REST.
Dans cet article, on parle uniquement de synchronisation des utilisateurs et des Demandes de support entre Salesforce et Cirrus Shield, alors qu’on peut facilement intégrer d’autres objets et développer d’autres fonctionnalités pour rendre le portail encore plus intéressant, par exemple :
- Accès aux contrats de support à partir du portail pour permettre aux clients de suivre leur statut de support et la date de renouvellement de leurs contrats.
- Renouvellement des contrats et paiement en ligne à partir du portail. Les informations de renouvellement et de paiement seront envoyées ensuite à Salesforce.com.
- Accès aux articles de la base de connaissance à partir du portail pour que les clients cherchent une solution à leurs requêtes sans devoir avoir recours au support à chaque fois.
- Enquête de satisfaction client remplie à partir de Cirrus Shield et spécifique au Case. Les enquêtes de satisfaction seront envoyées ensuite à Salesforce.com.
- Accès aux produits, services, catalogues, informations sur les produits…à partir du portail.