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;
}
public class PaymentResult {— a plain wrapper class.public Boolean success;— true only on a confirmed charge.public Boolean declined;— true only on an explicit gateway rejection.public String gatewayTransactionId;— populated on success.public String message;— populated on a decline or error.}— 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;
}
}
public class PaymentGatewayService {— the service class.public PaymentResult charge(Decimal amount, String paymentToken) {— noticepaymentToken, not a raw card number — Lesson 5 covers exactly why.HttpRequest request = new HttpRequest();— an empty request.request.setEndpoint('callout:Payment_Gateway/charge');— a Named Credential endpoint.request.setMethod('POST');— sending data.request.setBody(JSON.serialize(new Map<String, Object>{— opens the body, built from a map.'amount' => amount,— the charge amount.'token' => paymentToken— the token, never a raw card number.}));— closes the map and thesetBodycall.- (blank line)
HttpResponse response = new Http().send(request);— sends it.Map<String, Object> parsed = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());— parses the response body regardless of outcome.- (blank line)
PaymentResult result = new PaymentResult();— an empty result to populate.if (response.getStatusCode() == 200) {— the success branch.result.success = true;— marks success.result.gatewayTransactionId = (String) parsed.get('transactionId');— captures the gateway's reference.} else if (response.getStatusCode() == 402) { // "Payment Required" — the gateway's decline status— the decline branch, a specific status code.result.success = false;— not a success.result.declined = true;— but a definitive decline, not an error.result.message = (String) parsed.get('reason');— captures the decline reason.} else {— the error branch — any other status.result.success = false;— not a success.result.declined = false;— and not a decline either — a technical failure.result.message = 'Gateway error: ' + response.getStatusCode();— records the unexpected status.}— closes the three-way branch.return result;— hands back the typed result.}— closes the method.}— 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.
Build the Callout Service Quiz
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.