0

I have a scheduled batch class that updates my order payments however I am receiving an error that the callout is not supported. I have read other posts that for batch classes if Database.AllowCallouts is included it would be able to hit my other class methods to retrieve a token. However it is not working, this line is where the error is coming from:

global final String authToken = orderService.getAccessToken();

Here is my global class for mass order payments:

global class MassOrderPayments implements Database.Batchable<sObject>, Database.AllowsCallouts{

        Map<Id, String> errorMap = new Map<Id,String>(); 
        global final String authToken = orderService.getAccessToken();

     global Database.QueryLocator start(Database.BatchableContext BC) {
         if(authToken == null || authToken == ''){
             throw new EDFException('Error sending scheduled payments, could not authenticate');
         }
          return Database.getQueryLocator('SELECT ID FROM Order WHERE Scheduled_Payment__c = TRUE');
        }

    global void execute(Database.BatchableContext BC, List<Order> orders) {
     for(Order order : orders){        
         String result = orderService.processRESTPayments(check.id,true,authToken);
         if(result != 'success'){
                errorMap.put(check.id, result);
         }
         check.Scheduled__Payment__c = false;
        }
         update orders;
        }

        global void finish(Database.BatchableContext BC){
        for(Id id : errorMap.keySet()){
            EmailService.sendGenericEmail('Error Mass payingOrders', 'Order Id:' + id + ' Error Message:'+errorMap.get(id));
         }
    }
}

Here is my OrderService.getAccessToken() method:

public static String getAccessToken(){
    try {
        if(Test.isRunningTest()) {
            return '12345678';
        } else {        
            Http http = new Http();
            HttpResponse response;
            HttpRequest req = new HttpRequest();
            req.setMethod('POST');
            req.setEndPoint(System.Label.TokenEndpoint);
            req.setHeader('Accept', 'application/json');
            req.setHeader('Accept-Language', 'en_US');
            Blob headerValue = Blob.valueOf(System.Label.ClientID + ':' + System.Label.Secret);
            String authorizationHeader = 'BASIC ' + EncodingUtil.base64Encode(headerValue);
            req.setHeader('Authorization', authorizationHeader);
            req.setBody('grant_type=client_credentials');
            response = http.send(req);
            if(response != null && response.getBody() != null){
                TokenResponse jsonResponse = (TokenResponse) JSON.deserialize(response.getBody(), TokenResponse.class);
                return jsonResponse.access_token;
            }
        }
    }
    catch(Exception e){
        throw new EDFException(‘Error retrieving token'+e.getMessage()+''+e.getStackTraceString());
    }
    return null;
}

How would I proceed in grabbing my token?

The error I received:

Scheduled job 'ScheduleOrderPayments' threw unhandled exception.
caused by: EDFException: Error retreivingl auth tokenCallout from scheduled Apex not supported.
Class.orderService.getAccessToken: line 248, column 1
Class.MassOrderPayments.: line 4, column 1
Class.ScheduleOrderPayments.execute: line 3, column 1
Class.orderService.getAccessToken: line 256, column 1
Class.MassOrderPayments.: line 4, column 1
Class.ScheduleOrderPayments.execute: line 3, column 1

Schedule Class:

global class ScheduleMassOrderPayments implements Schedulable {
    global void execute(SchedulableContext sc) {
        MassOrderPayments orders = new MassOrderPayments();
        Database.executeBatch(orders);
    }
}
6
  • 5
    You are abusing Test.isRunningTest() where you should instead use mocks. It won't fix your issue to resolve, but you should avoid this pattern at all costs.
    – Adrian Larson
    Commented Dec 12, 2018 at 15:18
  • Yes I understand that, I'm trying to get this to work and then going in to clean up my code.
    – j.koh
    Commented Dec 12, 2018 at 15:20
  • @j.koh You say Schedule APEX but I can only see Batch Apex in your code, can you confirm its Batch Apex you want and not Schduled Apex? Commented Dec 12, 2018 at 15:41
  • Sorry it is a batch apex
    – j.koh
    Commented Dec 12, 2018 at 15:41
  • Try moving your call to orderService.getAccessToken() inside your start method. You'll have to make it non-final.
    – Adrian Larson
    Commented Dec 12, 2018 at 16:26

1 Answer 1

3

You need to move the initialization point of your auth token variable.

global class MassOrderPayments implements Database.Batchable<sObject>, Database.AllowsCallouts{
    Map<Id, String> errorMap = new Map<Id,String>(); 
    global final String authToken = orderService.getAccessToken();

This initializer statement is executed at the time your scheduled class is executed, because that's when you create the instance of MassOrderPayments. It presumably looks something like

public class MySchedulable implements Schedulable {
    public void execute(SchedulableContext sc) {
        Database.executeBatch(new MassOrderPayments(), 200);
    }
}

The callout in getAccessToken() goes off there, in the context of the Schedulable that fires the batch job, because that's where the class instance is created. The batch class instance is then serialized and enqueued for execution.

You should probably move the initialization of authToken into start(), or possibly even into execute(). This ensures that your token is fresh (it's possible for the execution of your batch to be delayed significantly past the time it's enqueued by the Schedulable, based on system load and the state of the queue), and that the callout is executed in the context of your batch class rather than the spawning Schedulable.

7
  • So move this line global final String authToken = orderService.getAccessToken(); into global Database.QueryLocator start(Database.BatchableContext BC, ) {? So it would look like this: global Database.QueryLocator start(Database.BatchableContext BC, ) { authToken = orderService.getAccessToken();
    – j.koh
    Commented Dec 12, 2018 at 15:40
  • Something like that, yeah.
    – David Reed
    Commented Dec 12, 2018 at 15:52
  • I moved authToken to start() It completes successfully and sets my boolean "Scheduled_Payment__c" correctly but doesnt look like it hits: String result = orderService.processRESTPayments(check.id,true,authToken); Could it be my scheduled class? When i execute anonymously it works if i call the class but the scheduled class seems to be the problem
    – j.koh
    Commented Dec 12, 2018 at 19:07
  • @j.koh I am not sure what you are asking. You may need to open a new question and include the relevant code.
    – David Reed
    Commented Dec 12, 2018 at 19:29
  • @j.koh - I think what you are asking is why this line String result = orderService.processRESTPayments(check.id,true,authToken); in the for loop in the execute statement is not executing. First thing to check is if there is data i.e. Orders with Scheduled_Payment__c = true. If it is then you need to look at the debug log for any errors. Hope this helps. Commented Dec 12, 2018 at 20:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .