Newer Version Available
Building an Asynchronous Gateway Adapter
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.
- 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.
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}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}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);1commercepayments.NotificationSaveResult saveResult = commercepayments.NotificationClient.record(notification);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.