This article provides a quick guide to set up a controller accepting server callbacks
from the various payment providers in a single generic way.
Code sample
The code necessary to support callbacks consists of two parts, (1) extracting the HTTP
request information into a readable format for the extension and (2) calling the
correct callback handler. We will start with the latter, as it gives a better
overview of the whole method.
Calling the correct callback handler
The callback handlers implement a CanHandle/Handle pattern, which means you need to
dependency inject a list of them,
i.e. IEnumerable<IPaymentCallbackMessageHandler> (_messageHandlers below is of
that type).
publicrecordSampleContext(intSegmentationId):PaymentContext;publicclassPaymentGatewayController:Controller{...[HttpGet, HttpPost, HttpPut, HttpDelete, HttpPatch]publicasyncTask<IActionResult>HandleMessage(stringpaymentIdentifier,stringpath){// extract information from http context// which is expanded upon later...varpaymentMessage=newPaymentCallbackMessage(httpMethod,PaymentIdentifier.Create(paymentIdentifier),path.ToLowerInvariant(),query,headers,formData,postBody,remoteIpAddress,isLocal);// Simple example here, normally you could be able // to get more information from the http method, http context etc.varsampleContext=newSampleContext(1);try{IPaymentCallbackMessageHandler<SampleContext>?handler=null;foreach(varpotentialHandlerin_messageHandlers){if(awaitpotentialHandler.CanHandleAsync(paymentMessage)){handler=potentialHandler;break;}}if(handlerisnull)returnBadRequest();varresponse=awaithandler.HandleAsync(sampleContext,paymentMessage);returnHandleResponse(response);}catch(PaymentExceptionex){returnBadRequest(ex.Message);}}privateIActionResultHandleResponse(PaymentResponseresponse){returnresponseswitch{PaymentResponseRedirectredirectResponse=>Redirect(redirectResponse.AbsoluteUrl),PaymentResponseStatusCodestatusCodeResponse=>StatusCode(statusCodeResponse.StatusCode),PaymentResponseContentcontentResponse=>Content(contentResponse.Content),_=>Ok(),};}}
Extracting the necessary information from the request
You can use the below code snippet as inspiration for how to extract the required
properties for the PaymentCallbackMessage.
publicstaticclassHttpRequestExtensions{publicstaticboolIsLocal(thisHttpRequestreq){if(req.Headers.ContainsKey("X-Forwarded-For")){returnfalse;}varconnection=req.HttpContext.Connection;if(connection.RemoteIpAddress!=null){returnconnection.LocalIpAddress!=null?connection.RemoteIpAddress.Equals(connection.LocalIpAddress):IPAddress.IsLoopback(connection.RemoteIpAddress);}// for in memory TestServer or // when dealing with default connection infoif(connection.RemoteIpAddress==null&&connection.LocalIpAddress==null){returntrue;}returnfalse;}publicstaticasyncTask<string>GetPostBodyAsync(thisHttpRequestrequest){stringbodyStr;// Allows using the stream multiple timesrequest.EnableBuffering();// important: keep stream openedusing(varreader=newStreamReader(request.Body,Encoding.UTF8,detectEncodingFromByteOrderMarks:true,1024,leaveOpen:true)){bodyStr=awaitreader.ReadToEndAsync();}request.Body.Position=0;returnbodyStr;}publicstaticDictionary<string,string[]>?GetFormData(thisHttpRequestrequest){if(!request.HasFormContentType){returnnull;}returnrequest.Form.ToDictionary(e=>e.Key,e=>e.Value.Where(x=>xisnotnull).ToArray()asstring[],StringComparer.Ordinal);}}
Registering the route
Lastly, before the endpoint is set up properly, we add it as a route in our
controller setup.
If you have followed all the steps so far in the "Getting Started" guide, you are
now done with the initial setup. The next step is to look at the documentation for
the payment providers you are interested in using under the integrations section.