salesforce-composite-envelo.../composite-envelope-builder/force-app/main/default/classes/DocusignCredentials.cls

188 lines
6.2 KiB
OpenEdge ABL

/**
* @description Manages Docusign API credentials and access tokens
* @author Paul Huliganga
* @date 2026-02-23
*/
public with sharing class DocusignCredentials {
private String baseUrl;
private String accountId;
private String accessToken;
private DateTime tokenExpiry;
// Singleton instance
private static DocusignCredentials instance;
// Flag to skip loadCredentials for setTestCredentials
private static Boolean skipLoadForNextInstance = false;
/**
* @description Private constructor (singleton pattern)
*/
private DocusignCredentials() {
// Only load credentials if not explicitly skipped (for setTestCredentials)
if (!skipLoadForNextInstance) {
loadCredentials();
}
skipLoadForNextInstance = false; // Reset flag
}
/**
* @description Gets singleton instance of credentials
* @return DocusignCredentials instance
*/
public static DocusignCredentials getInstance() {
if (instance == null) {
instance = new DocusignCredentials();
}
// Refresh token if expired
if (instance.isTokenExpired()) {
instance.refreshAccessToken();
}
return instance;
}
/**
* @description Loads credentials from Named Credential or Custom Settings
*/
private void loadCredentials() {
// Option 1: Using Named Credential (preferred)
// When using Named Credential, the platform handles authentication automatically
// We just need the account ID and base URL
try {
// Try to load from Custom Settings first
Docusign_Configuration__c config = Docusign_Configuration__c.getOrgDefaults();
if (config != null && String.isNotBlank(config.Account_Id__c)) {
this.accountId = config.Account_Id__c;
this.baseUrl = String.isNotBlank(config.Base_URL__c)
? config.Base_URL__c
: 'callout:DocusignAPI'; // Default to Named Credential
// If using Named Credential, token is managed by platform
if (this.baseUrl.startsWith('callout:')) {
this.accessToken = 'MANAGED_BY_NAMED_CREDENTIAL';
this.tokenExpiry = DateTime.now().addHours(1);
} else {
// Manual token management would go here
// For now, throw exception - must configure Named Credential
throw new CredentialException('Manual token management not implemented. Please use Named Credential.');
}
} else {
throw new CredentialException('Docusign credentials not configured. Please set up Custom Settings: Docusign_Configuration__c');
}
} catch (Exception e) {
throw new CredentialException('Failed to load Docusign credentials: ' + e.getMessage());
}
}
/**
* @description Checks if access token is expired
* @return True if token is expired or about to expire (within 5 minutes)
*/
private Boolean isTokenExpired() {
if (this.tokenExpiry == null) {
return true;
}
// Refresh 5 minutes before expiry to avoid edge cases
DateTime threshold = DateTime.now().addMinutes(5);
return this.tokenExpiry < threshold;
}
/**
* @description Refreshes access token (JWT or OAuth2)
* Note: In production with Named Credential, this is handled automatically by Salesforce
*/
private void refreshAccessToken() {
// If using Named Credential, no manual refresh needed
if (this.baseUrl.startsWith('callout:')) {
this.tokenExpiry = DateTime.now().addHours(1);
return;
}
// Manual JWT token refresh would go here
// This is a placeholder for future enhancement
throw new CredentialException('Manual token refresh not implemented. Use Named Credential for automatic token management.');
}
/**
* @description Gets access token
* @return Access token string
*/
public String getAccessToken() {
if (isTokenExpired()) {
refreshAccessToken();
}
return this.accessToken;
}
/**
* @description Gets Docusign account ID
* @return Account ID (GUID)
*/
public String getAccountId() {
return this.accountId;
}
/**
* @description Gets base URL for Docusign API
* @return Base URL (either Named Credential callout or full URL)
*/
public String getBaseUrl() {
return this.baseUrl;
}
/**
* @description Validates that credentials are properly configured
* @return True if valid
* @throws CredentialException if invalid
*/
public Boolean validate() {
if (String.isBlank(this.accountId)) {
throw new CredentialException('Docusign Account ID is not configured');
}
if (String.isBlank(this.baseUrl)) {
throw new CredentialException('Docusign Base URL is not configured');
}
if (String.isBlank(this.accessToken)) {
throw new CredentialException('Docusign Access Token is not available');
}
return true;
}
/**
* @description Resets singleton instance (for testing)
*/
@TestVisible
private static void resetInstance() {
instance = null;
}
/**
* @description Sets credentials manually (for testing)
*/
@TestVisible
private static void setTestCredentials(String testAccountId, String testBaseUrl, String testAccessToken) {
// Set flag to skip loadCredentials for this instance
skipLoadForNextInstance = true;
instance = new DocusignCredentials();
// Override with specific test values
instance.accountId = testAccountId;
instance.baseUrl = testBaseUrl;
instance.accessToken = testAccessToken;
instance.tokenExpiry = DateTime.now().addHours(1);
}
/**
* @description Custom exception for credential errors
*/
public class CredentialException extends Exception {}
}