Integrating Payment Gateways in Dotnet core

Integrating Payment Gateways in Dotnet core

And maximizing reusability : GemsPay

INTRODUCTION

A payment gateway is a technology that authorizes payments for online merchants or customers. In today's digital age, online payments are a crucial part of many web applications and e-commerce websites. Integrating a payment system into your .NET application can be a challenging but necessary task to facilitate secure and convenient transactions. In this blog post, I will guide you through the process of integrating a payment system in a .NET application, covering the fundamental steps and considerations along the way.

Choosing a Payment Gateway

The first step in integrating a payment system into your .NET application is selecting a suitable payment gateway. A payment gateway acts as an intermediary between your application and the financial institutions, handling the transaction process securely. Some popular payment gateways that support .NET include Gemspay, PayPal, Stripe, Square, and Braintree. For this blog, I'm going to be using Gemspay API.

What is GemsPay?

GemsPay is a centralized payment platform that enables clients to increase efficiency in their payment processing by ways of receiving and making payments across diverse channels. They are developing various solutions that will seamlessly integrate with the leading payment and switching platform providers in Nigeria. They have a range of services which can be seen here.

GemsPay Implementation

  1. Set up a GemsPay account

  2. Set up your development Environment

  3. Generate merchant tokens

  4. Start Paying

    Set up a GemsPay account

    Firstly, in your browser, you have to register at https://merchant.gemspaysolution.com/register for signup and that will create an account for you after which you'll be redirected to verify your email and proceed to login

    After the Login, you'll be directed to a Dashboard, where you'll navigate to the Settings. Here, you'll see your Test Key, Private Key and a slot for you to enter a Callback URL ( If you need to )

    Set up the development environment

    Let’s get started by opening an existing application. At the moment Gemspay does not have a Nuget package, so I will create a custom GemsPayclient that will do the work, but before that, I will create a class that mirrors the values in my appsettings.json

     "GemsPay": {
         "HosttUrl": "https://gemspaysolution.com",
         "Secret": "gpk_test_BMlfzHw****hNI8r9F2****a1xq**D",
         "ServiceCharge":"2000"
       },
    

    After that, we then create the class that mirrors these credentials.

    
         public class GemsPayCredentials
         {
             public string HostUrl { get; set; }
             public string Secret { get; set; }
             public string ServiceCharge{get; set;}
         }
    

    After which we create our GemsPayClient.

      public class GemsPayClient
         {
    
             private readonly GemsPayCredentials _cred;
    
             private readonly ILogger<GemsPayClient> _logger;
    
             private readonly IHttpClientFactory _httpClient;
    
             /// <summary>
             /// Default constructor
             /// </summary>
             public GemsPayClient(IOptions<GemsPayCredentials> cred, IHttpClientFactory httpClient, ILogger<GemsPayClient> logger)
             {
                 _cred = cred.Value;
                 _logger = logger;
                 _httpClient = httpClient;
             }
    
             public async Task<GemsPayResponse<TResponse, TErrorResponse>> SendAsync<TBody, TResponse, TErrorResponse>(GemsPayInitializationRequestApiModel gemspayRequeust)
             {
                 string appendedRoute  = $"{_cred.HostUrl}/transactions";
                 var jsonOptions = new JsonSerializerOptions
                 {
                     PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
                     DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
                 };
                 var response = default(HttpResponseMessage);
    
                 using var client = _httpClient.CreateClient();
                 try
                 {
                     response = await client.SendAsync(new HttpRequestMessage
                     {
    
                         RequestUri = new Uri(appendedRoute),
                         Method = HttpMethod.Post,
                         Headers = { { "API_KEY", _apiKey.Secret } }
                     });
                         // Extract the response
                     using var responseStream = await response.Content.ReadAsStreamAsync();
    
                     // Get the content
                     var content = await JsonSerializer.DeserializeAsync<TResponse>(responseStream, jsonOptions);
    
                     // Return the response
                     return new GemsPayResponse<TResponse, TErrorResponse>
                     {
                         HttpResponse = response,
                         Result = content,
                     };
    
                 }
                 catch (Exception ex)
                 {    
                     // Log the exception
                     _logger.LogError("An error occurred. Details: {error}", ex.Message);
                 }    
             }
    

This class is responsible for making HTTP requests and handling responses, for interacting with the GemsPay API using the IHttpClientFactory.

Note: Using IHttpClientFactory over directly creating instances of HttpClient is considered a best practice in .NET

Here's a breakdown of the key components and functionality of this class:

  1. Constructor: The class has a constructor that takes three parameters: IOptions<GemsPayCredentials> cred, IHttpClientFactory httpClient, and ILogger<GemsPayClient> logger. These parameters are used for dependency injection.

  2. Initialization: Inside the constructor, it initializes private fields _cred, _logger, and _httpClient with the values in the constructor.

  3. SendAsync Method: The SendAsync method is where the main HTTP request logic is implemented and it takes generic type parameters TBody, TResponse, and TErrorResponse as well as a GemsPayInitializationRequestModel parameter.

    • It creates a JsonSerializerOptions object to customize JSON serialization.

    • It creates an HttpResponseMessage object to store the HTTP response.

    • It uses the _httpClient to create an HTTP client for sending the request.

    • It sends an HTTP POST request to the URL specified in _cred.HostUrl and includes an "API_KEY" header with the value _apiKey.Secret.

    • It reads the response content as a stream and deserializes it into an object of type TResponse using JSON deserialization.

    • Finally, it returns a GemsPayResponse object containing the HTTP response and the deserialized content.

  4. Exception Handling: If an exception is thrown during the HTTP request, it catches the exception, logs an error message using _logger, and does not return a response. This means that if an exception occurs, the method does not propagate the exception but rather handles it internally.

    Note: It's important to know that no error handling was performed here. You can do so on your own.

    Then, we create a service class to work with GemsPayClient

    But before that, we create a GemsPayInitializationRequestModel to model our Payment Details and a GemsPayCustomerModel to mimic a customer.

    public class GemsPayInitializationRequestModel
    {
        /// <summary>
        /// The public key of the merchant
        /// </summary>
        public string MerchantPublicKey { get; set; }

        /// <summary>
        /// The call back url for this transaction
        /// </summary>
        public string CallbackUrl { get; set; }

        /// <summary>
        /// The amount to pay
        /// </summary>
        public float Amount { get; set; }

        /// <summary>
        /// The customer information for this transaction
        /// </summary>
        public GemsPayCustomerModel Customer { get; set; }

        /// <summary>
        /// The order id for this transaction
        /// </summary>
        public string OrderId { get; set; }

        /// <summary>
        /// The unique id for this transaction
        /// </summary>
        public string UniqueId { get; set; }

        /// <summary>
        /// The payment transaction reference
        /// </summary>
        public string TransactionReference { get; set; }

        /// <summary>
        /// The description of this transaction
        /// </summary>
        public string TransactionDescription { get; set; }

    }

After creating GemsPayInitializationRequest we thereafter create a GemsPayCustomerModel to Mimic our customer model.

 public class GemsPayCustomerModel
    {
        /// <summary>
        /// The id of the customer
        /// </summary>
        public string Id { get; set; }

        /// <summary>
        /// The customer's first name
        /// </summary>
        public string FirstName { get; set; }

        /// <summary>
        /// The customer's last name
        /// </summary>
        public string LastName { get; set; }

        /// <summary>
        /// The customer's email address
        /// </summary>
        public string Email { get; set; }

        /// <summary>
        /// The customer's phone number
        /// </summary>
        public string PhoneNumber { get; set; }
    }

Then, we go on to create the GemsPayService to interact directly with our Client.

 public class GemsPayService
    {
        /// <summary>
        /// Scoped instance of ApplicationDbContext
        /// </summary>
        private readonly ApplicationDbContext _context;

         /// <summary>
        /// Scoped instance of GemsPayClient
        /// </summary>
        private readonly GemsPayClient _client;



        public GemsPayService(ApplicationDbContext context, GemsPayClient client)
        {
            _context = context;
            _client = client;

        }
        public async Task<PaymentResult<GemsPayInitializationResponseModel, object>> InitializeAsync(GemsPayInitializationRequestModel paymentDetails)
        {

            // initialize amount
            var amount = Order.TotalPrice;

            // Initialize payment result
            var result = new PaymentResult<GemsPayInitializationResponseApiModel, object>
            {
                ErrorMessage = default,
                Result = new GemsPayInitializationResponseApiModel { },
                Error = new object { }
            };

            // Sum the amount to pay with the Gemspay service charge
            amount += float.Parse(_apiKey.ServiceCharge);

            // Set the payment transaction request
            var response = await _client.SendAsync<GemsPayInitializationRequestApiModel, GemsPayInitializationResponseApiModel, object>(

             // Initialize the transaction
             new GemsPayInitializationRequestModel
             {

                 CallbackUrl = paymentDetails.CallbackUrl,
                 MerchantPublicKey = _apiKey.Secret,
                 TransactionDescription = paymentDetails.TransactionDescription,
                 TransactionReference = Guid.NewGuid().ToString(),
                 Amount = amount,
                 OrderId = Order.Id,
                 Customer = new GemsPayCustomerApiModel
                 {
                     Id = paymentDetails.Customer.Id,
                     Email = paymentDetails.Customer.Email,
                     FirstName = paymentDetails.Customer.FirstName,
                     LastName = paymentDetails.Customer.LastName,
                     PhoneNumber = paymentDetails.Customer.PhoneNumber,  
                 }

             });

            return new PaymentResult<GemsPayInitializationResponseApiModel, object>
            {
                ErrorMessage = response.ErrorMessage,
                Result = response.Successful ? response.Result : new GemsPayInitializationResponseApiModel { }
            };
        }

    }

Then we create an extension to add GemsPayService, GemsPayCredentials and GemsPayClient to DI

 /// <summary>
        /// Transient instance of gemspay service
        /// </summary>
        /// <param name="services"></param>
        /// <returns></returns>
        public static IServiceCollection AddGemsPayService(this IServiceCollection services)
        {
            services.AddTransient(provider => new GemsPayClient(
                provider.GetRequiredService<IOptions<GemsPayCredentials>>(),
                provider.GetRequiredService<IHttpClientFactory>(),
                provider.GetRequiredService<ILogger<GemsPayClient>>()
                ));

            services.AddScoped<GemsPayService>();
            services.AddScoped<GemsPayCredentials>();

            // Return services for further chaining
            return services;
        }

Then, we add the extension method to our program.cs

builder.Services.AddGemsPayService(builder.Configuration)

Now, you can go on to add a controller that exposes the endpoints initialize and verify your transactions.

Stimulating a Test Transaction

Now to simulate a transaction we go to https://simulator.gemspaysolution.com/transfer to stimulate a transfer and then Verify our payment by calling a verify endpoint to verify the payment.

The UI of the simulator

Conclusion

This article covers payment gateway for testing purposes using Dotnet core. You have learned a lot of things I believe and obviously, you can implement a Gemspay payment gateway using this process in a real-world application. Hope you guys enjoyed this article.