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);
    }
}
  • 4
    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 Dec 12 '18 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 Dec 12 '18 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? – Pranay Jaiswal Dec 12 '18 at 15:41
  • Sorry it is a batch apex – j.koh Dec 12 '18 at 15:41
  • Try moving your call to orderService.getAccessToken() inside your start method. You'll have to make it non-final. – Adrian Larson Dec 12 '18 at 16:26
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.

  • 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 Dec 12 '18 at 15:40
  • Something like that, yeah. – David Reed Dec 12 '18 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 Dec 12 '18 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 Dec 12 '18 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. – Vinod Agrawal Dec 12 '18 at 20:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.