salesforce-appraiser-review.../force-app/main/default/classes/DocusignESignatureService.cls

340 lines
15 KiB
OpenEdge ABL

public with sharing class DocusignESignatureService {
public class ESignatureAccountConfig {
@AuraEnabled public String accountCode;
@AuraEnabled public String accountDisplayName;
@AuraEnabled public String environment;
@AuraEnabled public String eSignatureAuthNamedCredential;
@AuraEnabled public String eSignatureRestNamedCredential;
@AuraEnabled public String eSignatureAccountId;
}
public class ApiResponse {
@AuraEnabled public Boolean success;
@AuraEnabled public Integer statusCode;
@AuraEnabled public String message;
@AuraEnabled public String requestPath;
@AuraEnabled public String responseBody;
}
public class ESignatureAccountSummary {
@AuraEnabled public String accountId;
@AuraEnabled public String accountName;
@AuraEnabled public String baseUri;
@AuraEnabled public Boolean isDefault;
@AuraEnabled public String rawJson;
}
public class TemplateSummary {
@AuraEnabled public String templateId;
@AuraEnabled public String name;
@AuraEnabled public String description;
@AuraEnabled public String shared;
@AuraEnabled public String lastModified;
@AuraEnabled public String rawJson;
}
public class EnvelopeSummary {
@AuraEnabled public String envelopeId;
@AuraEnabled public String emailSubject;
@AuraEnabled public String status;
@AuraEnabled public String createdDateTime;
@AuraEnabled public String sentDateTime;
@AuraEnabled public String completedDateTime;
@AuraEnabled public String rawJson;
}
@AuraEnabled(cacheable=true)
public static ESignatureAccountConfig getAccountConfig(String accountCode) {
CLM_Account_Setting__mdt row = requireAccountSetting(accountCode);
ESignatureAccountConfig config = new ESignatureAccountConfig();
config.accountCode = String.isNotBlank(row.Account_Code__c) ? row.Account_Code__c : row.DeveloperName;
config.accountDisplayName = String.isNotBlank(row.Account_Display_Name__c) ? row.Account_Display_Name__c : row.DeveloperName;
config.environment = row.Environment_Code__c;
config.eSignatureAuthNamedCredential = row.ESignature_Auth_Named_Credential__c;
config.eSignatureRestNamedCredential = row.ESignature_Rest_Named_Credential__c;
config.eSignatureAccountId = row.ESignature_Account_Id__c;
return config;
}
@AuraEnabled(cacheable=false)
public static ApiResponse probe(String accountCode, String relativePath) {
CLM_Account_Setting__mdt row = requireAccountSetting(accountCode);
String normalizedPath = normalizePath(relativePath);
HttpRequest req = new HttpRequest();
req.setEndpoint(buildEndpoint(normalizedPath, row.ESignature_Rest_Named_Credential__c));
req.setMethod('GET');
req.setTimeout(30000);
HttpResponse res = new Http().send(req);
ApiResponse response = new ApiResponse();
response.success = res.getStatusCode() >= 200 && res.getStatusCode() < 300;
response.statusCode = res.getStatusCode();
response.message = response.success ? 'eSignature request succeeded.' : 'eSignature request failed.';
response.requestPath = normalizedPath;
response.responseBody = res.getBody();
return response;
}
@AuraEnabled(cacheable=false)
public static List<ESignatureAccountSummary> listAccounts(String accountCode) {
ApiResponse response = getLoginInformation(accountCode);
if (!response.success) {
throw new AuraHandledException('eSignature API Error (HTTP ' + response.statusCode + '): ' + response.responseBody);
}
return parseAccountList(response.responseBody);
}
@AuraEnabled(cacheable=false)
public static ApiResponse getLoginInformation(String accountCode) {
return probe(accountCode, '/v2.1/login_information');
}
@AuraEnabled(cacheable=false)
public static ApiResponse getUserInfo(String accountCode) {
CLM_Account_Setting__mdt row = requireAccountSetting(accountCode);
HttpRequest req = new HttpRequest();
req.setEndpoint(buildEndpoint('/oauth/userinfo', authNamedCredential(row)));
req.setMethod('GET');
req.setTimeout(30000);
HttpResponse res = new Http().send(req);
ApiResponse response = new ApiResponse();
response.success = res.getStatusCode() >= 200 && res.getStatusCode() < 300;
response.statusCode = res.getStatusCode();
response.message = response.success ? 'eSignature user info request succeeded.' : 'eSignature user info request failed.';
response.requestPath = '/oauth/userinfo';
response.responseBody = res.getBody();
return response;
}
@AuraEnabled(cacheable=false)
public static ApiResponse getAccountInformation(String accountCode, String eSignatureAccountId) {
CLM_Account_Setting__mdt row = requireAccountSetting(accountCode);
String targetAccountId = requireESignatureAccountId(row, accountCode, eSignatureAccountId);
return probe(accountCode, '/v2.1/accounts/' + targetAccountId);
}
@AuraEnabled(cacheable=false)
public static List<TemplateSummary> listTemplates(String accountCode) {
CLM_Account_Setting__mdt row = requireAccountSetting(accountCode);
String targetAccountId = requireESignatureAccountId(row, accountCode, null);
ApiResponse response = probe(accountCode, '/v2.1/accounts/' + targetAccountId + '/templates');
if (!response.success) {
throw new AuraHandledException('eSignature API Error (HTTP ' + response.statusCode + '): ' + response.responseBody);
}
return parseTemplateList(response.responseBody);
}
@AuraEnabled(cacheable=false)
public static List<EnvelopeSummary> listEnvelopes(String accountCode, String fromDate) {
CLM_Account_Setting__mdt row = requireAccountSetting(accountCode);
String targetAccountId = requireESignatureAccountId(row, accountCode, null);
String normalizedFromDate = String.isBlank(fromDate)
? DateTime.newInstanceGMT(Date.today().addDays(-30), Time.newInstance(0, 0, 0, 0)).formatGMT('yyyy-MM-dd')
: fromDate.trim();
ApiResponse response = probe(
accountCode,
'/v2.1/accounts/' + targetAccountId + '/envelopes?from_date=' + EncodingUtil.urlEncode(normalizedFromDate, 'UTF-8')
);
if (!response.success) {
throw new AuraHandledException('eSignature API Error (HTTP ' + response.statusCode + '): ' + response.responseBody);
}
return parseEnvelopeList(response.responseBody);
}
@TestVisible
private static List<ESignatureAccountSummary> parseAccountList(String body) {
Object root = JSON.deserializeUntyped(body);
List<Object> records = new List<Object>();
if (root instanceof List<Object>) {
records = (List<Object>) root;
} else if (root instanceof Map<String, Object>) {
Object loginAccounts = ((Map<String, Object>) root).get('loginAccounts');
if (loginAccounts instanceof List<Object>) {
records = (List<Object>) loginAccounts;
}
Object accounts = ((Map<String, Object>) root).get('accounts');
if (records.isEmpty() && accounts instanceof List<Object>) {
records = (List<Object>) accounts;
}
}
List<ESignatureAccountSummary> summaries = new List<ESignatureAccountSummary>();
for (Object record : records) {
if (!(record instanceof Map<String, Object>)) {
continue;
}
Map<String, Object> row = (Map<String, Object>) record;
ESignatureAccountSummary summary = new ESignatureAccountSummary();
summary.accountId = firstString(row, new List<String>{ 'accountId', 'account_id' });
summary.accountName = firstString(row, new List<String>{ 'accountName', 'account_name', 'name' });
summary.baseUri = firstString(row, new List<String>{ 'baseUri', 'base_uri', 'baseUrl', 'base_url' });
summary.isDefault = parseBoolean(row.get('isDefault'));
summary.rawJson = JSON.serialize(row);
summaries.add(summary);
}
return summaries;
}
@TestVisible
private static List<TemplateSummary> parseTemplateList(String body) {
List<Object> records = extractCollection(body, new List<String>{ 'envelopeTemplates', 'templates' });
List<TemplateSummary> summaries = new List<TemplateSummary>();
for (Object record : records) {
if (!(record instanceof Map<String, Object>)) {
continue;
}
Map<String, Object> row = (Map<String, Object>) record;
TemplateSummary summary = new TemplateSummary();
summary.templateId = firstString(row, new List<String>{ 'templateId', 'template_id' });
summary.name = firstString(row, new List<String>{ 'name' });
summary.description = firstString(row, new List<String>{ 'description' });
summary.shared = firstString(row, new List<String>{ 'shared' });
summary.lastModified = firstString(row, new List<String>{ 'lastModified', 'last_modified', 'lastModifiedDateTime' });
summary.rawJson = JSON.serialize(row);
summaries.add(summary);
}
return summaries;
}
@TestVisible
private static List<EnvelopeSummary> parseEnvelopeList(String body) {
List<Object> records = extractCollection(body, new List<String>{ 'envelopes' });
List<EnvelopeSummary> summaries = new List<EnvelopeSummary>();
for (Object record : records) {
if (!(record instanceof Map<String, Object>)) {
continue;
}
Map<String, Object> row = (Map<String, Object>) record;
EnvelopeSummary summary = new EnvelopeSummary();
summary.envelopeId = firstString(row, new List<String>{ 'envelopeId', 'envelope_id' });
summary.emailSubject = firstString(row, new List<String>{ 'emailSubject', 'email_subject' });
summary.status = firstString(row, new List<String>{ 'status' });
summary.createdDateTime = firstString(row, new List<String>{ 'createdDateTime', 'created_datetime' });
summary.sentDateTime = firstString(row, new List<String>{ 'sentDateTime', 'sent_datetime' });
summary.completedDateTime = firstString(row, new List<String>{ 'completedDateTime', 'completed_datetime' });
summary.rawJson = JSON.serialize(row);
summaries.add(summary);
}
return summaries;
}
@TestVisible
private static String buildEndpoint(String relativePath, String namedCredential) {
if (String.isBlank(namedCredential)) {
throw new AuraHandledException('No eSignature named credential is configured for this account.');
}
return 'callout:' + namedCredential + normalizePath(relativePath);
}
private static String normalizePath(String relativePath) {
if (String.isBlank(relativePath)) {
throw new AuraHandledException('A relative path is required.');
}
return relativePath.startsWith('/') ? relativePath : '/' + relativePath;
}
private static CLM_Account_Setting__mdt requireAccountSetting(String accountCode) {
String normalizedCode = String.isBlank(accountCode) ? null : accountCode.trim();
List<CLM_Account_Setting__mdt> rows = new List<CLM_Account_Setting__mdt>();
if (String.isNotBlank(normalizedCode)) {
rows = [
SELECT DeveloperName,
Account_Code__c,
Account_Display_Name__c,
Environment_Code__c,
ESignature_Auth_Named_Credential__c,
ESignature_Rest_Named_Credential__c,
ESignature_Account_Id__c,
Active__c
FROM CLM_Account_Setting__mdt
WHERE Active__c = true
AND DeveloperName = :normalizedCode
LIMIT 1
];
if (rows.isEmpty()) {
rows = [
SELECT DeveloperName,
Account_Code__c,
Account_Display_Name__c,
Environment_Code__c,
ESignature_Auth_Named_Credential__c,
ESignature_Rest_Named_Credential__c,
ESignature_Account_Id__c,
Active__c
FROM CLM_Account_Setting__mdt
WHERE Active__c = true
AND Account_Code__c = :normalizedCode
LIMIT 1
];
}
}
if (rows.isEmpty()) {
throw new AuraHandledException('No active CLM account setting was found for ' + accountCode + '.');
}
CLM_Account_Setting__mdt row = rows[0];
if (String.isBlank(row.ESignature_Rest_Named_Credential__c)) {
throw new AuraHandledException('No eSignature named credential is configured for ' + accountCode + '.');
}
return row;
}
private static String authNamedCredential(CLM_Account_Setting__mdt row) {
return String.isNotBlank(row.ESignature_Auth_Named_Credential__c)
? row.ESignature_Auth_Named_Credential__c
: row.ESignature_Rest_Named_Credential__c;
}
private static String requireESignatureAccountId(CLM_Account_Setting__mdt row, String accountCode, String eSignatureAccountId) {
String targetAccountId = String.isNotBlank(eSignatureAccountId) ? eSignatureAccountId : row.ESignature_Account_Id__c;
if (String.isBlank(targetAccountId)) {
throw new AuraHandledException('No eSignature account id is configured for ' + accountCode + '.');
}
return targetAccountId;
}
private static List<Object> extractCollection(String body, List<String> keys) {
Object root = JSON.deserializeUntyped(body);
if (root instanceof List<Object>) {
return (List<Object>) root;
}
if (root instanceof Map<String, Object>) {
Map<String, Object> source = (Map<String, Object>) root;
for (String key : keys) {
Object records = source.get(key);
if (records instanceof List<Object>) {
return (List<Object>) records;
}
}
}
return new List<Object>();
}
private static String firstString(Map<String, Object> source, List<String> keys) {
for (String key : keys) {
Object value = source.get(key);
if (value != null) {
String text = String.valueOf(value);
if (String.isNotBlank(text)) {
return text;
}
}
}
return null;
}
private static Boolean parseBoolean(Object value) {
if (value == null) {
return false;
}
if (value instanceof Boolean) {
return (Boolean) value;
}
return String.valueOf(value).toLowerCase() == 'true';
}
}