PayChangu.NET 0.2.5

There is a newer version of this package available.
See the version list below for details.
dotnet add package PayChangu.NET --version 0.2.5
                    
NuGet\Install-Package PayChangu.NET -Version 0.2.5
                    
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="PayChangu.NET" Version="0.2.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PayChangu.NET" Version="0.2.5" />
                    
Directory.Packages.props
<PackageReference Include="PayChangu.NET" />
                    
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 PayChangu.NET --version 0.2.5
                    
#r "nuget: PayChangu.NET, 0.2.5"
                    
#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 PayChangu.NET@0.2.5
                    
#: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=PayChangu.NET&version=0.2.5
                    
Install as a Cake Addin
#tool nuget:?package=PayChangu.NET&version=0.2.5
                    
Install as a Cake Tool

PayChangu.NET

A .NET SDK for integrating with the PayChangu payment gateway.

PayChangu.NET provides a strongly typed, asynchronous interface for interacting with PayChangu APIs from .NET applications. It simplifies payment initiation, transaction verification, direct charges, and payouts through a reusable client with built-in dependency injection support.

Features

  • Standard Checkout payment initiation.
  • Mobile Money Direct Charge initiation and transaction details retrieval.
  • Bank Transfer Direct Charge initiation and transaction details retrieval.
  • Card Direct Charge initiation, verification, and refunds.
  • Payment verification across supported payment flows.
  • Mobile Money payout initiation.
  • Bank payout initiation and transaction details retrieval.
  • Retrieval of supported Mobile Money operators.
  • Retrieval of supported banks and payout providers.
  • Wallet balance retrieval.
  • Paginated bank payout transaction retrieval.
  • Strongly typed request and response models.
  • Dependency injection support through IHttpClientFactory.
  • Configurable API base URL.
  • Bearer token authentication.
  • Asynchronous operations with cancellation token support.
  • Standardized API response handling.
  • Optional diagnostic logging through Microsoft.Extensions.Logging.
  • Webhook signature validation and event parsing.

Supported Currencies

PayChangu.NET exposes the PayChanguCurrency enum for currency values accepted by supported payment requests and currency-specific client methods.

The SDK currently supports:

Enum value Currency
PayChanguCurrency.MWK Malawian Kwacha
PayChanguCurrency.USD United States Dollar
PayChanguCurrency.ZMW Zambian Kwacha

MWK is the default currency for checkout, bank-transfer Direct Charge, card Direct Charge, wallet balance retrieval, and supported-bank lookup where a currency can be specified.

When serializing requests, the SDK writes the enum values as their three-letter currency codes (for example, "MWK", "USD", and "ZMW").

Use the enum when setting a request currency:

using PayChangu.NET;

var request = new InitiateCheckoutRequest
{
    Amount = 5000m,
    Currency = PayChanguCurrency.MWK
};

Currency values returned by PayChangu remain strings in response models, allowing the SDK to handle currency codes that may be introduced by the API in the future.

Installation

Install it using the .NET CLI:

dotnet add package PayChangu.NET

Configuration

Register PayChangu.NET with your application's dependency injection container using AddPayChangu.

The PayChangu secret key is required. The API base URL defaults to https://api.paychangu.com/.

builder.Services.AddPayChangu(options =>
{
    options.SecretKey = builder.Configuration["PayChangu:SecretKey"]!;
});

If you intend to process webhooks, configure the webhook secret as well:

builder.Services.AddPayChangu(options =>
{
    options.SecretKey = builder.Configuration["PayChangu:SecretKey"]!;
    options.WebhookSecret = builder.Configuration["PayChangu:WebhookSecret"];
});

To override the default API base URL:

builder.Services.AddPayChangu(options =>
{
    options.SecretKey = builder.Configuration["PayChangu:SecretKey"]!;
    options.BaseUrl = "https://api.paychangu.com/";
});

Logging

PayChangu.NET uses the standard .NET logging abstractions to provide diagnostic information about API operations. Logging is optional; the SDK does not require you to configure a logging provider.

Configure logging through your application's dependency injection container. For example, to write SDK logs to the console, add the Microsoft.Extensions.Logging.Console package and configure it as follows:

builder.Services.AddLogging(logging =>
{
    logging.AddConsole();
    logging.SetMinimumLevel(LogLevel.Debug);
});

builder.Services.AddPayChangu(options =>
{
    options.SecretKey = builder.Configuration["PayChangu:SecretKey"]!;
});

The SDK records request activity at Debug, unsuccessful PayChangu responses and response parsing issues at Warning, and HTTP transport failures at Error. Transport exceptions are logged and rethrown; API-level failures continue to be returned through the PayChanguApiResponse types.

The SDK does not log API keys, authorization headers, request bodies, or raw response bodies. Configure logging levels and providers according to your application's requirements.

HTTP client configuration

AddPayChangu accepts an optional configureHttpClient action. Use it when you need to customize the underlying HttpClient configuration.

builder.Services.AddPayChangu(
    options =>
    {
        options.SecretKey = builder.Configuration["PayChangu:SecretKey"]!;
    },
    httpClient =>
    {
        // Add any required HttpClient configuration here.
    });

The SDK automatically configures Bearer token authentication and the JSON Accept header.

Dependency injection

Registration provides the following services:

  • IPayChanguClient — exposes grouped API operations through Checkout, DirectCharges, Payouts, Transactions, Wallet, and ReferenceData.
  • IPayChanguWebhookService — validates webhook signatures and parses webhook events. A webhook secret must be configured when using this service.

Keep your secret keys in configuration or a secure secrets provider. Do not hardcode them in source code or expose them in client-side applications.

Wallet Balance

PayChangu.NET provides Wallet.GetBalanceAsync to retrieve wallet balance information for a specified currency.

Retrieving the wallet balance

The currency defaults to MWK. You can also specify USD or ZMW by passing PayChanguCurrency.USD or PayChanguCurrency.ZMW.

var response = await _payChanguClient.Wallet.GetBalanceAsync(
    cancellationToken: cancellationToken);

if (!response.Success || response.Result?.Data == null)
{
    // Handle the unsuccessful API response.
    // Inspect response.ErrorMessage and response.StatusCode.
    return;
}

var balance = response.Result.Data;

// Access the wallet balance information.
var environment = balance.Environment;
var currency = balance.Currency;
var mainBalance = balance.MainBalance;
var collectionBalance = balance.CollectionBalance;

To retrieve the balance for a different currency:

var response = await _payChanguClient.Wallet.GetBalanceAsync(
    PayChanguCurrency.USD,
    cancellationToken);

if (!response.Success || response.Result?.Data == null)
{
    // Handle the unsuccessful API response.
    return;
}

var balance = response.Result.Data;

var currency = balance.Currency;
var mainBalance = balance.MainBalance;
var collectionBalance = balance.CollectionBalance;

The returned BalanceData contains:

  • Environment — the PayChangu environment associated with the wallet.
  • Currency — the currency of the retrieved balance.
  • MainBalance — the main wallet balance, returned as a string.
  • CollectionBalance — the collection wallet balance, represented as a decimal.

Note: The method performs a balance retrieval request. It does not initiate a payout or transfer funds.

Standard Checkout

Standard Checkout allows you to initiate a payment and redirect the customer to a PayChangu-hosted checkout page.

Initiating a payment

Inject IPayChanguClient into your application service or controller. Create an InitiateCheckoutRequest and pass it to Checkout.InitiateAsync.

using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using PayChangu.NET;
using PayChangu.NET.Models;

public class PaymentsController : Controller
{
    private readonly IPayChanguClient _payChanguClient;

    public PaymentsController(IPayChanguClient payChanguClient)
    {
        _payChanguClient = payChanguClient;
    }

    [HttpPost]
    public async Task<IActionResult> CreateCheckout(
        CancellationToken cancellationToken)
    {
        var request = new InitiateCheckoutRequest
        {
            Amount = 5000m,
            Currency = PayChanguCurrency.MWK,
            Email = "customer@example.com",
            FirstName = "John",
            LastName = "Banda",
            TransactionReference = Guid.NewGuid().ToString(),

            CallbackUrl = "https://example.com/payments/callback",
            ReturnUrl = "https://example.com/payment/return",

            Customization = new PaymentCustomization
            {
                Title = "Order Payment",
                Description = "Payment for order #12345"
            },

            Meta = new PaymentMeta
            {
                Id = Guid.NewGuid(),
                Response = "order-12345"
            }
        };

        var response = await _payChanguClient.Checkout.InitiateAsync(
            request,
            cancellationToken);

        if (!response.Success || response.Result?.Data == null)
        {
            // Handle the unsuccessful API response.
            // Inspect response.ErrorMessage and response.StatusCode.
            return StatusCode(
                (int)(response.StatusCode ?? HttpStatusCode.BadGateway));
        }

        // Redirect the customer to PayChangu's hosted checkout page.
        return Redirect(response.Result.Data.CheckoutUrl);
    }
}

A successful initiation response provides the checkout URL through response.Result.Data.CheckoutUrl. Redirect the customer to this URL to complete the payment.

Callback and return URLs

Standard Checkout uses two separate URLs for different purposes:

  • CallbackUrl — PayChangu sends a callback for successful transactions to this URL. Provide a publicly accessible endpoint in your application to handle it.
  • ReturnUrl — PayChangu redirects the customer to this URL when checkout is canceled or fails. Use it to bring the customer back to your application and display the appropriate outcome.

These URLs serve different purposes from the webhook endpoint. Configure your webhook endpoint separately in the PayChangu dashboard.

Important: Do not rely solely on a callback or customer redirect to fulfil an order. Verify the transaction and confirm its final status before treating the payment as completed.

Mobile Money Direct Charge

PayChangu.NET supports initiating Mobile Money Direct Charge payments. This sends a payment request to the customer's mobile phone, prompting them to authorize the transaction.

Initiating a Mobile Money Direct Charge

First, retrieve the supported mobile money operators using ReferenceData.MobileMoneyOperators.GetAllAsync. Each operator provides a RefId that must be supplied when initiating a charge.

var operatorsResponse = await _payChanguClient.ReferenceData.MobileMoneyOperators.GetAllAsync(
    cancellationToken);

if (!operatorsResponse.Success || operatorsResponse.Result == null)
{
    // Handle the unsuccessful API response.
    return;
}

// Select the appropriate operator.
var operatorRefId = operatorsResponse.Result.Data
    .First(x => x.Name == "Airtel Money")
    .RefId;

Once you have the operator reference ID, create and submit the charge request:

var request = new InitiateMobileMoneyDirectChargeRequest
{
    Mobile = "+265991234567",
    MobileMoneyOperatorRefId = operatorRefId,
    Amount = 5000m,
    ChargeId = Guid.NewGuid().ToString(),
    Email = "customer@example.com",
    FirstName = "John",
    LastName = "Banda"
};

var response = await _payChanguClient.DirectCharges.MobileMoney.InitiateAsync(
    request,
    cancellationToken);

if (!response.Success || response.Result?.Data == null)
{
    // Handle the unsuccessful API response.
    // Inspect response.ErrorMessage and response.StatusCode.
    return;
}

// The charge request was accepted by PayChangu.
var chargeId = response.Result.Data.ChargeId;
var status = response.Result.Data.Status;

Verifying the transaction

A successful initiation response does not mean the customer has completed the payment. The customer must authorize the payment on their mobile device.

Use Transactions.VerifyAsync with the returned charge identifier to check the transaction status:

var verificationResponse = await _payChanguClient.Transactions.VerifyAsync(
    chargeId,
    PayChanguPaymentFlow.DirectCharge,
    PayChanguPaymentMethod.MobileMoney,
    cancellationToken);

if (!verificationResponse.Success ||
    !verificationResponse.Result.IsValid(chargeId, "MWK", 5000m))
{
    // The verification request failed or the payment details did not match.
    return;
}

// The transaction status, identifier, currency, and amount are valid.

The verification response provides normalized transaction details, including the payment status, amount, currency, customer information, and transaction timestamps.

Use the IsValid extension method on VerifyPaymentResponse to validate the transaction status and ensure the transaction identifier, currency, and amount match your expected values. It returns false when the response is null or any of these checks fail.

Important: Always verify the transaction before treating a Mobile Money Direct Charge as successfully paid. Do not rely solely on the initiation response.

Retrieving Mobile Money Direct Charge details

After initiating a Mobile Money Direct Charge, retrieve its transaction details using DirectCharges.MobileMoney.GetDetailsAsync and the charge identifier:

var detailsResponse = await _payChanguClient.DirectCharges.MobileMoney.GetDetailsAsync(
    chargeId,
    cancellationToken);

if (!detailsResponse.Success || detailsResponse.Result?.Data == null)
{
    // Handle the unsuccessful API response.
    // Inspect detailsResponse.ErrorMessage and detailsResponse.StatusCode.
    return;
}

var transaction = detailsResponse.Result.Data;

// Access the transaction details.
var transactionStatus = transaction.Status;
var amount = transaction.Amount;
var currency = transaction.Currency;
var transactionReference = transaction.ReferenceId;
var providerTransactionId = transaction.TransactionId;

// Access the mobile money operator details.
var operatorName = transaction.MobileMoney?.Name;
var operatorCountry = transaction.MobileMoney?.Country;

// Access transaction timestamps.
var createdAt = transaction.CreatedAt;
var completedAt = transaction.CompletedAt;

The returned PayChanguTransaction provides information such as the charge identifier, transaction reference, amount, currency, status, customer details, mobile money operator, transaction charges, authorization details, processing logs, and timestamps.

Important: Retrieving transaction details does not replace payment verification. When deciding whether to fulfil an order, use Transactions.VerifyAsync and validate the returned VerifyPaymentResponse against your expected transaction identifier, currency, and amount using the IsValid extension method.

Bank Transfer Direct Charge

PayChangu.NET supports Bank Transfer Direct Charge payments. This flow generates bank account details that the customer can use to complete a payment.

Initiating a Bank Transfer Direct Charge

Create an InitiateBankTransferDirectChargeRequest and pass it to DirectCharges.BankTransfer.InitiateAsync.

using System;

var chargeId = Guid.NewGuid().ToString();

var request = new InitiateBankTransferDirectChargeRequest
{
    Amount = 5000m,
    Currency = PayChanguCurrency.MWK,
    ChargeId = chargeId,
    Email = "customer@example.com",
    FirstName = "John",
    LastName = "Banda",
    Mobile = "+265991234567",
    CreatePermanentAccount = false
};

var response = await _payChanguClient.DirectCharges.BankTransfer.InitiateAsync(
    request,
    cancellationToken);

if (!response.Success || response.Result?.Data == null)
{
    // Handle the unsuccessful API response.
    // Inspect response.ErrorMessage and response.StatusCode.
    return;
}

var paymentDetails = response.Result.Data.PaymentAccountDetails;
var transaction = response.Result.Data.Transaction;

// Provide these bank details to the customer.
var bankName = paymentDetails.BankName;
var accountNumber = paymentDetails.AccountNumber;
var accountName = paymentDetails.AccountName;

var accountExpiry = DateTimeOffset.FromUnixTimeSeconds(
    paymentDetails.AccountExpirationTimestamp);

var transactionStatus = transaction.Status;

The response provides the bank name, account number, account name, account expiration timestamp, and transaction details.

The SDK sets the payment method to mobile_bank_transfer; you do not need to set it yourself. The currency defaults to MWK. CreatePermanentAccount is an optional nullable flag and defaults to null when omitted.

Retrieving Bank Transfer Direct Charge Details

Use DirectCharges.BankTransfer.GetDetailsAsync with the charge identifier to retrieve the transaction details:

var detailsResponse = await _payChanguClient.DirectCharges.BankTransfer.GetDetailsAsync(
    chargeId,
    cancellationToken);

if (!detailsResponse.Success || detailsResponse.Result?.Data == null)
{
    // Handle the unsuccessful API response.
    return;
}

var transactionDetails = detailsResponse.Result.Data.Transaction;
var status = transactionDetails.Status;

Verifying the Transaction

Initiating a Bank Transfer Direct Charge generates the bank account details; it does not mean the customer has completed the payment.

Use Transactions.VerifyAsync to verify the transaction:

var verificationResponse = await _payChanguClient.Transactions.VerifyAsync(
    chargeId,
    PayChanguPaymentFlow.DirectCharge,
    PayChanguPaymentMethod.BankTransfer,
    cancellationToken);

if (!verificationResponse.Success ||
    verificationResponse.Result == null ||
    !verificationResponse.Result.IsValid(chargeId, "MWK", 5000m))
{
    // The verification request failed or the payment details did not match.
    return;
}

// The transaction status, identifier, currency, and amount are valid.

Important: Always verify the transaction and confirm its final status before fulfilling an order. Do not treat the initiation response or the generated bank account details as proof of payment.

Card Direct Charge

PayChangu.NET supports Card Direct Charge payments, including transactions that require 3-D Secure (3DS) authentication.

Initiating a Card Direct Charge

Create an InitiateCardDirectChargeRequest and pass it to DirectCharges.Card.InitiateAsync.

var chargeId = Guid.NewGuid().ToString();

var request = new InitiateCardDirectChargeRequest
{
    CardNumber = cardNumber,
    Expiry = cardExpiry,
    Cvv = cardCvv,
    CardholderName = "John Banda",
    Amount = 5000m,
    Currency = PayChanguCurrency.MWK,
    ChargeId = chargeId,
    RedirectUrl = "https://example.com/payments/card/return",
    Email = "customer@example.com"
};

var response = await _payChanguClient.DirectCharges.Card.InitiateAsync(
    request,
    cancellationToken);

if (!response.Success || response.Result == null)
{
    // Handle the unsuccessful API response.
    // Inspect response.ErrorMessage and response.StatusCode.
    return;
}

var chargeResponse = response.Result;

if (!chargeResponse.Success)
{
    // PayChangu did not accept the card charge.
    return;
}

if (chargeResponse.Requires3DSAuth &&
    !string.IsNullOrWhiteSpace(chargeResponse.ThreeDSAuthLink))
{
    // Redirect the customer to complete 3-D Secure authentication.
    return Redirect(chargeResponse.ThreeDSAuthLink);
}

// Continue with transaction verification.

The request includes the card details, amount, currency, charge identifier, redirect URL, and optional customer email.

Handling 3-D Secure authentication

When Requires3DSAuth is true, redirect the customer to ThreeDSAuthLink to complete the authentication process.

PayChangu uses the supplied RedirectUrl to return the customer to your application after the authentication process.

If 3DS authentication is not required, proceed directly to transaction verification.

Important: A successful initiation response does not confirm that the payment has been completed. Do not fulfil an order based solely on the initiation response or the customer's return to your application.

Verifying the card transaction

Use Transactions.VerifyAsync with the charge identifier and the Card Direct Charge payment flow:

var verificationResponse = await _payChanguClient.Transactions.VerifyAsync(
    chargeId,
    PayChanguPaymentFlow.DirectCharge,
    PayChanguPaymentMethod.Card,
    cancellationToken);

if (!verificationResponse.Success ||
    !verificationResponse.Result.IsValid(chargeId, "MWK", 5000m))
{
    // The verification request failed or the payment details did not match.
    return;
}

// The transaction status, identifier, currency, and amount are valid.
// Continue with order fulfilment.

The IsValid extension method checks the transaction status, identifier, currency, and amount against your expected values.

Refunding a card transaction

Use DirectCharges.Card.RefundAsync with the charge identifier of the card transaction:

var refundResponse = await _payChanguClient.DirectCharges.Card.RefundAsync(
    chargeId,
    cancellationToken);

if (!refundResponse.Success)
{
    // Handle the unsuccessful refund request.
    // Inspect refundResponse.ErrorMessage and refundResponse.StatusCode.
    return;
}

// PayChangu accepted the refund request with HTTP 200.

The SDK considers the refund request successful when PayChangu returns HTTP 200. The response body is not deserialized.

Important: A successful refund request response should not automatically be treated as confirmation that the funds have been returned to the customer. Handle the refund according to the applicable PayChangu transaction and reconciliation process.

Security: Never log or expose card numbers or CVVs. Handle card details securely and avoid storing sensitive card verification data.

Mobile Money Payout

PayChangu.NET supports initiating Mobile Money payouts using Payouts.MobileMoney.InitiateAsync. This allows your application to send funds to a recipient's mobile money account.

Initiating a Mobile Money Payout

First, retrieve the supported mobile money operators using ReferenceData.MobileMoneyOperators.GetAllAsync. Use the selected operator's RefId when creating the payout request.

var operatorsResponse = await _payChanguClient.ReferenceData.MobileMoneyOperators.GetAllAsync(
    cancellationToken);

if (!operatorsResponse.Success || operatorsResponse.Result == null)
{
    // Handle the unsuccessful API response.
    return;
}

// Select the appropriate operator.
var operatorRefId = operatorsResponse.Result.Data
    .First(x => x.Name == "Airtel Money")
    .RefId;

Create an InitiateMobileMoneyPayoutRequest and pass it to Payouts.MobileMoney.InitiateAsync:

var request = new InitiateMobileMoneyPayoutRequest
{
    Mobile = "+265992123456",
    MobileMoneyOperatorRefId = operatorRefId,
    Amount = 5000m,
    ChargeId = Guid.NewGuid().ToString(),
    Email = "recipient@example.com",
    FirstName = "John",
    LastName = "Banda"
};

var response = await _payChanguClient.Payouts.MobileMoney.InitiateAsync(
    request,
    cancellationToken);

if (!response.Success || response.Result?.Data == null)
{
    // Handle the unsuccessful API response.
    // Inspect response.ErrorMessage and response.StatusCode.
    return;
}

var transaction = response.Result.Data;

// Access the payout transaction details.
var chargeId = transaction.ChargeId;
var referenceId = transaction.ReferenceId;
var status = transaction.Status;
var amount = transaction.Amount;
var currency = transaction.Currency;

// Access the mobile money operator details.
var operatorName = transaction.MobileMoney?.Name;
var operatorCountry = transaction.MobileMoney?.Country;

The request requires the recipient's mobile number, mobile money operator reference ID, payout amount, and a unique charge identifier. Recipient name and email details are optional.

The response provides the payout transaction details, including the PayChangu reference ID, transaction status, recipient mobile number, operator information, transaction charges, authorization details, and timestamps.

Sandbox testing

The request supports an optional TransactionStatus property for simulating payout outcomes in PayChangu's sandbox environment.

Accepted values are "failed" and "successful". Do not set this property when using the production environment.

Important: A successful API response does not necessarily mean the payout has been completed. Inspect the returned transaction status and handle payout notifications through your configured webhook endpoint. The SDK recognizes the api.payout webhook event for payout notifications.

Supported Banks and Payout Providers

PayChangu.NET provides ReferenceData.Banks.GetSupportedAsync to retrieve the banks and payout providers supported by PayChangu for a specified currency.

Retrieving supported banks and payout providers

The currency defaults to MWK. You can provide another value from PayChanguCurrency when retrieving providers for a different currency.

var response = await _payChanguClient.ReferenceData.Banks.GetSupportedAsync(
    PayChanguCurrency.MWK,
    cancellationToken);

if (!response.Success || response.Result?.Data == null)
{
    // Handle the unsuccessful API response.
    // Inspect response.ErrorMessage and response.StatusCode.
    return;
}

var providers = response.Result.Data;

foreach (var provider in providers)
{
    var bankId = provider.Id;
    var bankName = provider.Name;
}

Each returned SupportedBank contains:

  • Id — the provider UUID, required when initiating a Bank Payout.
  • Name — the bank or payout provider name.

Using a provider when initiating a payout

Use the provider UUID returned by ReferenceData.Banks.GetSupportedAsync as the BankId in an InitiateBankPayoutRequest.

var banksResponse = await _payChanguClient.ReferenceData.Banks.GetSupportedAsync(
    PayChanguCurrency.MWK,
    cancellationToken);

if (!banksResponse.Success || banksResponse.Result?.Data == null)
{
    // Handle the unsuccessful API response.
    return;
}

var selectedBank = banksResponse.Result.Data
    .FirstOrDefault(x => x.Name == "National Bank of Malawi");

if (selectedBank == null)
{
    // The requested provider was not returned by the API.
    return;
}

var request = new InitiateBankPayoutRequest
{
    BankId = selectedBank.Id,
    Amount = 3000m,
    ChargeId = Guid.NewGuid().ToString(),
    BankAccountName = "Madalitso Kamwendo",
    BankAccountNumber = "1000000010"
};

// Pass the request to InitiateBankPayoutAsync.

Note: The returned list is filtered by currency. Use the UUID provided by PayChangu rather than hardcoding provider identifiers.

Bank Payout

PayChangu.NET supports initiating bank payouts, retrieving individual bank payout details, and retrieving paginated bank payout transactions.

Initiating a Bank Payout

Create an InitiateBankPayoutRequest and pass it to Payouts.Bank.InitiateAsync.

The BankId property expects the UUID of the recipient's bank or payout provider. You can retrieve supported banks and payout providers using ReferenceData.Banks.GetSupportedAsync.

var request = new InitiateBankPayoutRequest
{
    BankId = bankId,
    Amount = 3000m,
    ChargeId = Guid.NewGuid().ToString(),
    BankAccountName = "Madalitso Kamwendo",
    BankAccountNumber = "1000000010",
    Email = "recipient@example.com",
    FirstName = "Madalitso",
    LastName = "Kamwendo"
};

var response = await _payChanguClient.Payouts.Bank.InitiateAsync(
    request,
    cancellationToken);

if (!response.Success || response.Result?.Data?.Transaction == null)
{
    // Handle the unsuccessful API response.
    // Inspect response.ErrorMessage and response.StatusCode.
    return;
}

var transaction = response.Result.Data.Transaction;

// Access the payout transaction details.
var chargeId = transaction.ChargeId;
var referenceId = transaction.ReferenceId;
var status = transaction.Status;
var amount = transaction.Amount;
var currency = transaction.Currency;

// Access the recipient's bank account details.
var bankName = transaction.RecipientAccountDetails.BankName;
var accountName = transaction.RecipientAccountDetails.AccountName;
var accountNumber = transaction.RecipientAccountDetails.AccountNumber;

The request uses bank_transfer as its default payout method. The amount is serialized as a JSON string. The recipient's email, first name, and last name are optional.

A successful initiation response means PayChangu accepted the payout request; it does not confirm that the payout has been completed. The returned transaction status can be used to track its progress.

Retrieving an individual bank payout

Use Payouts.Bank.GetDetailsAsync with the payout's charge identifier to retrieve its transaction details.

var detailsResponse = await _payChanguClient.Payouts.Bank.GetDetailsAsync(
    chargeId,
    cancellationToken);

if (!detailsResponse.Success || detailsResponse.Result?.Data == null)
{
    // Handle the unsuccessful API response.
    // Inspect detailsResponse.ErrorMessage and detailsResponse.StatusCode.
    return;
}

var transaction = detailsResponse.Result.Data;

var status = transaction.Status;
var amount = transaction.Amount;
var currency = transaction.Currency;
var referenceId = transaction.ReferenceId;

var bankName = transaction.RecipientAccountDetails.BankName;
var accountName = transaction.RecipientAccountDetails.AccountName;
var accountNumber = transaction.RecipientAccountDetails.AccountNumber;

The response provides the bank payout transaction, including its status, amount, currency, reference identifiers, transaction charges, and recipient account details.

Retrieving bank payout transactions

Use Payouts.Bank.GetAllAsync to retrieve bank payout transactions.

var payoutsResponse = await _payChanguClient.Payouts.Bank.GetAllAsync(
    cancellationToken);

if (!payoutsResponse.Success || payoutsResponse.Result?.Data == null)
{
    // Handle the unsuccessful API response.
    // Inspect payoutsResponse.ErrorMessage and payoutsResponse.StatusCode.
    return;
}

var pagination = payoutsResponse.Result.Data;

// Access pagination information.
var currentPage = pagination.CurrentPage;
var totalPages = pagination.TotalPages;
var pageSize = pagination.PerPage;
var nextPageUrl = pagination.NextPageUrl;

// Process transactions returned on the current page.
foreach (var transaction in pagination.Transactions)
{
    var chargeId = transaction.ChargeId;
    var status = transaction.Status;
    var amount = transaction.Amount;
    var bankName = transaction.RecipientAccountDetails.BankName;
}

The response includes the current page, total pages, page size, next page URL, and the transactions returned for that page.

Important: Payouts.Bank.GetAllAsync retrieves only the page returned by the API. It does not automatically fetch subsequent pages. Use the pagination information to manage additional page retrieval separately.

Payout notifications: Bank payouts use the api.payout webhook event. Configure your webhook endpoint and process payout notifications to track transaction outcomes. Do not treat an accepted payout request or a pending transaction as confirmation that funds have reached the recipient.

Webhooks

PayChangu uses a single webhook endpoint for notifications from Standard Checkout, Direct Charge, and Payout transactions.

Configure your application's publicly accessible webhook endpoint in the PayChangu dashboard. This endpoint is separate from the CallbackUrl and ReturnUrl supplied when initiating a Standard Checkout payment.

PayChangu.NET provides IPayChanguWebhookService to validate webhook signatures and deserialize incoming notifications into a strongly typed PayChanguWebhookEvent.

When registering the SDK, configure both your API secret key and webhook secret:

builder.Services.AddPayChangu(options =>
{
    options.SecretKey = builder.Configuration["PayChangu:SecretKey"]!;
    options.WebhookSecret = builder.Configuration["PayChangu:WebhookSecret"];
});

Receiving and validating a webhook

Read the raw request body before parsing it. The signature must be verified against the original payload sent by PayChangu.

The following example uses an ASP.NET Core Minimal API endpoint:

using System.Text;
using System.Text.Json;
using PayChangu.NET;

app.MapPost("/v1/webhooks/paychangu", async (
    HttpRequest request,
    IPayChanguWebhookService webhookService,
    CancellationToken cancellationToken) =>
{
    if (!request.Headers.TryGetValue("Signature", out var signature))
    {
        return Results.Unauthorized();
    }

    // Allow the request body to be read again if required.
    request.EnableBuffering();

    using var reader = new StreamReader(
        request.Body,
        Encoding.UTF8,
        leaveOpen: true);

    var payload = await reader.ReadToEndAsync(cancellationToken);

    // Reset the stream position after reading the raw payload.
    request.Body.Position = 0;

    if (!webhookService.VerifySignature(payload, signature.ToString()))
    {
        return Results.Unauthorized();
    }

    PayChanguWebhookEvent webhookEvent;

    try
    {
        // Parse the payload only after its signature has been validated.
        webhookEvent = webhookService.ParseWebhook(payload);
    }
    catch (ArgumentException)
    {
        return Results.BadRequest();
    }
    catch (JsonException)
    {
        return Results.BadRequest();
    }

    // Process the verified webhook event in your application.
    // For example, pass it to your payment or order service.

    return Results.Ok();
});

The example assumes that WebhookSecret has been configured during SDK registration. The SDK's webhook service uses that configured secret; you do not need to retrieve the secret or implement HMAC signature verification yourself.

Handling webhook events

The PayChanguWebhookEvent model exposes the event type, transaction details, payment flow, payment method, and relevant customer or recipient information.

The SDK recognizes these event types:

Event type Payment flow Transaction identifier
checkout.payment Checkout Merchant transaction reference (tx_ref)
api.charge.payment DirectCharge PayChangu charge identifier (charge_id)
api.payout Payout PayChangu charge identifier (charge_id)

You can use the derived PaymentFlow, PaymentMethod, and TransactionIdentifier properties to determine how to handle an event.

switch (webhookEvent.PaymentFlow)
{
    case PayChanguPaymentFlow.Checkout:
    case PayChanguPaymentFlow.DirectCharge:
        var transactionIdentifier = webhookEvent.TransactionIdentifier;
        var paymentMethod = webhookEvent.PaymentMethod;

        // Handle or verify the payment.
        break;

    case PayChanguPaymentFlow.Payout:
        // Handle the payout notification.
        break;

    default:
        // Handle an unrecognized event type.
        break;
}

TransactionIdentifier returns the appropriate identifier for Checkout and Direct Charge events. Although it also exposes the charge identifier for Payout events, payout notifications should be handled through your payout workflow rather than passed to Transactions.VerifyAsync.

Important: Signature validation confirms that the webhook payload was signed using the configured webhook secret. Your application should still process transaction statuses appropriately, verify payments where applicable, and avoid treating an initiation or pending notification as a completed transaction.

Raw JSON Response

Every PayChanguApiResponse<T> exposes a RawJsonResponse property containing the original JSON response body returned by PayChangu, as a string. This is provided alongside the strongly typed Result property; it does not replace or change the deserialized response model.

The typed Result is the recommended way to work with the fields modelled by PayChangu.NET. The raw JSON is available when you need to inspect the complete payload, access a field that is not currently represented in the SDK models, or troubleshoot an API response. This can also help when PayChangu adds response fields before a corresponding SDK model update is available.

For example, you can inspect or parse the raw response using System.Text.Json:

using System.Text.Json;

var response = await _payChanguClient.Transactions.VerifyAsync(
    transactionIdentifier,
    PayChanguPaymentFlow.DirectCharge,
    PayChanguPaymentMethod.MobileMoney,
    cancellationToken);

if (!string.IsNullOrWhiteSpace(response.RawJsonResponse))
{
    using var document = JsonDocument.Parse(response.RawJsonResponse);

    // Inspect the original JSON payload.
    var root = document.RootElement;

    if (root.TryGetProperty("status", out var statusElement))
    {
        var apiStatus = statusElement.GetString();
    }
}

RawJsonResponse is a raw response body, not a parsed or normalized object. It may be null when no response body is available. Use Result for normal SDK interactions, and use the raw JSON when you specifically need access to the original payload. Be mindful that API responses may contain customer or transaction information; avoid writing the raw payload to logs that are not appropriately protected.

License

This project is licensed under the MIT License. See LICENSE.md for details.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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 was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos 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
0.3.1 0 11/13/2026
0.2.9 0 11/13/2026
0.2.7 85 11/9/2026
0.2.5 82 11/9/2026
0.2.4 91 11/9/2026
0.2.3 81 11/9/2026
0.2.2 81 11/9/2026
0.2.1 87 11/9/2026
0.2.0 86 11/9/2026
0.1.2 83 11/9/2026
0.1.1 80 11/9/2026
0.1.0 91 11/8/2026