LibertePay.Core.Sdk 1.0.0-beta

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

LibertePay SDK

LibertePay SDK is a comprehensive payment integration solution that provides seamless access to LibertePay's payment processing services. The SDK simplifies the integration of payment features into your .NET applications with a clean, intuitive API.

Features

💳 Payment Processing

  • Mobile Money Collections
  • Bank Account Transfers
  • Card Payments (coming soon)

💸 Disbursements

  • Mobile Money Wallets
  • Bank Accounts
  • Bulk Payments (Mobile Money & Bank Account)

🔄 Transaction Management

  • Status Tracking
  • Reversals (coming soon)
  • Reconciliation (coming soon)

👤 Account Services

  • Name Enquiry (Banks & Mobile Money)
  • Balance Checking
  • KYC Verification (coming soon)

📈 Advanced Features

  • Built-in Telemetry
  • High Performance
  • Thread-safe Token Management
  • Dependency Injection Support
  • Type-safe Institution Codes
  • Automatic Callback Handling

Installation

Install the LibertePay SDK via NuGet:

dotnet add package LibertePay.Core.Sdk --version 1.0.0-alpha

Configuration

Basic Configuration

Add the LibertePay configuration to your appsettings.json:

{
  "LibertePay": {
    "BaseUrl": "https://app-base-url/",
    "Username": "your-username",
    "Password": "your-password",
    "HttpClientTimeoutSeconds": 30,
    "CallbackUrl": "https://your-domain.com/webhook",
    "TelemetryApiUrl": "https://your-telemetry-service.com/" // Optional
  }
}

Service Registration

Register the LibertePay services in your Program.cs or Startup.cs:

using LibertePay.Core.DependencyInjection;

// ...

services.AddLibertePay(configuration);

Usage Guide

1. Name Enquiry Services

Bank Account Name Enquiry (Single & Bulk)
public class PaymentService
{
    private readonly ILibertePayClient _client;

    public PaymentService(ILibertePayClient client) => _client = client;

    [HttpGet("VerifyBankAccount")]
    public async Task<IActionResult> VerifyBankAccountAsync()
    {
        var request = new NameEnquiryRequest
        {
            AccountNumber = "1020820171412", // test account number
            PaymentChannel = Channels.INTERBANK,
            Institution = InstitutionCodes.TestGhanaLimited, //this is a test institution code
            TransactionId = Guid.NewGuid().ToString("N")
        };

        var response = await _client.NameEnquiry.GetNameEnquiryAsync(request);
        return Ok(response.Data?.AccountName ?? response.StatusDescription);
    }


    [HttpGet("VerifyBulkBankAccountAsync", Name = "VerifyBulkBankAccountAsync")]
    public async Task<IActionResult> VerifyBulkBankAccountAsync()
    {

        var requestBulkNameCheck = new BulkNameEnquiryRequest
        {
            BatchTransactionId = Guid.NewGuid().ToString("N"),
            BulkNameEnquiryData =
            [
                new BulkNameEnquiryItem
                {
                    AccountNumber = "1020820171412",
                    Institution = InstitutionCodes.TestGhanaLimited
                },
                new BulkNameEnquiryItem
                {
                    AccountNumber = "1020820171412",
                    Institution = InstitutionCodes.TestGhanaLimited
                }
            ]
        };

        var response = await _client.NameEnquiry.GetBulkNameEnquiryAsync(requestBulkNameCheck);

        if (response.IsSuccess)
        {
            return Ok(response!.Data!.BatchTransactionId);
        }

        return Ok($"Bulk Name Enquiry : {response.StatusDescription}");

    }
}
Mobile Money Name Enquiry (Single & Bulk)
    [HttpGet("VerifyMobileMoneyAsync", Name = "VerifyMobileMoneyAsync")]
    public async Task<IActionResult> VerifyMobileMoneyAsync()
    {
        var request = new NameEnquiryRequest
        {
            AccountNumber = "233246089019", //test phone number
            PaymentChannel = Channels.INTERBANK,
            Institution = InstitutionCodes.Mtn, // e.g., "300591" for MTN
            TransactionId = Guid.NewGuid().ToString("N")
        };

        var response = await _client.NameEnquiry.GetNameEnquiryAsync(request);

        if (response.IsSuccess)
        {
            return Ok(response!.Data!.AccountName);
        }

        return Ok($"Name Enquiry : {response.StatusDescription}");
    }



    [HttpGet("VerifyBulkMobileMoneyAsync", Name = "VerifyBulkMobileMoneyAsync")]
    public async Task<IActionResult> VerifyBulkMobileMoneyAsync()
    {
        var requestBulkNameCheck = new BulkNameEnquiryRequest
        {
            BatchTransactionId = Guid.NewGuid().ToString("N"),
            BulkNameEnquiryData =
            [
                new BulkNameEnquiryItem
                {
                    AccountNumber = TestPhoneNumber,
                    Institution = InstitutionCodes.TestGhanaLimited
                },
                new BulkNameEnquiryItem
                {
                    AccountNumber = TestPhoneNumber,
                    Institution = InstitutionCodes.Mtn
                }
            ]
        };

        var response = await _client.NameEnquiry.GetBulkNameEnquiryAsync(requestBulkNameCheck);

        if (response.IsSuccess)
        {
            return Ok(response!.Data!.BatchTransactionId);
        }

        return Ok($"Bulk Name Enquiry : {response.StatusDescription}");
    }

    

2. Payment Collections

Mobile Money Collection
[HttpGet("MobileMoneyCollection", Name = "MobileMoneyCollection")]
public async Task<IActionResult> MobileMoneyCollection(decimal amount, string description)
{    
    // Process the collection
        var collectionRequest = new DebitMoneyRequest
        {
            AccountNumber = TestPhoneNumber,
            AccountName = /* from the name enquiry*/,
            Amount = amount,
            PaymentChannel = Channels.MNO,
            Institution = InstitutionCodes.Mtn, // e.g., "300591" for MTN
            TransactionId = Guid.NewGuid().ToString("N"),
            DebitNarration = description,
            Currency = "GHS"
        };
        var response = await _client.Collections.DebitMoneyAsync(collectionRequest);

        if (response.IsSuccess){
            return Ok(response!.Data!.TransactionId);
        }
        return Ok(response.StatusDescription);
}
Bank Collection (Not available)
    /*  .. not available  */

3. Disbursements

Bank Disbursement

    /*** Single Disbursement ***/
    public async Task<IActionResult> DisburseToBankAsync(decimal amount, string description)
    {
        var request = new DisbursementRequest 
        {
            AccountNumber = "TEST_ACCOUNT_NUMBER",
            AccountName = /* from the name enquiry*/,
            Amount = amount,
            PaymentChannel = Channels.INTERBANK,
            Institution = InstitutionCodes.StandardCharteredBank,
            TransactionId = Guid.NewGuid().ToString("N"),
            CreditNarration = description,
            Currency = "GHS"
        };

        var response = await _client.Disbursements.DisburseAsync(request);

        if (response.IsSuccess){
            return Ok(response!.Data!.TransactionId);
        }
        return Ok(response.StatusDescription);
    }

    /*** Bulk Disbursement ***/
    [HttpGet("DisburseBulkToBank", Name = "DisburseBulkToBank")]
    public async Task<IActionResult> ProcessBulkDisbursementAsync(decimal amount)
    {
        /* you can have your recipient come from the database */
        var recipients = new List<Recipient>(){
            new Recipient {
                AccountNumber = "_acount_number_here,
                AccountName = "_acount_name_from_entity_name_check",
                BankInstitutionCode = "_code_from_entity_name_check"
            }
            /** more...... **/
        }
            
        var request = new BulkPaymentRequest
        {
            BatchTransactionId = Guid.NewGuid().ToString("N"),
            BulkPaymentData = recipients.Select(r => new BulkPaymentItem
            {
                AccountNumber = r.AccountNumber,
                AccountName = r.AccountName,
                Amount = amount,
                Institution = (InstitutionCodes) int.Parse(r.BankInstitutionCode),
                Narration = "This is a Bulk Payment for mass employee"
            }).ToList()!
        };

        var response = await _client.BulkTransactions.BulkDisbursementAsync(request);

        if (response.IsSuccess){
            return Ok(response!.Data);
        }
        return Ok(response.StatusDescription);
    }

Mobile Money Disbursement

    /*** Single Disbursement ***/
    public async Task<IActionResult> DisburseToMobileMoneyAsync(decimal amount, string description)
    {
        var request = new DisbursementRequest 
        {
            AccountNumber = SCAccountNumber,
            AccountName = /* from the name enquiry*/,
            Amount = amount,
            PaymentChannel = Channels.INTERBANK,
            Institution = InstitutionCodes.StandardCharteredBank,
            TransactionId = Guid.NewGuid().ToString("N"),
            CreditNarration = description,
            Currency = "GHS"
        };

        var response = await _client.Disbursements.DisburseAsync(request);

        if (response.IsSuccess){
            return Ok(response!.Data!.TransactionId);
        }
        return Ok(response.StatusDescription);
    }

    /*** Bulk Disbursement ***/
    public async Task<IActionResult> ProcessBulkMobileMoneyDisbursementAsync(decimal amount)
    {
        /* you can have your recipient come from the database */
        var recipients = new List<Recipient>(){
            new Recipient {
                AccountNumber = "_acount_number_here,
                AccountName = "_acount_name_from_entity_name_check",
                MomoInstitutionCode = "_code_from_entity_name_check"
            }
            /** more...... **/
        }
            
        var request = new BulkPaymentRequest
        {
            BatchTransactionId = Guid.NewGuid().ToString("N"),
            BulkPaymentData = recipients.Select(r => new BulkPaymentItem
            {
                AccountNumber = r.AccountNumber,
                AccountName = r.AccountName,
                Amount = amount,
                Institution = (InstitutionCodes) int.Parse(r.MomoInstitutionCode),
                Narration = "This is a Bulk Payment for mass employee"
            }).ToList()!
        };

        var response = await _client.BulkTransactions.BulkDisbursementAsync(request);

        if (response.IsSuccess){
            return Ok(response!.Data!);
        }
        return Ok(response.StatusDescription);
    }

4. Transaction Management

Check Transaction Status
public async Task<TransactionStatusResponse> CheckTransactionStatusAsync(
    string transactionId,
    string transactionType)
{
    var request = new TransactionStatusRequest
    {
        TransactionId = transactionId,
        TransactionType = transactionType // "DEBIT" or "CREDIT"
    };

    var response = await _client.Transactions.GetTransactionStatusAsync(request);
    return response.Data!;
}

/* You can also have it as a job */

private async Task MonitorTransactionStatusAsync(
        string transactionId, string transactionType,
        CancellationToken cancellationToken)
    {
        const int maxAttempts = 10;
        const int delayMilliseconds = 2000;

        for (int attempt = 0; attempt < maxAttempts; attempt++)
        {
            var response = await _client.Transactions.GetTransactionStatusAsync(
                new TransactionStatusRequest
                {
                    TransactionId = transactionId,
                    TransactionType = transactionType
                },
                cancellationToken);

            if (!ErrorCodes.IsPending(response.StatusCode))
            {
                if (response.IsSuccess)
                {
                    _logger.LogInformation("Transaction {TransactionId} completed successfully", transactionId);
                    return;
                }

                _logger.LogError("Transaction {TransactionId} failed: {StatusDescription}", 
                    transactionId, response.StatusDescription);
                return;
            }

            await Task.Delay(delayMilliseconds, cancellationToken);
        }

        _logger.LogWarning("Transaction {TransactionId} status check timed out", transactionId);
    }

Reverse Transaction (Coming Soon)
 /* .... */

5. Account Services

Check Balance
public async Task<(decimal Available, decimal Current)> GetBalanceAsync()
{
    var response = await _client.Balance.GetBalanceAsync();
    return (response.Data.AvailableBalance, response.Data.CurrentBalance);
}
KYC Verification (Coming Soon)
public async Task<KycResponse> VerifyCustomerAsync(
    string customerId,
    string documentType)
{
    var request = new KycRequest
    {
        CustomerId = customerId,
        DocumentType = documentType
    };

    var response = await _client.Kyc.VerifyCustomerAsync(request);
    return response.Data;
}

Error Handling

The SDK uses a consistent response format LibertePayResponse<T> that includes:

  • IsSuccess: Boolean indicating if the operation succeeded
  • StatusCode: String status code from the API
  • StatusDesc: Human-readable status description
  • Data: The actual response data (when successful)
  • Message: Error message (when unsuccessful)

Example Error Handling

try
{
    var response = await _client.Collections.DebitMoneyAsync(request);
    if (!response.IsSuccess)
    {
        _logger.LogError("Collection failed: {Message}", response.Message);
        throw new PaymentException(response.Message);
    }
    
    return response.Data;
}
catch (Exception ex)
{
    _logger.LogError(ex, "Unexpected error during collection");
    throw;
}

Callback Implementation

LibertePay sends callbacks to notify your application about transaction and mandate status updates. Here's how to implement callbacks:

1. Configure Callback URL

First, set your callback URL in the configuration:

{
  "LibertePay": {
    "CallbackUrl": "https://your-domain.com/api/callback", /*Provided during libertey pay setup*/
  }
}

2. Callback Types and Scenarios

Transaction Callbacks

The gateway providor sends the actual status and results of an asynchronous request to the consumer via the url that has been configured for the consumer during onboarding. The consumer is expected to respond to the callback request with a specific response to close the transaction end to end.

Transaction callbacks are sent for various payment scenarios:

  1. Mobile Money Collection

    • Sent when a mobile money payment is initiated
    • Sent when payment status changes (pending, success, failed)
    • Sent when payment is reversed
  2. Bank Transfer

    • Sent when bank transfer is initiated
    • Sent when transfer status changes
    • Sent when transfer is reversed
  3. Bulk Payment

    • Sent for each individual payment in a bulk transaction
    • Sent when bulk payment batch status changes
Mandate Callbacks

Mandate callbacks are sent for recurring payment scenarios:

  1. Mandate Creation

    • Sent when a new mandate is created
    • Sent when mandate creation fails
    • Sent when mandate is cancelled
  2. Recurring Payment

    • Sent when a recurring payment is initiated
    • Sent when recurring payment status changes
    • Sent when recurring payment fails

3. Callback Payload Structure

Transaction Callback
{
  "transactionId": "string",
  "reference": "string",
  "amount": 0.00,
  "currency": "string",
  "status": "string",
  "statusCode": "string",
  "statusDescription": "string",
  "timestamp": "2024-03-20T10:00:00Z",
  "channel": "string",
  "institutionCode": "string",
  "accountNumber": "string",
  "accountName": "string",
  "narration": "string",
  "isSuccess": true,
  "metadata": {
    "key": "value"
  }
}
Mandate Callback
{
  "mandateId": "string",
  "transactionId": "string",
  "reference": "string",
  "status": "string",
  "statusCode": "string",
  "statusDescription": "string",
  "timestamp": "2024-03-20T10:00:00Z",
  "customerId": "string",
  "customerName": "string",
  "accountNumber": "string",
  "accountName": "string",
  "institutionCode": "string",
  "channel": "string",
  "frequency": "string",
  "startDate": "2024-03-20",
  "endDate": "2024-03-20",
  "isSuccess": true,
  "metadata": {
    "key": "value"
  }
}

4. Create Callback Controller

[ApiController]
[Route("api/[controller]")]
public class CallbackController : ControllerBase
{
    private readonly ILibertePayClient _libertePayClient;
    private readonly ILogger<CallbackController> _logger;

    public CallbackController(
        ILibertePayClient libertePayClient,
        ILogger<CallbackController> logger)
    {
        _libertePayClient = libertePayClient;
        _logger = logger;
    }

    [HttpPost("transaction")]
    public async Task<IActionResult> HandleTransactionCallback(
        [FromBody] CallbackRequest request)
    {
        try
        {

            var response = await _libertePayClient.Callbacks.HandleTransactionCallbackAsync(
                async (_, ct) =>
                {
                    // Log callback details
                    _logger.LogInformation(
                        "Received transaction callback for {TransactionId} with status {Status}",
                        request.TransactionId,
                        request.Status);

                    // example
                    switch (request.StatusCode)
                    {
                        case "00": // Success
                            _logger.LogInformation("SUCCESS RESPONSE");
                            break;
                        case "01": // Pending
                            _logger.LogInformation("PENDING RESPONSE");
                            break;
                        case "02": // Failed
                            _logger.LogInformation("FAILED RESPONSE");
                            break;
                        case "03": // Reversed
                            _logger.LogInformation("REVERSED RESPONSE");
                            break;
                        default:
                            _logger.LogWarning("Unknown status code: {StatusCode}", request.StatusCode);
                            break;
                    }

                    return await Task.FromResult(new CallbackResponse
                    {
                        Status = "OK",
                        Message = $"Transaction {request.TransactionId} processed successfully"
                    });
                });

            return Ok(response);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing transaction callback");
            return StatusCode(500, new CallbackResponse { Status = "ERROR" });
        }
    }

    [HttpPost("mandate")]
    public async Task<IActionResult> HandleMandateCallback(
        [FromBody] MandateCallbackRequest request)
    {
        try
        {

            _logger.LogInformation(
                "Received mandate callback for TransactionId: {TransactionId}, MandateId: {MandateId}",
                request.TransactionId,
                request.MandateId);

            var response = await _libertePayClient.Callbacks.HandleMandateCallbackAsync(
                async (_, ct) =>
                {
                    // Handle different mandate statuses
                    switch (request.Status)
                    {
                        case "ACTIVE":
                            _logger.LogInformation("ACTIVE RESPONSE");
                            break;
                        case "CANCELLED":
                            _logger.LogInformation("CANCELLED RESPONSE");
                            break;
                        case "EXPIRED":
                            _logger.LogInformation("EXPIRED RESPONSE");
                            break;
                        case "FAILED":
                            _logger.LogInformation("FAILED RESPONSE");
                            break;
                        default:
                            _logger.LogWarning("Unknown mandate status: {Status}", request.Status);
                            break;
                    }

                    return await Task.FromResult(new CallbackResponse 
                    { 
                        Status = "OK",
                        Message = $"Mandate {request.MandateId} processed successfully"
                    });
                });

            return Ok(response);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing mandate callback");
            return StatusCode(500, new CallbackResponse 
            { 
                Status = "ERROR",
                Message = "Internal server error"
            });
        }
    }

}

Institution Codes

The SDK provides a type-safe way to use institution codes through the InstitutionCodes enum. This ensures:

  • Compile-time checking of valid institution codes
  • IntelliSense support for available institutions
  • Automatic conversion to the correct string format for API calls

Available Institution Codes

Mobile Money Operators
  • InstitutionCodes.Mtn (300591)
  • InstitutionCodes.Vodafone (300592)
  • InstitutionCodes.AirtelTigo (300593)
  • /* check InstitutionCodes Enum for more */
Banks
  • InstitutionCodes.GcbBankLimited (300304)
  • InstitutionCodes.EcobankGhanaLtd (300312)
  • InstitutionCodes.AgriculturalDevelopmentBank (300307)
  • InstitutionCodes.CalBankLimited (300313)
  • InstitutionCodes.FidelityBankLimited (300323)
  • InstitutionCodes.AccessBankLtd (300329)
  • InstitutionCodes.ZenithBankGhanaLtd (300311)
  • InstitutionCodes.GuarantyTrustBank (300322)
  • InstitutionCodes.UnitedBankOfAfrica (300325)
  • InstitutionCodes.StanbicBank (300318)
  • InstitutionCodes.RepublicBankLimited (300310)
  • InstitutionCodes.FirstBankOfNigeria (300319)
  • InstitutionCodes.AbsaBankGhanaLimited (300303)
  • InstitutionCodes.NationalInvestmentBank (300305)
  • InstitutionCodes.Omnibisic (300324)
  • InstitutionCodes.ConsolidatedBankGhana (300331)
  • /* check InstitutionCodes Enum for more */

Using Institution Codes

// Direct enum usage
var request = new NameEnquiryRequest
{
    AccountNumber = "1234567890",
    InstitutionCode = InstitutionCodes.Mtn //example
};


// Get institution code as string
string code = InstitutionCodes.Mtn.ToString(); // Returns "300591"

Channel Types

  • INTERBANK: For bank transactions
  • MNO: For mobile money transactions
  • CARD: For card payments (coming soon)

Support

For support, please contact:

License

This SDK is licensed under the MIT License. See the LICENSE file for details.

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