Milkyway.Payments.Sdk 1.1.0

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

MilkyWay Payments SDK for .NET

NuGet Downloads .NET CI License: MIT

Official C# client for the MilkyWay Payments API (/payments/v1) — the partner-facing API that banks use to initiate, quote, track, and cancel cross-bank payments.

Batteries included:

  • Keycloak client-credentials auth with in-memory token caching and automatic refresh (and a one-shot refresh-and-replay on 401).
  • Retries via Polly: exponential backoff with jitter on transient failures (5xx, 408, network), with deterministic errors (400/401/402/404) never retried.
  • Typed models & exceptions — money is decimal, status is an enum, and each HTTP error maps to a specific exception type.
  • IHttpClientFactory / DI integration, plus a plain new MilkywayPaymentsClient(options) for non-DI apps.
  • Multi-targets netstandard2.0 (works on .NET Framework 4.6.1+, Mono, Xamarin) and net8.0.

Install

The package is on NuGet.org — install it with whichever tool you use:

# .NET CLI
dotnet add package Milkyway.Payments.Sdk
# Package Manager Console (Visual Studio)
Install-Package Milkyway.Payments.Sdk

<PackageReference Include="Milkyway.Payments.Sdk" Version="1.0.0" />

Targets net8.0 and netstandard2.0, so it works on modern .NET as well as .NET Framework 4.6.1+, Mono, and Xamarin. No other setup — bring your Keycloak ClientId/ClientSecret and you're ready.

Quick start

using Milkyway.Payments.Sdk;
using Milkyway.Payments.Sdk.Models;

using var client = new MilkywayPaymentsClient(new MilkywayOptions
{
    BaseUrl     = "https://milkyway.stage.planet9.ae",
    TokenUrl    = "https://keycloak.ac8o.planet9.ae/realms/planet9-stage/protocol/openid-connect/token",
    ClientId    = "your-client-id",      // issued to your institution
    ClientSecret = "your-client-secret",
});

// 1. Is the recipient bank's service online?
await client.HealthcheckAsync("bank-beta", "card-payout");

// 2. Quote the payment (FX markup + commission applied here).
PrecheckResult quote = await client.PrecheckAsync(new PrecheckRequest
{
    ThirdPartyIdDebit = "bank-beta",
    ServiceId         = "card-payout",
    RecipientId       = "recipient-9999",
    AmountCredit      = 100.00m,
    CurrencyCredit    = "USD",
});
Console.WriteLine($"Rate {quote.Rate}, debit {quote.AmountDebit} {quote.CurrencyDebit}, commission {quote.Commission}");

// 3. Initiate the payment. Pass an Idempotency-Key so retries are safe.
long transactionId = await client.PayAsync(new PayRequest
{
    ThirdPartyIdDebit = "bank-beta",
    ServiceId         = "card-payout",
    SenderId          = "sender-0001",
    RecipientId       = "recipient-9999",
    AmountCredit      = 100.00m,
    CurrencyCredit    = "USD",
    Data              = new Dictionary<string, object?> { ["passport"] = "AA1234567" },
}, idempotencyKey: Guid.NewGuid().ToString());

// 4. Poll until the payment reaches a terminal status.
PostcheckResult result = await client.WaitForCompletionAsync(transactionId);
Console.WriteLine($"Final status: {result.Status}");

Discovery

Before quoting or paying, you can browse the catalog of recipient banks and the services each offers — two read-only calls:

// List every destination, or filter by country (ISO 3166-1 alpha-2, case-insensitive).
IReadOnlyList<Destination> destinations = await client.DiscoveryAsync("TJ");
foreach (var d in destinations)
{
    Console.WriteLine($"{d.ThirdPartyId} ({d.PartnerName}, {d.Country})");
    foreach (var s in d.Services)
        Console.WriteLine($"  {s.ServiceId} via {s.PayoutMethod} → {string.Join(", ", s.PayoutCurrencies ?? Array.Empty<string>())}");
}

// Fetch the Draft-2020 JSON Schema a service validates its `data` payload against.
JsonElement schema = await client.ServiceSchemaAsync("bank-beta", "card-payout");
Console.WriteLine(schema.GetProperty("required").GetArrayLength());

DiscoveryAsync unwraps the destinations list for you; the per-service JSON Schema is omitted from the catalog and fetched on demand with ServiceSchemaAsync (returned as a System.Text.Json.JsonElement). Both throw MilkywayAuthException on 401; ServiceSchemaAsync throws MilkywayNotFoundException when the bank/service pair is unknown.

Dependency injection (ASP.NET Core)

using Milkyway.Payments.Sdk.DependencyInjection;

builder.Services.AddMilkywayPayments(o =>
{
    o.BaseUrl      = builder.Configuration["Milkyway:BaseUrl"]!;
    o.TokenUrl     = builder.Configuration["Milkyway:TokenUrl"]!;
    o.ClientId     = builder.Configuration["Milkyway:ClientId"]!;
    o.ClientSecret = builder.Configuration["Milkyway:ClientSecret"]!;
});

// Then inject IMilkywayPaymentsClient anywhere.
public sealed class PayoutService(IMilkywayPaymentsClient milkyway) { /* ... */ }

The data field

Each service requires extra per-partner fields (sender name, document number, birthday, …) in the Data dictionary. Which keys are required depends on your ServiceId and the recipient bank — look them up in the Услуги registry at https://milkyway-docs.stage.planet9.ae. The server validates data against the service's JSON Schema during Precheck, so a missing field is rejected before any money moves.

Errors

All API errors throw a subclass of MilkywayApiException (carrying StatusCode and the server's message):

HTTP Exception Meaning
400 MilkywayValidationException Bad request (invalid amount, missing field, unresolvable FX rate).
401 MilkywayAuthException Token missing/invalid (also thrown if token acquisition fails).
402 MilkywayExposureBlockedException Payment would breach a block-action exposure limit.
404 MilkywayNotFoundException Transaction not found or not owned by your institution.
5xx MilkywayServiceUnavailableException API or downstream recipient unavailable (retried automatically first).

Retries & idempotency

Transient failures are retried automatically with exponential backoff + jitter (tunable via MilkywayOptions.MaxRetries / RetryBaseDelay). PayAsync is only auto-retried when you supply an idempotencyKey — without one, a retry could create a duplicate payment, so the SDK sends it exactly once.

Configuration

Option Default Purpose
BaseUrl — (required) Payments API base URL.
TokenUrl — (required) Keycloak token endpoint.
ClientId / ClientSecret — (required) Your institution's credentials.
Scope none Optional OAuth scope.
TokenRefreshSkew 30s Refresh this long before token expiry.
RequestTimeout 30s Per-attempt request timeout.
MaxRetries 3 Max transient-failure retries.
RetryBaseDelay 500ms Base delay for exponential backoff.

Building from source

dotnet build
dotnet test
dotnet pack src/Milkyway.Payments.Sdk -c Release   # produces the NuGet package

Releasing

Releases are fully automated by semantic-release on every push to main:

  1. Conventional commits are analysed (feat: → minor, fix:/perf: → patch, ! / BREAKING CHANGE → major). No releasable commits → no release.
  2. The package is packed with the computed version and pushed to NuGet.org via Trusted Publishing (OIDC — no long-lived API key stored anywhere).
  3. A GitHub release + vX.Y.Z tag is created with generated notes.

One-time setup (maintainers): on nuget.org → Trusted Publishing, add a policy for owner bankplanet9, repo milkyway-csharp-sdk, workflow file ci.yml. The nuget.org user is hardcoded in the workflow; no secrets or variables are required.

License

MIT — see LICENSE.

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 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. 
.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
1.1.0 190 7/27/2026
1.0.1 126 6/22/2026
1.0.0 118 6/22/2026