This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
minfraud-api-dotnet is MaxMind's official .NET client library for the minFraud fraud detection web services:
- minFraud Score: Risk score for transactions
- minFraud Insights: Score plus GeoIP2 data and device/email intelligence
- minFraud Factors: All Insights data plus risk reasons and deprecated subscores
- Transaction Reporting API: Report chargebacks and fraud to improve minFraud accuracy
The library provides both synchronous and asynchronous methods, supports ASP.NET Core dependency injection, and integrates deeply with MaxMind's GeoIP2 library for IP intelligence.
Key Technologies:
- .NET 10.0, .NET 9.0, .NET 8.0, .NET Standard 2.1, and .NET Standard 2.0
- System.Text.Json for JSON serialization/deserialization
- MaxMind.GeoIP2 library (critical dependency for response models)
- xUnit for testing
- Modern C# features (nullable reference types, init-only properties, C# 14.0)
# Build main library
dotnet build MaxMind.MinFraud
# Build tests
dotnet build MaxMind.MinFraud.UnitTest
# Build everything
dotnet build MaxMind.MinFraud.sln# Run all tests
dotnet test MaxMind.MinFraud.UnitTest/MaxMind.MinFraud.UnitTest.csproj
# Run specific test class
dotnet test --filter "FullyQualifiedName~WebServiceClientTest"
# Run tests with detailed output
dotnet test -v normal# Check for outdated dependencies
dotnet-outdated
# Package for NuGet (requires dev-bin/release.ps1 PowerShell script)
# See README.dev.md for full release processMaxMind.MinFraud/
├── Exception/ # 6 exception types (MinFraudException hierarchy)
├── Request/ # Request models (Transaction, TransactionReport, etc.)
│ ├── Transaction.cs # Main request aggregator (11 optional sub-models)
│ ├── TransactionReport.cs # Fraud/chargeback reporting
│ ├── Device.cs, Account.cs, Email.cs, etc.
│ └── CustomInputs.cs # Builder pattern for custom fields
├── Response/ # Response models (Score, Insights, Factors)
│ ├── Score.cs # Base response (RiskScore, Id, Warnings)
│ ├── Insights.cs # Extends Score, adds GeoIP2 & device data
│ ├── Factors.cs # Extends Insights, adds RiskScoreReasons
│ └── IPAddress.cs, Device.cs, Email.cs, etc.
├── Util/ # JSON converters and helpers
├── WebServiceClient.cs # Main API client
└── IWebServiceClient.cs # Interface for DI/mocking
MaxMind.MinFraud.UnitTest/
├── Request/, Response/, Exception/ # Mirror structure
├── TestData/ # JSON fixtures for testing
└── WebServiceClientTest.cs
The Transaction model aggregates 11 optional sub-models using init-only
properties:
var transaction = new Transaction {
Device = new Device { IPAddress = IPAddress.Parse("1.2.3.4") },
Account = new Account { UserId = "user123" },
Email = new Email { Address = "user@example.com" },
// ... 8 more optional components
};Key Points:
- All request properties use
initsetters (immutable after construction) - Validation happens immediately in property setters (fail-fast)
- JSON serialization uses
[JsonPropertyName("snake_case")]attributes - Auto-hashing for usernames/addresses (MD5) when configured
Score (base)
├─ Properties: RiskScore, Id, Disposition, Warnings, FundsRemaining, IPAddress
└─ Insights (extends Score)
├─ Overrides IPAddress with full GeoIP2 data
├─ Adds: CreditCard, Device, Email, BillingAddress, ShippingAddress, Phones
└─ Factors (extends Insights)
└─ Adds: RiskScoreReasons, Subscores (obsolete as of 2025-03)
Key Points:
- Each level adds more detail without breaking compatibility
- Interface-based polymorphism for
IIPAddress(Score uses minimal, Insights uses full) - Response models inherit from GeoIP2 models (e.g.,
IPAddress : InsightsResponse) - All response properties are init-only with default empty objects (never null)
minFraud response models inherit from and extend GeoIP2 models:
public sealed class IPAddress : InsightsResponse, IIPAddress
{
// Inherits: City, Country, Location, Postal, RegisteredCountry, RepresentedCountry, Traits
// Adds minFraud-specific:
public double? Risk { get; init; }
public IReadOnlyList<IPRiskReason> RiskReasons { get; init; }
}Implications:
- Changes to GeoIP2 models affect minFraud responses
- When adding fields, check if they belong in GeoIP2 or minFraud layer
- Use GeoIP2 patterns for location-related data
The Email class performs sophisticated normalization when
HashAddress = true:
- Domain typo fixes:
gmai.com→gmail.com - TLD typo fixes:
example.comm→example.com - Equivalent domains:
googlemail.com→gmail.com - Plus/dash alias removal for common providers
- Gmail period removal:
f.o.o@gmail.com→foo@gmail.com - Fastmail subdomain normalization
- NFC normalization of local parts
Location: MaxMind.MinFraud/Request/Email.cs
For user-defined fields (up to 25 keys, max 255 characters per value):
var customInputs = new CustomInputs.Builder {
{ "float_input", 12.1d },
{ "integer_input", 3123 },
{ "string_input", "value" },
{ "boolean_input", true }
}.Build();Key Points:
- One-time use (builder invalidated after
Build()) - Type-safe overloads for supported types
- Validation: key format
^[a-z0-9_]{1,25}$, numeric range ±10^13 - Uses
[JsonExtensionData]for dynamic serialization
using var client = new WebServiceClient(accountId: 10, licenseKey: "KEY");
// Three query methods (all async):
var score = await client.ScoreAsync(transaction);
var insights = await client.InsightsAsync(transaction);
var factors = await client.FactorsAsync(transaction);
// Reporting method:
await client.ReportAsync(report);Key Points:
- Thread-safe, designed for singleton/reuse across requests
- Implements
IDisposablefor proper HttpClient lifecycle - Base path:
https://minfraud.maxmind.com/minfraud/v2.0/{endpoint} - Sandbox support via
host: "sandbox.maxmind.com"parameter - Custom User-Agent:
"minFraud-api-dotnet/{version}"
// Startup.cs
services.Configure<WebServiceClientOptions>(Configuration.GetSection("MinFraud"));
services.AddHttpClient<WebServiceClient>();
// appsettings.json
{
"MinFraud": {
"AccountId": 10,
"LicenseKey": "LICENSEKEY",
"Timeout": "00:00:05", // optional
"Host": "minfraud.maxmind.com" // optional
}
}
// Controller
public MyController(WebServiceClient client) { ... }- Tests use xUnit framework
- HTTP mocking via RichardSzalay.MockHttp
- JSON fixtures in
TestData/for response deserialization tests - Test structure mirrors main project structure
1. HTTP Mocking:
var mockHttp = new MockHttpMessageHandler();
mockHttp.When(HttpMethod.Post, "https://minfraud.maxmind.com/minfraud/v2.0/score")
.Respond(HttpStatusCode.OK, "application/json", responseContent);
var client = new WebServiceClient(new HttpClient(mockHttp), options);2. Validation Testing:
[Theory]
[InlineData(-1.0)] // Invalid
[InlineData(3600.1)] // Valid
public void TestSessionAge(double age) { ... }3. JSON Round-Trip Testing: Test that response objects can be serialized back to JSON and match the original structure.
-
Add property with JSON attribute and validation using C# 14
fieldkeyword:[JsonPropertyName("field_name")] public double? FieldName { get => field; init { if (value is < 0) { throw new ArgumentException("must be non-negative."); } field = value; } }
Note: Use the
fieldkeyword (C# 14) instead of explicit backing fields. This eliminates boilerplate while maintaining validation logic. Only use explicit backing fields if you need cross-property assignments (e.g., Email.cs where_domainis set fromAddress). -
Update
releasenotes.mdwith the change -
Add tests for validation and serialization
-
Determine if field is GeoIP2 or minFraud-specific
- GeoIP2: Location data, ISP, traits, etc. → Add to GeoIP2 library first
- minFraud: Risk, device intelligence, email intelligence → Add here
-
Add property with init-only setter:
[JsonInclude] [JsonPropertyName("field_name")] public TypeName? FieldName { get; init; }
-
For MINOR version releases: Add deprecated constructor matching old signature to avoid breaking changes:
// New constructor with added parameter public ResponseClass( string? existingField = null, double? newField = null // NEW ) { ... } // Deprecated constructor for backward compatibility [Obsolete("Use constructor with newField parameter")] public ResponseClass(string? existingField = null) : this(existingField, null) { }
-
For MAJOR versions: No need for deprecated constructor
-
Update tests with assertions for the new field
-
Update
releasenotes.md
When MaxMind adds new payment processors, event types, etc.:
-
Add to enum (e.g.,
PaymentProcessor,EventType):[EnumMember(Value = "new_processor")] NewProcessor,
-
Update
releasenotes.mdlisting the new value -
No tests needed for simple enum additions
Forward Compatibility: The EnumMemberValueConverter<T> gracefully handles
unknown enum values from the API by returning null instead of throwing
exceptions. This ensures that when MaxMind adds new enum values to the web
service, existing client versions won't break - the new value will simply be
treated as null. This is critical for maintaining backward compatibility and
preventing client breakage during API evolution.
Exception Hierarchy:
System.Exception
├─ IOException
│ └─ HttpException (transport errors, 5xx)
└─ MinFraudException (service errors)
├─ AuthenticationException (401, invalid credentials)
├─ InsufficientFundsException (payment required)
├─ PermissionRequiredException (403, service not enabled)
└─ InvalidRequestException (400, bad request)
Error Handling:
- HTTP-level errors (network, 500s) →
HttpException(inherits fromIOException) - Service errors (4xx) → Parse JSON error body → Specific
MinFraudExceptionsubclass - Validation errors (construction) →
ArgumentException(fail-fast) - Non-fatal issues →
Score.Warningslist (not thrown)
Always update releasenotes.md for user-facing changes:
## 5.x.0 (YYYY-MM-DD)
- Added `NewProperty` property to `MaxMind.MinFraud.Response.ResponseClass`.
This provides information about...
- Added `NewValue` to the `EnumName` enum.
- The `OldProperty` property in `MaxMind.MinFraud.Model.ModelClass` has been
marked `Obsolete`. Please use `NewProperty` instead.For MAJOR versions, prefix breaking changes with **BREAKING:**
-
Use
[Obsolete]attribute with helpful messages:[Obsolete("Use NewProperty instead. This will be removed in v6.0.0.")] public string? OldProperty { get; init; }
-
Keep deprecated functionality working through the minor version series
-
Update
releasenotes.mdwith deprecation notice -
Remove in next MAJOR version (not minor/patch)
The project enforces strict standards:
- EnforceCodeStyleInBuild: Code style violations are build errors
- TreatWarningsAsErrors: All warnings must be resolved
- EnableNETAnalyzers: .NET code analyzers enabled
- .editorconfig: Defines consistent coding style
- Language Version: C# 13.0
- Nullable Reference Types: Enabled (
<Nullable>enable</Nullable>)
This library targets multiple frameworks. When adding features:
- Prefer standard types that work across all targets
- For newer types (e.g.,
DateOnlyin .NET 6+), use conditional compilation:#if NET6_0_OR_GREATER public DateOnly? SomeDate { get; init; } #endif
- Test on both modern .NET and .NET Standard if possible
- All request/response models use init-only properties
- No public setters after construction
- Defensive copying (readonly collections, immutable types)
- Use
IReadOnlyList<T>for collections in responses
public double? Value {
get => field;
init {
if (value is < 0 or > 100) {
throw new ArgumentException("must be between 0 and 100.");
}
field = value;
}
}The field keyword (C# 14) creates a compiler-synthesized backing field,
eliminating the need for explicit private readonly declarations. Use this for
all properties with validation logic unless you need cross-property assignments.
public CreditCard CreditCard { get; init; } = new();
public IReadOnlyList<Warning> Warnings { get; init; } = [];This prevents null reference exceptions and simplifies client code.
[JsonIgnore]
public string? Username { get; init; }
[JsonPropertyName("username_md5")]
public string? UsernameMD5 {
get {
if (Username == null) return null;
// ... compute MD5
}
}public enum EventType
{
[EnumMember(Value = "account_creation")]
AccountCreation,
[EnumMember(Value = "account_login")]
AccountLogin,
}Requires EnumMemberValueConverter<T> in JSON options.
MaxMind.GeoIP2
- Provides base models for response classes (InsightsResponse, Location, Traits, etc.)
- minFraud responses inherit from and extend these models
- Changes to GeoIP2 models can affect minFraud API surface
System.Text.Json
- Modern JSON serialization (not Newtonsoft.Json)
- Requires custom converters for enums, IP addresses, GeoIP2 types
- Uses snake_case naming via
[JsonPropertyName]attributes
Microsoft.Extensions.Options
- ASP.NET Core configuration binding
- Enables dependency injection pattern
IsExternalInit (.NET Standard only)
- Polyfill for C# 9.0
initkeyword on older frameworks
- Target Frameworks: net10.0, net9.0, net8.0, netstandard2.1, netstandard2.0
- Language Version: C# 14.0
- Assembly Signing: Uses MaxMind.snk strong name key
- NuGet Package: MaxMind.MinFraud
Last Updated: 2025-11-18