01. Getting started - Viincenttt/MollieApi GitHub Wiki

Installing the library

The easiest way to install the Mollie Api library is to use the Nuget Package.

Install-Package Mollie.Api

Creating a API client

Every API has it's own API client class. For example: PaymentClient, PaymentMethodClient, CustomerClient, MandateClient, SubscriptionClient, IssuerClient and RefundClient classes. All of these API client classes also have their own interface. The recommended way to instantiate API clients, is to use the built in dependency injection extension method:

builder.Services.AddMollieApi(options => {
    options.ApiKey = builder.Configuration["Mollie:ApiKey"];
    options.RetryPolicy = MollieHttpRetryPolicies.TransientHttpErrorRetryPolicy();
});

Alternatively, you can create the API client manually using the constructor. All API clients require a api/oauth key in the constructor and you can also pass in an optional HttpClient object. If you do not pass a HttpClient object, one will be created automatically. In the latter case, you are responsible for disposing the API client by calling the Dispose() method on the API client object or by using a using statement.

using IPaymentClient paymentClient = new PaymentClient("{yourApiKey}", new HttpClient());

Multi-tenant setup

For multi-tenant setups, you can create and configure a custom implementation of the IMollieSecretManager interface. For example:

builder.Services.AddMollieApi(options => {
    options.SetCustomMollieSecretManager<TenantSecretManager>();
    options.RetryPolicy = MollieHttpRetryPolicies.TransientHttpErrorRetryPolicy();
});

Custom secret manager for multi-tenant setup:

public class TenantSecretManager : IMollieSecretManager {
    private readonly ITenantService _tenantService;

    public string GetBearerToken() => _tenantService.GetCurrentTenant().ApiKey;
}

Example projects

An example ASP.NET Core web application project is included. In order to use this project you have to set your Mollie API key in the appsettings.json file. The example project demonstrates the Payment API, Mandate API, Customer API and Subscription API.

Testing

During the process of building your integration, it is important to properly test it. You can access the test mode of the Mollie API in two ways: by using the Test API key, or, if you are using organization access tokens or app tokens, by providing the testmode parameter in your API request. If you are using the Test API key, you do not have to set the testmode parameter anywhere. Any entity you create, retrieve, update or delete using a Test API key can only interact with the test system of Mollie, which is completely isolated from the production environment.

Webhooks

Mollie offers two different webhook systems:

Both systems are supported through the Mollie.Api.AspNet NuGet package included in this library.

Classic webhook system

The classic webhook system works by specifying a webhookUrl when creating a payment or order. When the payment status changes, Mollie sends a POST request to that URL. Payload contains only one field: the id of the changed payment/order. You must fetch the payment via the Mollie API to determine the new status.

Example of a classic webhook:

[ApiController]
[Route("api/webhook/classic/controllers")]
public class PaymentController : ControllerBase {
    private readonly ILogger<PaymentController> _logger;
    private readonly IPaymentClient _paymentClient;

    public PaymentController(ILogger<PaymentController> logger, IPaymentClient paymentClient) {
        _logger = logger;
        _paymentClient = paymentClient;
    }

    [HttpPost]
    public async Task<ActionResult> Webhook([FromForm] string id) {
        PaymentResponse payment = await _paymentClient.GetPaymentAsync(id);
        _logger.LogInformation("Webhook called for PaymentId={PaymentId}, PaymentStatus={Status}",
            id,
            payment.Status);

        return Ok();
    }
}

Next-gen webhook system

The next-gen webhook system allows your application to subscribe to multiple event types at once. This system supports events such as:

  • paymentlink.paid,
  • sales-invoice.issued,
  • balance-transaction.created

Unlike the classic system, these webhook requests include full context (type, resource, related entity ID, embedded resource snapshot) and are signed by Mollie using HMAC SHA256 via the header X-Mollie-Signature. Mollie can deliver either a full payload, which includes _embedded with full entity or a simple payload, which only includes event metadata.

In the Mollie Dashboard → Developers → Webhooks, create a new webhook and choose the events you want to subscribe to.

Install the package:

dotnet add package Mollie.Api.AspNet

Then add the configuration:

builder.Services.AddMollieWebhook(options => {
    options.Secret = builder.Configuration["Mollie:WebhookSecret"]!;
});

If you are using controllers, you'll then be able to use the new Mollie webhook system like this:

[ApiController]
[Route("api/webhook/nextgen/controllers")]
public class PaymentLinkController : ControllerBase {
    // Example of full webhook event with specific data entity type, such as PaymentLinkResponse
    [HttpPost("full/specific")]
    [ServiceFilter(typeof(MollieSignatureFilter))]
    public Task<ActionResult> WebhookWithSpecificType([FromMollieWebhook] FullWebhookEventResponse<PaymentLinkResponse> data) {
        return Task.FromResult<ActionResult>(Ok());
    }

    // Example of full webhook event with generic data entity type
    [HttpPost("full/generic")]
    [ServiceFilter(typeof(MollieSignatureFilter))]
    public Task<ActionResult> WebhookWithGenericType([FromMollieWebhook] FullWebhookEventResponse data) {
        return Task.FromResult<ActionResult>(Ok());
    }

    // Example of a simple webhook event response, that does not include the entity data
    [HttpPost("simple")]
    [ServiceFilter(typeof(MollieSignatureFilter))]
    public Task<ActionResult> WebhookWithGenericType([FromMollieWebhook] SimpleWebhookEventResponse data) {
        return Task.FromResult<ActionResult>(Ok());
    }
}

If you are using minimal API's, you can listen to webhooks like this:


public static class WebhookHandler {

    public static void RegisterEndpoints(IEndpointRouteBuilder app) {
        // Example of full webhook event with specific data entity type, such as PaymentLinkResponse
        app
            .MapPost("api/webhook/nextgen/minimalapi/full/specific",
                (MollieModelBinder<FullWebhookEventResponse<PaymentLinkResponse>> data) => {
                    if (data.Model == null) {
                        return Results.BadRequest();
                    }

                    return Results.Ok();
                })
            .AddEndpointFilter<MollieSignatureEndpointFilter>();

        // Example of full webhook event with generic data entity type
        app
            .MapPost("api/webhook/nextgen/minimalapi/full/generic",
                (MollieModelBinder<FullWebhookEventResponse> data) => {
                    if (data.Model == null) {
                        return Results.BadRequest();
                    }

                    return Results.Ok();
                })
            .AddEndpointFilter<MollieSignatureEndpointFilter>();

        // Example of a simple webhook event response, that does not include the entity data
        app
            .MapPost("api/webhook/nextgen/minimalapi/simple",
                (MollieModelBinder<SimpleWebhookEventResponse> data) => Results.Ok())
            .AddEndpointFilter<MollieSignatureEndpointFilter>();
    }
}

Testing webhooks on your local development machine

In the example project, you can also find a webhook example in the PaymentController.cs. In order for this endpoint to be called by the webhook, it needs to be exposed to the internet using a HTTP tunnel such as ngrok. Setting up ngrok is quite easy and can be done using the following steps:

  1. Install ngrok
  2. Start ngrok with the following command: ngrok http https://localhost:{your-https-port}
  3. Copy the ngrok forwarding URL

image

  1. Create a new payment in the example app and set the webhook URL to the ngrok forwarding url. Don't forget to append the /api/payment route.

image

  1. Perform an action that will cause the payment status to be updated. For example, by paying the the payment or cancelling it.

image

Idempotency and retrying requests

When issuing requests to an API, there is always a small chance of issues on either side of the connection. For example, the API may not respond to the request within a reasonable timeframe. Your server will then consider the request to have ‘timed out’. However, your request may still arrive at the API eventually and get executed, despite your server considering it a timeout.

Mollie supports the Idempotency-Key industry standard. When sending a request to the Mollie API, you can send along a header with a unique value. If another request is made with the exact same header value within one hour, the Mollie API will return a cached version of the initial response. This way, your API requests become what we call idempotent. Read more on this in the Mollie documentation on Idempotency

The library automatically generates a unique GUID for each request and adds it to the Idempotency-Key header. Using a transient fault handling library such as Polly, you are able to automatically retry requests in case of a timeout or 5xx status code error using the following example code:

using Polly;
using Polly.Extensions.Http;

public static class MollieApiClientServiceExtensions {
        public static IServiceCollection AddMollieApi(this IServiceCollection services, IConfiguration configuration) {
		MollieConfiguration mollieConfiguration = configuration.GetSection("Mollie").Get<MollieConfiguration>();
            
            	services.AddHttpClient<IPaymentClient, PaymentClient>(httpClient => new PaymentClient(mollieConfiguration.ApiKey, httpClient))
                	.AddPolicyHandler(GetDefaultRetryPolicy());
	}
	
	static IAsyncPolicy<HttpResponseMessage> GetDefaultRetryPolicy() {
            return HttpPolicyExtensions
	    	// Timeout errors or 5xx static code errors
                .HandleTransientHttpError() 
		// Requests are retried three times, with different intervals
                .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2,
                    retryAttempt)));
        }
}

It is also possible to specify your own custom idempotency key when sending requests. All requests within the using block will be sent using the custom idempotency key configured in the WithIdempotencyKey method.

PaymentRequest paymentRequest = new PaymentRequest() {
	Amount = new Amount(Currency.EUR, "100.00"),
	Description = "Description",
	RedirectUrl = DefaultRedirectUrl
};

using (_paymentClient.WithIdempotencyKey("my-idempotency-key"))
{
	PaymentResponse response = await _paymentClient.CreatePaymentAsync(paymentRequest);
}

List of constant value strings

In the past, this library used enums that were decorated with the EnumMemberAttribute for static values that were defined in the Mollie documentation. We have now moved away from this idea and are using constant strings everywhere. The reason for this is that enum values often broke when Mollie added new values to their API. This means that I had to release a new version every time when Mollie added a new value and all library consumers had to update their version. The following static classes are available with const string values that you can use to set and compare values in your code:

  • Mollie.Api.Models.Payment.PaymentMethod
  • Mollie.Api.Models.Payment.PaymentStatus
  • Mollie.Api.Models.Payment.SequenceType
  • Mollie.Api.Models.Payment.Request.KbcIssuer
  • Mollie.Api.Models.Payment.Response.CreditCardAudience
  • Mollie.Api.Models.Payment.Response.CreditCardSecurity
  • Mollie.Api.Models.Payment.Response.CreditCardFailureReason
  • Mollie.Api.Models.Payment.Response.CreditCardLabel
  • Mollie.Api.Models.Payment.Response.CreditCardFeeRegion
  • Mollie.Api.Models.Payment.Response.PayPalSellerProtection
  • Mollie.Api.Models.Mandate.InvoiceStatus
  • Mollie.Api.Models.Mandate.MandateStatus
  • Mollie.Api.Models.Order.OrderLineDetailsCategory
  • Mollie.Api.Models.Order.OrderLineDetailsType
  • Mollie.Api.Models.Order.OrderLineStatus
  • Mollie.Api.Models.Order.OrderStatus
  • Mollie.Api.Models.Order.OrderLineOperation
  • Mollie.Api.Models.Profile.CategoryCode
  • Mollie.Api.Models.Profile.ProfileStatus
  • Mollie.Api.Models.Refund.RefundStatus
  • Mollie.Api.Models.Settlement.SettlementStatus
  • Mollie.Api.Models.Subscription.SubscriptionStatus
  • Mollie.Api.Models.Connect.AppPermissions
  • Mollie.Api.Models.Onboarding.Response.OnboardingStatus
  • Mollie.Api.Models.Balance.Response.BalanceReport.ReportGrouping
  • Mollie.Api.Models.Balance.Response.BalanceTransaction.BalanceTransactionContextType
  • Mollie.Api.Models.Payment.Locale
  • Mollie.Api.Models.Currency
  • Mollie.Api.Models.CompanyEntityType

You can use these classes similar to how you use enums. For example, when creating a new payment, you can do the following:

PaymentRequest paymentRequest = new PaymentRequest() {
    Amount = new Amount(Currency.EUR, 100.00m),
    Description = "Test payment of the example project",
    RedirectUrl = "http://google.com",
	Method = Mollie.Api.Models.Payment.PaymentMethod.Ideal // instead of "Ideal"
};

Customizing the User-Agent header

By default, the Mollie .NET client library sends the following User-Agent header with each API request:

Mollie.Api.NET/{version}

However, you can append a custom string to the default User-Agent. This is especially useful if you're a SaaS partner and have received a tracking token from Mollie to help monitor the payments you initiate.

Example:

services.AddMollieApi(options => {
	options.ApiKey = "my-api-key";
	options.CustomUserAgent = "my-custom-user-agent";
});

With the above configuration, the final User-Agent header sent will look like:

Mollie.Api.NET/{version} my-custom-user-agent

Overriding Default Base URLs

When writing integration or end-to-end tests (e.g., using tools like WireMock), it can be useful to override the default base URLs used by the Mollie .NET client. This allows you to redirect requests to a local server or mock endpoint.

You can customize these base URLs via the MollieOptions class.

services.AddMollieApi(options => {
	options.ApiKey = context.Configuration["Mollie:ApiKey"]!;
	options.ApiBaseUrl = "https://localhost:5000/mollie/v2/";
	options.ConnectTokenEndPoint = "https://localhost:5000/mollie/oauth2/"; // Only used by the ConnectClient
	options.ConnectOAuthAuthorizeEndPoint = "https://localhost:5000/oauth2/authorize"; // Only used by the ConnectClient
});

This setup allows you to safely simulate or intercept Mollie API requests during testing, without hitting the real Mollie endpoints.