Advanced 35 min read

Build the Callout Service

By the end of this lesson, you'll be able to:

  • Build the payment gateway callout matching the contract from Lesson 1
  • Return a typed result distinguishing success, decline, and error
  • Keep the service itself unaware of Queueable or trigger concerns

Prerequisites: "Design the Payment Flow"

A typed result matching the three outcomes

public class PaymentResult {
    public Boolean success;
    public Boolean declined;
    public String gatewayTransactionId;
    public String message;
}
  1. public class PaymentResult { — a plain wrapper class.
  2. public Boolean success; — true only on a confirmed charge.
  3. public Boolean declined; — true only on an explicit gateway rejection.
  4. public String gatewayTransactionId; — populated on success.
  5. public String message; — populated on a decline or error.
  6. } — closes the class.

Three fields directly mirroring Lesson 1's three outcomes — success and declined together let a caller distinguish all three cases (success=true; success=false, declined=true; success=false, declined=false for an error), without needing a separate enum or extra parsing logic.

The callout itself

public class PaymentGatewayService {
    public PaymentResult charge(Decimal amount, String paymentToken) {
        HttpRequest request = new HttpRequest();
        request.setEndpoint('callout:Payment_Gateway/charge');
        request.setMethod('POST');
        request.setBody(JSON.serialize(new Map<String, Object>{
            'amount' => amount,
            'token' => paymentToken
        }));

        HttpResponse response = new Http().send(request);
        Map<String, Object> parsed = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());

        PaymentResult result = new PaymentResult();
        if (response.getStatusCode() == 200) {
            result.success = true;
            result.gatewayTransactionId = (String) parsed.get('transactionId');
        } else if (response.getStatusCode() == 402) { // "Payment Required" — the gateway's decline status
            result.success = false;
            result.declined = true;
            result.message = (String) parsed.get('reason');
        } else {
            result.success = false;
            result.declined = false;
            result.message = 'Gateway error: ' + response.getStatusCode();
        }
        return result;
    }
}
  1. public class PaymentGatewayService { — the service class.
  2. public PaymentResult charge(Decimal amount, String paymentToken) { — notice paymentToken, not a raw card number — Lesson 5 covers exactly why.
  3. HttpRequest request = new HttpRequest(); — an empty request.
  4. request.setEndpoint('callout:Payment_Gateway/charge'); — a Named Credential endpoint.
  5. request.setMethod('POST'); — sending data.
  6. request.setBody(JSON.serialize(new Map<String, Object>{ — opens the body, built from a map.
  7. 'amount' => amount, — the charge amount.
  8. 'token' => paymentToken — the token, never a raw card number.
  9. })); — closes the map and the setBody call.
  10. (blank line)
  11. HttpResponse response = new Http().send(request); — sends it.
  12. Map<String, Object> parsed = (Map<String, Object>) JSON.deserializeUntyped(response.getBody()); — parses the response body regardless of outcome.
  13. (blank line)
  14. PaymentResult result = new PaymentResult(); — an empty result to populate.
  15. if (response.getStatusCode() == 200) { — the success branch.
  16. result.success = true; — marks success.
  17. result.gatewayTransactionId = (String) parsed.get('transactionId'); — captures the gateway's reference.
  18. } else if (response.getStatusCode() == 402) { // "Payment Required" — the gateway's decline status — the decline branch, a specific status code.
  19. result.success = false; — not a success.
  20. result.declined = true; — but a definitive decline, not an error.
  21. result.message = (String) parsed.get('reason'); — captures the decline reason.
  22. } else { — the error branch — any other status.
  23. result.success = false; — not a success.
  24. result.declined = false; — and not a decline either — a technical failure.
  25. result.message = 'Gateway error: ' + response.getStatusCode(); — records the unexpected status.
  26. } — closes the three-way branch.
  27. return result; — hands back the typed result.
  28. } — closes the method.
  29. } — closes the class.

Notice paymentToken, not a raw card number — Lesson 5 covers exactly why. The three-way branch here is a direct implementation of Lesson 1's success/decline/error distinction, using the gateway's actual status codes to tell them apart.

Exercise

As a comment, explain why the service checks response.getStatusCode() == 402 specifically for a decline, rather than treating any non-200 as a decline.

Show hint

Think about what a 500 status would mean instead.

APEX

Build the Callout Service Quiz

1. What do the success and declined fields together let a caller distinguish?

Log in to submit the quiz and save your score.

My Notes

Log in to keep private notes on this lesson.

Questions about this lesson

No questions yet — be the first to ask.

Log in to ask a question about this lesson.

Summary

This lesson builds PaymentGatewayService — a plain, synchronous-looking callout service, deliberately unaware that it will only ever be called from inside an async context.