Paystack.SDK 1.1.1

dotnet add package Paystack.SDK --version 1.1.1
                    
NuGet\Install-Package Paystack.SDK -Version 1.1.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Paystack.SDK" Version="1.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Paystack.SDK" Version="1.1.1" />
                    
Directory.Packages.props
<PackageReference Include="Paystack.SDK" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Paystack.SDK --version 1.1.1
                    
#r "nuget: Paystack.SDK, 1.1.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Paystack.SDK@1.1.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Paystack.SDK&version=1.1.1
                    
Install as a Cake Addin
#tool nuget:?package=Paystack.SDK&version=1.1.1
                    
Install as a Cake Tool

Paystack.SDK

A .NET class library for the Paystack API with a result-based error model, automatic amount-to-subunit conversion, built-in fee absorption, and ready-made ASP.NET Core DI registration. Covers the Transaction, Charge (direct mobile money/OTP/PIN), Settlement, and Subaccount resources. Targets .NET 8 and .NET 10.


Installation

dotnet add package Paystack.SDK

Configuration

Add your Paystack secret key to appsettings.json:

"Paystack": {
  "SecretKey": "sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

Get your secret key from the Paystack dashboard. Use sk_test_... during development.


Registration

// Program.cs
builder.Services.AddPaystackSdk(config =>
{
    config.SecretKey = builder.Configuration["Paystack:SecretKey"]!;
});

With a custom base URL (rarely needed — defaults to https://api.paystack.co):

builder.Services.AddPaystackSdk(config =>
{
    config.SecretKey = "sk_live_...";
    config.BaseUrl = "https://api.paystack.co";
});

This registers the following as scoped services:

  • IPaystackTransactionService
  • IPaystackChargeService
  • IPaystackSettlementService
  • IPaystackSubaccountService

Usage

Every method returns a PaystackResult<T> — no exceptions are thrown for API errors. Check IsSuccess before using Data.

Initialize a transaction (redirect checkout)

public class CheckoutService(IPaystackTransactionService transactions)
{
    public async Task<string?> StartCheckoutAsync(string email, decimal amount)
    {
        var result = await transactions.InitializeAsync(new InitializeTransactionRequest(
            Email: email,
            Amount: amount,
            CallbackUrl: "https://myapp.com/payment/callback"
        ));

        if (!result.IsSuccess)
        {
            // result.Message contains the Paystack error message
            return null;
        }

        // Redirect the user here to complete payment
        return result.Data!.AuthorizationUrl;
    }
}

Verify a transaction after callback

using Paystack.SDK.Transactions.Extensions;

var result = await transactions.VerifyAsync(reference);

if (result.IsVerifiedSuccess())
{
    // Payment confirmed — fulfill the order
}

IsVerifiedSuccess() combines the API-level and transaction-level checks: the call must have succeeded and Paystack must report the transaction as success. A parallel IsChargeSuccess() exists in Paystack.SDK.Charge.Extensions for mobile money / direct charges.

Charge mobile money directly (Ghana)

public class MobileMoneyService(IPaystackChargeService charge)
{
    public async Task PayWithMomoAsync(string email, decimal amount, string phone)
    {
        var result = await charge.ChargeMobileMoneyAsync(new MobileMoneyChargeRequest(
            Email: email,
            Amount: amount,
            Phone: phone,
            Network: charge.GetNetworkProvider(phone)!   // auto-detect mtn | vod | atl
        ));

        if (!result.IsSuccess) return;

        var status = result.Data!.Data?.Status;

        // status = "send_otp"      → prompt user, call SubmitOtpAsync
        // status = "send_birthday" → prompt user, call SubmitBirthdayAsync
        // status = "pending"       → call PollUntilCompleteAsync
        // status = "success"       → charge completed
    }
}

List transactions with pagination

var result = await transactions.ListAsync(new ListTransactionsRequest(
    PerPage: 50,
    Page: 1,
    Status: "success"
));

if (result.IsSuccess)
{
    foreach (var tx in result.Data!.Items) { /* ... */ }

    var meta = result.Data.Meta;
    // meta.Total, meta.Page, meta.PerPage, meta.PageCount
}

Poll until a pending charge completes

var final = await charge.PollUntilCompleteAsync(
    reference: "abc123",
    maxAttempts: 20,
    intervalSeconds: 5,
    ct: cancellationToken
);

Fee absorption

Customers can absorb the Paystack processing fee so the merchant receives exactly the intended amount. Use PaystackFeeCalculator to show the fee up-front, or set AbsorbFee: true on any request to apply the gross-up automatically.

Show the fee before checkout

decimal fee = PaystackFeeCalculator.FeeOnly(100m);
// fee → 1.99  (1.95% of 101.99)

var breakdown = PaystackFeeCalculator.GrossUp(100m);
// breakdown.OriginalAmount → 100.00   (merchant receives)
// breakdown.FeeAmount      → 1.99     (customer absorbs)
// breakdown.TotalCharged   → 101.99   (passed to Paystack)

Apply the fee automatically

await transactions.InitializeAsync(new InitializeTransactionRequest(
    Email: email,
    Amount: 100m,
    AbsorbFee: true           // Paystack gets 101.99, merchant receives 100
));

Custom fee rate (e.g. international cards at 3.9%)

await transactions.InitializeAsync(new InitializeTransactionRequest(
    Email: email,
    Amount: 100m,
    AbsorbFee: true,
    FeeRate: 0.039m
));

The AbsorbFee flag is available on InitializeTransactionRequest, ChargeAuthorizationRequest, and MobileMoneyChargeRequest.


Transaction splits

Paystack supports two ways to split a single payment between your main account and a subaccount. Which one runs is decided by which fields you send on InitializeAsync / ChargeAuthorizationAsync — there is no separate endpoint.

Mode 1 — Flat split (pre-configured, reusable)

Create a split once via Paystack's dashboard or /split endpoint. You get back a split_code like SPL_xxx. On every transaction just pass the code:

await transactions.InitializeAsync(new InitializeTransactionRequest(
    Email: email,
    Amount: 100m,
    SplitCode: "SPL_xxx"            // Paystack applies the saved rules
));

Bearer and TransactionCharge are ignored when SplitCode is set.

Mode 2 — Dynamic split (per-transaction)

No pre-created split. Define the split inline with the Subaccount of the party to pay, optional Bearer (who pays the Paystack fee), and optional TransactionCharge (a flat fee the subaccount owes on this transaction):

await transactions.InitializeAsync(new InitializeTransactionRequest(
    Email: email,
    Amount: 100m,
    Subaccount: "ACCT_xxx",         // party to split to
    Bearer: "subaccount",           // "account" (you) or "subaccount" — optional
    TransactionCharge: 2.00m        // major units (GHS/NGN), converted to subunits — optional
));

Paystack picks the mode by presence of fields: SplitCode → Mode 1 · Subaccount without SplitCode → Mode 2 · neither → normal transaction.

The same four fields — SplitCode, Subaccount, Bearer, TransactionCharge — are also accepted on ChargeAuthorizationRequest.


Webhook verification

Paystack signs every webhook body with HMAC-SHA512 using your secret key and sends the hex digest in the x-paystack-signature header. Use PaystackWebhook.IsValid to verify it before trusting the payload.

using Paystack.SDK.Webhooks;

app.MapPost("/webhooks/paystack", async (HttpRequest req, IConfiguration config) =>
{
    using var reader = new StreamReader(req.Body);
    var rawBody = await reader.ReadToEndAsync();
    var signature = req.Headers[PaystackWebhook.SignatureHeader].ToString();
    var secretKey = config["Paystack:SecretKey"]!;

    if (!PaystackWebhook.IsValid(rawBody, signature, secretKey))
        return Results.Unauthorized();

    // Signature valid — safe to deserialize and handle the event
    return Results.Ok();
});

Important: verify against the raw request body bytes. Any re-serialization, reformatting, or model-binding before verification will break the signature.


API Reference

IPaystackTransactionService

Method Description
InitializeAsync(request, ct) Starts a redirect-based checkout and returns an AuthorizationUrl
VerifyAsync(reference, ct) Verifies a transaction by reference
ListAsync(request?, ct) Lists transactions with optional filters. Returns a PagedResult<TransactionData> exposing Items and Meta (total, page, perPage, pageCount)
FetchAsync(transactionId, ct) Fetches a single transaction by ID
ChargeAuthorizationAsync(request, ct) Charges a saved authorization code (recurring payment)
TotalsAsync(ct) Returns aggregate transaction totals
result.IsVerifiedSuccess() (extension) One-liner: true if the call succeeded AND the transaction status is success

IPaystackChargeService

Method Description
ChargeMobileMoneyAsync(request, ct) Charges a Ghana mobile money account
SubmitOtpAsync(reference, otp, ct) Submits OTP when status is send_otp
SubmitPhoneAsync(reference, phone, ct) Submits phone when status is send_phone
SubmitBirthdayAsync(reference, birthday, ct) Submits birthday (yyyy-MM-dd) when status is send_birthday
SubmitPinAsync(reference, pin, ct) Submits PIN when status is send_pin
CheckPendingAsync(reference, ct) Checks the current status of a charge
PollUntilCompleteAsync(reference, maxAttempts, intervalSeconds, ct) Polls until status is success, failed, or cancelled
result.IsChargeSuccess() (extension) One-liner: true if the call succeeded AND the charge status is success
DetectMobileNetwork(phone) Returns GhanaMobileNetwork enum from a Ghanaian phone number
GetNetworkProvider(phone) Returns "mtn" \| "vod" \| "atl" or null if undetectable

IPaystackSettlementService

Method Description
ListAsync(request?, ct) Lists settlements with optional filters (page, perPage, date range, status, subaccount). Returns PagedResult<SettlementData>
ListTransactionsAsync(settlementId, page?, perPage?, ct) Lists the transactions included in a specific settlement

IPaystackSubaccountService

Method Description
CreateAsync(request, ct) Creates a subaccount. Requires BusinessName, SettlementBank, AccountNumber, PercentageCharge (0–100)
ListAsync(request?, ct) Lists subaccounts with optional pagination/date filters. Returns PagedResult<SubaccountData>
FetchAsync(idOrCode, ct) Fetches a single subaccount by numeric id or code (e.g. ACCT_xxx)
UpdateAsync(idOrCode, request, ct) Updates a subaccount. All fields optional — only non-null values are sent

PaystackWebhook

Member Description
IsValid(rawBody, signature, secretKey) Returns true if the signature is a valid HMAC-SHA512 of the raw body. Constant-time comparison.
SignatureHeader Constant: "x-paystack-signature"

PaystackFeeCalculator

Method Description
GrossUp(amount, feeRate?) Returns a FeeBreakdown with the gross-up calculation
FeeOnly(amount, feeRate?) Returns just the fee amount to display to the customer
DefaultFeeRate Constant: 0.0195m (1.95% — Paystack Ghana local rate)

PaystackResult<T>

Property Description
IsSuccess true if the API call succeeded AND Paystack returned status: true
Message Error message on failure, optional status message on success
Data The deserialized response payload (null on failure)
StatusCode HTTP status code from Paystack. null when the failure occurred before a response was received (e.g. DNS/connection errors). Use this to distinguish 401 (bad key) from 400 (bad request) from 5xx

PagedResult<T>

Property Description
Items The page of results (IReadOnlyList<T>)
Meta PaystackMetaTotal, Skipped, PerPage, Page, PageCount

PaystackConfig

Property Type Default Description
SecretKey string — (required) Your Paystack secret key
BaseUrl string "https://api.paystack.co" API base URL
Timeout TimeSpan 100s HTTP request timeout. Paystack's list/totals endpoints can be slow under load — raise this if you see timeouts

Defaults & behaviour

Setting Value
Base URL https://api.paystack.co
HTTP client Flurl.Http
Serialization System.Text.Json with snake_case naming
Amount conversion decimal → smallest unit (kobo/pesewas) applied automatically
Error handling PaystackResult<T> — no exceptions for API errors
Default fee rate 1.95% (Paystack Ghana local cards + mobile money)
Cancellation All async methods accept CancellationToken

Supported networks (Ghana mobile money)

Code Network Prefixes
mtn MTN 024, 054, 055, 059, 053
vod Vodafone 020, 050, 023, 028
atl AirtelTigo 027, 057, 026, 056

Use IPaystackChargeService.DetectMobileNetwork(phone) to auto-detect from a phone number (with or without +233 country code).


Requirements

  • .NET 8 or .NET 10
  • A Paystack account with API keys enabled
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.1 88 4/26/2026
1.1.0 78 4/26/2026
1.0.0 92 4/21/2026

1.1.1 — README polish to reflect .NET 8 + .NET 10 multi-target. No code changes from 1.1.0.