Skip to content

Integrate with the .NET API Client

Learn how to integrate ICEPAY payments in a .NET application using the official .NET API Client.

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

It handles authentication, serializes request models, sends requests to the ICEPAY Checkout API, and deserializes 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; and
  • forwarding payment funds.

The API client 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 API Client requires:

  • .NET 8 or newer;
  • an ICEPAY Merchant ID;
  • an ICEPAY Merchant Secret;
  • an HTTPS URL for your redirect endpoint; and
  • 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.");
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 completes the payment on the hosted ICEPAY Checkout 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 expiration period.

You can set values using object initializers:

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; and
  • 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 API Client does not provide a built-in postback 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; and
  5. deserialize 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(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:

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.");
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();
}
var payment = JsonSerializer.Deserialize<Payment>(
rawBody,
new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
if (payment is null)
{
return Results.BadRequest();
}
// Locate your payment using payment.Key.
// Update your stored payment status.
// Keep webhook processing idempotent.
return Results.Ok();
});

Your webhook endpoint may receive the same notification more than once.

Use the payment key and status, or another unique event identifier if available, to prevent duplicate processing:

if (await paymentRepository.HasAlreadyProcessedAsync(
payment.Key,
payment.Status))
{
return Results.Ok();
}
await paymentRepository.UpdateAsync(
payment.Key,
payment.Status);
await paymentRepository.MarkAsProcessedAsync(
payment.Key,
payment.Status);
return Results.Ok();

Your application should return a successful response after a valid notification has been accepted.

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 API Client 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 direct API integration approach described in the API Explorer, 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 deserializes 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; and
  • 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

    Deserialize 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.

The .NET API Client handles authentication, request serialization, and response deserialization for the operations it supports.

The underlying Checkout API is still useful when you need to understand exact endpoint behavior, request fields, response structures, or operations that are not currently exposed by the client.