188 lines
6.2 KiB
OpenEdge ABL
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 {}
|
|
}
|