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
Set up a GemsPay account
Set up your development Environment
Generate merchant tokens
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 myappsettings.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:
Constructor: The class has a constructor that takes three parameters:
IOptions<GemsPayCredentials> cred
,IHttpClientFactory httpClient
, andILogger<GemsPayClient> logger
. These parameters are used for dependency injection.Initialization: Inside the constructor, it initializes private fields
_cred
,_logger
, and_httpClient
with the values in the constructor.SendAsync Method: The
SendAsync
method is where the main HTTP request logic is implemented and it takes generic type parametersTBody
,TResponse
, andTErrorResponse
as well as aGemsPayInitializationRequestModel
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.
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 aGemsPayCustomerModel
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.