Server

C# / .NET SDK

Drop-in C# implementation for integrating YardPay Pro into .NET 6+ applications. Uses HttpClient and System.Text.Json — no NuGet packages required.

API Client

Create YardPayProClient.cs

// YardPayProClient.cs

using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace YardPayPro;

public class YppException : Exception
{
    public int Status { get; }
    public string Code { get; }

    public YppException(int status, string code, string message)
        : base(message)
    {
        Status = status;
        Code = code;
    }
}

public class YardPayProClient : IDisposable
{
    private readonly HttpClient _http;
    private readonly string _baseUrl;
    private static readonly JsonSerializerOptions JsonOpts = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    };

    public YardPayProClient(string apiKey, string baseUrl = "")
    {
        _baseUrl = baseUrl;
        _http = new HttpClient();
        _http.DefaultRequestHeaders.Add("x-api-key", apiKey);
        _http.Timeout = TimeSpan.FromSeconds(30);
    }

    public async Task<T?> RequestAsync<T>(HttpMethod method, string path,
        object? body = null, Dictionary<string, string>? queryParams = null)
    {
        var url = _baseUrl + path;
        if (queryParams is { Count: > 0 })
        {
            var qs = string.Join("&", queryParams
                .Where(kv => kv.Value != null)
                .Select(kv => $"{Uri.EscapeDataString(kv.Key)}={Uri.EscapeDataString(kv.Value)}"));
            url += "?" + qs;
        }

        using var request = new HttpRequestMessage(method, url);
        if (body != null)
        {
            request.Content = JsonContent.Create(body, options: JsonOpts);
        }

        var response = await _http.SendAsync(request);

        if (!response.IsSuccessStatusCode)
        {
            var errBody = await response.Content.ReadAsStringAsync();
            try
            {
                var err = JsonSerializer.Deserialize<ApiError>(errBody, JsonOpts);
                throw new YppException((int)response.StatusCode, err?.Code ?? "api_error", err?.Message ?? response.ReasonPhrase ?? "Unknown error");
            }
            catch (JsonException)
            {
                throw new YppException((int)response.StatusCode, "api_error", response.ReasonPhrase ?? "Unknown error");
            }
        }

        if (response.StatusCode == System.Net.HttpStatusCode.NoContent)
            return default;

        var json = await response.Content.ReadAsStringAsync();
        using var doc = JsonDocument.Parse(json);

        // Handle OData { "value": [...] } wrapper
        if (doc.RootElement.TryGetProperty("value", out var valueEl))
        {
            return JsonSerializer.Deserialize<T>(valueEl.GetRawText(), JsonOpts);
        }

        return JsonSerializer.Deserialize<T>(json, JsonOpts);
    }

    public Task<T?> GetAsync<T>(string path, Dictionary<string, string>? queryParams = null)
        => RequestAsync<T>(HttpMethod.Get, path, queryParams: queryParams);

    public Task<T?> PostAsync<T>(string path, object body)
        => RequestAsync<T>(HttpMethod.Post, path, body);

    public Task<T?> PutAsync<T>(string path, object body)
        => RequestAsync<T>(HttpMethod.Put, path, body);

    public Task DeleteAsync(string path)
        => RequestAsync<object>(HttpMethod.Delete, path);

    public void Dispose() => _http.Dispose();

    private record ApiError(string? Code, string? Message);
}
Models

Create Models.cs

// Models.cs

namespace YardPayPro;

public record PayInvoiceRequest
{
    public int? InvoiceId { get; init; }
    public string? InvoiceIdentifier { get; init; }
    public int PaymentOptionId { get; init; }
    public int? FundingSourceId { get; init; }
    public CardInfo? Card { get; init; }
    public BillingAddress? BillingAddress { get; init; }
}

public record CardInfo
{
    public string Name { get; init; } = "";
    public string CardNumber { get; init; } = "";
    public string ExpiryDate { get; init; } = "";
    public string Cvv { get; init; } = "";
}

public record BillingAddress
{
    public string FirstName { get; init; } = "";
    public string LastName { get; init; } = "";
    public string AddressLine1 { get; init; } = "";
    public string City { get; init; } = "";
    public string Country { get; init; } = "";
}

public record PaymentResponse
{
    public string? ReferenceNumber { get; init; }
    public decimal? AmountPaid { get; init; }
    public decimal? AmountDue { get; init; }
    public string? Currency { get; init; }
    public string? PaymentStatusId { get; init; }
    public bool IsSuccess { get; init; }
    public string? ApprovalAction { get; init; }
    public string? ApprovalDataOrUrl { get; init; }
    public string? HostMessage { get; init; }
}

public record PaymentMethodInfo
{
    public int Id { get; init; }
    public string Code { get; init; } = "";
    public string Name { get; init; } = "";
    public string PaymentMethodId { get; init; } = "";
    public bool IsEnabled { get; init; }
    public string? CurrencyCode { get; init; }
}

public record Invoice
{
    public int Id { get; init; }
    public string Identifier { get; init; } = "";
    public string? Description { get; init; }
    public string Status { get; init; } = "";
    public decimal TotalCost { get; init; }
    public string CurrencyCode { get; init; } = "";
    public decimal? TotalDue { get; init; }
    public decimal? TotalPaid { get; init; }
}

public record CreateInvoiceRequest
{
    public string? Description { get; init; }
    public string CurrencyCode { get; init; } = "JMD";
    public string? PaymentDueDate { get; init; }
    public List<InvoiceItemRequest> Items { get; init; } = new();
    public CounterPartyInfo SourceInfo { get; init; } = new();
    public string? SuccessUrl { get; init; }
}

public record InvoiceItemRequest
{
    public string Name { get; init; } = "";
    public decimal UnitCost { get; init; }
    public int Quantity { get; init; }
    public decimal? TaxRate { get; init; }
    public string? InvoiceItemTypeId { get; init; }
}

public record CounterPartyInfo
{
    public string FirstName { get; init; } = "";
    public string LastName { get; init; } = "";
    public string Email { get; init; } = "";
}
Services

Create Services.cs

// Services.cs

namespace YardPayPro;

public class PaymentsService
{
    private readonly YardPayProClient _client;
    public PaymentsService(YardPayProClient client) => _client = client;

    public Task<PaymentResponse?> PayInvoiceAsync(PayInvoiceRequest request)
        => _client.PostAsync<PaymentResponse>("/api/payment/payinvoice", request);

    public Task<List<PaymentMethodInfo>?> GetPaymentMethodsAsync(int invoiceId)
        => _client.GetAsync<List<PaymentMethodInfo>>($"/v1/invoice/{invoiceId}/PaymentMethods");

    public Task<List<FundingSource>?> GetFundingSourcesAsync()
        => _client.GetAsync<List<FundingSource>>("/v1/FundingSource");
}

public record FundingSource
{
    public int Id { get; init; }
    public string Name { get; init; } = "";
    public int PaymentOptionId { get; init; }
    public string StatusId { get; init; } = "";
    public bool IsVerified { get; init; }
    public string MaskedAccountNumber { get; init; } = "";
}

public class InvoicesService
{
    private readonly YardPayProClient _client;
    public InvoicesService(YardPayProClient client) => _client = client;

    public Task<Invoice?> GetByReferenceAsync(string code)
        => _client.GetAsync<Invoice>($"/v1/invoice/getByReference(code='{code}')");

    public Task<Invoice?> GetByIdAsync(int id)
        => _client.GetAsync<Invoice>($"/v1/invoice/{id}");

    public Task<Invoice?> CreateAsync(CreateInvoiceRequest request)
        => _client.PostAsync<Invoice>("/v1/invoice", request);

    public Task<List<Invoice>?> ListAsync(string? filter = null, string? orderBy = null, int top = 20, int skip = 0)
    {
        var qp = new Dictionary<string, string>();
        if (filter != null) qp["$filter"] = filter;
        if (orderBy != null) qp["$orderby"] = orderBy;
        qp["$top"] = top.ToString();
        qp["$skip"] = skip.ToString();
        return _client.GetAsync<List<Invoice>>("/v1/invoice", qp);
    }

    public string GetQrCodeUrl(string identifier, string baseUrl = "")
        => $"{baseUrl}/v1/invoice/qrcode/{identifier}";
}
ASP.NET Core Usage
// Program.cs — register as singleton
using YardPayPro;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton(
    new YardPayProClient(builder.Configuration["YardPayPro:ApiKey"]!)
);
builder.Services.AddSingleton<PaymentsService>();
builder.Services.AddSingleton<InvoicesService>();

var app = builder.Build();

// PaymentController.cs
using Microsoft.AspNetCore.Mvc;
using YardPayPro;

[ApiController]
[Route("api/[controller]")]
public class PaymentController : ControllerBase
{
    private readonly PaymentsService _payments;
    private readonly InvoicesService _invoices;

    public PaymentController(PaymentsService payments, InvoicesService invoices)
    {
        _payments = payments;
        _invoices = invoices;
    }

    [HttpPost("pay")]
    public async Task<IActionResult> Pay([FromBody] PayInvoiceRequest request)
    {
        try
        {
            var result = await _payments.PayInvoiceAsync(request);
            if (result?.IsSuccess == true)
                return Ok(result);
            return BadRequest(result);
        }
        catch (YppException ex)
        {
            return StatusCode(ex.Status, new { ex.Code, ex.Message });
        }
    }

    [HttpGet("invoice/{code}")]
    public async Task<IActionResult> GetInvoice(string code)
    {
        var invoice = await _invoices.GetByReferenceAsync(code);
        return invoice != null ? Ok(invoice) : NotFound();
    }
}
Webhook Signature Verification
// WebhookHelper.cs

using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

namespace YardPayPro;

public static class WebhookHelper
{
    public static bool VerifySignature(string payload, string signature, string secret)
    {
        using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
        var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
        var expected = Convert.ToHexString(hash).ToLowerInvariant();
        return CryptographicOperations.FixedTimeEquals(
            Encoding.UTF8.GetBytes(expected),
            Encoding.UTF8.GetBytes(signature)
        );
    }

    public static T? ParseEvent<T>(string payload)
    {
        return JsonSerializer.Deserialize<T>(payload, new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        });
    }
}

// Usage in a controller:
[HttpPost("webhooks")]
public async Task<IActionResult> HandleWebhook()
{
    var payload = await new StreamReader(Request.Body).ReadToEndAsync();
    var signature = Request.Headers["x-ypp-signature"].ToString();

    if (!WebhookHelper.VerifySignature(payload, signature, _webhookSecret))
        return Unauthorized();

    var evt = WebhookHelper.ParseEvent<WebhookEvent>(payload);
    // Handle event based on evt.Type
    return Ok();
}

public record WebhookEvent
{
    public string Type { get; init; } = "";
    public JsonElement Data { get; init; }
}
Requirements
  • .NET 6.0 or later
  • No NuGet packages required — uses System.Net.Http and System.Text.Json
  • Compatible with ASP.NET Core, Azure Functions, console apps, and worker services