-
Notifications
You must be signed in to change notification settings - Fork 81
Developing a new Gateway
Do you like to develop a new gateway for Parbad and publish it in package managers like nuget.org? Then this is the right article for you.
In this article you will learn:
- Start from the beginning
- Implementing the methods and using the Parbad features
- Saving data inside the Storage
- Creating and publishing a Nuget
- Samples
As their name suggests, these are 3 main methods that a gateway must have.
- Request: In this step, you usually send the invoice and user to the payment provider (read it bank). After that the user pays the invoice inside the payment provider website and will be transferred again to your website by payment provider. You need to know how does your payment provider accept the requests.
- Verify: This methods will be called when a user comes back from the payment provider website and want to proceed the payment operation. In this step, you must verify the invoice and make sure if the invoice is paid successfully or not.
- Refund: Not every gateway has this feature enabled. But it can be used to refund an already paid invoice.
We want to create a new gateway which called Hero.
First of all, let's create the project.
- Open the Visual Studio
- File -> New -> Project -> Class Library (.NET Standard 2 or later)
- Enter the project name. Suggested name pattern: Hero.Parbad.Gateway - This is the name of your nuget package that users will see.
Enter the bellow command inside the Package Manager Console or install it using the Visual Package Manager.
Install-Package Parbad
We start with defining the Account class like so:
public class HeroGatewayAccount : GatewayAccount
{
public int MyPropertyA { get; set; }
public int MyPropertyB { get; set; }
}
Then define your gateway class and implement the methods:
public class HeroGateway : GatewayBase<HeroGatewayAccount>
{
public HeroGateway(IGatewayAccountProvider<HeroGatewayAccount> accountProvider) : base(accountProvider)
{
}
public Task<IPaymentRequestResult> RequestAsync(Invoice invoice, CancellationToken cancellationToken = default)
{
// implementation...
}
public Task<IPaymentVerifyResult> VerifyAsync(InvoiceContext context, CancellationToken cancellationToken = default)
{
// implementation...
}
public Task<IPaymentRefundResult> RefundAsync(InvoiceContext context, Money amount, CancellationToken cancellationToken = default)
{
// implementation...
}
}
Users can use your gateway by giving the name "Hero" (without "Gateway") when they create an invoice. Another way to set the name of your gateway is using the [Gateway] attribute like so:
[Gateway("Hero")]
public class HeroGateway : GatewayBase<HeroGatewayAccount>
{
}
In this case, Parbad will find your gateway by looking the name that you specified in this attribute.
Until here is everything done with the definitions.
Let's create some extension methods to make it easier for users to use our gateway.
public static class HeroGatewayBuilderExtensions
{
public static IGatewayConfigurationBuilder<HeroGateway> AddHero(this IGatewayBuilder builder)
{
return builder.AddGateway<HeroGateway>();
}
public static IGatewayConfigurationBuilder<HeroGateway> WithAccounts(this IGatewayConfigurationBuilder<HeroGateway> builder, Action<IGatewayAccountBuilder<HeroGatewayAccount>> configureAccounts)
{
return builder.WithAccounts(configureAccounts);
}
public static IInvoiceBuilder UseHero(this IInvoiceBuilder builder)
{
return builder.SetGateway("Hero");
}
}
Users can add their bank account information in startup of their websites. You can get the accounts like so:
public class HeroGateway : GatewayBase<HeroGatewayAccount>
{
public HeroGateway(IGatewayAccountProvider<HeroGatewayAccount> accountProvider) : base(accountProvider)
{
}
public async Task<IPaymentRequestResult> RequestAsync(Invoice invoice, CancellationToken cancellationToken = default)
{
var account = await GetAccountAsync(invoice);
}
public Task<IPaymentVerifyResult> VerifyAsync(InvoiceContext context, CancellationToken cancellationToken = default)
{
var account = await GetAccountAsync(context.Payment);
}
public Task<IPaymentRefundResult> RefundAsync(InvoiceContext context, Money amount, CancellationToken cancellationToken = default)
{
var account = await GetAccountAsync(context.Payment);
}
}
Parbad will give you an account for the specified invoice.
Parbad can give you a specific HttpClient for communicating with the bank. You only need to inject it inside the constructor of your gateway class like so:
public class HeroGateway : GatewayBase<HeroGatewayAccount>
{
private readonly HttpClient _httpClient;
public HeroGateway(IGatewayAccountProvider<HeroGatewayAccount> accountProvider,
IHttpClientFactory httpClientFactory) : base(accountProvider)
{
_httpClient = _httpClientFactory.CreateClient(this); // The complete name of your gateway
}
}
The method _httpClientFactory.CreateClient(this)
gives you the unique HttpClient with pre defined base address that you need.
Now you can communicate with the bank that you want like so:
public async Task<IPaymentRequestResult> RequestAsync(Invoice invoice, CancellationToken cancellationToken = default)
{
var responseMessage = await _httpClient.PostAsync([URL], [DATA], cancellationToken);
}
When the clients pay the money inside the bank website, they will return to your website and this is the moment that you must read the information that the bank has sent to you. You can do this like so:
public class HeroGateway : GatewayBase<HeroGatewayAccount>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public HeroGateway(IGatewayAccountProvider<HeroGatewayAccount> accountProvider,
IHttpContextAccessor httpContextAccessor) : base(accountProvider)
{
_httpContextAccessor = httpContextAccessor;
}
public Task<IPaymentVerifyResult> VerifyAsync(InvoiceContext context, CancellationToken cancellationToken = default)
{
var httpContext = _httpContextAccessor.HttpContext;
var key1 = httpContext.Request.Form["key1"];
var key2 = httpContext.Request.Form["key2"];
}
}
Transporting by posting a form:
public class HeroGateway : GatewayBase<HeroGatewayAccount>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public HeroGateway(IGatewayAccountProvider<HeroGatewayAccount> accountProvider,
IHttpContextAccessor httpContextAccessor) : base(accountProvider)
{
_httpContextAccessor = httpContextAccessor;
}
public async Task<IPaymentRequestResult> RequestAsync(Invoice invoice, CancellationToken cancellationToken = default)
{
var transporter = new GatewayPost(
httpContextAccessor: httpContextAccessor,
url: "BANK PAYMENT PAGE URL",
formData: new Dictionary<string, string>
{
{"FORM FIELD NAME", FORM FIELD VALUE},
{"FORM FIELD NAME", FORM FIELD VALUE},
// other form fields...
});
return new PaymentRequestResult
{
IsSucceed = True | False,
GatewayAccountName = "Gateway Account Name",
GatewayTransporter = transporter,
Message = "" // optional
};
}
}
Transporting by redirecting the user:
public class HeroGateway : GatewayBase<HeroGatewayAccount>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public HeroGateway(IGatewayAccountProvider<HeroGatewayAccount> accountProvider,
IHttpContextAccessor httpContextAccessor) : base(accountProvider)
{
_httpContextAccessor = httpContextAccessor;
}
public async Task<IPaymentRequestResult> RequestAsync(Invoice invoice, CancellationToken cancellationToken = default)
{
var transporter = new GatewayRedirect(
httpContextAccessor: httpContextAccessor,
url: "BANK PAYMENT PAGE URL");
return new PaymentRequestResult
{
IsSucceed = True | False,
GatewayAccountName = "Gateway Account Name",
GatewayTransporter = transporter,
Message = "" // optional
};
}
}
public class HeroGateway : GatewayBase<HeroGatewayAccount>
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly HttpClient _httpClient;
public HeroGateway(IGatewayAccountProvider<HeroGatewayAccount> accountProvider,
IHttpContextAccessor httpContextAccessor,
IHttpClientFactory httpClientFactory) : base(accountProvider)
{
_httpContextAccessor = httpContextAccessor;
_httpClient = httpClientFactory.CreateClient(this);
}
public async Task<IPaymentRequestResult> RequestAsync(Invoice invoice, CancellationToken cancellationToken = default)
{
var account = await GetAccountAsync(invoice);
// If necessary, use the HttpClient to send and receive data with banks
var transporter = // Create a transporter
return new PaymentRequestResult
{
IsSucceed = True | False,
GatewayAccountName = "Gateway Account Name",
GatewayTransporter = transporter,
Message = "" // optional
};
}
}
public class HeroGateway : GatewayBase<HeroGatewayAccount>
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly HttpClient _httpClient;
public HeroGateway(IGatewayAccountProvider<HeroGatewayAccount> accountProvider,
IHttpContextAccessor httpContextAccessor,
IHttpClientFactory httpClientFactory) : base(accountProvider)
{
_httpContextAccessor = httpContextAccessor;
_httpClient = httpClientFactory.CreateClient(this);
}
public Task<IPaymentVerifyResult> VerifyAsync(InvoiceContext context, CancellationToken cancellationToken = default)
{
var account = await GetAccountAsync(context.Payment);
// Use the HttpContext and HttpClient to communicate with banks
return new PaymentVerifyResult
{
IsSucceed = True | False,
GatewayAccountName = "GATEWAY ACCOUNT NAME",
TransactionCode = "THE IMPORTANT CODE THAT YOU RECEIVED FROM THE BANK",
Message = "" // optional
}
}
}
public class HeroGateway : GatewayBase<HeroGatewayAccount>
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly HttpClient _httpClient;
public HeroGateway(IGatewayAccountProvider<HeroGatewayAccount> accountProvider,
IHttpContextAccessor httpContextAccessor,
IHttpClientFactory httpClientFactory) : base(accountProvider)
{
_httpContextAccessor = httpContextAccessor;
_httpClient = httpClientFactory.CreateClient(this);
}
public Task<IPaymentVerifyResult> RefundAsync(InvoiceContext context, Money amount, CancellationToken cancellationToken = default)
{
var account = await GetAccountAsync(context.Payment);
// Use the HttpContext and HttpClient to communicate with banks
return new PaymentRefundResult
{
IsSucceed = True | False,
GatewayAccountName = "GATEWAY ACCOUNT NAME",
Message = "" // optional
}
}
}
Sometimes you need to save some data inside the Storage and load them sometime later for example in verifying an invoice.
All you need to do is to add your data inside the DatabaseAdditionalData
property of your current method.
Parbad saves the DatabaseAdditionalData inside the Storage as string JSON and you can get them later in other methods.
Example:
public class HeroGateway : GatewayBase<HeroGatewayAccount>
{
public HeroGateway(IGatewayAccountProvider<HeroGatewayAccount> accountProvider) : base(accountProvider)
{
}
public async Task<IPaymentRequestResult> RequestAsync(Invoice invoice, CancellationToken cancellationToken = default)
{
var result = new PaymentRequestResult
{
};
// Saving some data inside the Storage.
result.DatabaseAdditionalData.Add("KEY", "VALUE");
return result;
}
public Task<IPaymentVerifyResult> VerifyAsync(InvoiceContext context, CancellationToken cancellationToken = default)
{
var transaction = context.Transactions.FirstOrDefault(); // geting a specific Transaction
var data = JsonConvert.DeserializeObject<IDictionary<string, string>>(transaction.AdditionalData);
var value = data["KEY"];
}
}
- Inside the Visual Studio, click on your project and then press
Alt + Enter
to open the project settings. - Open the
Package
tab and fill the information of your project there. - Make sure again that the package name is the correct one (For example: Hero.Parbad.Gateway).
- Change the Solution Configuration to
Release
and finally right click on your project and select thePack
menu.
You can now publish your created Nuget package in package managers like nuget.org.