Even after researching it a fair bit, I don't understand why I keep getting the CalloutException. No matter what I do to prevent it, the exception happens when triggering my API class via Process Builder:
An Apex error occurred: System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out
The goal is to do the following when an Opportunity's Stage is changed:
- Query for info from Opportunity ID
- Send HttpRequest to API
- Interpret HttpResponse
Here is the code:
private static Api_Settings__c Settings {get;set;}
@InvocableMethod(label = 'Request API Access'
description = 'Requests access to API a SF Opportunity meets criteria.')
public static List<Boolean> CreateAndSendApiAccessRequests(List<ID> opportunityIds)
{
Settings = Api_Settings__c.getOrgDefaults();
List<AccessRequest> requests = new List<AccessRequest>();
for (ID id: opportunityIds)
{
System.debug(id);
AccessRequest request = CreateApiAccessRequest(id);
requests.add(request);
}
List<Boolean> results = new List<Boolean>();
for (AccessRequest request: requests)
{
Boolean result = SendApiAccessRequest(request);
results.add(result);
System.debug(result);
}
return results;
}
public static AccessRequest CreateApiAccessRequest(ID opportunityId)
{
// Get info from the Opportunity ID
Opportunity opp = [
SELECT Name
,Primary_Contact__r.Name
,Primary_Contact__r.Email
,Account.Name
,Account.BillingAddress
FROM Opportunity
WHERE ID=:opportunityId];
// Create the address info from the Opportunity's Primary Contact
Address address = new Address();
address.line1 = opp.Account.BillingAddress.getStreet();
address.line2 = '';
address.city = opp.Account.BillingAddress.getCity();
address.postalCode = opp.Account.BillingAddress.getPostalCode();
address.stateOrProvinceCode = opp.Account.BillingAddress.getStateCode();
address.countryCode = opp.Account.BillingAddress.getCountryCode();
// Create the access request
AccessRequest request = new AccessRequest();
request.contactName = opp.Primary_Contact__r.Name;
request.contactEmail = opp.Primary_Contact__r.Email;
request.organizationName = opp.Account.Name;
request.address = address;
request.reason = 'Salesforce API Access Request';
return request;
}
public static Boolean SendApiAccessRequest(AccessRequest request)
{
Boolean result = false;
// Construct the request url
String requestUrl = Settings.ApiBaseurl__c + Settings.ApiEndpoint__c;
// Construct the Basic Auth string
String basicAuthString = BasicAuthService.GetBasicAuthString(Settings.ClientId__c, Settings.ClientSecret__c);
// Create the API access request
HttpRequest httpRequest = new HttpRequest();
httpRequest.setEndpoint(requestUrl);
httpRequest.setMethod('POST');
httpRequest.setHeader('Username', Settings.ApiHeaderUsername__c);
httpRequest.setHeader('Password', Settings.ApiHeaderPassword__c);
httpRequest.setHeader('Content-Type', 'application/json');
httpRequest.setHeader('Authorization', BasicAuthString);
httpRequest.setBody(JSON.serialize(request));
// Send the request
HttpResponse httpResponse = new Http().send(httpRequest);
// Ensure we received the correct status code
if (httpResponse.getStatusCode() == 201)
{
result = true;
}
else
{
result = false;
}
return result;
}
I've tried to separate CRUD operations from callouts, but obviously it hasn't worked.
What am I missing here?
@future
method.@future (callout=true)
. I was able to fix it, and would gladly preserve the solution here, but I can't post my answer yet (not enough reputation on this SE site).