Paybox.AspNetCore 0.1.0

dotnet add package Paybox.AspNetCore --version 0.1.0
                    
NuGet\Install-Package Paybox.AspNetCore -Version 0.1.0
                    
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="Paybox.AspNetCore" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Paybox.AspNetCore" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Paybox.AspNetCore" />
                    
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 Paybox.AspNetCore --version 0.1.0
                    
#r "nuget: Paybox.AspNetCore, 0.1.0"
                    
#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 Paybox.AspNetCore@0.1.0
                    
#: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=Paybox.AspNetCore&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Paybox.AspNetCore&version=0.1.0
                    
Install as a Cake Tool

Paybox

A modular .NET 10 payment gateway library. Install only the providers you need, plug in your own persistence layer, and get webhook endpoints wired up in two lines.

Currently supported: PayMe (payme.uz) · Click (click.uz)
Planned: Stripe · Paymob · Uzum Bank


Packages

Package Description
Paybox.Abstractions Interfaces and models only — no dependencies
Paybox.Core DI builder, PaymentService, signature utilities
Paybox.PayMe PayMe Merchant API + webhook handler
Paybox.Click Click Shop API + Merchant API + webhook handler
Paybox.AspNetCore MapPayboxWebhooks() minimal API routing

Installation

dotnet add package Paybox.Core
dotnet add package Paybox.PayMe
dotnet add package Paybox.Click
dotnet add package Paybox.AspNetCore

Quick start

1. Register services

// Program.cs
builder.Services.AddPaybox()
    .AddPayMe(o =>
    {
        o.MerchantId = builder.Configuration["PayMe:MerchantId"]!;
        o.SecretKey  = builder.Configuration["PayMe:SecretKey"]!;
        o.IsSandbox  = builder.Environment.IsDevelopment();
    })
    .AddClick(o =>
    {
        o.ServiceId      = int.Parse(builder.Configuration["Click:ServiceId"]!);
        o.SecretKey      = builder.Configuration["Click:SecretKey"]!;
        o.MerchantUserId = int.Parse(builder.Configuration["Click:MerchantUserId"]!);
        o.IsSandbox      = builder.Environment.IsDevelopment();
    });

// required — implement with your own DB/ORM (see below)
builder.Services.AddScoped<IPaymentEventStore, MyPaymentEventStore>();

// optional — side effects after payment lifecycle transitions
builder.Services.AddScoped<IPaymentLifecycleHook, MyNotificationHook>();

2. Map webhook endpoints

app.MapPayboxWebhooks(); // uses prefix "/payments" by default

This registers:

Method Route Description
POST /payments/payme/callback PayMe JSON-RPC callback
POST /payments/click/prepare Click prepare phase (action=0)
POST /payments/click/complete Click complete phase (action=1)

Custom prefix:

app.MapPayboxWebhooks(prefix: "/api/v1/payments");

3. Initiate a payment

public class OrderService(IPaymentService payments)
{
    public async Task<string> CreatePaymentAsync(string orderId, long amountTiyin)
    {
        var request = new PaymentRequest(
            OrderId:  orderId,
            Amount:   amountTiyin,   // always in smallest unit (tiyin for UZS)
            Currency: PaymentCurrency.UZS,
            ReturnUrl: "https://yoursite.com/orders/thank-you"
        );

        var result = await payments.InitiateAsync("PayMe", request);
        // result.CheckoutUrl → redirect the user here
        return result.CheckoutUrl!;
    }
}

Implement IPaymentEventStore

Paybox has no opinion on your database. Implement this interface with whatever ORM or driver you use:

public sealed class PaymentEventStore(AppDbContext db) : IPaymentEventStore
{
    public async Task SaveAsync(PaymentEvent @event, CancellationToken ct = default)
    {
        db.PaymentEvents.Add(new PaymentEventEntity
        {
            Id            = @event.Id,
            OrderId       = @event.OrderId,
            TransactionId = @event.TransactionId,
            Provider      = @event.Provider,
            Amount        = @event.Amount,
            Currency      = @event.Currency.ToString(),
            Status        = @event.Status.ToString(),
            CreatedAt     = @event.CreatedAt,
            UpdatedAt     = @event.UpdatedAt
        });
        await db.SaveChangesAsync(ct);
    }

    public async Task<PaymentEvent?> FindByTransactionIdAsync(string transactionId, CancellationToken ct = default)
    {
        var entity = await db.PaymentEvents
            .FirstOrDefaultAsync(e => e.TransactionId == transactionId, ct);
        return entity is null ? null : Map(entity);
    }

    public async Task<PaymentEvent?> FindByOrderIdAsync(string orderId, CancellationToken ct = default)
    {
        var entity = await db.PaymentEvents
            .FirstOrDefaultAsync(e => e.OrderId == orderId, ct);
        return entity is null ? null : Map(entity);
    }

    public async Task UpdateStatusAsync(string transactionId, PaymentStatus status, CancellationToken ct = default)
    {
        await db.PaymentEvents
            .Where(e => e.TransactionId == transactionId)
            .ExecuteUpdateAsync(s => s
                .SetProperty(e => e.Status, status.ToString())
                .SetProperty(e => e.UpdatedAt, DateTimeOffset.UtcNow), ct);
    }

    private static PaymentEvent Map(PaymentEventEntity e) => new()
    {
        Id            = e.Id,
        OrderId       = e.OrderId,
        TransactionId = e.TransactionId,
        Provider      = e.Provider,
        Amount        = e.Amount,
        Currency      = Enum.Parse<PaymentCurrency>(e.Currency),
        Status        = Enum.Parse<PaymentStatus>(e.Status),
        CreatedAt     = e.CreatedAt,
        UpdatedAt     = e.UpdatedAt
    };
}

Implement IPaymentLifecycleHook (optional)

Fires after the event is persisted. Use it for order updates, email/SMS notifications, etc.

public sealed class OrderNotificationHook(IOrderService orders, IEmailService email) : IPaymentLifecycleHook
{
    public async Task OnCompletedAsync(PaymentEvent @event, CancellationToken ct = default)
    {
        await orders.MarkPaidAsync(@event.OrderId, ct);
        await email.SendPaymentConfirmationAsync(@event.OrderId, ct);
    }

    public async Task OnCancelledAsync(PaymentEvent @event, CancellationToken ct = default)
    {
        await orders.MarkCancelledAsync(@event.OrderId, ct);
    }
}

Configuration reference

appsettings.json

{
  "PayMe": {
    "MerchantId": "your_merchant_id",
    "SecretKey":  "your_secret_key",
    "IsSandbox":  true
  },
  "Click": {
    "ServiceId":      12345,
    "SecretKey":      "your_secret_key",
    "MerchantUserId": 99,
    "IsSandbox":      false
  }
}

Bind from config directly:

builder.Services.AddPaybox()
    .AddPayMe(o => builder.Configuration.GetSection("PayMe").Bind(o))
    .AddClick(o => builder.Configuration.GetSection("Click").Bind(o));

Payment flow

PayMe

User → Your server (InitiateAsync) → checkout URL → User redirected to PayMe
PayMe → POST /payments/payme/callback (JSON-RPC: CheckPerform → Create → Perform)
Paybox → IPaymentEventStore.UpdateStatusAsync → IPaymentLifecycleHook.OnCompletedAsync

Amount: tiyin (1 UZS = 100 tiyin). Pass 50000 for 500 UZS.

Click

User → Your server (InitiateAsync) → checkout URL → User redirected to Click
Click → POST /payments/click/prepare  (action=0, validates order)
Click → POST /payments/click/complete (action=1, confirms payment)
Paybox → IPaymentEventStore.UpdateStatusAsync → IPaymentLifecycleHook.OnCompletedAsync

Amount: convert to som internally. Pass 50000 tiyin — Paybox converts to 500.00 som for Click.


Check payment status

var status = await payments.GetStatusAsync("PayMe", transactionId);
// status.Status → PaymentStatus.Completed / Pending / Cancelled / Failed

Refund

var refund = await payments.RefundAsync("Click", transactionId, amount: 50000);

Click allows refunds only for same-month transactions (except the 1st day of the month).


PayMe IP whitelist

PayMe callbacks come from 185.234.113.1–185.234.113.15. The handler rejects requests outside this range by default. Override for testing:

.AddPayMe(o =>
{
    // ...
    o.AllowedIps = ["185.234.113.", "10.0.0."]; // custom ranges
});

Adding a new provider

  1. Create a class library referencing Paybox.Core
  2. Implement IPaymentProvider and IWebhookHandler
  3. Add a AddMyProvider(this PayboxBuilder builder, ...) extension method
  4. Register with builder.Services.TryAddEnumerable

License

MIT

Product Compatible and additional computed target framework versions.
.NET 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
0.1.0 106 4/18/2026