Newer Version Available

This content describes an older version of this product. View Latest

Building an Asynchronous Gateway Adapter

In an asynchronous payments configuration, the payments platform first sends transaction information to the gateway. The gateway responds with an acknowledgment that it received the transaction, and then the platform creates a pending transaction. The gateway sends a notification, which contains the final transaction status. The platform then updates the transaction’s status accordingly.

The asynchronous process differs from synchronous transactions, where the platform does not create a pending transaction after the initial gateway request. Instead, the platform creates a transaction only after the gateway sends a response containing the final transaction status. For information on building a synchronous adapter, review Building a Synchronous Gateway Adapter.

An asynchronous configuration requires both a synchronous gateway adapter and an asynchronous adapter. In this topic, we’ll break down a sample asynchronous adapter by looking at several important areas.
  • Defining an asynchronous payment gateway adapter
  • Processing the initial payment request
  • Processing a notification from the payment gateway
  • Debugging gateway responses using system debug logs.

Payment gateway adapters can’t make future calls, external callouts using System.Http, asynchronous calls, queueable calls, or execute DMLs using SOQL.

Note

Asynchronous Payment Gateway Adapter Definition

An asynchronous gateway adapter class must implement both the PaymentGatewayAdapter Interface and the PaymentGatewayAsyncAdapter Interface. The adapter class must also implement the processRequest method for PaymentGatewayAdapter and the processNotification method for PaymentGatewayAsyncAdapter.

1global with sharing class SampleAdapter implements commercepayments.PaymentGatewayAsyncAdapter, commercepayments.PaymentGatewayAdapter {
2    global SampleAdapter() {}
3    
4    global commercepayments.GatewayResponse processRequest(commercepayments.paymentGatewayContext gatewayContext) {
5    }
6    
7    global commercepayments.GatewayNotificationResponse processNotification(commercepayments.PaymentGatewayNotificationContext gatewayNotificationContext) {
8    }
9}

Processing an Initial Payment Request

When the payments platform receives a payments API request, it passes the request to your gateway adapter for further evaluation. The adapter begins the request evaluation process by calling the processRequest method, which represents the first step in an asynchronous payment flow. We can break the processRequest implementation into three parts.

First, it builds a payment request object that the gateway can understand.

1commercepayments.RequestType requestType = gatewayContext.getPaymentRequestType();
2if (requestType == commercepayments.RequestType.Capture) {
3   req.setEndpoint('/pal/servlet/Payment/v52/capture');
4    body = buildCaptureRequest((commercepayments.CaptureRequest)gatewayContext.getPaymentRequest());
5} else if (requestType == commercepayments.RequestType.ReferencedRefund) {
6    req.setEndpoint('/pal/servlet/Payment/v52/refund');
7    body = buildRefundRequest((commercepayments.ReferencedRefundRequest)gatewayContext.getPaymentRequest());
8}

Then, the adapter sends the request to the payment gateway.

1req.setBody(body);
2req.setMethod('POST');
3commercepayments.PaymentsHttp http = new commercepayments.PaymentsHttp();
4HttpResponse res = null;
5try {
6    res = http.send(req);
7} catch(CalloutException ce) {
8    commercepayments.GatewayErrorResponse error = new commercepayments.GatewayErrorResponse('500', ce.getMessage());
9    return error;
10}
Finally, the adapter creates a response object to store data from the gateway’s response. The type of response object will vary based on whether you originally made a payment capture request or a refund request.
1if ( requestType == commercepayments.RequestType.Capture) {
2   // Refer to the end of this doc for sample createCaptureResponse implementation
3    response =  createCaptureResponse(res);
4} else if ( requestType == commercepayments.RequestType.ReferencedRefund) {
5    response =  createRefundResponse(res);
6}
7return response;

Processing a Notification from the Payment Gateway

After the customer bank processes the transaction and sends the results to the gateway, the gateway sends the adapter a notification indicating that it’s ready to provide the final transaction status. For this part of an asynchronous transaction flow, the adapter needs to call the processNotification class. We can split the processNotification implementation into four parts.

First, the adapter verifies the signature in the notification request. For more information on verifying signatures, review [TOPIC].

1private Boolean verifySignature(NotificationRequest requestItem) {
2    String payload = requestItem.pspReference + ':'
3        + (requestItem.originalReference == null ? '' : requestItem.originalReference) + ':'
4        + requestItem.merchantAccountCode + ':'
5        + requestItem.merchantReference + ':'
6        + requestItem.amount.value.intValue() + ':'
7        + requestItem.amount.currencyCode + ':'
8        + requestItem.eventCode + ':'
9        + requestItem.success;
10    String myHMacKey = getHMacKey();
11    String generatedSign = EncodingUtil.base64Encode(Crypto.generateMac('hmacSHA256', Blob.valueOf(payload), 
12                                EncodingUtil.convertFromHex(myHMacKey)));
13    return generatedSign.equals(requestItem.additionalData.hmacSignature);
14}
Next, the adapter parses the gateway’s notification request and builds a notification object. The getPaymentGatewayNotificationRequest method evaluates data from the gateway’s notification request items, which include status, referenceNumber, event, and amount. The notificationStatus object is set to Success or Failed based on whether the platform successfully received the notification. If the notification’s event code indicates that the gateway processed a payment capture transaction, the adapter builds a notification object using the CaptureNotification class. If the event code indicates that the gateway processed a refund transaction, the adapter builds a notification object using the ReferencedRefundNotification class.
1commercepayments.PaymentGatewayNotificationRequest gatewayNotificationRequest = gatewayNotificationContext.getPaymentGatewayNotificationRequest();
2Blob request = gatewayNotificationRequest.getRequestBody();
3SampleNotificationRequest notificationRequest = SampleNotificationRequest.parse(request.toString().replace('currency', 'currencyCode'));                
4List<SampleNotificationRequest.NotificationItems> notificationItems = notificationRequest.notificationItems;
5SampleNotificationRequest.NotificationRequestItem notificationRequestItem = notificationItems[0].NotificationRequestItem;
6
7Boolean success = Boolean.valueOf(notificationRequestItem.success);
8String pspReference = notificationRequestItem.pspReference;
9String eventCode = notificationRequestItem.eventCode;
10Double amount = notificationRequestItem.amount.value;
11
12commercepayments.NotificationStatus notificationStatus = null;
13if (success) {
14    notificationStatus = commercepayments.NotificationStatus.Success;
15} else {
16    notificationStatus = commercepayments.NotificationStatus.Failed;
17}
18commercepayments.BaseNotification notification = null;
19if ('CAPTURE'.equals(eventCode)) {
20    notification = new commercepayments.CaptureNotification();
21} else if ('REFUND'.equals(eventCode)) {
22    notification = new commercepayments.ReferencedRefundNotification();
23}
24notification.setStatus(notificationStatus);
25notification.setGatewayReferenceNumber(pspReference);
26notification.setAmount(amount);
The adapter then requests that the payments platform records the results of the notification.
1commercepayments.NotificationSaveResult saveResult = commercepayments.NotificationClient.record(notification);
All asynchronous gateways require that the platform acknowledges that it received the notification, regardless of whether the platform successfully saved the notification’s data. The platform calls the GatewayNotificationResponse class to send the acknowledgment.
1commercepayments.GatewayNotificationResponse gnr = new commercepayments.GatewayNotificationResponse();
2if (saveResult.isSuccess()) {
3    system.debug('Notification accepted by platform');
4} else {
5    system.debug('Errors in the result '+ Blob.valueOf(saveResult.getErrorMessage()));
6}
7gnr.setStatusCode(200);
8gnr.setResponseBody(Blob.valueOf('[accepted]'));
9return gnr;

Debugging

Usually, Apex debug logs are available in the developer console. However, Salesforce doesn’t store debug logs from the processNotification method in the developer console. To view this part of the method flow using system.debug, review the Collect Debug Logs for Guest Users section of Set Up Debug Logging.