Skip to content

Integrate with the .NET Checkout SDK

Learn how to integrate ICEPAY payments in a .NET application using the official .NET Checkout SDK.

The ICEPAY .NET Checkout SDK provides a .NET interface around the ICEPAY Checkout API.

It handles authentication, serialises request models, sends requests to the ICEPAY Checkout API, and deserialises API responses into .NET models.

This guide walks through a complete integration, including:

  • installing and configuring the client;
  • creating a checkout;
  • redirecting the customer;
  • retrieving available payment methods;
  • handling webhook signatures;
  • issuing refunds
  • forwarding payment funds.

The .NET Checkout SDK runs in your server-side .NET application and communicates with the ICEPAY Checkout API.

  1. Create a checkout request

    Build a checkout using the SDK request models.

  2. Configure the client

    Configure the client with your Merchant ID and Merchant Secret.

  3. Send the checkout to ICEPAY

    Call CreateCheckout() to create the payment.

  4. Redirect the customer

    ICEPAY returns a hosted checkout URL. Redirect the customer to that URL.

  5. Handle payment updates

    ICEPAY sends payment updates to your webhook URL. Verify the webhook signature before processing the payment.

  6. Manage the payment when needed

    Use the client to issue refunds or forward payment funds.

The .NET Checkout SDK requires:

  • .NET 8 or newer;
  • an ICEPAY Merchant ID;
  • an ICEPAY Merchant Secret;
  • an HTTPS URL for your redirect endpoint
  • an HTTPS URL for your webhook endpoint.

The current SDK project targets .NET 8:

ICEPAY_CheckoutSDK.csproj
<TargetFramework>net8.0</TargetFramework>

The SDK can be added to your solution as a project reference.

From your application directory, add a reference to the SDK project:

Terminal window
dotnet add reference ../Checkout-DotNet-SDK/ICEPAY_CheckoutSDK/ICEPAY_CheckoutSDK.csproj

Then import the SDK namespace in your application:

using ICEPAY.CheckoutSDK;
using ICEPAY.CheckoutSDK.Models;
using ICEPAY.CheckoutSDK.Models.Request;

Keep your Merchant ID and Merchant Secret outside your source code.

For local development, the example application uses .NET user secrets:

Terminal window
dotnet user-secrets init
dotnet user-secrets set "ICEPAY:MERCHANT_ID" "your-merchant-id"
dotnet user-secrets set "ICEPAY:MERCHANT_SECRET" "your-merchant-secret"

In an ASP.NET Core application, you can also use environment variables:

Terminal window
ICEPAY__MERCHANT_ID=your-merchant-id
ICEPAY__MERCHANT_SECRET=your-merchant-secret

Read the credentials through IConfiguration and configure the client:

using ICEPAY.CheckoutSDK;
var merchantId = configuration["ICEPAY:MERCHANT_ID"]
?? throw new InvalidOperationException(
"Missing ICEPAY Merchant ID.");
var merchantSecret = configuration["ICEPAY:MERCHANT_SECRET"]
?? throw new InvalidOperationException(
"Missing ICEPAY Merchant Secret.");
if (string.IsNullOrWhiteSpace(merchantId) ||
string.IsNullOrWhiteSpace(merchantSecret))
{
throw new InvalidOperationException("Missing ICEPAY credentials.");
}
var checkoutClient = new CheckoutClient()
.WithAuthorization(merchantId, merchantSecret);

WithAuthorization() configures HTTP Basic Authentication using your Merchant ID and Merchant Secret.

A payment starts with a Checkout request model.

The amount is represented in minor currency units. For example, €2.99 is represented as 299.

using ICEPAY.CheckoutSDK.Models;
using ICEPAY.CheckoutSDK.Models.Request;
var checkoutRequest = new Checkout()
.WithReference("ORD-16307")
.WithDescription("Ice cream dipped in sprinkles")
.WithAmount(new Amount(299, Amount.CurrencyEur))
.WithRedirectUrl(
"https://merchant.example.com/payment-complete")
.WithWebhookUrl(
"https://merchant.example.com/payment-webhook");
var payment = await checkoutClient.CreateCheckout(checkoutRequest);

The response contains the payment key and links returned by ICEPAY:

if (payment is null)
{
throw new InvalidOperationException(
"ICEPAY did not return a payment.");
}
var paymentKey = payment.Key;
var checkoutUrl = payment.Links.Checkout;
var paymentStatus = payment.Status;

Store the returned payment key with your own order or payment record. You need it when issuing a refund or forwarding payment funds.

The Amount model accepts an integer value and a currency.

For currencies with two decimal places:

Customer-facing amount SDK value
€0.01 1
€2.99 299
€10.00 1000
€149.95 14995

For example:

var amount = new Amount(
299,
Amount.CurrencyEur
);

The SDK provides constants for supported currencies:

Amount.CurrencyEur
Amount.CurrencyGbp
Amount.CurrencyUsd
Amount.CurrencyPln
Amount.CurrencySek
Amount.CurrencyNok
Amount.CurrencyDkk
Amount.CurrencyCzk

After creating the payment, redirect the customer to the hosted ICEPAY Checkout Page.

For example, in an ASP.NET Core minimal API:

app.MapPost("/checkout", async (
CheckoutClient checkoutClient) =>
{
var checkoutRequest = new Checkout()
.WithReference("ORD-16307")
.WithDescription("Ice cream dipped in sprinkles")
.WithAmount(new Amount(299, Amount.CurrencyEur))
.WithRedirectUrl(
"https://merchant.example.com/payment-complete")
.WithWebhookUrl(
"https://merchant.example.com/payment-webhook");
var payment = await checkoutClient.CreateCheckout(checkoutRequest);
if (payment?.Links?.Checkout is null)
{
return Results.Problem(
"ICEPAY did not return a checkout URL.");
}
return Results.Redirect(payment.Links.Checkout);
});

The customer selects a method on the ICEPAY Checkout Page, then completes the payment on the ICEPAY Payment Page or the payment provider’s page.

The payment response may also contain a direct payment-method URL:

if (payment?.Links?.Direct is not null)
{
var directPaymentUrl = payment.Links.Direct;
}

Use the direct link only when your application deliberately preselects a supported payment method.

The Checkout request supports the following properties:

Property Type Purpose
Reference string Your payment or order reference.
Description string Description of the payment.
Amount Amount Payment amount and currency.
RedirectUrl string URL where the customer returns after checkout.
WebhookUrl string URL where ICEPAY sends payment updates.
PaymentMethod PaymentMethod Optional payment method selection.
Meta Metadata Additional metadata sent with the payment.
ExpireAfter int? Optional payment expiry period.

You can set values using object initialisers:

var checkoutRequest = new Checkout
{
Reference = "ORD-16307",
Description = "Ice cream dipped in sprinkles",
Amount = new Amount(299, Amount.CurrencyEur),
RedirectUrl = "https://merchant.example.com/payment-complete",
WebhookUrl = "https://merchant.example.com/payment-webhook",
ExpireAfter = 3600
};

Or use the fluent helper methods:

var checkoutRequest = new Checkout()
.WithReference("ORD-16307")
.WithDescription("Ice cream dipped in sprinkles")
.WithAmount(new Amount(299, Amount.CurrencyEur))
.WithRedirectUrl("https://merchant.example.com/payment-complete")
.WithWebhookUrl("https://merchant.example.com/payment-webhook")
.WithExpireAfter(3600);

Add a customer email address with WithCustomerEmail():

checkoutRequest.WithCustomerEmail(
"customer@example.com");

The customer information is stored in the request metadata:

var checkoutRequest = new Checkout()
.WithReference("ORD-16307")
.WithDescription("Example order")
.WithAmount(new Amount(299, Amount.CurrencyEur))
.WithCustomerEmail("customer@example.com");

For more customer fields, use a JsonObject:

using System.Text.Json.Nodes;
var customer = new JsonObject
{
["email"] = "customer@example.com",
["firstName"] = "John",
["lastName"] = "Doe"
};
checkoutRequest.WithCustomer(customer);

If you do not set a payment method, redirect the customer to the hosted checkout URL:

var checkoutUrl = payment.Links.Checkout;

The hosted Checkout Page can then present the available payment methods to the customer.

If the customer already selected a payment method in your own checkout, include it in the request:

checkoutRequest.WithPaymentMethod("ideal");

You can also set the request property directly:

checkoutRequest.PaymentMethod = new PaymentMethod
{
Type = "ideal"
};

The payment-method type must be supported and enabled for the merchant account.

For platform integrations or reusable modules, include integration information in the payment metadata:

checkoutRequest.WithIntegrationInformation(
type: "custom",
version: "1.0.0",
developer: "Example Developer"
);

This can be useful when the same ICEPAY Account receives payments from different integration types or versions.

Use GetPaymentMethods() to retrieve the payment methods available to the authenticated merchant:

var paymentMethods =
await checkoutClient.GetPaymentMethods();
foreach (var paymentMethod in paymentMethods ?? [])
{
Console.WriteLine(paymentMethod.Id);
Console.WriteLine(paymentMethod.Description);
Console.WriteLine(paymentMethod.Logo);
}

Each payment method can contain:

  • Id;
  • Description
  • Logo.

After a payment status changes, ICEPAY sends a request to the WebhookUrl supplied when the payment was created.

A webhook is a server-to-server notification. It does not depend on the customer keeping the browser open or successfully returning to your website.

The current .NET Checkout SDK does not provide a built-in webhook handler. Your webhook endpoint must:

  1. read the raw request body;
  2. read the ICEPAY-Signature header;
  3. calculate the expected HMAC signature;
  4. compare the signatures using a timing-safe comparison
  5. deserialise and process the payment only after verification succeeds.

The signature is a Base64-encoded HMAC-SHA256 value generated from the raw request body and your Merchant Secret.

The following helper verifies a signature:

using System.Security.Cryptography;
using System.Text;
static bool VerifyWebhookSignature(
string rawBody,
string receivedSignature,
string merchantSecret)
{
if (string.IsNullOrWhiteSpace(merchantSecret))
{
throw new InvalidOperationException("Missing ICEPAY Merchant Secret.");
}
if (string.IsNullOrWhiteSpace(receivedSignature))
{
return false;
}
byte[] expectedSignature;
using (var hmac = new HMACSHA256(
Encoding.UTF8.GetBytes(merchantSecret)))
{
expectedSignature = hmac.ComputeHash(
Encoding.UTF8.GetBytes(rawBody));
}
byte[] receivedSignatureBytes;
try
{
receivedSignatureBytes =
Convert.FromBase64String(receivedSignature);
}
catch (FormatException)
{
return false;
}
return CryptographicOperations.FixedTimeEquals(
expectedSignature,
receivedSignatureBytes);
}

Use the helper in an ASP.NET Core endpoint. Replace the processing comments with the validation and durable update logic described below before returning a successful response:

using System.Text.Json;
using ICEPAY.CheckoutSDK.Models;
app.MapPost("/payment-webhook", async (
HttpRequest request,
IConfiguration configuration) =>
{
var merchantSecret =
configuration["ICEPAY:MERCHANT_SECRET"]
?? throw new InvalidOperationException(
"Missing ICEPAY Merchant Secret.");
if (string.IsNullOrWhiteSpace(merchantSecret))
{
throw new InvalidOperationException("Missing ICEPAY Merchant Secret.");
}
using var reader = new StreamReader(request.Body);
var rawBody = await reader.ReadToEndAsync();
var receivedSignature =
request.Headers["ICEPAY-Signature"].ToString();
var signatureIsValid = VerifyWebhookSignature(
rawBody,
receivedSignature,
merchantSecret);
if (!signatureIsValid)
{
return Results.Unauthorized();
}
Payment? payment;
try
{
payment = JsonSerializer.Deserialize<Payment>(
rawBody,
new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
}
catch (JsonException)
{
return Results.BadRequest();
}
if (payment is null)
{
return Results.BadRequest();
}
// Find the order using payment.Key.
// Check that the payment matches the stored order details.
// Ignore duplicate or older updates.
// Update the payment and queue any additional work safely.
return Results.Ok();
});

Validate the payment before updating your order

Section titled “Validate the payment before updating your order”

After verifying the webhook signature, make sure the payment belongs to the order you expect.

Use the payment Key that you stored when the payment was created. Before updating or fulfilling the order, check important values such as:

  • the merchant;
  • the processing mode (Test mode or Live mode);
  • the amount
  • the currency.

Reject unknown payment keys or values that do not match your stored payment information.

Your endpoint may receive the same payment update more than once.

Make sure processing the same update again does not repeat actions such as:

  • fulfilling an order;
  • sending a confirmation email;
  • updating inventory; or
  • performing another action that should only happen once.

Store the payment state and completed actions in your database. When updating an order, use a database transaction or equivalent protection so two webhook requests arriving at the same time cannot perform the same action twice.

If additional work is processed asynchronously, make sure those background jobs are also safe to run more than once.

Webhook updates may arrive late or in a different order than expected.

Do not allow an older update to overwrite a newer payment state. If you cannot reliably determine which update is newer, retrieve the current payment through the ICEPAY Checkout API before updating your order.

Refunds and forwarded payments can change independently from the main payment status, so track them separately.

Return HTTP 200 only after the verified update has been processed or safely stored for later processing.

If storing or processing the update fails, return an unsuccessful response so the webhook can be retried.

A duplicate update that was already processed successfully can return HTTP 200.

See Process webhooks for more information about validating and processing webhook updates.

The SDK maps payment status values to the Status enum:

public enum Status
{
Started,
Completed,
Pending,
Expired,
Cancelled
}

For example:

switch (payment.Status)
{
case Status.Started:
// The payment has started.
break;
case Status.Pending:
// The payment is awaiting completion.
break;
case Status.Completed:
// The payment was completed.
break;
case Status.Expired:
// The payment expired.
break;
case Status.Cancelled:
// The payment was cancelled.
break;
}

Store the ICEPAY payment status separately from your own order status. Your application may need additional business rules before an order is fulfilled.

Store the payment key returned when the payment is created:

var paymentKey = payment.Key;

The current .NET Checkout SDK exposes methods for creating payments, retrieving payment methods, issuing refunds, and forwarding funds. It does not currently expose a public method for retrieving an existing payment by key.

If you need to retrieve a payment after creation, use the Checkout API integration approach described in the Checkout API integration guide, or extend the client with a payment-retrieval method.

Create a refund request using ICEPAY.CheckoutSDK.Models.Request.Refund.

The refund amount uses minor currency units:

using ICEPAY.CheckoutSDK.Models;
using ICEPAY.CheckoutSDK.Models.Request;
var refundRequest = new Refund(
reference: "RFD-00069",
amount: new Amount(
299,
Amount.CurrencyEur
),
description: "Customer refund"
);
var refund = await checkoutClient.Refund(
refundRequest,
paymentKey
);

You can also build the request with the fluent methods:

var refundRequest = new Refund(
"RFD-00069",
new Amount(299, Amount.CurrencyEur)
);
refundRequest
.WithDescription("Customer refund");
var refund = await checkoutClient.Refund(
refundRequest,
paymentKey
);

The paymentKey must identify the payment being refunded.

Use the Forward request model to forward payment funds to another recipient.

using ICEPAY.CheckoutSDK.Models;
using ICEPAY.CheckoutSDK.Models.Request;
var forwardRequest = new Forward()
.WithReference("FWD-00042")
.WithDescription("Forwarding payment funds")
.WithAmount(new Amount(299, Amount.CurrencyEur))
.WithRecipient("1001");
var forwarded = await checkoutClient.Forward(
forwardRequest,
paymentKey
);

You can also provide a Recipient object:

var recipient = new Recipient
{
Id = "1001"
};
var forwardRequest = new Forward()
.WithReference("FWD-00042")
.WithDescription("Forwarding payment funds")
.WithAmount(new Amount(299, Amount.CurrencyEur))
.WithRecipient(recipient);

The recipient ID must identify a valid ICEPAY recipient.

The current client deserialises the response returned by ICEPAY but does not expose a dedicated SDK exception hierarchy or automatically call EnsureSuccessStatusCode().

For production integrations, check whether the returned model is null and consider wrapping calls with your own error-handling logic:

try
{
var payment = await checkoutClient.CreateCheckout(
checkoutRequest);
if (payment is null)
{
throw new InvalidOperationException(
"ICEPAY returned an empty response.");
}
}
catch (HttpRequestException exception)
{
// Handle network failures, DNS errors, and timeouts.
Console.Error.WriteLine(exception.Message);
}
catch (TaskCanceledException exception)
{
// Handle request cancellation or timeout.
Console.Error.WriteLine(exception.Message);
}

For detailed HTTP status handling and API error payloads, use a custom HttpClient or extend the SDK’s request handling.

The CheckoutClient accepts an optional HttpMessageInvoker. This allows you to provide a configured HttpClient:

using System.Net.Http;
var httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(30)
};
var checkoutClient = new CheckoutClient(httpClient)
.WithAuthorization(
merchantId,
merchantSecret
);

You can use this to configure:

  • request timeouts;
  • proxy settings;
  • custom handlers;
  • logging
  • test HTTP handlers.

For ASP.NET Core applications, register the client with dependency injection:

builder.Services.AddHttpClient("icepay");
builder.Services.AddSingleton(serviceProvider =>
{
var httpClientFactory =
serviceProvider.GetRequiredService<IHttpClientFactory>();
var configuration =
serviceProvider.GetRequiredService<IConfiguration>();
var httpClient =
httpClientFactory.CreateClient("icepay");
var merchantId =
configuration["ICEPAY:MERCHANT_ID"]
?? throw new InvalidOperationException(
"Missing ICEPAY Merchant ID.");
var merchantSecret =
configuration["ICEPAY:MERCHANT_SECRET"]
?? throw new InvalidOperationException(
"Missing ICEPAY Merchant Secret.");
return new CheckoutClient(httpClient)
.WithAuthorization(merchantId, merchantSecret);
});

The main CheckoutClient operations are:

Method Purpose
CreateCheckout(checkout) Create a payment.
GetPaymentMethods() Retrieve available payment methods.
Refund(refund, checkoutId) Refund a payment.
Forward(forward, checkoutId) Forward payment funds.
WithAuthorization(merchantId, merchantSecret) Configure merchant authentication.

These methods correspond to the main Checkout API operations supported by the current .NET client.

For a normal .NET integration:

  1. Configure the client

    Configure CheckoutClient with your Merchant ID and Merchant Secret.

  2. Create the payment

    Build a Checkout request with your reference, amount, redirect URL, and webhook URL.

  3. Store the payment key

    Save payment.Key with your own order or payment record.

  4. Redirect the customer

    Send the customer to payment.Links.Checkout.

  5. Process webhook notifications

    Read the raw request body and verify the ICEPAY-Signature header.

  6. Update your payment record

    Deserialise the verified webhook payload and update your stored payment status.

  7. Use additional operations when needed

    Issue a refund or forward payment funds through the same CheckoutClient.

Contact the ICEPAY team

Contact the ICEPAY team with any questions about your SDK integration.