Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/own-pay/OwnPay-Documentation/llms.txt

Use this file to discover all available pages before exploring further.

.NET’s HttpClient combined with System.Text.Json (built into .NET 6+) gives you a clean, allocation-efficient foundation for integrating with OwnPay’s REST API. This guide covers creating a reusable, typed C# client, initiating payment intents, verifying outcomes, processing webhooks in ASP.NET Core, issuing refunds, and managing customer profiles — with idiomatic C# patterns including record types, nullable annotations, and async/await throughout.
OwnPay has no official NuGet package at this time. All integration is done over HTTP using System.Net.Http.HttpClient and System.Text.Json. The examples target .NET 6 or later.

Configuration

Add your credentials to appsettings.json and read them through the options pattern, or inject them via environment variables:
export OWNPAY_API_KEY="op.xxxxxxxxxxxxx"
export OWNPAY_BASE_URL="https://pay.yourbrand.com/api/v1"
{
  "OwnPay": {
    "BaseUrl": "https://pay.yourbrand.com/api/v1",
    "ApiKey": "op.xxxxxxxxxxxxx"
  }
}

Models

// Models/OwnPayModels.cs
using System.Text.Json.Serialization;

namespace MyApp.OwnPay;

public record CreatePaymentRequest(
    [property: JsonPropertyName("amount")]         string Amount,
    [property: JsonPropertyName("currency")]       string Currency,
    [property: JsonPropertyName("callback_url")]   string? CallbackUrl   = null,
    [property: JsonPropertyName("redirect_url")]   string? RedirectUrl   = null,
    [property: JsonPropertyName("cancel_url")]     string? CancelUrl     = null,
    [property: JsonPropertyName("customer_email")] string? CustomerEmail = null,
    [property: JsonPropertyName("customer_name")]  string? CustomerName  = null,
    [property: JsonPropertyName("customer_phone")] string? CustomerPhone = null,
    [property: JsonPropertyName("reference")]      string? Reference     = null,
    [property: JsonPropertyName("gateway")]        string? Gateway       = null,
    [property: JsonPropertyName("metadata")]       Dictionary<string, object>? Metadata = null
);

public record PaymentIntent(
    [property: JsonPropertyName("payment_id")]   string PaymentId,
    [property: JsonPropertyName("token")]        string Token,
    [property: JsonPropertyName("checkout_url")] string CheckoutUrl,
    [property: JsonPropertyName("status")]       string Status
);

public record Transaction(
    [property: JsonPropertyName("id")]              int?    Id,
    [property: JsonPropertyName("trx_id")]          string? TrxId,
    [property: JsonPropertyName("gateway_trx_id")]  string? GatewayTrxId,
    [property: JsonPropertyName("amount")]          string  Amount,
    [property: JsonPropertyName("currency")]        string  Currency,
    [property: JsonPropertyName("fee")]             string? Fee,
    [property: JsonPropertyName("net_amount")]      string? NetAmount,
    [property: JsonPropertyName("status")]          string  Status,
    [property: JsonPropertyName("gateway")]         string? Gateway,
    [property: JsonPropertyName("method")]          string? Method,
    [property: JsonPropertyName("reference")]       string? Reference,
    [property: JsonPropertyName("created_at")]      string  CreatedAt,
    [property: JsonPropertyName("completed_at")]    string? CompletedAt
);

public record CreateRefundRequest(
    [property: JsonPropertyName("trx_id")]         string  TrxId,
    [property: JsonPropertyName("transaction_id")] int?    TransactionId = null,
    [property: JsonPropertyName("amount")]         string? Amount        = null,
    [property: JsonPropertyName("reason")]         string? Reason        = null
);

public record Refund(
    [property: JsonPropertyName("id")]             int    Id,
    [property: JsonPropertyName("uuid")]           string Uuid,
    [property: JsonPropertyName("transaction_id")] int    TransactionId,
    [property: JsonPropertyName("trx_id")]         string TrxId,
    [property: JsonPropertyName("gateway_trx_id")] string GatewayTrxId,
    [property: JsonPropertyName("amount")]         string Amount,
    [property: JsonPropertyName("reason")]         string Reason,
    [property: JsonPropertyName("status")]         string Status,
    [property: JsonPropertyName("processed_at")]   string ProcessedAt,
    [property: JsonPropertyName("created_at")]     string CreatedAt
);

public record FieldError(
    [property: JsonPropertyName("code")]    string Code,
    [property: JsonPropertyName("message")] string Message,
    [property: JsonPropertyName("field")]   string Field
);

Custom Exception

// OwnPayException.cs
namespace MyApp.OwnPay;

public class OwnPayException : Exception
{
    public int         StatusCode  { get; }
    public FieldError[]  Errors    { get; }
    public string      RequestId   { get; }

    public OwnPayException(
        int statusCode,
        string message,
        FieldError[] errors,
        string requestId)
        : base($"[{statusCode}] {message} (request_id={requestId})")
    {
        StatusCode = statusCode;
        Errors     = errors;
        RequestId  = requestId;
    }
}

Client

// OwnPayClient.cs
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Nodes;

namespace MyApp.OwnPay;

public class OwnPayClient
{
    private readonly HttpClient _http;

    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
    };

    public OwnPayClient(HttpClient http) => _http = http;

    private async Task<T> SendAsync<T>(
        HttpMethod method,
        string path,
        object? body = null,
        CancellationToken ct = default)
    {
        using var request = new HttpRequestMessage(method, path);

        if (body is not null)
            request.Content = JsonContent.Create(body, options: JsonOptions);

        using var resp = await _http.SendAsync(request, ct);
        var json      = await resp.Content.ReadAsStringAsync(ct);
        var root      = JsonNode.Parse(json);

        if (!resp.IsSuccessStatusCode)
        {
            var error     = root?["error"]?.GetValue<string>()     ?? $"HTTP {(int)resp.StatusCode}";
            var requestId = root?["request_id"]?.GetValue<string>() ?? string.Empty;
            var errors    = root?["errors"]?.Deserialize<FieldError[]>(JsonOptions) ?? [];

            throw new OwnPayException((int)resp.StatusCode, error, errors, requestId);
        }

        var data = root?["data"]?.ToJsonString()
            ?? throw new InvalidOperationException("Missing data in response");

        return JsonSerializer.Deserialize<T>(data, JsonOptions)
            ?? throw new InvalidOperationException("Failed to deserialise response data");
    }

    public Task<JsonNode?> HealthAsync(CancellationToken ct = default)
        => SendAsync<JsonNode?>(HttpMethod.Get, "/health", ct: ct);

    public Task<PaymentIntent> CreatePaymentAsync(
        CreatePaymentRequest req, CancellationToken ct = default)
        => SendAsync<PaymentIntent>(HttpMethod.Post, "/payments", req, ct);

    public Task<Transaction> GetPaymentAsync(
        string paymentId, CancellationToken ct = default)
        => SendAsync<Transaction>(HttpMethod.Get, $"/payments/{paymentId}", ct: ct);

    public Task<Transaction[]> ListTransactionsAsync(
        string? status  = null,
        string? gateway = null,
        string? from    = null,
        string? to      = null,
        int     page    = 1,
        int     perPage = 25,
        CancellationToken ct = default)
    {
        var qs = new List<string> { $"page={page}", $"per_page={perPage}" };
        if (status  is not null) qs.Add($"status={status}");
        if (gateway is not null) qs.Add($"gateway={gateway}");
        if (from    is not null) qs.Add($"from={from}");
        if (to      is not null) qs.Add($"to={to}");
        return SendAsync<Transaction[]>(HttpMethod.Get, $"/transactions?{string.Join("&", qs)}", ct: ct);
    }

    public Task<Transaction> GetTransactionAsync(
        string trxId, CancellationToken ct = default)
        => SendAsync<Transaction>(HttpMethod.Get, $"/transactions/{trxId}", ct: ct);

    public Task<Refund> CreateRefundAsync(
        CreateRefundRequest req, CancellationToken ct = default)
        => SendAsync<Refund>(HttpMethod.Post, "/refunds", req, ct);
}

Registration (ASP.NET Core)

// Program.cs
builder.Services.AddHttpClient<OwnPayClient>(client =>
{
    client.BaseAddress = new Uri(builder.Configuration["OwnPay:BaseUrl"]!);
    client.DefaultRequestHeaders.Add(
        "Authorization",
        $"Bearer {builder.Configuration["OwnPay:ApiKey"]}");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
    client.Timeout = TimeSpan.FromSeconds(30);
});

Creating a Payment Intent

// In your controller or service
public class CheckoutService(OwnPayClient ownPay)
{
    public async Task<string> StartCheckoutAsync(string orderId, string customerEmail)
    {
        var intent = await ownPay.CreatePaymentAsync(new CreatePaymentRequest(
            Amount:        "500.00",
            Currency:      "BDT",
            Reference:     orderId,
            CallbackUrl:   "https://my-store.com/webhooks/ownpay",
            RedirectUrl:   "https://my-store.com/checkout/success",
            CancelUrl:     "https://my-store.com/checkout/cancel",
            CustomerEmail: customerEmail,
            CustomerName:  "John Doe"
        ));

        // Store intent.PaymentId in user session for verification on return
        return intent.CheckoutUrl; // Redirect the user here
    }
}

Verifying a Payment

public async Task<IActionResult> ReturnAsync(
    [FromQuery] string paymentId,
    CancellationToken ct)
{
    var payment = await _ownPay.GetPaymentAsync(paymentId, ct);

    if (payment.Status != "completed")
        return BadRequest($"Payment not completed: {payment.Status}");

    // Fulfil the order
    _logger.LogInformation("Order confirmed: {TrxId}", payment.TrxId);
    return RedirectToAction("Success");
}
Do not trust query parameters on your redirect_url. Always call GetPaymentAsync from your server before fulfilling orders.

Issuing a Refund

var refund = await _ownPay.CreateRefundAsync(new CreateRefundRequest(
    TrxId:  "OP-481029304",
    Amount: "150.00",
    Reason: "Customer requested return"
));

Console.WriteLine($"Refund {refund.Uuid}{refund.Status}");

Handling Webhooks (ASP.NET Core)

// Controllers/WebhooksController.cs
using Microsoft.AspNetCore.Mvc;
using System.Text.Json.Nodes;

[ApiController]
[Route("webhooks")]
public class WebhooksController(OwnPayClient ownPay, ILogger<WebhooksController> logger)
    : ControllerBase
{
    [HttpPost("ownpay")]
    public async Task<IActionResult> OwnPay(
        [FromBody] JsonNode payload,
        CancellationToken ct)
    {
        var eventType = payload["event"]?.GetValue<string>();

        if (eventType != "payment.completed")
            return BadRequest(new { ok = false, reason = "unrecognised event" });

        var trxId = payload["data"]?["trx_id"]?.GetValue<string>();
        if (trxId is null)
            return BadRequest(new { ok = false, reason = "missing trx_id" });

        try
        {
            // Re-verify before acting
            var transaction = await ownPay.GetTransactionAsync(trxId, ct);

            if (transaction.Status != "completed")
                return BadRequest(new { ok = false, reason = "not completed" });

            logger.LogInformation("Fulfilling order: {Reference}", transaction.Reference);
            // await _orderService.FulfillAsync(transaction.Reference, ct);

            return Ok(new { ok = true });
        }
        catch (OwnPayException ex)
        {
            logger.LogError("OwnPay error {Status}: {Message}", ex.StatusCode, ex.Message);
            return StatusCode(ex.StatusCode, new { ok = false });
        }
    }
}
Return 200 OK immediately and defer heavy processing to a background task (IBackgroundTaskQueue, Hangfire, Azure Service Bus, etc.). OwnPay expects a response within 10 seconds.

Error Handling

try
{
    var intent = await _ownPay.CreatePaymentAsync(
        new CreatePaymentRequest(Amount: "-1", Currency: "BDT"));
}
catch (OwnPayException ex)
{
    Console.WriteLine($"API error {ex.StatusCode}: {ex.Message}");

    foreach (var fe in ex.Errors)
        Console.WriteLine($"  Field '{fe.Field}': {fe.Message}");
}
catch (HttpRequestException ex)
{
    Console.WriteLine($"Network error: {ex.Message}");
}
StatusMeaning.NET action
400Business rule violationRender error to operator
401Invalid or missing API keyCheck OwnPay:ApiKey config
403Insufficient scopeRegenerate key with required scopes
404Resource not foundValidate IDs before requesting
409Duplicate customer emailLook up existing customer instead
422Validation failureInspect Errors array
503System degradedRetry with Polly exponential back-off

Build docs developers (and LLMs) love