Implementing a custom payment provider

What you will need

To implement a payment provider, you will need to build the following:

  • A background service to set up the definition.

  • A request to model callbacks from the payment provider.

  • A payment provider class.

  • A middleware to process callbacks from the payment provider.

The Background Service

The background service is used to set up the definition for the provider on startup. You should be able to follow the guide for Bootstrapping data on startup using the PaymentMethods Definitions as the definition type:

// Gets the definition type from the database
var definitionType = await dbContext.Set<DefinitionTypeEntity>()
                    .FirstOrDefaultAsync(x => x.Name == "PaymentMethod Definitions", token);

First, build up the definition field entities:

// Creates a list of definition field names and data types matching the 
// payment provider's needs
var fields = new List<KeyValuePair<string, string>>
{
    new("PublicKey", "ShortText"),
    new("SecretKey", "ShortText"),
    new("SuccessUrl", "ShortText"),
    new("CancelUrl", "ShortText"),
    new("WebhookSecret", "ShortText")
}.ToImmutableDictionary();
var definitionFields = new List<DefinitionFieldEntity>();
fields.ForEach(field => definitionFields.Add(new DefinitionFieldEntity
{
    BuiltIn = false,
    DataType = field.Value,
    Definition = definition,
    Multilingual = false,
    Name = field.Key,
    DisplayOnSite = true,
    RenderInEditor = true
}));

Then build the definition with the list of definition fields and the correct definition type, and add it to change tracking:

// Creates and tracks the definition
var definition = new DefinitionEntity
    {
        BuiltIn = false,
        Description = "Payment provider for X",
        Name = "X",
        DefinitionType = definitionType,
        DefinitionFields = definitionFields
    };
await dbContext.AddAsync(definition, token);

Finally, the background service should save using the dbContext:

// Saves via DbContext
await dbContext.SaveChangesAsync(token);

The Request Model

The request must inherit from CallbackRequestBase and pass the payment entity to the base class. It's also recommended that the request takes any objects the payment provider may send to the application, as parameters.

// Record that takes in payment, a Stripe Event, Stripe Signature and JSON
public record StripeCheckoutCallbackRequest(PaymentEntity Payment, Event StripeEvent, string StripeSignature, string Json) : CallbackRequestBase(Payment);

The Payment Provider

The provider must inherit from one of the payment provider base classes supported. Currently, there are 2 options:

  • RenderpagePaymentProvider This is for payment providers that render inside the storefront. This provider contains a GetForm method to render the payment form in the storefront.

  • RedirectionPaymentProvider This is for payment providers that redirect to a new page to complete the payment. This provider contains a GetRedirectUrl method, which can be used to redirect the customer to their payment.

Both types contain the following methods:

  • Cancel, to cancel an authorized payment.

  • Capture, to capture the payment when goods have been shipped.

  • ProcessCallback, to process the callback from the provider when the payment is created.

  • Refund, to refund a payment.

  • Alias, a string value that ties the definition and provider together. It must match the name of the definition created with the background service.

The payment provider wraps the callback request, so in this example, it will look like this:

// Payment provider class for stripe
public class StripeCheckoutPaymentProvider : RenderPagePaymentProvider<StripeCheckoutCallbackRequest>

Once the provider class is created, add the logic to handle the corresponding operation of each method as needed.

Refer to the payment provider's documentation to best determine how to handle various operations.

The Middleware

The middleware must inherit from ProcessCallbackMiddlewareBase<T>, where T is the request model, e.g.:

public class StripeCheckoutCallbackMiddleware : ProcessCallbackMiddlewareBase<StripeCheckoutCallbackRequest>

The base class contains 2 abstract methods you must implement:

  • ParseCallback

  • ValidateCallback

These methods will be run as the first step of the middleware and are used to ensure the validity of the callback and protect the system.

Registration

In order to encapsulate all logic related to the provider, and making it easy to reuse, it's recommended to create extension methods for the payment provider using the PaymentBuilder and IPaymentApplicationBuilder, here is an example of how such methods could look:

/// <summary>
/// Adds Stripe and all needed services to the application builder.
/// </summary>
public static PaymentBuilder AddStripe(this PaymentBuilder builder)
{
    builder.UcommerceBuilder.Services.AddHostedService<SetupStripeDefinitions>();
    builder.UcommerceBuilder.Services.AddScoped<IPaymentProvider, StripeCheckoutPaymentProvider>();
    builder.UcommerceBuilder.Services.AddUnique<IPaymentProvider<StripeCheckoutCallbackRequest>, StripeCheckoutPaymentProvider>(ServiceLifetime.Scoped);

    return builder;
}
/// <summary>
/// Tells the application builder to use Stripe with the given callbackUri.
/// </summary>
public static IPaymentApplicationBuilder UseStripe(this IPaymentApplicationBuilder builder, string processCallbackUri = "/Stripe/process/callback")
{
    builder.UseProvider<StripeCheckoutCallbackRequest, StripeCheckoutCallbackMiddleware, StripeCheckoutPaymentProvider>(processCallbackUri);

    return builder;
}

These methods can now be called on the PaymentBuilder and IPaymentApplicationBuilder on startup to register and use the provider.

Last updated