├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── test.yml │ └── zizmor.yml ├── .gitignore ├── LICENSE ├── MaxMind-logo.png ├── MaxMind.MinFraud.UnitTest ├── Exception │ ├── HttpExceptionTest.cs │ └── InvalidRequestExceptionTest.cs ├── GlobalSuppressions.cs ├── JsonElementComparer.cs ├── MaxMind.MinFraud.UnitTest.csproj ├── Properties │ └── AssemblyInfo.cs ├── Request │ ├── AccountTest.cs │ ├── BillingTest.cs │ ├── CreditCardTest.cs │ ├── CustomInputsTest.cs │ ├── DeviceTest.cs │ ├── EmailTest.cs │ ├── EventTest.cs │ ├── MinFraudRequestTest.cs │ ├── OrderTest.cs │ ├── PaymentTest.cs │ ├── ShippingTest.cs │ ├── ShoppingCartItemTest.cs │ ├── TestHelper.cs │ └── TransactionReportTest.cs ├── Response │ ├── AddressTestHelper.cs │ ├── BillingAddressTest.cs │ ├── CreditCardTest.cs │ ├── DeviceTest.cs │ ├── DispositionTest.cs │ ├── EmailDomainTest.cs │ ├── EmailTest.cs │ ├── GeoIP2CountryTest.cs │ ├── GeoIP2LocationTest.cs │ ├── IPAddressTest.cs │ ├── InsightsTest.cs │ ├── IssuerTest.cs │ ├── MultiplierReasonTest.cs │ ├── PhoneTest.cs │ ├── RiskScoreReasonTest.cs │ ├── ScoreTest.cs │ ├── ShippingAddressTest.cs │ ├── SubscoresTest.cs │ └── WarningTest.cs ├── TestData │ ├── factors-response.json │ ├── full-request.json │ ├── insights-response.json │ ├── report-request.json │ └── score-response.json └── WebServiceClientTest.cs ├── MaxMind.MinFraud.sln ├── MaxMind.MinFraud.sln.DotSettings ├── MaxMind.MinFraud ├── Exception │ ├── AuthenticationException.cs │ ├── HttpException.cs │ ├── InsufficientFundsException.cs │ ├── InvalidRequestException.cs │ ├── MinFraudException.cs │ └── PermissionRequiredException.cs ├── GlobalSuppressions.cs ├── IWebServiceClient.cs ├── MaxMind.MinFraud.csproj ├── MaxMind.MinFraud.ruleset ├── Properties │ └── AssemblyInfo.cs ├── Request │ ├── Account.cs │ ├── Billing.cs │ ├── CreditCard.cs │ ├── CustomInputs.cs │ ├── Device.cs │ ├── Email.cs │ ├── Event.cs │ ├── Location.cs │ ├── Order.cs │ ├── Payment.cs │ ├── Shipping.cs │ ├── ShoppingCartItem.cs │ ├── Transaction.cs │ └── TransactionReport.cs ├── Response │ ├── Address.cs │ ├── BillingAddress.cs │ ├── CreditCard.cs │ ├── Device.cs │ ├── Disposition.cs │ ├── Email.cs │ ├── EmailDomain.cs │ ├── Factors.cs │ ├── GeoIP2Location.cs │ ├── IIPAddress.cs │ ├── IPAddress.cs │ ├── IPRiskReason.cs │ ├── Insights.cs │ ├── Issuer.cs │ ├── MultiplierReason.cs │ ├── Phone.cs │ ├── RiskScoreReason.cs │ ├── Score.cs │ ├── ScoreIPAddress.cs │ ├── ShippingAddress.cs │ ├── Subscores.cs │ └── Warning.cs ├── Util │ ├── DateConverter.cs │ ├── EnumMemberValueConverter.cs │ ├── IPAddressConverter.cs │ ├── NetworkConverter.cs │ ├── ScoreIPAddressConverter.cs │ └── WebServiceError.cs ├── WebServiceClient.cs └── WebServiceClientOptions.cs ├── MaxMind.snk ├── README.dev.md ├── README.md ├── dev-bin └── release.ps1 └── releasenotes.md /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directories: 5 | - "**/*" 6 | schedule: 7 | interval: daily 8 | open-pull-requests-limit: 10 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: daily 13 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Code scanning - action" 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - 'dependabot/**' 7 | pull_request: 8 | schedule: 9 | - cron: '0 10 * * 5' 10 | 11 | jobs: 12 | CodeQL-Build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | permissions: 17 | security-events: write 18 | 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v4 22 | with: 23 | # We must fetch at least the immediate parents so that if this is 24 | # a pull request then we can checkout the head. 25 | fetch-depth: 2 26 | persist-credentials: false 27 | 28 | # If this run was triggered by a pull request event, then checkout 29 | # the head of the pull request instead of the merge commit. 30 | - run: git checkout HEAD^2 31 | if: ${{ github.event_name == 'pull_request' }} 32 | 33 | - name: Setup .NET 34 | uses: actions/setup-dotnet@v4 35 | with: 36 | dotnet-version: | 37 | 8.0.x 38 | 9.0.x 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v3 43 | # Override language selection by uncommenting this and choosing your languages 44 | # with: 45 | # languages: go, javascript, csharp, python, cpp, java 46 | 47 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 48 | # If this step fails, then you should remove it and run the build manually (see below) 49 | - name: Autobuild 50 | uses: github/codeql-action/autobuild@v3 51 | 52 | # ℹ️ Command-line programs to run using the OS shell. 53 | # 📚 https://git.io/JvXDl 54 | 55 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 56 | # and modify them (or add more) to build your code if your project 57 | # uses a compiled language 58 | 59 | #- run: | 60 | # make bootstrap 61 | # make release 62 | 63 | - name: Perform CodeQL Analysis 64 | uses: github/codeql-action/analyze@v3 65 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | on: 3 | push: 4 | pull_request: 5 | schedule: 6 | - cron: '3 20 * * SUN' 7 | 8 | permissions: {} 9 | 10 | jobs: 11 | build: 12 | strategy: 13 | matrix: 14 | platform: [ubuntu-latest, macos-latest, windows-latest] 15 | runs-on: ${{ matrix.platform }} 16 | name: Dotnet on ${{ matrix.platform }} 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | submodules: true 21 | persist-credentials: false 22 | 23 | - name: Setup .NET 24 | uses: actions/setup-dotnet@v4 25 | with: 26 | dotnet-version: | 27 | 8.0.x 28 | 9.0.x 29 | 30 | - name: Build 31 | run: | 32 | dotnet build MaxMind.MinFraud 33 | dotnet build MaxMind.MinFraud.UnitTest 34 | 35 | - name: Run tests 36 | run: dotnet test MaxMind.MinFraud.UnitTest/MaxMind.MinFraud.UnitTest.csproj 37 | -------------------------------------------------------------------------------- /.github/workflows/zizmor.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Actions Security Analysis with zizmor 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["**"] 8 | 9 | jobs: 10 | zizmor: 11 | name: zizmor latest via PyPI 12 | runs-on: ubuntu-latest 13 | permissions: 14 | security-events: write 15 | # required for workflows in private repositories 16 | contents: read 17 | actions: read 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v4 21 | with: 22 | persist-credentials: false 23 | 24 | - name: Install the latest version of uv 25 | uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # 6.1.0 26 | with: 27 | enable-cache: false 28 | 29 | - name: Run zizmor 30 | run: uvx zizmor@1.7.0 --format plain . 31 | env: 32 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # dotnet CLI stuff 6 | .dotnetcli/ 7 | scripts/ 8 | 9 | # mstest test results 10 | TestResults 11 | 12 | ## Ignore Visual Studio temporary files, build results, and 13 | ## files generated by popular Visual Studio add-ons. 14 | 15 | # Visual Studo 2015 cache/options directory 16 | .vs/ 17 | 18 | # User-specific files 19 | *.suo 20 | *.user 21 | *.sln.docstates 22 | 23 | # Build results 24 | [Dd]ebug/ 25 | [Rr]elease/ 26 | x64/ 27 | *_i.c 28 | *_p.c 29 | *.ilk 30 | *.meta 31 | *.nupkg 32 | *.obj 33 | *.pch 34 | *.pdb 35 | *.pgc 36 | *.pgd 37 | *.rsp 38 | *.sbr 39 | *.tlb 40 | *.tli 41 | *.tlh 42 | *.tmp 43 | *.log 44 | *.vspscc 45 | *.vssscc 46 | .builds 47 | 48 | # Visual C++ cache files 49 | ipch/ 50 | *.aps 51 | *.ncb 52 | *.opensdf 53 | *.sdf 54 | 55 | # Visual Studio profiler 56 | *.psess 57 | *.vsp 58 | *.vspx 59 | 60 | # Guidance Automation Toolkit 61 | *.gpState 62 | 63 | # ReSharper is a .NET coding add-in 64 | _ReSharper* 65 | 66 | # NCrunch 67 | *.ncrunch* 68 | .*crunch*.local.xml 69 | 70 | # Installshield output folder 71 | [Ee]xpress 72 | 73 | # DocProject is a documentation generator add-in 74 | DocProject/buildhelp/ 75 | DocProject/Help/*.HxT 76 | DocProject/Help/*.HxC 77 | DocProject/Help/*.hhc 78 | DocProject/Help/*.hhk 79 | DocProject/Help/*.hhp 80 | DocProject/Help/Html2 81 | DocProject/Help/html 82 | 83 | # Click-Once directory 84 | publish 85 | 86 | # Publish Web Output 87 | *.Publish.xml 88 | 89 | # NuGet Packages Directory 90 | packages 91 | *.lock.json 92 | 93 | # Windows Azure Build Output 94 | csx 95 | *.build.csdef 96 | 97 | # Windows Store app package directory 98 | AppPackages/ 99 | 100 | # Idea 101 | .idea 102 | *.iml 103 | 104 | # Others 105 | [Bb]in 106 | [Oo]bj 107 | artifacts 108 | sql 109 | TestResults 110 | [Tt]est[Rr]esult* 111 | *.Cache 112 | ClientBin 113 | [Ss]tyle[Cc]op.* 114 | ~$* 115 | *.dbmdl 116 | *.sw? 117 | Generated_Code #added for RIA/Silverlight projects 118 | 119 | # Backup & report files from converting an old project file to a newer 120 | # Visual Studio version. Backup files are not needed, because we have git ;-) 121 | _UpgradeReport_Files/ 122 | Backup*/ 123 | UpgradeLog*.XML 124 | 125 | .doxygen.conf 126 | -------------------------------------------------------------------------------- /MaxMind-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmind/minfraud-api-dotnet/d66282b58bf50471d15ba2cce83481b6f078490a/MaxMind-logo.png -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Exception/HttpExceptionTest.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using MaxMind.MinFraud.Exception; 4 | using System; 5 | using System.Net; 6 | using Xunit; 7 | 8 | #endregion 9 | 10 | namespace MaxMind.MinFraud.UnitTest.Exception 11 | { 12 | public class HttpExceptionTest 13 | { 14 | [Fact] 15 | public void TestHttpException() 16 | { 17 | var url = new Uri("https://www.maxmind.com/"); 18 | var e = new HttpException("message", HttpStatusCode.OK, url); 19 | Assert.Equal(HttpStatusCode.OK, e.HttpStatus); 20 | Assert.Equal(url, e.Uri); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Exception/InvalidRequestExceptionTest.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using MaxMind.MinFraud.Exception; 4 | using System; 5 | using Xunit; 6 | 7 | #endregion 8 | 9 | namespace MaxMind.MinFraud.UnitTest.Exception 10 | { 11 | public class InvalidRequestExceptionTest 12 | { 13 | [Fact] 14 | public void TestInvalidRequestException() 15 | { 16 | var url = new Uri("https://www.maxmind.com/"); 17 | var code = "INVALID_INPUT"; 18 | var e = new InvalidRequestException("message", code, url); 19 | Assert.Equal(code, e.Code); 20 | Assert.Equal(url, e.Uri); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Minor Code Smell", "S1905:Redundant casts should not be used", Justification = "", Scope = "member", Target = "~M:MaxMind.MinFraud.UnitTest.Request.CustomInputsTest.TestInvalidDouble")] 9 | [assembly: SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "", Scope = "member", Target = "~M:MaxMind.MinFraud.UnitTest.WebServiceClientTest.TestFullScoreRequest~System.Threading.Tasks.Task")] 10 | [assembly: SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "", Scope = "member", Target = "~M:MaxMind.MinFraud.UnitTest.Response.BillingAddressTest.TestBillingAddress")] 11 | [assembly: SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "", Scope = "member", Target = "~M:MaxMind.MinFraud.UnitTest.WebServiceClientTest.TestFullScoreRequest~System.Threading.Tasks.Task")] 12 | [assembly: SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "", Scope = "member", Target = "~M:MaxMind.MinFraud.UnitTest.WebServiceClientTest.TestFullFactorsRequest~System.Threading.Tasks.Task")] 13 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/JsonElementComparer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.Json; 5 | 6 | namespace MaxMind.MinFraud.UnitTest 7 | { 8 | // This is taken from https://stackoverflow.com/questions/60580743/what-is-equivalent-in-jtoken-deepequal-in-system-text-json 9 | // 10 | // Per https://stackoverflow.com/help/licensing, it is licensed under CC BY-SA 4.0. 11 | // 12 | // Hopefully a future version of System.Text.Json will include this functionality. 13 | public class JsonElementComparer : IEqualityComparer 14 | { 15 | public JsonElementComparer() : this(-1) { } 16 | 17 | public JsonElementComparer(int maxHashDepth) 18 | { 19 | MaxHashDepth = maxHashDepth; 20 | } 21 | 22 | private int MaxHashDepth { get; } 23 | 24 | #region IEqualityComparer Members 25 | 26 | public bool JsonEquals(JsonDocument x, JsonDocument y) 27 | { 28 | return Equals(x.RootElement, y.RootElement); 29 | } 30 | 31 | public bool Equals(JsonElement x, JsonElement y) 32 | { 33 | if (x.ValueKind != y.ValueKind) 34 | return false; 35 | switch (x.ValueKind) 36 | { 37 | case JsonValueKind.Null: 38 | case JsonValueKind.True: 39 | case JsonValueKind.False: 40 | case JsonValueKind.Undefined: 41 | return true; 42 | 43 | case JsonValueKind.Number: 44 | return Math.Abs(x.GetDouble() - y.GetDouble()) < 0.0001; 45 | 46 | case JsonValueKind.String: 47 | return x.GetString() == y.GetString(); // Do not use GetRawText() here, it does not automatically resolve JSON escape sequences to their corresponding characters. 48 | 49 | case JsonValueKind.Array: 50 | return x.EnumerateArray().SequenceEqual(y.EnumerateArray(), this); 51 | 52 | case JsonValueKind.Object: 53 | { 54 | // Surprisingly, JsonDocument fully supports duplicate property names. 55 | // I.e. it's perfectly happy to parse {"Value":"a", "Value" : "b"} and will store both 56 | // key/value pairs inside the document! 57 | // A close reading of https://tools.ietf.org/html/rfc8259#section-4 seems to indicate that 58 | // such objects are allowed but not recommended, and when they arise, interpretation of 59 | // identically-named properties is order-dependent. 60 | // So stably sorting by name then comparing values seems the way to go. 61 | var xPropertiesUnsorted = x.EnumerateObject().ToList(); 62 | var yPropertiesUnsorted = y.EnumerateObject().ToList(); 63 | if (xPropertiesUnsorted.Count != yPropertiesUnsorted.Count) 64 | return false; 65 | var xProperties = xPropertiesUnsorted.OrderBy(p => p.Name, StringComparer.Ordinal); 66 | var yProperties = yPropertiesUnsorted.OrderBy(p => p.Name, StringComparer.Ordinal); 67 | foreach (var (px, py) in xProperties.Zip(yProperties, (x, y) => (x, y))) 68 | { 69 | if (px.Name != py.Name) 70 | return false; 71 | if (!Equals(px.Value, py.Value)) 72 | return false; 73 | } 74 | return true; 75 | } 76 | 77 | default: 78 | throw new JsonException($"Unknown JsonValueKind {x.ValueKind}"); 79 | } 80 | } 81 | 82 | public int GetHashCode(JsonElement obj) 83 | { 84 | var hash = new HashCode(); // New in .Net core: https://docs.microsoft.com/en-us/dotnet/api/system.hashcode 85 | ComputeHashCode(obj, ref hash, 0); 86 | return hash.ToHashCode(); 87 | } 88 | 89 | private void ComputeHashCode(JsonElement obj, ref HashCode hash, int depth) 90 | { 91 | hash.Add(obj.ValueKind); 92 | 93 | switch (obj.ValueKind) 94 | { 95 | case JsonValueKind.Null: 96 | case JsonValueKind.True: 97 | case JsonValueKind.False: 98 | case JsonValueKind.Undefined: 99 | break; 100 | 101 | case JsonValueKind.Number: 102 | hash.Add(obj.GetRawText()); 103 | break; 104 | 105 | case JsonValueKind.String: 106 | hash.Add(obj.GetString()); 107 | break; 108 | 109 | case JsonValueKind.Array: 110 | if (depth != MaxHashDepth) 111 | { 112 | foreach (var item in obj.EnumerateArray()) 113 | ComputeHashCode(item, ref hash, depth + 1); 114 | } 115 | else 116 | { 117 | hash.Add(obj.GetArrayLength()); 118 | } 119 | break; 120 | 121 | case JsonValueKind.Object: 122 | foreach (var property in obj.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal)) 123 | { 124 | hash.Add(property.Name); 125 | if (depth != MaxHashDepth) 126 | ComputeHashCode(property.Value, ref hash, depth + 1); 127 | } 128 | break; 129 | 130 | default: 131 | throw new JsonException($"Unknown JsonValueKind {obj.ValueKind}"); 132 | } 133 | } 134 | 135 | #endregion 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/MaxMind.MinFraud.UnitTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test project for minFraud API 5 | 0.6.0 6 | net9.0;net8.0;net481 7 | net9.0;net8.0 8 | MaxMind.MinFraud.UnitTest 9 | MaxMind.MinFraud.UnitTest 10 | Apache-2.0 11 | true 12 | false 13 | false 14 | false 15 | false 16 | false 17 | false 18 | false 19 | false 20 | 13.0 21 | enable 22 | latest 23 | true 24 | true 25 | true 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | runtime; build; native; contentfiles; analyzers 39 | all 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | 8 | [assembly: AssemblyTitle("MaxMind.MinFraud.UnitTest")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("MaxMind.MinFraud.UnitTest")] 13 | [assembly: AssemblyCopyright("Copyright © 2015-2025")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | 21 | [assembly: ComVisible(false)] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | 25 | [assembly: Guid("b94cd151-77d9-4423-bb17-504264b109d2")] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [assembly: AssemblyVersion("1.0.*")] 37 | 38 | [assembly: AssemblyVersion("5.0.0.0")] 39 | [assembly: AssemblyFileVersion("5.0.0.0")] 40 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Request/AccountTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Request; 2 | using Xunit; 3 | 4 | namespace MaxMind.MinFraud.UnitTest.Request 5 | { 6 | public class AccountTest 7 | { 8 | [Fact] 9 | public void TestUserId() 10 | { 11 | var account = new Account( 12 | userId: "usr" 13 | ); 14 | 15 | Assert.Equal("usr", account.UserId); 16 | } 17 | 18 | [Fact] 19 | public void TestUsername() 20 | { 21 | var account = new Account( 22 | username: "username" 23 | ); 24 | Assert.Equal("14c4b06b824ec593239362517f538b29", account.UsernameMD5); 25 | } 26 | 27 | // This test exist as there was a situation where a null 28 | // username was causing an exception due to not properly 29 | // checking for null before converting to an MD5. 30 | [Fact] 31 | public void TestNullUsername() 32 | { 33 | var account = new Account(username: null); 34 | Assert.Null(account.UsernameMD5); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Request/BillingTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Request; 2 | using System; 3 | using Xunit; 4 | 5 | namespace MaxMind.MinFraud.UnitTest.Request 6 | { 7 | // This code is identical to code in ShippingTest. I couldn't 8 | // come up with a good way to share it due to to specifiy arguments 9 | // to new() for a variable type. There are ways around this generally 10 | // but most defeat the purpose of the Tests. 11 | public class BillingTest 12 | { 13 | [Fact] 14 | public void TestFirstName() 15 | { 16 | var loc = new Billing( 17 | firstName: "frst" 18 | ); 19 | Assert.Equal("frst", loc.FirstName); 20 | } 21 | 22 | [Fact] 23 | public void TestLastName() 24 | { 25 | var loc = new Billing(lastName: "last"); 26 | Assert.Equal("last", loc.LastName); 27 | } 28 | 29 | [Fact] 30 | public void TestCompany() 31 | { 32 | var loc = new Billing(company: "company"); 33 | Assert.Equal("company", loc.Company); 34 | } 35 | 36 | [Fact] 37 | public void TestAddress() 38 | { 39 | var loc = new Billing(address: "addr"); 40 | Assert.Equal("addr", loc.Address); 41 | } 42 | 43 | [Fact] 44 | public void TestAddress2() 45 | { 46 | var loc = new Billing(address2: "addr2"); 47 | Assert.Equal("addr2", loc.Address2); 48 | } 49 | 50 | [Fact] 51 | public void TestCity() 52 | { 53 | var loc = new Billing(city: "Pdx"); 54 | Assert.Equal("Pdx", loc.City); 55 | } 56 | 57 | [Fact] 58 | public void TestRegion() 59 | { 60 | var loc = new Billing(region: "MN"); 61 | Assert.Equal("MN", loc.Region); 62 | } 63 | 64 | [Fact] 65 | public void TestCountry() 66 | { 67 | var loc = new Billing(country: "US"); 68 | Assert.Equal("US", loc.Country); 69 | } 70 | 71 | [Fact] 72 | public void TestCountryThatIsTooLong() 73 | { 74 | Assert.Throws(() => new Billing(country: "USA")); 75 | } 76 | 77 | [Fact] 78 | public void TestCountryWithNumbers() 79 | { 80 | Assert.Throws(() => new Billing(country: "U1")); 81 | } 82 | 83 | [Fact] 84 | public void TestCountryInWrongCase() 85 | { 86 | Assert.Throws(() => new Billing(country: "us")); 87 | } 88 | 89 | [Fact] 90 | public void TestPostal() 91 | { 92 | var loc = new Billing(postal: "03231"); 93 | Assert.Equal("03231", loc.Postal); 94 | } 95 | 96 | [Fact] 97 | public void TestPhoneNumber() 98 | { 99 | var phone = "321-321-3213"; 100 | var loc = new Billing(phoneNumber: phone); 101 | Assert.Equal(phone, loc.PhoneNumber); 102 | } 103 | 104 | [Fact] 105 | public void TestPhoneCountryCode() 106 | { 107 | var loc = new Billing(phoneCountryCode: "1"); 108 | Assert.Equal("1", loc.PhoneCountryCode); 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Request/CreditCardTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Request; 2 | using System; 3 | using Xunit; 4 | 5 | namespace MaxMind.MinFraud.UnitTest.Request 6 | { 7 | public class CreditCardTest 8 | { 9 | [Fact] 10 | public void TestCountry() 11 | { 12 | var cc = new CreditCard(country: "US"); 13 | Assert.Equal("US", cc.Country); 14 | } 15 | 16 | [Fact] 17 | public void TestCountryInit() 18 | { 19 | var cc = new CreditCard 20 | { 21 | Country = "US" 22 | }; 23 | Assert.Equal("US", cc.Country); 24 | } 25 | 26 | [Fact] 27 | public void TestCountryThatIsTooLong() 28 | { 29 | Assert.Throws(() => new CreditCard(country: "USA")); 30 | } 31 | 32 | [Fact] 33 | public void TestCountryWithNumbers() 34 | { 35 | Assert.Throws(() => new CreditCard(country: "U1")); 36 | } 37 | 38 | [Fact] 39 | public void TestCountryInWrongCase() 40 | { 41 | Assert.Throws(() => new CreditCard(country: "us")); 42 | } 43 | 44 | [Fact] 45 | public void TestIssuerIdNumber() 46 | { 47 | var cc6 = new CreditCard(issuerIdNumber: "123456"); 48 | Assert.Equal("123456", cc6.IssuerIdNumber); 49 | 50 | var cc8 = new CreditCard(issuerIdNumber: "12345678"); 51 | Assert.Equal("12345678", cc8.IssuerIdNumber); 52 | } 53 | 54 | [Fact] 55 | public void TestIssuerIdNumberThatIsInvalidLength() 56 | { 57 | Assert.Throws(() => new CreditCard(issuerIdNumber: "1234567")); 58 | } 59 | 60 | [Fact] 61 | public void TestIssuerIdNumberThatIsTooLong() 62 | { 63 | Assert.Throws(() => new CreditCard(issuerIdNumber: "123456789")); 64 | } 65 | 66 | [Fact] 67 | public void TestIssuerIdNumberThatIsTooShort() 68 | { 69 | Assert.Throws(() => new CreditCard(issuerIdNumber: "12345")); 70 | } 71 | 72 | [Fact] 73 | public void TestIssuerIdNumberThatHasLetters() 74 | { 75 | Assert.Throws(() => new CreditCard(issuerIdNumber: "12345a")); 76 | } 77 | 78 | [Fact] 79 | public void TestLastDigits() 80 | { 81 | var cc2 = new CreditCard(lastDigits: "12"); 82 | Assert.Equal("12", cc2.LastDigits); 83 | 84 | var cc4 = new CreditCard(lastDigits: "1234"); 85 | Assert.Equal("1234", cc4.LastDigits); 86 | } 87 | 88 | [Fact] 89 | public void TestLastDigitsThatIsTooLong() 90 | { 91 | Assert.Throws(() => new CreditCard(lastDigits: "12345")); 92 | } 93 | 94 | [Fact] 95 | public void TestLastDigitsThatIsTooShort() 96 | { 97 | Assert.Throws(() => new CreditCard(lastDigits: "1")); 98 | } 99 | 100 | [Fact] 101 | public void TestLastDigitsThatHasLetters() 102 | { 103 | Assert.Throws(() => new CreditCard(lastDigits: "123a")); 104 | } 105 | 106 | [Fact] 107 | public void TestBankName() 108 | { 109 | var cc = new CreditCard(bankName: "Bank"); 110 | Assert.Equal("Bank", cc.BankName); 111 | } 112 | 113 | [Fact] 114 | public void TestBankPhoneCountryCode() 115 | { 116 | var cc = new CreditCard(bankPhoneCountryCode: "1"); 117 | Assert.Equal("1", cc.BankPhoneCountryCode); 118 | } 119 | 120 | [Fact] 121 | public void TestBankPhoneNumber() 122 | { 123 | var phone = "231-323-3123"; 124 | var cc = new CreditCard(bankPhoneNumber: phone); 125 | Assert.Equal(phone, cc.BankPhoneNumber); 126 | } 127 | 128 | [Fact] 129 | public void TestAvsResult() 130 | { 131 | var cc = new CreditCard(avsResult: 'Y'); 132 | Assert.Equal('Y', cc.AvsResult); 133 | } 134 | 135 | [Fact] 136 | public void TestCvvResult() 137 | { 138 | var cc = new CreditCard(cvvResult: 'N'); 139 | Assert.Equal('N', cc.CvvResult); 140 | } 141 | 142 | [Theory] 143 | [InlineData("4485921507912924")] 144 | [InlineData("432312")] 145 | [InlineData("this is invalid")] 146 | [InlineData("")] 147 | [InlineData( 148 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")] 149 | public void TestInvalidToken(string token) 150 | { 151 | Assert.Throws(() => new CreditCard(token: token)); 152 | } 153 | 154 | [Theory] 155 | [InlineData("t4485921507912924")] 156 | [InlineData("a7f6%gf83fhAu")] 157 | [InlineData("valid_token")] 158 | public void TestValidToken(string token) 159 | { 160 | var cc = new CreditCard(token: token); 161 | Assert.Equal(token, cc.Token); 162 | } 163 | 164 | [Fact] 165 | public void TestWas3DSecureSuccessful() 166 | { 167 | var cc = new CreditCard(was3DSecureSuccessful: true); 168 | Assert.True(cc.Was3DSecureSuccessful); 169 | } 170 | } 171 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Request/CustomInputsTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Request; 2 | using System; 3 | using System.Text.Json; 4 | using Xunit; 5 | 6 | namespace MaxMind.MinFraud.UnitTest.Request 7 | { 8 | public class CustomInputsTest 9 | { 10 | [Fact] 11 | public void TestJson() 12 | { 13 | var inputs = new CustomInputs.Builder 14 | { 15 | {"string_input_1", "test string"}, 16 | {"int_input", 19}, 17 | {"long_input", 12L}, 18 | {"float_input", 3.2f}, 19 | {"double_input", 32.123d}, 20 | {"bool_input", true} 21 | }.Build(); 22 | 23 | var json = JsonSerializer.Serialize(inputs); 24 | var comparer = new JsonElementComparer(); 25 | Assert.True(comparer.JsonEquals( 26 | JsonDocument.Parse( 27 | """ 28 | { 29 | "string_input_1": "test string", 30 | "int_input": 19, 31 | "long_input": 12, 32 | "float_input": 3.20000005, 33 | "double_input": 32.122999999999998, 34 | "bool_input": true 35 | } 36 | """), 37 | JsonDocument.Parse(json) 38 | ), 39 | json 40 | ); 41 | } 42 | 43 | [Fact] 44 | public void TestStringThatIsTooLong() 45 | { 46 | Assert.Throws(() => new CustomInputs.Builder 47 | { 48 | {"string_input_1", new string('x', 256)} 49 | }.Build()); 50 | } 51 | 52 | [Fact] 53 | public void TestStringWithNewLine() 54 | { 55 | Assert.Throws(() => new CustomInputs.Builder 56 | { 57 | {"string", "test\n"} 58 | }.Build()); 59 | } 60 | 61 | [Fact] 62 | public void TestInvalidKey() 63 | { 64 | Assert.Throws(() => new CustomInputs.Builder 65 | { 66 | {"InvalidKey", "test"} 67 | }.Build()); 68 | } 69 | 70 | [Fact] 71 | public void TestInvalidLong() 72 | { 73 | Assert.Throws(() => new CustomInputs.Builder 74 | { 75 | {"invalid_long", (long) 1e13} 76 | }); 77 | } 78 | 79 | [Fact] 80 | public void TestInvalidFloat() 81 | { 82 | Assert.Throws(() => new CustomInputs.Builder 83 | { 84 | {"invalid_float", (float) 1e13} 85 | }.Build()); 86 | } 87 | 88 | [Fact] 89 | public void TestInvalidDouble() 90 | { 91 | Assert.Throws(() => new CustomInputs.Builder 92 | { 93 | {"invalid_double", (double) -1e13} 94 | }.Build()); 95 | } 96 | 97 | [Fact] 98 | public void TestBuilderCannotBeReused() 99 | { 100 | var builder = new CustomInputs.Builder 101 | { 102 | {"string", "test"} 103 | }; 104 | 105 | builder.Build(); 106 | 107 | Assert.Throws(() => builder.Add("nope", true)); 108 | Assert.Throws(() => builder.Build()); 109 | Assert.Throws(() => builder.GetEnumerator()); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Request/DeviceTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Request; 2 | using System; 3 | using System.Net; 4 | using Xunit; 5 | 6 | namespace MaxMind.MinFraud.UnitTest.Request 7 | { 8 | public class DeviceTest 9 | { 10 | [Fact] 11 | public void TestIPAddress() 12 | { 13 | var ip = IPAddress.Parse("1.1.1.1"); 14 | var device = new Device(ipAddress: ip); 15 | Assert.Equal(ip, device.IPAddress); 16 | } 17 | 18 | [Fact] 19 | public void TestUserAgent() 20 | { 21 | var ua = "Mozila 5"; 22 | var device = new Device(userAgent: ua); 23 | Assert.Equal(ua, device.UserAgent); 24 | } 25 | 26 | [Fact] 27 | public void TestAcceptLanguage() 28 | { 29 | var al = "en-US"; 30 | var device = new Device(acceptLanguage: al); 31 | Assert.Equal(al, device.AcceptLanguage); 32 | } 33 | 34 | [Fact] 35 | public void TestSessionAge() 36 | { 37 | var device = new Device(sessionAge: 3600); 38 | Assert.Equal(3600, device.SessionAge); 39 | } 40 | 41 | [Fact] 42 | public void TestSessionAgeIsNegative() 43 | { 44 | Assert.Throws(() => new Device(sessionAge: -1)); 45 | } 46 | 47 | [Fact] 48 | public void TestSessionId() 49 | { 50 | var device = new Device(sessionId: "foo"); 51 | Assert.Equal("foo", device.SessionId); 52 | } 53 | 54 | [Fact] 55 | public void TestSessionIdIsTooLong() 56 | { 57 | Assert.Throws(() => new Device(sessionId: new string('x', 256))); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Request/EventTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Request; 2 | using System; 3 | using System.Text.Json; 4 | using Xunit; 5 | 6 | namespace MaxMind.MinFraud.UnitTest.Request 7 | { 8 | public class EventTest 9 | { 10 | [Fact] 11 | public void TestTransactionId() 12 | { 13 | var eventReq = new Event(transactionId: "t12"); 14 | Assert.Equal("t12", eventReq.TransactionId); 15 | } 16 | 17 | [Fact] 18 | public void TestShopId() 19 | { 20 | var eventReq = new Event(shopId: "s12"); 21 | Assert.Equal("s12", eventReq.ShopId); 22 | } 23 | 24 | [Fact] 25 | public void TestTime() 26 | { 27 | var date = new DateTimeOffset(); 28 | var eventReq = new Event(time: date); 29 | Assert.Equal(date, eventReq.Time); 30 | } 31 | 32 | [Fact] 33 | public void TestType() 34 | { 35 | var eventReq = new Event(type: EventType.AccountCreation); 36 | Assert.Equal(EventType.AccountCreation, eventReq.Type); 37 | } 38 | 39 | [Fact] 40 | public void TestSerialization() 41 | { 42 | var eventReq = new Event( 43 | transactionId: "txn123", 44 | shopId: "shop123", 45 | time: new DateTimeOffset(2020, 7, 12, 46 | 15, 30, 0, 0, 47 | new TimeSpan(2, 0, 0)), 48 | type: EventType.AccountCreation 49 | ); 50 | 51 | var json = JsonSerializer.Serialize(eventReq); 52 | var comparer = new JsonElementComparer(); 53 | Assert.True( 54 | comparer.JsonEquals( 55 | JsonDocument.Parse( 56 | """ 57 | { 58 | "transaction_id": "txn123", 59 | "shop_id": "shop123", 60 | "time": "2020-07-12T15:30:00+02:00", 61 | "type": "account_creation" 62 | } 63 | """), 64 | JsonDocument.Parse(json) 65 | ), 66 | json 67 | ); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Request/MinFraudRequestTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Request; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using Xunit; 5 | 6 | namespace MaxMind.MinFraud.UnitTest.Request 7 | { 8 | public class MinFraudRequestTest 9 | { 10 | [Fact] 11 | public void TestAccount() 12 | { 13 | var request = new Transaction(account: new Account(userId: "1")); 14 | Assert.Equal("1", request.Account!.UserId); 15 | } 16 | 17 | [Fact] 18 | public void TestBilling() 19 | { 20 | var request = new Transaction(billing: new Billing(address: "add")); 21 | Assert.Equal("add", request.Billing!.Address); 22 | } 23 | 24 | [Fact] 25 | public void TestCreditCard() 26 | { 27 | var request = new Transaction(creditCard: new CreditCard(bankName: "name")); 28 | Assert.Equal("name", request.CreditCard!.BankName); 29 | } 30 | 31 | [Fact] 32 | public void TestDevice() 33 | { 34 | var ip = IPAddress.Parse("1.1.1.1"); 35 | var request = new Transaction(device: new Device(ip)); 36 | Assert.Equal(ip, request.Device?.IPAddress); 37 | } 38 | 39 | [Fact] 40 | public void TestEmail() 41 | { 42 | var request = new Transaction(email: new Email(domain: "test.com")); 43 | Assert.Equal("test.com", request.Email!.Domain); 44 | } 45 | 46 | [Fact] 47 | public void TestEvent() 48 | { 49 | var request = new Transaction(userEvent: new Event(shopId: "1")); 50 | Assert.Equal("1", request.Event!.ShopId); 51 | } 52 | 53 | [Fact] 54 | public void TestOrder() 55 | { 56 | var request = new Transaction(order: new Order(affiliateId: "af1")); 57 | Assert.Equal("af1", request.Order!.AffiliateId); 58 | } 59 | 60 | [Fact] 61 | public void TestPayment() 62 | { 63 | var request = new Transaction(payment: new Payment(declineCode: "d")); 64 | Assert.Equal("d", request.Payment!.DeclineCode); 65 | } 66 | 67 | [Fact] 68 | public void TestShipping() 69 | { 70 | var request = new Transaction(shipping: new Shipping(lastName: "l")); 71 | Assert.Equal("l", request.Shipping!.LastName); 72 | } 73 | 74 | [Fact] 75 | public void TestShoppingCart() 76 | { 77 | var request = new Transaction( 78 | shoppingCart: new List { new ShoppingCartItem(itemId: "1") }); 79 | Assert.Equal("1", request.ShoppingCart![0].ItemId); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Request/OrderTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Request; 2 | using System; 3 | using Xunit; 4 | 5 | namespace MaxMind.MinFraud.UnitTest.Request 6 | { 7 | public class OrderTest 8 | { 9 | [Fact] 10 | public void TestAmount() 11 | { 12 | var order = new Order(amount: 1.1m); 13 | Assert.Equal(1.1m, order.Amount); 14 | } 15 | 16 | [Fact] 17 | public void TestCurrency() 18 | { 19 | var order = new Order(currency: "USD"); 20 | Assert.Equal("USD", order.Currency); 21 | } 22 | 23 | [Fact] 24 | public void TestCurrencyWithDigits() 25 | { 26 | Assert.Throws(() => new Order(currency: "US1")); 27 | } 28 | 29 | [Fact] 30 | public void TestCurrencyThatIsTooShort() 31 | { 32 | Assert.Throws(() => new Order(currency: "US")); 33 | } 34 | 35 | [Fact] 36 | public void TestCurrencyThatIsTooLong() 37 | { 38 | Assert.Throws(() => new Order(currency: "USDE")); 39 | } 40 | 41 | [Fact] 42 | public void TestCurrencyInWrongCase() 43 | { 44 | Assert.Throws(() => new Order(currency: "usd")); 45 | } 46 | 47 | [Fact] 48 | public void TestDiscountCode() 49 | { 50 | var order = new Order(discountCode: "dsc"); 51 | Assert.Equal("dsc", order.DiscountCode); 52 | } 53 | 54 | [Fact] 55 | public void TestAffiliateId() 56 | { 57 | var order = new Order(affiliateId: "af"); 58 | Assert.Equal("af", order.AffiliateId); 59 | } 60 | 61 | [Fact] 62 | public void TestSubaffiliateId() 63 | { 64 | var order = new Order(subaffiliateId: "saf"); 65 | Assert.Equal("saf", order.SubaffiliateId); 66 | } 67 | 68 | [Fact] 69 | public void TestReferrerUri() 70 | { 71 | var uri = new Uri("http://www.mm.com/"); 72 | var order = new Order(referrerUri: uri); 73 | Assert.Equal(uri, order.ReferrerUri); 74 | } 75 | 76 | [Fact] 77 | public void TestIsGift() 78 | { 79 | var order = new Order(isGift: true); 80 | Assert.True(order.IsGift); 81 | } 82 | 83 | [Fact] 84 | public void TestHasGiftMessage() 85 | { 86 | var order = new Order(hasGiftMessage: true); 87 | Assert.True(order.HasGiftMessage); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Request/PaymentTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Request; 2 | using Xunit; 3 | 4 | namespace MaxMind.MinFraud.UnitTest.Request 5 | { 6 | public class PaymentTest 7 | { 8 | [Fact] 9 | public void TestProcessor() 10 | { 11 | var payment = new Payment(processor: PaymentProcessor.Adyen); 12 | Assert.Equal(PaymentProcessor.Adyen, payment.Processor); 13 | } 14 | 15 | [Fact] 16 | public void TestWasAuthorized() 17 | { 18 | var payment = new Payment(wasAuthorized: true); 19 | Assert.True(payment.WasAuthorized); 20 | } 21 | 22 | [Fact] 23 | public void TestDeclineCode() 24 | { 25 | var payment = new Payment(declineCode: "declined"); 26 | Assert.Equal("declined", payment.DeclineCode); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Request/ShippingTest.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using MaxMind.MinFraud.Request; 4 | using System; 5 | using Xunit; 6 | 7 | #endregion 8 | 9 | namespace MaxMind.MinFraud.UnitTest.Request 10 | { 11 | public class ShippingTest 12 | { 13 | // Same as code in BillingTest 14 | [Fact] 15 | public void TestFirstName() 16 | { 17 | var loc = new Shipping( 18 | firstName: "frst" 19 | ); 20 | Assert.Equal("frst", loc.FirstName); 21 | } 22 | 23 | [Fact] 24 | public void TestLastName() 25 | { 26 | var loc = new Shipping(lastName: "last"); 27 | Assert.Equal("last", loc.LastName); 28 | } 29 | 30 | [Fact] 31 | public void TestCompany() 32 | { 33 | var loc = new Shipping(company: "company"); 34 | Assert.Equal("company", loc.Company); 35 | } 36 | 37 | [Fact] 38 | public void TestAddress() 39 | { 40 | var loc = new Shipping(address: "addr"); 41 | Assert.Equal("addr", loc.Address); 42 | } 43 | 44 | [Fact] 45 | public void TestAddress2() 46 | { 47 | var loc = new Shipping(address2: "addr2"); 48 | Assert.Equal("addr2", loc.Address2); 49 | } 50 | 51 | [Fact] 52 | public void TestCity() 53 | { 54 | var loc = new Shipping(city: "Pdx"); 55 | Assert.Equal("Pdx", loc.City); 56 | } 57 | 58 | [Fact] 59 | public void TestRegion() 60 | { 61 | var loc = new Shipping(region: "MN"); 62 | Assert.Equal("MN", loc.Region); 63 | } 64 | 65 | [Fact] 66 | public void TestCountry() 67 | { 68 | var loc = new Shipping(country: "US"); 69 | Assert.Equal("US", loc.Country); 70 | } 71 | 72 | [Fact] 73 | public void TestCountryThatIsTooLong() 74 | { 75 | Assert.Throws(() => new Shipping(country: "USA")); 76 | } 77 | 78 | [Fact] 79 | public void TestCountryWithNumbers() 80 | { 81 | Assert.Throws(() => new Shipping(country: "U1")); 82 | } 83 | 84 | [Fact] 85 | public void TestCountryInWrongCase() 86 | { 87 | Assert.Throws(() => new Shipping(country: "us")); 88 | } 89 | 90 | [Fact] 91 | public void TestPostal() 92 | { 93 | var loc = new Shipping(postal: "03231"); 94 | Assert.Equal("03231", loc.Postal); 95 | } 96 | 97 | [Fact] 98 | public void TestPhoneNumber() 99 | { 100 | var phone = "321-321-3213"; 101 | var loc = new Shipping(phoneNumber: phone); 102 | Assert.Equal(phone, loc.PhoneNumber); 103 | } 104 | 105 | [Fact] 106 | public void TestPhoneCountryCode() 107 | { 108 | var loc = new Shipping(phoneCountryCode: "1"); 109 | Assert.Equal("1", loc.PhoneCountryCode); 110 | } 111 | 112 | // End shared code 113 | 114 | [Fact] 115 | public void TestDeliverySpeed() 116 | { 117 | var loc = new Shipping(deliverySpeed: ShippingDeliverySpeed.Expedited); 118 | Assert.Equal(ShippingDeliverySpeed.Expedited, loc.DeliverySpeed); 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Request/ShoppingCartItemTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Request; 2 | using Xunit; 3 | 4 | namespace MaxMind.MinFraud.UnitTest.Request 5 | { 6 | public class ShoppingCartItemTest 7 | { 8 | [Fact] 9 | public void TestCategory() 10 | { 11 | var item = new ShoppingCartItem(category: "cat1"); 12 | Assert.Equal("cat1", item.Category); 13 | } 14 | 15 | [Fact] 16 | public void TestItemId() 17 | { 18 | var item = new ShoppingCartItem(itemId: "id5"); 19 | Assert.Equal("id5", item.ItemId); 20 | } 21 | 22 | [Fact] 23 | public void TestQuantity() 24 | { 25 | var item = new ShoppingCartItem(quantity: 100); 26 | Assert.Equal(100, item.Quantity); 27 | } 28 | 29 | [Fact] 30 | public void TestPrice() 31 | { 32 | var item = new ShoppingCartItem(price: 10m); 33 | Assert.Equal(10m, item.Price); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Request/TransactionReportTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Request; 2 | using System; 3 | using System.Net; 4 | using Xunit; 5 | 6 | namespace MaxMind.MinFraud.UnitTest.Request 7 | { 8 | public class TransactionReportTest 9 | { 10 | private IPAddress IP { get; } = IPAddress.Parse("1.1.1.1"); 11 | 12 | [Fact] 13 | public void TestRequired() 14 | { 15 | var maxmindId = "12345678"; 16 | var minfraudId = Guid.NewGuid(); 17 | var tag = TransactionReportTag.NotFraud; 18 | var transactionId = "txn123"; 19 | 20 | TransactionReport report; 21 | 22 | // ipAddress supplied as identifier 23 | Assert.Throws(() => new TransactionReport(tag: tag, ipAddress: null)); 24 | report = new TransactionReport(tag: tag, ipAddress: IP); 25 | Assert.Equal(TransactionReportTag.NotFraud, report.Tag); 26 | Assert.Equal(IP, report.IPAddress); 27 | 28 | // maxmindId supplied as identifier 29 | Assert.Throws(() => new TransactionReport(tag: tag, maxmindId: "")); 30 | report = new TransactionReport(tag: tag, ipAddress: null, maxmindId: maxmindId); 31 | Assert.Equal(TransactionReportTag.NotFraud, report.Tag); 32 | Assert.Null(report.IPAddress); 33 | Assert.Equal(maxmindId, report.MaxMindId); 34 | 35 | // minfraudId supplied as identifier 36 | Assert.Throws(() => new TransactionReport(tag: tag, minfraudId: Guid.Empty)); 37 | report = new TransactionReport(tag: tag, ipAddress: null, minfraudId: minfraudId); 38 | Assert.Equal(TransactionReportTag.NotFraud, report.Tag); 39 | Assert.Null(report.IPAddress); 40 | Assert.Equal(minfraudId, report.MinFraudId); 41 | 42 | // tranactionId supplied as identifier 43 | Assert.Throws(() => new TransactionReport(tag: tag, transactionId: "")); 44 | report = new TransactionReport(tag: tag, ipAddress: null, transactionId: transactionId); 45 | Assert.Equal(TransactionReportTag.NotFraud, report.Tag); 46 | Assert.Null(report.IPAddress); 47 | Assert.Equal(transactionId, report.TransactionId); 48 | } 49 | 50 | [Fact] 51 | public void TestAll() 52 | { 53 | var chargebackCode = "4853"; 54 | var maxmindId = "abcd1234"; 55 | var minfraudId = Guid.NewGuid(); 56 | var notes = "This was an account takeover."; 57 | var transactionId = "txn123"; 58 | 59 | var report = new TransactionReport( 60 | ipAddress: IP, 61 | tag: TransactionReportTag.Chargeback, 62 | chargebackCode: chargebackCode, 63 | maxmindId: maxmindId, 64 | minfraudId: minfraudId, 65 | notes: notes, 66 | transactionId: transactionId); 67 | 68 | Assert.Equal(IP, report.IPAddress); 69 | Assert.Equal(TransactionReportTag.Chargeback, report.Tag); 70 | Assert.Equal(maxmindId, report.MaxMindId); 71 | Assert.Equal(minfraudId, report.MinFraudId); 72 | Assert.Equal(notes, report.Notes); 73 | Assert.Equal(transactionId, report.TransactionId); 74 | } 75 | 76 | [Theory] 77 | [InlineData("abcd123")] 78 | [InlineData("")] 79 | [InlineData("abcd12345")] 80 | public void TestMaxMindIdIsInvalid(string? maxmindId) 81 | { 82 | Assert.Throws(() => new TransactionReport( 83 | tag: TransactionReportTag.SpamOrAbuse, maxmindId: maxmindId)); 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Response/AddressTestHelper.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Response; 2 | using Xunit; 3 | 4 | namespace MaxMind.MinFraud.UnitTest.Response 5 | { 6 | internal static class AddressTestHelper 7 | { 8 | internal static void TestAddress(Address address) 9 | { 10 | Assert.True(address.IsInIPCountry); 11 | Assert.True(address.IsPostalInCity); 12 | Assert.Equal(100, address.DistanceToIPLocation); 13 | Assert.Equal(32.1, address.Longitude); 14 | Assert.Equal(43.1, address.Latitude); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Response/BillingAddressTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Response; 2 | using System.Text.Json; 3 | using Xunit; 4 | using static MaxMind.MinFraud.UnitTest.Response.AddressTestHelper; 5 | 6 | namespace MaxMind.MinFraud.UnitTest.Response 7 | { 8 | public class BillingAddressTest 9 | { 10 | [Fact] 11 | public void TestBillingAddress() 12 | { 13 | var address = JsonSerializer.Deserialize( 14 | """ 15 | { 16 | "is_in_ip_country": true, 17 | "latitude": 43.1, 18 | "longitude": 32.1, 19 | "distance_to_ip_location": 100, 20 | "is_postal_in_city": true 21 | } 22 | """); 23 | 24 | TestAddress(address!); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Response/CreditCardTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Response; 2 | using System.Text.Json; 3 | using Xunit; 4 | 5 | namespace MaxMind.MinFraud.UnitTest.Response 6 | { 7 | public class CreditCardTest 8 | { 9 | [Fact] 10 | public void TestCreditCard() 11 | { 12 | var cc = JsonSerializer.Deserialize( 13 | """ 14 | { 15 | "issuer": {"name": "Bank"}, 16 | "brand": "Visa", 17 | "country": "US", 18 | "is_business": true, 19 | "is_issued_in_billing_address_country": true, 20 | "is_prepaid": true, 21 | "is_virtual": true, 22 | "type": "credit" 23 | } 24 | """)!; 25 | 26 | Assert.Equal("Bank", cc.Issuer.Name); 27 | Assert.Equal("US", cc.Country); 28 | Assert.True(cc.IsBusiness); 29 | Assert.True(cc.IsPrepaid); 30 | Assert.True(cc.IsIssuedInBillingAddressCountry); 31 | Assert.True(cc.IsVirtual); 32 | Assert.Equal("Visa", cc.Brand); 33 | Assert.Equal("credit", cc.Type); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Response/DeviceTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json; 3 | using Xunit; 4 | using Device = MaxMind.MinFraud.Response.Device; 5 | 6 | namespace MaxMind.MinFraud.UnitTest.Response 7 | { 8 | public class DeviceTest 9 | { 10 | [Fact] 11 | public void TestDevice() 12 | { 13 | var id = "35e5e22c-8bf2-44f8-aa99-716ec7530281"; 14 | var lastSeen = "2016-06-08T14:16:38+00:00"; 15 | var localTime = "2016-06-10T14:19:10-08:00"; 16 | var device = JsonSerializer.Deserialize( 17 | $$""" 18 | { 19 | "confidence": 99, 20 | "id": "{{id}}", 21 | "last_seen": "{{lastSeen}}", 22 | "local_time": "{{localTime}}" 23 | } 24 | """)!; 25 | 26 | Assert.Equal(99, device.Confidence); 27 | Assert.Equal(new Guid(id), device.Id); 28 | Assert.Equal(lastSeen, device.LastSeen?.ToString("yyyy-MM-ddTHH:mm:ssK")); 29 | Assert.Equal(localTime, device.LocalTime?.ToString("yyyy-MM-ddTHH:mm:ssK")); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Response/DispositionTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Response; 2 | using System.Text.Json; 3 | using Xunit; 4 | 5 | namespace MaxMind.MinFraud.UnitTest.Response 6 | { 7 | public class DispositionTest 8 | { 9 | [Fact] 10 | public void TestDisposition() 11 | { 12 | var disposition = JsonSerializer.Deserialize( 13 | """ 14 | { 15 | "action": "manual_review", 16 | "reason": "custom_rule", 17 | "rule_label": "the rule's label" 18 | } 19 | """)!; 20 | 21 | Assert.Equal("manual_review", disposition.Action); 22 | Assert.Equal("custom_rule", disposition.Reason); 23 | Assert.Equal("the rule's label", disposition.RuleLabel); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Response/EmailDomainTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Response; 2 | using System.Text.Json; 3 | using Xunit; 4 | 5 | namespace MaxMind.MinFraud.UnitTest.Response 6 | { 7 | public class EmailDomainTest 8 | { 9 | [Fact] 10 | public void TestEmailDomain() 11 | { 12 | var domain = JsonSerializer.Deserialize( 13 | """{"first_seen": "2017-01-02"}""")!; 14 | 15 | Assert.Equal("2017-01-02", domain.FirstSeen?.ToString("yyyy-MM-dd")); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Response/EmailTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Response; 2 | using System.Text.Json; 3 | using Xunit; 4 | 5 | namespace MaxMind.MinFraud.UnitTest.Response 6 | { 7 | public class EmailTest 8 | { 9 | [Fact] 10 | public void TestEmail() 11 | { 12 | var email = JsonSerializer.Deserialize( 13 | """ 14 | { 15 | "domain": { "first_seen": "2014-02-03" }, 16 | "first_seen": "2017-01-02", 17 | "is_disposable": true, 18 | "is_free": true, 19 | "is_high_risk": true 20 | } 21 | """)!; 22 | 23 | Assert.Equal("2014-02-03", email.Domain.FirstSeen?.ToString("yyyy-MM-dd")); 24 | Assert.Equal("2017-01-02", email.FirstSeen?.ToString("yyyy-MM-dd")); 25 | Assert.True(email.IsDisposable); 26 | Assert.True(email.IsFree); 27 | Assert.True(email.IsHighRisk); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Response/GeoIP2CountryTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.GeoIP2.Model; 2 | using System.Text.Json; 3 | using Xunit; 4 | 5 | namespace MaxMind.MinFraud.UnitTest.Response 6 | { 7 | public class GeoIP2CountryTest 8 | { 9 | [Fact] 10 | public void TestIsHighRisk() 11 | { 12 | var country = JsonSerializer.Deserialize( 13 | """ 14 | { 15 | "is_high_risk": true, 16 | "is_in_european_union": true 17 | } 18 | """)!; 19 | 20 | Assert.True(country.IsInEuropeanUnion); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Response/GeoIP2LocationTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Response; 2 | using System.Text.Json; 3 | using Xunit; 4 | 5 | namespace MaxMind.MinFraud.UnitTest.Response 6 | { 7 | public class GeoIP2LocationTest 8 | { 9 | [Fact] 10 | public void TestGetLocalTime() 11 | { 12 | var time = "2015-04-19T12:59:23-01:00"; 13 | var location = JsonSerializer.Deserialize( 14 | $$"""{"local_time": "{{time}}" }""")!; 15 | 16 | Assert.Equal(time, location.LocalTime?.ToString("yyyy-MM-ddTHH:mm:ssK")); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Response/IPAddressTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Response; 2 | using System.Text.Json; 3 | using Xunit; 4 | 5 | namespace MaxMind.MinFraud.UnitTest.Response 6 | { 7 | public class IPAddressTest 8 | { 9 | [Fact] 10 | public void TestIPAddress() 11 | { 12 | var time = "2015-04-19T12:59:23-01:00"; 13 | 14 | var address = JsonSerializer.Deserialize( 15 | $$""" 16 | { 17 | "risk": 99, 18 | "risk_reasons": [ 19 | { 20 | "code": "ANONYMOUS_IP", 21 | "reason": "The IP address belongs to an anonymous network. See /ip_address/traits for more details." 22 | }, 23 | { 24 | "code": "MINFRAUD_NETWORK_ACTIVITY", 25 | "reason": "Suspicious activity has been seen on this IP address across minFraud customers." 26 | } 27 | ], 28 | "country": {"is_high_risk": true}, 29 | "location": {"local_time": "{{time}}"}, 30 | "traits": { 31 | "is_anonymous": true, 32 | "is_anonymous_vpn": true, 33 | "is_hosting_provider": true, 34 | "is_public_proxy": true, 35 | "is_residential_proxy": true, 36 | "is_tor_exit_node": true, 37 | "mobile_country_code" : "310", 38 | "mobile_network_code" : "004" 39 | } 40 | } 41 | """)!; 42 | 43 | Assert.Equal(99, address.Risk); 44 | Assert.Equal("ANONYMOUS_IP", address.RiskReasons[0].Code); 45 | Assert.Equal( 46 | "The IP address belongs to an anonymous network. See /ip_address/traits for more details.", 47 | address.RiskReasons[0].Reason 48 | ); 49 | Assert.Equal("MINFRAUD_NETWORK_ACTIVITY", address.RiskReasons[1].Code); 50 | Assert.Equal( 51 | "Suspicious activity has been seen on this IP address across minFraud customers.", 52 | address.RiskReasons[1].Reason 53 | ); 54 | Assert.Equal(time, address.Location.LocalTime?.ToString("yyyy-MM-ddTHH:mm:ssK")); 55 | Assert.True(address.Traits.IsAnonymous); 56 | Assert.True(address.Traits.IsAnonymousVpn); 57 | Assert.True(address.Traits.IsHostingProvider); 58 | Assert.True(address.Traits.IsPublicProxy); 59 | Assert.True(address.Traits.IsResidentialProxy); 60 | Assert.True(address.Traits.IsTorExitNode); 61 | Assert.Equal("310", address.Traits.MobileCountryCode); 62 | Assert.Equal("004", address.Traits.MobileNetworkCode); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Response/InsightsTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Response; 2 | using System.Text.Json; 3 | using Xunit; 4 | 5 | namespace MaxMind.MinFraud.UnitTest.Response 6 | { 7 | public class InsightsTest 8 | { 9 | [Fact] 10 | public void TestInsights() 11 | { 12 | var id = "b643d445-18b2-4b9d-bad4-c9c4366e402a"; 13 | var insights = JsonSerializer.Deserialize( 14 | $$$""" 15 | { 16 | "id": "{{{id}}}", 17 | "ip_address": {"country": {"iso_code": "US"}}, 18 | "credit_card": {"is_business": true, "is_prepaid": true}, 19 | "device": {"id": "{{{id}}}"}, 20 | "disposition": {"action": "accept"}, 21 | "email": 22 | { 23 | "domain": { "first_seen": "2014-02-03"}, 24 | "is_free": true 25 | }, 26 | "shipping_address": {"is_in_ip_country": true}, 27 | "shipping_phone": {"is_voip": true}, 28 | "billing_address": {"is_in_ip_country": true}, 29 | "billing_phone": {"is_voip": false}, 30 | "funds_remaining": 1.20, 31 | "queries_remaining": 123, 32 | "risk_score": 0.01, 33 | "warnings": [{"code": "INVALID_INPUT"}] 34 | } 35 | """)!; 36 | 37 | Assert.Equal("2014-02-03", insights.Email.Domain.FirstSeen?.ToString("yyyy-MM-dd")); 38 | Assert.Equal("US", insights.IPAddress.Country.IsoCode); 39 | Assert.True(insights.CreditCard.IsBusiness); 40 | Assert.True(insights.CreditCard.IsPrepaid); 41 | Assert.Equal(id, insights.Device.Id.ToString()); 42 | Assert.Equal("accept", insights.Disposition.Action); 43 | Assert.True(insights.Email.IsFree); 44 | Assert.True(insights.ShippingAddress.IsInIPCountry); 45 | Assert.True(insights.ShippingPhone.IsVoip); 46 | Assert.True(insights.BillingAddress.IsInIPCountry); 47 | Assert.False(insights.BillingPhone.IsVoip); 48 | Assert.Equal(id, insights.Id.ToString()); 49 | Assert.Equal(1.20m, insights.FundsRemaining); 50 | Assert.Equal(123, insights.QueriesRemaining); 51 | Assert.Equal(0.01, insights.RiskScore); 52 | Assert.Equal("INVALID_INPUT", insights.Warnings[0].Code); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Response/IssuerTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Response; 2 | using System.Text.Json; 3 | using Xunit; 4 | 5 | namespace MaxMind.MinFraud.UnitTest.Response 6 | { 7 | public class IssuerTest 8 | { 9 | [Fact] 10 | public void TestIssuer() 11 | { 12 | var phone = "132-342-2131"; 13 | 14 | var issuer = JsonSerializer.Deserialize( 15 | $$""" 16 | { 17 | "name": "Bank", 18 | "matches_provided_name": true, 19 | "phone_number": "{{phone}}", 20 | "matches_provided_phone_number": true 21 | } 22 | """)!; 23 | 24 | Assert.Equal("Bank", issuer.Name); 25 | Assert.True(issuer.MatchesProvidedName); 26 | Assert.Equal(phone, issuer.PhoneNumber); 27 | Assert.True(issuer.MatchesProvidedPhoneNumber); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Response/MultiplierReasonTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Response; 2 | using System.Text.Json; 3 | using Xunit; 4 | 5 | namespace MaxMind.MinFraud.UnitTest.Response 6 | { 7 | public class MultiplierReasonTest 8 | { 9 | [Fact] 10 | public void TestMultiplierReason() 11 | { 12 | var code = "ANONYMOUS_IP"; 13 | var msg = "Risk due to IP being an Anonymous IP"; 14 | 15 | var reason = JsonSerializer.Deserialize( 16 | $$""" 17 | { 18 | "code": "{{code}}", 19 | "reason": "{{msg}}" 20 | } 21 | """)!; 22 | 23 | Assert.Equal(code, reason.Code); 24 | Assert.Equal(msg, reason.Reason); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Response/PhoneTest.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using Xunit; 3 | using Phone = MaxMind.MinFraud.Response.Phone; 4 | 5 | namespace MaxMind.MinFraud.UnitTest.Response 6 | { 7 | public class PhoneTest 8 | { 9 | [Fact] 10 | public void TestPhone() 11 | { 12 | var country = "US"; 13 | var networkOperator = "Verizon"; 14 | var numberType = "mobile"; 15 | var phone = JsonSerializer.Deserialize( 16 | $$""" 17 | { 18 | "country": "{{country}}", 19 | "is_voip": true, 20 | "matches_postal": false, 21 | "network_operator": "{{networkOperator}}", 22 | "number_type": "{{numberType}}" 23 | } 24 | """)!; 25 | 26 | Assert.Equal(country, phone.Country); 27 | Assert.True(phone.IsVoip); 28 | Assert.False(phone.MatchesPostal); 29 | Assert.Equal(networkOperator, phone.NetworkOperator); 30 | Assert.Equal(numberType, phone.NumberType); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Response/RiskScoreReasonTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Response; 2 | using System.Text.Json; 3 | using Xunit; 4 | 5 | namespace MaxMind.MinFraud.UnitTest.Response 6 | { 7 | public class RiskScoreReasonTest 8 | { 9 | [Fact] 10 | public void TestRiskScoreReason() 11 | { 12 | var reason = JsonSerializer.Deserialize( 13 | $$""" 14 | { 15 | "multiplier": 45.0, 16 | "reasons": [ 17 | { 18 | "code": "ANONYMOUS_IP", 19 | "reason": "Risk due to IP being an Anonymous IP" 20 | } 21 | ] 22 | } 23 | """)!; 24 | 25 | Assert.Equal(45.0, reason.Multiplier); 26 | Assert.Equal("ANONYMOUS_IP", reason.Reasons[0].Code); 27 | Assert.Equal( 28 | "Risk due to IP being an Anonymous IP", 29 | reason.Reasons[0].Reason 30 | ); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Response/ScoreTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Response; 2 | using System.Text.Json; 3 | using Xunit; 4 | 5 | namespace MaxMind.MinFraud.UnitTest.Response 6 | { 7 | public class ScoreTest 8 | { 9 | [Fact] 10 | public void TestScore() 11 | { 12 | var id = "b643d445-18b2-4b9d-bad4-c9c4366e402a"; 13 | var score = JsonSerializer.Deserialize( 14 | $$""" 15 | { 16 | "id": "{{id}}", 17 | "funds_remaining": 1.20, 18 | "queries_remaining": 123, 19 | "disposition": {"action": "accept"}, 20 | "ip_address": {"risk": 0.01}, 21 | "risk_score": 0.01, 22 | "warnings": [{"code": "INVALID_INPUT"}] 23 | } 24 | """)!; 25 | 26 | Assert.Equal(id, score.Id.ToString()); 27 | Assert.Equal(1.20m, score.FundsRemaining); 28 | Assert.Equal(123, score.QueriesRemaining); 29 | Assert.Equal("accept", score.Disposition.Action); 30 | Assert.Equal(0.01, score.IPAddress.Risk); 31 | Assert.Equal(0.01, score.RiskScore); 32 | Assert.Equal("INVALID_INPUT", score.Warnings[0].Code); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Response/ShippingAddressTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Response; 2 | using System.Text.Json; 3 | using Xunit; 4 | using static MaxMind.MinFraud.UnitTest.Response.AddressTestHelper; 5 | 6 | namespace MaxMind.MinFraud.UnitTest.Response 7 | { 8 | public class ShippingAddressTest 9 | { 10 | [Fact] 11 | public void TestShippingAddress() 12 | { 13 | var address = JsonSerializer.Deserialize( 14 | """ 15 | { 16 | "is_in_ip_country": true, 17 | "latitude": 43.1, 18 | "longitude": 32.1, 19 | "distance_to_ip_location": 100, 20 | "is_postal_in_city": true, 21 | "is_high_risk": false, 22 | "distance_to_billing_address": 200 23 | } 24 | """)!; 25 | 26 | TestAddress(address); 27 | 28 | Assert.False(address.IsHighRisk); 29 | Assert.Equal(200, address.DistanceToBillingAddress); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Response/SubscoresTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Response; 2 | using System.Text.Json; 3 | using Xunit; 4 | 5 | namespace MaxMind.MinFraud.UnitTest.Response 6 | { 7 | #pragma warning disable 0618 8 | public class SubScoresTest 9 | { 10 | [Fact] 11 | public void TestSubscores() 12 | { 13 | var subscores = JsonSerializer.Deserialize( 14 | """ 15 | { 16 | "avs_result": 0.01, 17 | "billing_address": 0.02, 18 | "billing_address_distance_to_ip_location": 0.03, 19 | "browser": 0.04, 20 | "chargeback": 0.05, 21 | "country": 0.06, 22 | "country_mismatch": 0.07, 23 | "cvv_result": 0.08, 24 | "device": 0.09, 25 | "email_address": 0.10, 26 | "email_domain": 0.11, 27 | "email_local_part": 0.12, 28 | "email_tenure": 0.13, 29 | "ip_tenure": 0.14, 30 | "issuer_id_number": 0.15, 31 | "order_amount": 0.16, 32 | "phone_number": 0.17, 33 | "shipping_address": 0.18, 34 | "shipping_address_distance_to_ip_location": 0.19, 35 | "time_of_day": 0.20 36 | } 37 | """)!; 38 | 39 | Assert.Equal(0.01, subscores.AvsResult); 40 | Assert.Equal(0.02, subscores.BillingAddress); 41 | Assert.Equal(0.03, subscores.BillingAddressDistanceToIPLocation); 42 | Assert.Equal(0.04, subscores.Browser); 43 | Assert.Equal(0.05, subscores.Chargeback); 44 | Assert.Equal(0.06, subscores.Country); 45 | Assert.Equal(0.07, subscores.CountryMismatch); 46 | Assert.Equal(0.08, subscores.CvvResult); 47 | Assert.Equal(0.09, subscores.Device); 48 | Assert.Equal(0.10, subscores.EmailAddress); 49 | Assert.Equal(0.11, subscores.EmailDomain); 50 | Assert.Equal(0.12, subscores.EmailLocalPart); 51 | Assert.Equal(0.15, subscores.IssuerIdNumber); 52 | Assert.Equal(0.16, subscores.OrderAmount); 53 | Assert.Equal(0.17, subscores.PhoneNumber); 54 | Assert.Equal(0.18, subscores.ShippingAddress); 55 | Assert.Equal(0.19, subscores.ShippingAddressDistanceToIPLocation); 56 | Assert.Equal(0.20, subscores.TimeOfDay); 57 | } 58 | } 59 | #pragma warning restore 0618 60 | } 61 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/Response/WarningTest.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Response; 2 | using System.Text.Json; 3 | using Xunit; 4 | 5 | namespace MaxMind.MinFraud.UnitTest.Response 6 | { 7 | public class WarningTest 8 | { 9 | [Fact] 10 | public void TestWarning() 11 | { 12 | var code = "INVALID_INPUT"; 13 | var msg = "Input invalid"; 14 | 15 | var warning = JsonSerializer.Deserialize( 16 | $$""" 17 | { 18 | "code": "{{code}}", 19 | "warning": "{{msg}}", 20 | "input_pointer": "/first/second" 21 | } 22 | """)!; 23 | 24 | Assert.Equal(code, warning.Code); 25 | Assert.Equal(msg, warning.Message); 26 | Assert.Equal("/first/second", warning.InputPointer); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/TestData/factors-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "27d26476-e2bc-11e4-92b8-962e705b4af5", 3 | "risk_score": 0.01, 4 | "funds_remaining": 10.01, 5 | "queries_remaining": 1000, 6 | "disposition": { 7 | "action": "reject", 8 | "reason": "custom_rule", 9 | "rule_label": "The label" 10 | }, 11 | "ip_address": { 12 | "risk": 0.01, 13 | "risk_reasons": [ 14 | { 15 | "code": "ANONYMOUS_IP", 16 | "reason": "The IP address belongs to an anonymous network. See /ip_address/traits for more details." 17 | }, 18 | { 19 | "code": "MINFRAUD_NETWORK_ACTIVITY", 20 | "reason": "Suspicious activity has been seen on this IP address across minFraud customers." 21 | } 22 | ], 23 | "city": { 24 | "confidence": 42, 25 | "geoname_id": 2643743, 26 | "names": { 27 | "de": "London", 28 | "en": "London", 29 | "es": "Londres", 30 | "fr": "Londres", 31 | "ja": "ロンドン", 32 | "pt-BR": "Londres", 33 | "ru": "Лондон" 34 | } 35 | }, 36 | "continent": { 37 | "code": "EU", 38 | "geoname_id": 6255148, 39 | "names": { 40 | "de": "Europa", 41 | "en": "Europe", 42 | "es": "Europa", 43 | "fr": "Europe", 44 | "ja": "ヨーロッパ", 45 | "pt-BR": "Europa", 46 | "ru": "Европа", 47 | "zh-CN": "欧洲" 48 | } 49 | }, 50 | "country": { 51 | "confidence": 99, 52 | "geoname_id": 2635167, 53 | "is_in_european_union": true, 54 | "iso_code": "GB", 55 | "names": { 56 | "de": "Vereinigtes Königreich", 57 | "en": "United Kingdom", 58 | "es": "Reino Unido", 59 | "fr": "Royaume-Uni", 60 | "ja": "イギリス", 61 | "pt-BR": "Reino Unido", 62 | "ru": "Великобритания", 63 | "zh-CN": "英国" 64 | } 65 | }, 66 | "location": { 67 | "accuracy_radius": 96, 68 | "latitude": 51.5142, 69 | "local_time": "2012-04-13T00:20:50-01:00", 70 | "longitude": -0.0931, 71 | "time_zone": "Europe\/London" 72 | }, 73 | "maxmind": {}, 74 | "postal": { 75 | "code": "90001", 76 | "confidence": 10 77 | }, 78 | "registered_country": { 79 | "geoname_id": 6252001, 80 | "is_in_european_union": true, 81 | "iso_code": "US", 82 | "names": { 83 | "de": "USA", 84 | "en": "United States", 85 | "es": "Estados Unidos", 86 | "fr": "États-Unis", 87 | "ja": "アメリカ合衆国", 88 | "pt-BR": "Estados Unidos", 89 | "ru": "США", 90 | "zh-CN": "美国" 91 | } 92 | }, 93 | "represented_country": { 94 | "geoname_id": 6252001, 95 | "is_in_european_union": true, 96 | "iso_code": "US", 97 | "names": { 98 | "de": "USA", 99 | "en": "United States", 100 | "es": "Estados Unidos", 101 | "fr": "États-Unis", 102 | "ja": "アメリカ合衆国", 103 | "pt-BR": "Estados Unidos", 104 | "ru": "США", 105 | "zh-CN": "美国" 106 | }, 107 | "type": "military" 108 | }, 109 | "subdivisions": [ 110 | { 111 | "confidence": 42, 112 | "geoname_id": 6269131, 113 | "iso_code": "ENG", 114 | "names": { 115 | "en": "England", 116 | "es": "Inglaterra", 117 | "fr": "Angleterre", 118 | "pt-BR": "Inglaterra" 119 | } 120 | } 121 | ], 122 | "traits": { 123 | "connection_type": "Cable/DSL", 124 | "domain": "in-addr.arpa", 125 | "ip_address": "152.216.7.110", 126 | "is_anonymous": true, 127 | "is_anonymous_proxy": true, 128 | "is_anonymous_vpn": true, 129 | "is_anycast": true, 130 | "is_hosting_provider": true, 131 | "is_legitimate_proxy": false, 132 | "is_public_proxy": true, 133 | "is_residential_proxy": true, 134 | "is_satellite_provider": true, 135 | "is_tor_exit_node": true, 136 | "isp": "Andrews & Arnold Ltd", 137 | "mobile_country_code": "310", 138 | "mobile_network_code": "004", 139 | "network": "81.2.69.0/24", 140 | "organization": "STONEHOUSE office network", 141 | "user_type": "government" 142 | } 143 | }, 144 | "billing_address": { 145 | "is_postal_in_city": false, 146 | "latitude": 41.310571, 147 | "longitude": -72.922891, 148 | "distance_to_ip_location": 5465, 149 | "is_in_ip_country": false 150 | }, 151 | "billing_phone": { 152 | "country": "US", 153 | "is_voip": true, 154 | "matches_postal": false, 155 | "network_operator": "Verizon/1", 156 | "number_type": "fixed" 157 | }, 158 | "credit_card": { 159 | "issuer": { 160 | "name": "Bank of No Hope", 161 | "matches_provided_name": true, 162 | "phone_number": "8003421232", 163 | "matches_provided_phone_number": true 164 | }, 165 | "brand": "Visa", 166 | "country": "US", 167 | "is_business": true, 168 | "is_issued_in_billing_address_country": true, 169 | "is_prepaid": true, 170 | "type": "credit" 171 | }, 172 | "device": { 173 | "confidence": 99.0, 174 | "id": "7835b099-d385-4e5b-969e-7df26181d73b", 175 | "last_seen": "2016-06-08T14:16:38+00:00" 176 | }, 177 | "email": { 178 | "domain": { 179 | "first_seen": "2014-02-03" 180 | }, 181 | "first_seen": "2017-01-02", 182 | "is_disposable": true, 183 | "is_free": true, 184 | "is_high_risk": true 185 | }, 186 | "shipping_address": { 187 | "distance_to_billing_address": 2227, 188 | "distance_to_ip_location": 7456, 189 | "is_in_ip_country": false, 190 | "is_high_risk": false, 191 | "is_postal_in_city": false, 192 | "latitude": 35.704729, 193 | "longitude": -97.568619 194 | }, 195 | "shipping_phone": { 196 | "country": "CA", 197 | "is_voip": true, 198 | "matches_postal": false, 199 | "network_operator": "Telus Mobility-SVR/2", 200 | "number_type": "mobile" 201 | }, 202 | "subscores": { 203 | "avs_result": 0.01, 204 | "billing_address": 0.02, 205 | "billing_address_distance_to_ip_location": 0.03, 206 | "browser": 0.04, 207 | "chargeback": 0.05, 208 | "country": 0.06, 209 | "country_mismatch": 0.07, 210 | "cvv_result": 0.08, 211 | "device": 0.09, 212 | "email_address": 0.10, 213 | "email_domain": 0.11, 214 | "email_local_part": 0.12, 215 | "issuer_id_number": 0.15, 216 | "order_amount": 0.16, 217 | "phone_number": 0.17, 218 | "shipping_address": 0.18, 219 | "shipping_address_distance_to_ip_location": 0.19, 220 | "time_of_day": 0.20 221 | }, 222 | "warnings": [ 223 | { 224 | "code": "INPUT_INVALID", 225 | "input_pointer": "/account/user_id", 226 | "warning": "Encountered value at \/account\/user_id that does meet the required constraints" 227 | }, 228 | { 229 | "code": "INPUT_INVALID", 230 | "input_pointer": "/account/username_md5", 231 | "warning": "Encountered value at \/account\/username_md5 that does meet the required constraints" 232 | } 233 | ], 234 | "risk_score_reasons": [ 235 | { 236 | "multiplier": 45.0, 237 | "reasons": [ 238 | { 239 | "code": "ANONYMOUS_IP", 240 | "reason": "Risk due to IP being an Anonymous IP" 241 | } 242 | ] 243 | }, 244 | { 245 | "multiplier": 1.8, 246 | "reasons": [ 247 | { 248 | "code": "TIME_OF_DAY", 249 | "reason": "Risk due to local time of day" 250 | } 251 | ] 252 | }, 253 | { 254 | "multiplier": 1.6, 255 | "reasons": [ 256 | { 257 | "reason": "Riskiness of newly-sighted email domain", 258 | "code": "EMAIL_DOMAIN_NEW" 259 | } 260 | ] 261 | }, 262 | { 263 | "multiplier": 0.34, 264 | "reasons": [ 265 | { 266 | "code": "EMAIL_ADDRESS_NEW", 267 | "reason": "Riskiness of newly-sighted email address" 268 | } 269 | ] 270 | } 271 | ] 272 | } 273 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/TestData/full-request.json: -------------------------------------------------------------------------------- 1 | { 2 | "event": { 3 | "transaction_id": "txn3134133", 4 | "shop_id": "s2123", 5 | "time": "2014-04-12T23:20:50.052+00:00", 6 | "type": "purchase" 7 | }, 8 | "account": { 9 | "user_id": "3132", 10 | "username_md5": "570a90bfbf8c7eab5dc5d4e26832d5b1" 11 | }, 12 | "email": { 13 | "address": "test@maxmind.com", 14 | "domain": "maxmind.com" 15 | }, 16 | "billing": { 17 | "first_name": "First", 18 | "last_name": "Last", 19 | "company": "Company", 20 | "address": "101 Address Rd.", 21 | "address_2": "Unit 5", 22 | "city": "City of Thorns", 23 | "region": "CT", 24 | "country": "US", 25 | "postal": "06510", 26 | "phone_number": "123-456-7890", 27 | "phone_country_code": "1" 28 | }, 29 | "shipping": { 30 | "first_name": "ShipFirst", 31 | "last_name": "ShipLast", 32 | "company": "ShipCo", 33 | "address": "322 Ship Addr. Ln.", 34 | "address_2": "St. 43", 35 | "city": "Nowhere", 36 | "region": "OK", 37 | "country": "US", 38 | "postal": "73003", 39 | "phone_number": "123-456-0000", 40 | "phone_country_code": "1", 41 | "delivery_speed": "same_day" 42 | }, 43 | "payment": { 44 | "processor": "stripe", 45 | "was_authorized": false, 46 | "decline_code": "invalid number" 47 | }, 48 | "credit_card": { 49 | "country": "US", 50 | "issuer_id_number": "411111", 51 | "last_digits": "7643", 52 | "bank_name": "Bank of No Hope", 53 | "bank_phone_country_code": "1", 54 | "bank_phone_number": "123-456-1234", 55 | "avs_result": "Y", 56 | "cvv_result": "N", 57 | "token": "123456abc1234", 58 | "was_3d_secure_successful": false 59 | }, 60 | "custom_inputs": { 61 | "float_input": 12.1, 62 | "integer_input": 3123, 63 | "string_input": "This is a string input.", 64 | "boolean_input": true 65 | }, 66 | "order": { 67 | "amount": 323.21, 68 | "currency": "USD", 69 | "discount_code": "FIRST", 70 | "affiliate_id": "af12", 71 | "subaffiliate_id": "saf42", 72 | "is_gift": true, 73 | "has_gift_message": false, 74 | "referrer_uri": "http://www.amazon.com/" 75 | }, 76 | "shopping_cart": [ 77 | { 78 | "category": "pets", 79 | "item_id": "ad23232", 80 | "quantity": 2, 81 | "price": 20.43 82 | }, 83 | { 84 | "category": "beauty", 85 | "item_id": "bst112", 86 | "quantity": 1, 87 | "price": 100.0 88 | } 89 | ], 90 | "device": { 91 | "ip_address": "152.216.7.110", 92 | "user_agent": 93 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36", 94 | "accept_language": "en-US,en;q=0.8", 95 | "session_age": 3600.5, 96 | "session_id": "foobar" 97 | } 98 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/TestData/insights-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "27d26476-e2bc-11e4-92b8-962e705b4af5", 3 | "risk_score": 0.01, 4 | "funds_remaining": 10.01, 5 | "queries_remaining": 1000, 6 | "disposition": { 7 | "action": "reject", 8 | "reason": "custom_rule", 9 | "rule_label": "The label" 10 | }, 11 | "ip_address": { 12 | "risk": 0.01, 13 | "risk_reasons": [ 14 | { 15 | "code": "ANONYMOUS_IP", 16 | "reason": "The IP address belongs to an anonymous network. See /ip_address/traits for more details." 17 | }, 18 | { 19 | "code": "MINFRAUD_NETWORK_ACTIVITY", 20 | "reason": "Suspicious activity has been seen on this IP address across minFraud customers." 21 | } 22 | ], 23 | "city": { 24 | "confidence": 42, 25 | "geoname_id": 2643743, 26 | "names": { 27 | "de": "London", 28 | "en": "London", 29 | "es": "Londres", 30 | "fr": "Londres", 31 | "ja": "ロンドン", 32 | "pt-BR": "Londres", 33 | "ru": "Лондон" 34 | } 35 | }, 36 | "continent": { 37 | "code": "EU", 38 | "geoname_id": 6255148, 39 | "names": { 40 | "de": "Europa", 41 | "en": "Europe", 42 | "es": "Europa", 43 | "fr": "Europe", 44 | "ja": "ヨーロッパ", 45 | "pt-BR": "Europa", 46 | "ru": "Европа", 47 | "zh-CN": "欧洲" 48 | } 49 | }, 50 | "country": { 51 | "confidence": 99, 52 | "geoname_id": 2635167, 53 | "is_in_european_union": true, 54 | "iso_code": "GB", 55 | "names": { 56 | "de": "Vereinigtes Königreich", 57 | "en": "United Kingdom", 58 | "es": "Reino Unido", 59 | "fr": "Royaume-Uni", 60 | "ja": "イギリス", 61 | "pt-BR": "Reino Unido", 62 | "ru": "Великобритания", 63 | "zh-CN": "英国" 64 | } 65 | }, 66 | "location": { 67 | "accuracy_radius": 96, 68 | "latitude": 51.5142, 69 | "local_time": "2012-04-13T00:20:50-01:00", 70 | "longitude": -0.0931, 71 | "time_zone": "Europe\/London" 72 | }, 73 | "maxmind": {}, 74 | "postal": { 75 | "code": "90001", 76 | "confidence": 10 77 | }, 78 | "registered_country": { 79 | "geoname_id": 6252001, 80 | "is_in_european_union": true, 81 | "iso_code": "US", 82 | "names": { 83 | "de": "USA", 84 | "en": "United States", 85 | "es": "Estados Unidos", 86 | "fr": "États-Unis", 87 | "ja": "アメリカ合衆国", 88 | "pt-BR": "Estados Unidos", 89 | "ru": "США", 90 | "zh-CN": "美国" 91 | } 92 | }, 93 | "represented_country": { 94 | "geoname_id": 6252001, 95 | "is_in_european_union": true, 96 | "iso_code": "US", 97 | "names": { 98 | "de": "USA", 99 | "en": "United States", 100 | "es": "Estados Unidos", 101 | "fr": "États-Unis", 102 | "ja": "アメリカ合衆国", 103 | "pt-BR": "Estados Unidos", 104 | "ru": "США", 105 | "zh-CN": "美国" 106 | }, 107 | "type": "military" 108 | }, 109 | "subdivisions": [ 110 | { 111 | "confidence": 42, 112 | "geoname_id": 6269131, 113 | "iso_code": "ENG", 114 | "names": { 115 | "en": "England", 116 | "es": "Inglaterra", 117 | "fr": "Angleterre", 118 | "pt-BR": "Inglaterra" 119 | } 120 | } 121 | ], 122 | "traits": { 123 | "connection_type": "Cable/DSL", 124 | "domain": "in-addr.arpa", 125 | "ip_address": "152.216.7.110", 126 | "is_anonymous": true, 127 | "is_anonymous_proxy": true, 128 | "is_anonymous_vpn": true, 129 | "is_anycast": true, 130 | "is_hosting_provider": true, 131 | "is_legitimate_proxy": false, 132 | "is_public_proxy": true, 133 | "is_residential_proxy": true, 134 | "is_satellite_provider": true, 135 | "is_tor_exit_node": true, 136 | "isp": "Andrews & Arnold Ltd", 137 | "mobile_country_code": "310", 138 | "mobile_network_code": "004", 139 | "network": "81.2.69.0/24", 140 | "organization": "STONEHOUSE office network", 141 | "user_type": "government" 142 | } 143 | }, 144 | "billing_address": { 145 | "is_postal_in_city": false, 146 | "latitude": 41.310571, 147 | "longitude": -72.922891, 148 | "distance_to_ip_location": 5465, 149 | "is_in_ip_country": false 150 | }, 151 | "billing_phone": { 152 | "country": "US", 153 | "is_voip": true, 154 | "matches_postal": false, 155 | "network_operator": "Verizon/1", 156 | "number_type": "fixed" 157 | }, 158 | "credit_card": { 159 | "issuer": { 160 | "name": "Bank of No Hope", 161 | "matches_provided_name": true, 162 | "phone_number": "8003421232", 163 | "matches_provided_phone_number": true 164 | }, 165 | "brand": "Visa", 166 | "country": "US", 167 | "is_business": true, 168 | "is_issued_in_billing_address_country": true, 169 | "is_prepaid": true, 170 | "type": "credit" 171 | }, 172 | "device": { 173 | "confidence": 99.0, 174 | "id": "7835b099-d385-4e5b-969e-7df26181d73b", 175 | "last_seen": "2016-06-08T14:16:38+00:00" 176 | }, 177 | "email": { 178 | "domain": { 179 | "first_seen": "2014-02-03" 180 | }, 181 | "first_seen": "2017-01-02", 182 | "is_disposable": true, 183 | "is_free": true, 184 | "is_high_risk": true 185 | }, 186 | "shipping_address": { 187 | "distance_to_billing_address": 2227, 188 | "distance_to_ip_location": 7456, 189 | "is_in_ip_country": false, 190 | "is_high_risk": false, 191 | "is_postal_in_city": false, 192 | "latitude": 35.704729, 193 | "longitude": -97.568619 194 | }, 195 | "shipping_phone": { 196 | "country": "CA", 197 | "is_voip": true, 198 | "matches_postal": false, 199 | "network_operator": "Telus Mobility-SVR/2", 200 | "number_type": "mobile" 201 | }, 202 | "warnings": [ 203 | { 204 | "code": "INPUT_INVALID", 205 | "input_pointer": "/account/user_id", 206 | "warning": "Encountered value at \/account\/user_id that does meet the required constraints" 207 | }, 208 | { 209 | "code": "INPUT_INVALID", 210 | "input_pointer": "/account/username_md5", 211 | "warning": "Encountered value at \/account\/username_md5 that does meet the required constraints" 212 | } 213 | ] 214 | } 215 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/TestData/report-request.json: -------------------------------------------------------------------------------- 1 | { 2 | "ip_address": "1.1.1.1", 3 | "tag": "suspected_fraud", 4 | "chargeback_code": "AA", 5 | "maxmind_id": "a1b2c3d4", 6 | "minfraud_id": "9194a1ac-0a81-475a-bf81-9bf8543a3f8f", 7 | "notes": "note", 8 | "transaction_id": "txn1" 9 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.UnitTest/TestData/score-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "risk_score": 0.01, 3 | "id": "27d26476-e2bc-11e4-92b8-962e705b4af5", 4 | "funds_remaining": 10.01, 5 | "queries_remaining": 1000, 6 | "disposition": { 7 | "action": "reject", 8 | "reason": "custom_rule", 9 | "rule_label": "The label" 10 | }, 11 | "ip_address": { 12 | "risk": 99.0 13 | }, 14 | "warnings": [ 15 | { 16 | "code": "INPUT_INVALID", 17 | "input_pointer": "/account/user_id", 18 | "warning": "Encountered value at \/account\/user_id that does meet the required constraints" 19 | }, 20 | { 21 | "code": "INPUT_INVALID", 22 | "input_pointer": "/account/username_md5", 23 | "warning": "Encountered value at \/account\/username_md5 that does meet the required constraints" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30204.135 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{22ABC5EB-1243-4A43-8596-C9395960700E}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MaxMind.MinFraud", "MaxMind.MinFraud\MaxMind.MinFraud.csproj", "{7208D9C8-F862-4675-80E5-77A2F8901B96}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MaxMind.MinFraud.UnitTest", "MaxMind.MinFraud.UnitTest\MaxMind.MinFraud.UnitTest.csproj", "{B94CD151-77D9-4423-BB17-504264B109D2}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {7208D9C8-F862-4675-80E5-77A2F8901B96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {7208D9C8-F862-4675-80E5-77A2F8901B96}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {7208D9C8-F862-4675-80E5-77A2F8901B96}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {7208D9C8-F862-4675-80E5-77A2F8901B96}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {B94CD151-77D9-4423-BB17-504264B109D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {B94CD151-77D9-4423-BB17-504264B109D2}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {B94CD151-77D9-4423-BB17-504264B109D2}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {B94CD151-77D9-4423-BB17-504264B109D2}.Release|Any CPU.Build.0 = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(SolutionProperties) = preSolution 28 | HideSolutionNode = FALSE 29 | EndGlobalSection 30 | GlobalSection(ExtensibilityGlobals) = postSolution 31 | SolutionGuid = {CD54BED9-2AA1-4346-B570-FE3C07238379} 32 | EndGlobalSection 33 | EndGlobal 34 | -------------------------------------------------------------------------------- /MaxMind.MinFraud.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | IP 3 | MD -------------------------------------------------------------------------------- /MaxMind.MinFraud/Exception/AuthenticationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MaxMind.MinFraud.Exception 4 | { 5 | /// 6 | /// This class represents an authentication error. 7 | /// 8 | [Serializable] 9 | public class AuthenticationException : MinFraudException 10 | { 11 | /// 12 | /// Constructor. 13 | /// 14 | public AuthenticationException() 15 | { 16 | } 17 | 18 | /// 19 | /// Constructor. 20 | /// 21 | /// Exception message. 22 | public AuthenticationException(string message) : base(message) 23 | { 24 | } 25 | 26 | /// 27 | /// Constructor. 28 | /// 29 | /// Exception message. 30 | /// The underlying exception that caused this one. 31 | public AuthenticationException(string message, System.Exception innerException) : base(message, innerException) 32 | { 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Exception/HttpException.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | using System.IO; 5 | using System.Net; 6 | 7 | #endregion 8 | 9 | namespace MaxMind.MinFraud.Exception 10 | { 11 | /// 12 | /// This class represents an HTTP transport error. This is not an error returned 13 | /// by the web service itself. As such, it is a IOException instead of a 14 | /// MinFraudException. 15 | /// 16 | [Serializable] 17 | public class HttpException : IOException 18 | { 19 | /// 20 | /// Constructor. 21 | /// 22 | public HttpException() 23 | { 24 | } 25 | 26 | /// 27 | /// Constructor. 28 | /// 29 | /// A message describing the reason why the exception was thrown. 30 | public HttpException(string message) : base(message) 31 | { 32 | } 33 | 34 | /// 35 | /// Constructor. 36 | /// 37 | public HttpException(string? message, int hresult) : base(message, hresult) 38 | { 39 | } 40 | 41 | /// 42 | /// Constructor. 43 | /// 44 | /// A message describing the reason why the exception was thrown. 45 | /// The underlying exception that caused this one. 46 | public HttpException(string message, System.Exception innerException) : base(message, innerException) 47 | { 48 | } 49 | 50 | /// 51 | /// Initializes a new instance of the class. 52 | /// 53 | /// A message describing the reason why the exception was thrown. 54 | /// The HTTP status of the response that caused the exception. 55 | /// The URL queried. 56 | public HttpException(string message, HttpStatusCode httpStatus, Uri? uri) 57 | : base(message) 58 | { 59 | HttpStatus = httpStatus; 60 | Uri = uri; 61 | } 62 | 63 | /// 64 | /// Initializes a new instance of the class. 65 | /// 66 | /// A message describing the reason why the exception was thrown. 67 | /// The HTTP status of the response that caused the exception. 68 | /// The URL queried. 69 | /// The underlying exception that caused this one. 70 | public HttpException(string message, HttpStatusCode httpStatus, Uri? uri, System.Exception innerException) 71 | : base(message, innerException) 72 | { 73 | HttpStatus = httpStatus; 74 | Uri = uri; 75 | } 76 | 77 | /// 78 | /// The HTTP status code returned by the web service. 79 | /// 80 | public HttpStatusCode? HttpStatus { get; init; } 81 | 82 | /// 83 | /// The URI queried by the web service. 84 | /// 85 | public Uri? Uri { get; init; } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Exception/InsufficientFundsException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MaxMind.MinFraud.Exception 4 | { 5 | /// 6 | /// This class is thrown when the request fails due to insufficient funds. 7 | /// 8 | [Serializable] 9 | public class InsufficientFundsException : System.Exception 10 | { 11 | /// 12 | /// Constructor. 13 | /// 14 | public InsufficientFundsException() 15 | { 16 | } 17 | 18 | /// 19 | /// Constructor. 20 | /// 21 | /// Exception message. 22 | public InsufficientFundsException(string message) : base(message) 23 | { 24 | } 25 | 26 | /// 27 | /// Constructor. 28 | /// 29 | /// Exception message. 30 | /// The underlying exception that caused this one. 31 | public InsufficientFundsException(string message, System.Exception innerException) : base(message, innerException) 32 | { 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Exception/InvalidRequestException.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | 5 | #endregion 6 | 7 | namespace MaxMind.MinFraud.Exception 8 | { 9 | /// 10 | /// This class is thrown when the web service rejected the request. Check 11 | /// the value of Code for the reason code. 12 | /// 13 | [Serializable] 14 | public class InvalidRequestException : MinFraudException 15 | { 16 | /// 17 | /// Constructor. 18 | /// 19 | public InvalidRequestException() 20 | { 21 | } 22 | 23 | /// 24 | /// Constructor. 25 | /// 26 | /// The message from the web service. 27 | public InvalidRequestException(string message) : base(message) 28 | { 29 | } 30 | 31 | /// 32 | /// Constructor. 33 | /// 34 | /// The message from the web service. 35 | /// The underlying exception that caused this one. 36 | public InvalidRequestException(string message, System.Exception innerException) : base(message, innerException) 37 | { 38 | } 39 | 40 | /// 41 | /// Constructor. 42 | /// 43 | /// The message from the web service. 44 | /// The machine-readable error code. 45 | /// The URI that was queried. 46 | public InvalidRequestException(string message, string code, Uri? uri) : base(message) 47 | { 48 | Code = code; 49 | Uri = uri; 50 | } 51 | 52 | /// 53 | /// Constructor. 54 | /// 55 | /// The message from the web service. 56 | /// The machine-readable error code. 57 | /// The URI that was queried. 58 | /// The underlying exception that caused this one. 59 | public InvalidRequestException(string message, string code, Uri? uri, System.Exception innerException) : base(message, innerException) 60 | { 61 | Code = code; 62 | Uri = uri; 63 | } 64 | 65 | /// 66 | /// The 67 | /// reason code for why the web service rejected the request. 68 | /// 69 | public string? Code { get; init; } 70 | 71 | /// 72 | /// The URI that was used for the request. 73 | /// 74 | public Uri? Uri { get; init; } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Exception/MinFraudException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MaxMind.MinFraud.Exception 4 | { 5 | /// 6 | /// This class represents a non-specific error with data returned by 7 | /// the web service. 8 | /// 9 | [Serializable] 10 | public class MinFraudException : System.Exception 11 | { 12 | /// 13 | /// Constructor. 14 | /// 15 | public MinFraudException() 16 | { 17 | } 18 | 19 | /// 20 | /// Constructor. 21 | /// 22 | /// Exception message. 23 | public MinFraudException(string message) : base(message) 24 | { 25 | } 26 | 27 | /// 28 | /// Constructor. 29 | /// 30 | /// Exception message. 31 | /// The underlying exception that caused this one. 32 | public MinFraudException(string message, System.Exception innerException) : base(message, innerException) 33 | { 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Exception/PermissionRequiredException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MaxMind.MinFraud.Exception 4 | { 5 | /// 6 | /// This class represents an authentication error. 7 | /// 8 | [Serializable] 9 | public class PermissionRequiredException : MinFraudException 10 | { 11 | /// 12 | /// Constructor. 13 | /// 14 | public PermissionRequiredException() 15 | { 16 | } 17 | 18 | /// 19 | /// Constructor. 20 | /// 21 | /// Exception message. 22 | public PermissionRequiredException(string message) : base(message) 23 | { 24 | } 25 | 26 | /// 27 | /// Constructor. 28 | /// 29 | /// Exception message. 30 | /// The underlying exception that caused this one. 31 | public PermissionRequiredException(string message, System.Exception innerException) : base(message, innerException) 32 | { 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/GlobalSuppressions.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmind/minfraud-api-dotnet/d66282b58bf50471d15ba2cce83481b6f078490a/MaxMind.MinFraud/GlobalSuppressions.cs -------------------------------------------------------------------------------- /MaxMind.MinFraud/IWebServiceClient.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Request; 2 | using MaxMind.MinFraud.Response; 3 | using System.Threading.Tasks; 4 | 5 | namespace MaxMind.MinFraud 6 | { 7 | /// 8 | /// Client for querying the minFraud Score and Insights web services. 9 | /// 10 | public interface IWebServiceClient 11 | { 12 | /// 13 | /// Asynchronously query Factors endpoint with transaction data 14 | /// 15 | /// Object containing the transaction data 16 | /// to be sent to the minFraud web service. 17 | /// Task that produces an object modeling the minFraud 18 | /// Factors response data 19 | Task FactorsAsync(Transaction transaction); 20 | 21 | /// 22 | /// Asynchronously query Insights endpoint with transaction data 23 | /// 24 | /// Object containing the transaction data 25 | /// to be sent to the minFraud web service. 26 | /// Task that produces an object modeling the minFraud 27 | /// Insights response data 28 | Task InsightsAsync(Transaction transaction); 29 | 30 | /// 31 | /// Asynchronously query Score endpoint with transaction data 32 | /// 33 | /// Object containing the transaction data 34 | /// to be sent to the minFraud web service. 35 | /// Task that produces an object modeling the minFraud Score 36 | /// response data 37 | Task ScoreAsync(Transaction transaction); 38 | 39 | /// 40 | /// Asynchronously query the minFraud Report Transaction API. 41 | /// 42 | /// 43 | /// Reporting transactions to MaxMind helps us detect about 10-50% more 44 | /// fraud and reduce false positives for you. 45 | /// 46 | /// The transaction report you would like to send. 47 | /// The Task on which to await. The web service returns no data and 48 | /// this API will throw an exception if there is an error. 49 | Task ReportAsync(TransactionReport report); 50 | } 51 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/MaxMind.MinFraud.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | API for MaxMind minFraud Score and Insights web services 5 | 5.2.0 6 | net9.0;net8.0;netstandard2.1;netstandard2.0 7 | true 8 | MaxMind.MinFraud 9 | ../MaxMind.snk 10 | true 11 | true 12 | MaxMind.MinFraud 13 | maxmind;minfraud;fraud;detection 14 | MaxMind-logo.png 15 | README.md 16 | https://github.com/maxmind/minfraud-api-dotnet 17 | Apache-2.0 18 | false 19 | false 20 | false 21 | false 22 | false 23 | false 24 | false 25 | false 26 | false 27 | 13.0 28 | enable 29 | latest 30 | true 31 | true 32 | true 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | runtime; build; native; contentfiles; analyzers; buildtransitive 43 | all 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | True 53 | 54 | 55 | 56 | True 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/MaxMind.MinFraud.ruleset: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | 9 | [assembly: AssemblyTitle("MaxMind.MinFraud")] 10 | [assembly: AssemblyDescription("API for MaxMind minFraud web services")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("MaxMind, Inc.")] 13 | [assembly: AssemblyProduct("MaxMind.MinFraud")] 14 | [assembly: AssemblyCopyright("Copyright © 2015-2025")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | 18 | // Setting ComVisible to false makes the types in this assembly not visible 19 | // to COM components. If you need to access a type in this assembly from 20 | // COM, set the ComVisible attribute to true on that type. 21 | 22 | [assembly: ComVisible(false)] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | 26 | [assembly: Guid("7208d9c8-f862-4675-80e5-77a2f8901b96")] 27 | [assembly: CLSCompliant(true)] 28 | 29 | // Version information for an assembly consists of the following four values: 30 | // 31 | // Major Version 32 | // Minor Version 33 | // Build Number 34 | // Revision 35 | // 36 | // You can specify all the values or you can default the Build and Revision Numbers 37 | // by using the '*' as shown below: 38 | // [assembly: AssemblyVersion("1.0.*")] 39 | 40 | [assembly: AssemblyVersion("5.0.0.0")] 41 | [assembly: AssemblyFileVersion("5.0.0")] 42 | [assembly: AssemblyInformationalVersion("5.0.0")] 43 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Request/Account.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | using System.Text; 4 | using System.Text.Json.Serialization; 5 | 6 | namespace MaxMind.MinFraud.Request 7 | { 8 | /// 9 | /// Account related data information for the transaction being sent to the 10 | /// web service. 11 | /// 12 | public sealed class Account 13 | { 14 | /// 15 | /// Constructor. 16 | /// 17 | /// A unique user ID associated with the end-user 18 | /// in your system. If your system allows the login name for the 19 | /// account to be changed, this should not be the login name for the 20 | /// account, but rather should be an internal ID that does not 21 | /// change. This is not your MaxMind account ID. 22 | /// The username associated with the account. 23 | /// This is not the MD5 of username. Rather, the MD is automatically 24 | /// generated from this string. 25 | public Account( 26 | string? userId = null, 27 | string? username = null 28 | ) 29 | { 30 | UserId = userId; 31 | Username = username; 32 | } 33 | 34 | /// 35 | /// A unique user ID associated with the end-user in your 36 | /// system. If your system allows the login name for the 37 | /// account to be changed, this should not be the login 38 | /// name for the account, but rather should be an internal 39 | /// ID that does not change. This is not your MaxMind user 40 | /// ID. 41 | /// 42 | [JsonPropertyName("user_id")] 43 | public string? UserId { get; init; } 44 | 45 | /// 46 | /// The username associated with the account. This is 47 | /// not the MD5 of username. Rather, the MD5 is automatically 48 | /// generated from this string. 49 | /// 50 | [JsonIgnore] 51 | public string? Username { get; init; } 52 | 53 | /// 54 | /// The MD5 generated from the Username 55 | /// 56 | [JsonPropertyName("username_md5")] 57 | public string? UsernameMD5 58 | { 59 | get 60 | { 61 | if (Username == null) 62 | { 63 | return null; 64 | } 65 | using var md5Generator = MD5.Create(); 66 | 67 | var bytes = Encoding.UTF8.GetBytes(Username); 68 | var md5 = md5Generator.ComputeHash(bytes); 69 | return BitConverter.ToString(md5) 70 | .Replace("-", string.Empty) 71 | .ToLower(); 72 | } 73 | } 74 | 75 | /// 76 | /// Returns a string that represents the current object. 77 | /// 78 | /// A string that represents the current object. 79 | public override string ToString() 80 | { 81 | return $"UserId: {UserId}, Username: {Username}, UsernameMD5: {UsernameMD5}"; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Request/Billing.cs: -------------------------------------------------------------------------------- 1 | namespace MaxMind.MinFraud.Request 2 | { 3 | /// 4 | /// The billing information for the transaction being sent to the 5 | /// web service. 6 | /// 7 | public sealed class Billing : Location 8 | { 9 | /// 10 | /// Constructor. 11 | /// 12 | /// The first name of the end user as provided in their billing information. 13 | /// The last name of the end user as provided in their billing information. 14 | /// The company of the end user as provided in their billing information. 15 | /// The first line of the user’s billing address. 16 | /// The second line of the user’s billing address. 17 | /// The city of the user’s billing address. 18 | /// The ISO 3166-2 19 | /// subdivision code for the user’s billing address. 20 | /// The two character ISO 21 | /// 3166-1 alpha-2 country code of the user’s billing address. 22 | /// The postal code of the user’s billing address. 23 | /// The phone number without the country code for the user’s billing address. 24 | /// The country code for phone number associated with the user’s billing address. 25 | public Billing( 26 | string? firstName = null, 27 | string? lastName = null, 28 | string? company = null, 29 | string? address = null, 30 | string? address2 = null, 31 | string? city = null, 32 | string? region = null, 33 | string? country = null, 34 | string? postal = null, 35 | string? phoneNumber = null, 36 | string? phoneCountryCode = null 37 | ) : base( 38 | firstName: firstName, 39 | lastName: lastName, 40 | company: company, 41 | address: address, 42 | address2: address2, 43 | city: city, 44 | region: region, 45 | country: country, 46 | postal: postal, 47 | phoneNumber: phoneNumber, 48 | phoneCountryCode: phoneCountryCode 49 | ) 50 | { 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Request/CustomInputs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Text.Json.Serialization; 6 | using System.Text.RegularExpressions; 7 | 8 | namespace MaxMind.MinFraud.Request 9 | { 10 | /// 11 | /// Custom inputs to be used in 12 | /// Custom Rules. 13 | /// In order to use custom inputs, you must set them up from your account portal. 14 | /// 15 | public sealed class CustomInputs 16 | { 17 | private readonly Dictionary _inputs = []; 18 | 19 | /// 20 | /// This is only to be used by the Builder. 21 | /// 22 | private CustomInputs() 23 | { 24 | } 25 | 26 | /// 27 | /// The custom inputs to be sent to the web service. 28 | /// 29 | [JsonExtensionData] 30 | public IDictionary Inputs => new ReadOnlyDictionary(_inputs); 31 | 32 | /// 33 | /// Builder class for CustomInputs objects. 34 | /// 35 | public sealed class Builder : IEnumerable> 36 | { 37 | private const long NumMax = (long)1e13; 38 | private static readonly Regex KeyRe = new("^[a-z0-9_]{1,25}$", RegexOptions.Compiled); 39 | 40 | // We do the builder this way so that we don't have to 41 | // make a copy of the dictionary after construction 42 | private readonly CustomInputs _customInputs = new(); 43 | private bool alreadyBuilt = false; 44 | 45 | /// 46 | /// Returns an enumerator that iterates through the inputs. 47 | /// 48 | public IEnumerator> GetEnumerator() 49 | { 50 | ValidateState(); 51 | return _customInputs._inputs.GetEnumerator(); 52 | } 53 | 54 | /// 55 | /// Returns an enumerator that iterates through the inputs. 56 | /// 57 | IEnumerator IEnumerable.GetEnumerator() 58 | { 59 | ValidateState(); 60 | return GetEnumerator(); 61 | } 62 | 63 | /// 64 | /// Build the CustomInputs object. 65 | /// 66 | /// The constructed CustomInputs object. 67 | public CustomInputs Build() 68 | { 69 | ValidateState(); 70 | 71 | var customInputs = _customInputs; 72 | 73 | // Invalidate the builder so additional inputs cannot be 74 | // added. 75 | alreadyBuilt = true; 76 | 77 | return customInputs; 78 | } 79 | 80 | /// 81 | /// Add an int as a numeric custom input. 82 | /// 83 | /// The key for the numeric input defined in your account portal. 84 | /// The input value. 85 | public void Add(string key, int value) 86 | { 87 | ValidatedAdd(key, value); 88 | } 89 | 90 | /// 91 | /// Add a long as a numeric custom input. 92 | /// 93 | /// The key for the numeric input defined in your account portal. 94 | /// 95 | /// The input. The value must be between -10^13 and 10^13, 96 | /// exclusive. 97 | /// 98 | public void Add(string key, long value) 99 | { 100 | if (value is <= -NumMax or >= NumMax) 101 | { 102 | throw new ArgumentException($"The custom input number {value} is invalid. " + 103 | $"The number must be between -{NumMax} and {NumMax}, exclusive"); 104 | } 105 | 106 | ValidatedAdd(key, value); 107 | } 108 | 109 | /// 110 | /// Add a float as a numeric custom input. 111 | /// 112 | /// The key for the numeric input defined in your account portal. 113 | /// 114 | /// The input value. The value must be between -10^13 and 10^13, 115 | /// exclusive. 116 | /// 117 | public void Add(string key, float value) 118 | { 119 | if (value is <= -NumMax or >= NumMax) 120 | { 121 | throw new ArgumentException($"The custom input number {value} is invalid. " + 122 | $"The number must be between -{NumMax} and {NumMax}, exclusive"); 123 | } 124 | 125 | ValidatedAdd(key, value); 126 | } 127 | 128 | /// 129 | /// Add a double as a numeric custom input. 130 | /// 131 | /// The key for the numeric input defined in your account portal. 132 | /// 133 | /// The input value. The value must be between -10^13 and 10^13, 134 | /// exclusive. 135 | /// 136 | public void Add(string key, double value) 137 | { 138 | if (value is <= -NumMax or >= NumMax) 139 | { 140 | throw new ArgumentException($"The custom input number {value} is invalid. " + 141 | $"The number must be between -{NumMax} and {NumMax}, exclusive"); 142 | } 143 | 144 | ValidatedAdd(key, value); 145 | } 146 | 147 | /// 148 | /// Add a custom input string. 149 | /// 150 | /// The key for the string input defined in your account portal. 151 | /// 152 | /// The input value. The string length must be less than or equal to 255 153 | /// characters and the string must not contain a newline character. 154 | /// 155 | public void Add(string key, string value) 156 | { 157 | if (value.Length > 255 || value.Contains("\n")) 158 | { 159 | throw new ArgumentException($"The custom input string {value} is invalid. " + 160 | "The string length must be <= 255 and it must not contain a newline."); 161 | } 162 | 163 | ValidatedAdd(key, value); 164 | } 165 | 166 | /// 167 | /// Add a boolean custom input. 168 | /// 169 | /// The key for the boolean input defined in your account portal. 170 | /// The input value. 171 | public void Add(string key, bool value) 172 | { 173 | ValidatedAdd(key, value); 174 | } 175 | 176 | private void ValidatedAdd(string key, object value) 177 | { 178 | ValidateState(); 179 | ValidateKey(key); 180 | _customInputs._inputs.Add(key, value); 181 | } 182 | 183 | private static void ValidateKey(string key) 184 | { 185 | if (!KeyRe.IsMatch(key)) 186 | throw new ArgumentException($"The custom input key {key} is invalid."); 187 | } 188 | 189 | private void ValidateState() 190 | { 191 | if (alreadyBuilt) 192 | { 193 | throw new InvalidOperationException( 194 | "CustomInputs.Builder cannot be reused after Build() has been called."); 195 | } 196 | } 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Request/Device.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Util; 2 | using System; 3 | using System.Net; 4 | using System.Text.Json.Serialization; 5 | 6 | namespace MaxMind.MinFraud.Request 7 | { 8 | /// 9 | /// The device information for the transaction being sent to the 10 | /// web service. 11 | /// 12 | public sealed class Device 13 | { 14 | private readonly double? _sessionAge; 15 | private readonly string? _sessionId; 16 | 17 | /// 18 | /// Constructor. 19 | /// 20 | /// The IP address associated with the device 21 | /// used by the customer in the transaction. 22 | /// The HTTP “User-Agent” header of the 23 | /// browser used in the transaction. 24 | /// The HTTP “Accept-Language” header of 25 | /// the device used in the transaction. 26 | /// The number of seconds between the 27 | /// creation of the user's session and the time of the transaction. 28 | /// Note that sessionAge is not the duration of the current visit, but 29 | /// the time since the start of the first visit. 30 | /// A string up to 255 characters in length. 31 | /// This is an ID that uniquely identifies a visitor's session on the 32 | /// site. 33 | public Device( 34 | IPAddress? ipAddress = null, 35 | string? userAgent = null, 36 | string? acceptLanguage = null, 37 | double? sessionAge = null, 38 | string? sessionId = null 39 | ) 40 | { 41 | IPAddress = ipAddress; 42 | UserAgent = userAgent; 43 | AcceptLanguage = acceptLanguage; 44 | SessionAge = sessionAge; 45 | SessionId = sessionId; 46 | } 47 | 48 | /// 49 | /// The IP address associated with the device used by the customer 50 | /// in the transaction. 51 | /// 52 | [JsonPropertyName("ip_address")] 53 | [JsonConverter(typeof(IPAddressConverter))] 54 | public IPAddress? IPAddress { get; init; } 55 | 56 | /// 57 | /// The HTTP “User-Agent” header of the browser used in the 58 | /// transaction. 59 | /// 60 | [JsonPropertyName("user_agent")] 61 | public string? UserAgent { get; init; } 62 | 63 | /// 64 | /// The HTTP “Accept-Language” header of the device used in the 65 | /// transaction. 66 | /// 67 | [JsonPropertyName("accept_language")] 68 | public string? AcceptLanguage { get; init; } 69 | 70 | /// 71 | /// The number of seconds between the creation of the user's 72 | /// session and the time of the transaction. Note that 73 | /// sessionAge is not the duration of the current visit, but 74 | /// the time since the start of the first visit. 75 | /// 76 | [JsonPropertyName("session_age")] 77 | public double? SessionAge 78 | { 79 | get => _sessionAge; 80 | init 81 | { 82 | if (value is < 0) 83 | { 84 | throw new ArgumentException($"{nameof(value)} must be non-negative."); 85 | } 86 | _sessionAge = value; 87 | } 88 | } 89 | 90 | /// 91 | /// A string up to 255 characters in length. This is an ID that 92 | /// uniquely identifies a visitor's session on the site. 93 | /// 94 | [JsonPropertyName("session_id")] 95 | public string? SessionId 96 | { 97 | get => _sessionId; 98 | init 99 | { 100 | if (value is { Length: > 255 }) 101 | { 102 | throw new ArgumentException($"{nameof(value)} must be less than 255 characters long."); 103 | } 104 | _sessionId = value; 105 | } 106 | } 107 | 108 | /// 109 | /// Returns a string that represents the current object. 110 | /// 111 | /// A string that represents the current object. 112 | public override string ToString() 113 | { 114 | return $"IPAddress: {IPAddress}, UserAgent: {UserAgent}, AcceptLanguage: {AcceptLanguage}, SessionAge: {SessionAge}, SessionId: {SessionId}"; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Request/Event.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Util; 2 | using System; 3 | using System.Runtime.Serialization; 4 | using System.Text.Json.Serialization; 5 | 6 | namespace MaxMind.MinFraud.Request 7 | { 8 | /// 9 | /// The enumerated event types supported by the web service. 10 | /// 11 | public enum EventType 12 | { 13 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member 14 | [EnumMember(Value = "account_creation")] 15 | AccountCreation, 16 | 17 | [EnumMember(Value = "account_login")] 18 | AccountLogin, 19 | 20 | [EnumMember(Value = "email_change")] 21 | EmailChange, 22 | 23 | [EnumMember(Value = "password_reset")] 24 | PasswordReset, 25 | 26 | [EnumMember(Value = "payout_change")] 27 | PayoutChange, 28 | 29 | [EnumMember(Value = "purchase")] 30 | Purchase, 31 | 32 | [EnumMember(Value = "recurring_purchase")] 33 | RecurringPurchase, 34 | 35 | [EnumMember(Value = "referral")] 36 | Referral, 37 | 38 | [EnumMember(Value = "survey")] 39 | Survey 40 | #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member 41 | } 42 | 43 | /// 44 | /// Event information for the transaction being sent to the 45 | /// web service. 46 | /// 47 | public sealed class Event 48 | { 49 | /// 50 | /// Constructor. 51 | /// 52 | /// Your internal ID for the transaction. 53 | /// We can use this to locate a specific transaction in our logs, and 54 | /// it will also show up in email alerts and notifications from us to 55 | /// you. 56 | /// Your internal ID for the shop, affiliate, or 57 | /// merchant this order is coming from. Required for minFraud users 58 | /// who are resellers, payment providers, gateways and affiliate 59 | /// networks. 60 | /// The date and time the event occurred. If this 61 | /// field is not in the request, the current time will be used. 62 | /// The type of event being scored. 63 | public Event( 64 | string? transactionId = null, 65 | string? shopId = null, 66 | DateTimeOffset? time = null, 67 | EventType? type = null 68 | ) 69 | { 70 | TransactionId = transactionId; 71 | ShopId = shopId; 72 | Time = time; 73 | Type = type; 74 | } 75 | 76 | /// 77 | /// Your internal ID for the transaction. We can use this to locate a 78 | /// specific transaction in our logs, and it will also show up in email 79 | /// alerts and notifications from us to you. 80 | /// 81 | [JsonPropertyName("transaction_id")] 82 | public string? TransactionId { get; init; } 83 | 84 | /// 85 | /// Your internal ID for the shop, affiliate, or merchant this order is 86 | /// coming from. Required for minFraud users who are resellers, payment 87 | /// providers, gateways and affiliate networks. 88 | /// 89 | [JsonPropertyName("shop_id")] 90 | public string? ShopId { get; init; } 91 | 92 | /// 93 | /// The date and time the event occurred. 94 | /// 95 | [JsonPropertyName("time")] 96 | public DateTimeOffset? Time { get; init; } 97 | 98 | /// 99 | /// The type of event being scored. 100 | /// 101 | [JsonConverter(typeof(EnumMemberValueConverter))] 102 | [JsonPropertyName("type")] 103 | public EventType? Type { get; init; } 104 | 105 | /// 106 | /// Returns a string that represents the current object. 107 | /// 108 | /// A string that represents the current object. 109 | public override string ToString() 110 | { 111 | return $"TransactionId: {TransactionId}, ShopId: {ShopId}, Time: {Time}, Type: {Type}"; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Request/Location.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace MaxMind.MinFraud.Request 6 | { 7 | /// 8 | /// The location information for the transaction being sent to the 9 | /// web service. 10 | /// 11 | public abstract class Location 12 | { 13 | private static readonly Regex CountryRe = new("^[A-Z]{2}$", RegexOptions.Compiled); 14 | private readonly string? _country; 15 | 16 | /// 17 | /// Constructor. 18 | /// 19 | /// The first name of the end user as provided in their address information. 20 | /// The last name of the end user as provided in their address information. 21 | /// The company of the end user as provided in their address information. 22 | /// The first line of the user’s address. 23 | /// The second line of the user’s address. 24 | /// The city of the user’s address. 25 | /// The ISO 3166-2 26 | /// subdivision code for the user’s address. 27 | /// The two character ISO 28 | /// 3166-1 alpha-2 country code of the user’s address. 29 | /// The postal code of the user’s address. 30 | /// The phone number without the country code for the user’s address. 31 | /// The country code for phone number associated with the user’s address. 32 | protected Location( 33 | string? firstName = null, 34 | string? lastName = null, 35 | string? company = null, 36 | string? address = null, 37 | string? address2 = null, 38 | string? city = null, 39 | string? region = null, 40 | string? country = null, 41 | string? postal = null, 42 | string? phoneNumber = null, 43 | string? phoneCountryCode = null 44 | ) 45 | { 46 | FirstName = firstName; 47 | LastName = lastName; 48 | Company = company; 49 | Address = address; 50 | Address2 = address2; 51 | City = city; 52 | Region = region; 53 | Country = country; 54 | Postal = postal; 55 | PhoneNumber = phoneNumber; 56 | PhoneCountryCode = phoneCountryCode; 57 | } 58 | 59 | /// 60 | /// The first name associated with the address. 61 | /// 62 | [JsonPropertyName("first_name")] 63 | public string? FirstName { get; init; } 64 | 65 | /// 66 | /// The last name associated with the address. 67 | /// 68 | [JsonPropertyName("last_name")] 69 | public string? LastName { get; init; } 70 | 71 | /// 72 | /// The company name associated with the address. 73 | /// 74 | [JsonPropertyName("company")] 75 | public string? Company { get; init; } 76 | 77 | /// 78 | /// The first line of the address. 79 | /// 80 | [JsonPropertyName("address")] 81 | public string? Address { get; init; } 82 | 83 | /// 84 | /// The second line of the address. 85 | /// 86 | [JsonPropertyName("address_2")] 87 | public string? Address2 { get; init; } 88 | 89 | /// 90 | /// The city associated with the address. 91 | /// 92 | [JsonPropertyName("city")] 93 | public string? City { get; init; } 94 | 95 | /// 96 | /// The ISO 3166-2 subdivision code for the region associated 97 | /// with the address. 98 | /// 99 | [JsonPropertyName("region")] 100 | public string? Region { get; init; } 101 | 102 | /// 103 | /// The ISO 3166-1 alpha-2 country code for the country 104 | /// associated with the address (e.g., "US") 105 | /// 106 | [JsonPropertyName("country")] 107 | public string? Country 108 | { 109 | get => _country; 110 | init 111 | { 112 | if (value != null && !CountryRe.IsMatch(value)) 113 | { 114 | throw new ArgumentException("Expected two-letter country code in the ISO 3166-1 alpha-2 format"); 115 | } 116 | _country = value; 117 | } 118 | } 119 | 120 | /// 121 | /// The postal code for associated with the address. 122 | /// 123 | [JsonPropertyName("postal")] 124 | public string? Postal { get; init; } 125 | 126 | /// 127 | /// The phone country code for the phone number associated with the address. 128 | /// 129 | [JsonPropertyName("phone_number")] 130 | public string? PhoneNumber { get; init; } 131 | 132 | /// 133 | /// The phone number, without the country code, associated with the address. 134 | /// 135 | [JsonPropertyName("phone_country_code")] 136 | public string? PhoneCountryCode { get; init; } 137 | 138 | /// 139 | /// Returns a string that represents the current object. 140 | /// 141 | /// A string that represents the current object. 142 | public override string ToString() 143 | { 144 | return 145 | $"FirstName: {FirstName}, LastName: {LastName}, Company: {Company}, Address: {Address}, Address2: {Address2}, Region: {Region}, Country: {Country}, Postal: {Postal}, City: {City}, PhoneNumber: {PhoneNumber}, PhoneCountryCode: {PhoneCountryCode}"; 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Request/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace MaxMind.MinFraud.Request 6 | { 7 | /// 8 | /// The order information for the transaction being sent to the 9 | /// web service. 10 | /// 11 | public sealed class Order 12 | { 13 | private static readonly Regex CurrencyRe = new("^[A-Z]{3}$", RegexOptions.Compiled); 14 | private readonly string? _currency; 15 | 16 | /// 17 | /// Constructor. 18 | /// 19 | /// The total order amount for the transaction. 20 | /// The ISO 4217 currency code for the currency 21 | /// used in the transaction. 22 | /// The discount code applied to the 23 | /// transaction. If multiple discount codes were used, please separate 24 | /// them with a comma. 25 | /// The ID of the affiliate where the order is 26 | /// coming from. 27 | /// The ID of the sub-affiliate where the 28 | /// order is coming from. 29 | /// The URI of the referring site for this 30 | /// order. 31 | /// Whether order was marked as a gift by the 32 | /// purchaser. 33 | /// Whether the purchaser included a gift 34 | /// message. 35 | public Order( 36 | decimal? amount = null, 37 | string? currency = null, 38 | string? discountCode = null, 39 | string? affiliateId = null, 40 | string? subaffiliateId = null, 41 | Uri? referrerUri = null, 42 | bool? isGift = null, 43 | bool? hasGiftMessage = null 44 | ) 45 | { 46 | Amount = amount; 47 | Currency = currency; 48 | DiscountCode = discountCode; 49 | AffiliateId = affiliateId; 50 | SubaffiliateId = subaffiliateId; 51 | ReferrerUri = referrerUri; 52 | IsGift = isGift; 53 | HasGiftMessage = hasGiftMessage; 54 | } 55 | 56 | /// 57 | /// The total order amount for the transaction. 58 | /// 59 | [JsonPropertyName("amount")] 60 | public decimal? Amount { get; init; } 61 | 62 | /// 63 | /// The ISO 4217 currency code for the currency used in the transaction. 64 | /// 65 | [JsonPropertyName("currency")] 66 | public string? Currency 67 | { 68 | get => _currency; 69 | init 70 | { 71 | if (value != null && !CurrencyRe.IsMatch(value)) 72 | { 73 | throw new ArgumentException($"The currency code {value} is invalid."); 74 | } 75 | _currency = value; 76 | } 77 | } 78 | 79 | /// 80 | /// The discount code applied to the transaction. If multiple discount 81 | /// codes were used, please separate them with a comma. 82 | /// 83 | [JsonPropertyName("discount_code")] 84 | public string? DiscountCode { get; init; } 85 | 86 | /// 87 | /// The ID of the affiliate where the order is coming from. 88 | /// 89 | [JsonPropertyName("affiliate_id")] 90 | public string? AffiliateId { get; init; } 91 | 92 | /// 93 | /// The ID of the sub-affiliate where the order is coming from. 94 | /// 95 | [JsonPropertyName("subaffiliate_id")] 96 | public string? SubaffiliateId { get; init; } 97 | 98 | /// 99 | /// The URI of the referring site for this order. 100 | /// 101 | [JsonPropertyName("referrer_uri")] 102 | public Uri? ReferrerUri { get; init; } 103 | 104 | /// 105 | /// Whether order was marked as a gift by the purchaser. 106 | /// 107 | [JsonPropertyName("is_gift")] 108 | public bool? IsGift { get; init; } 109 | 110 | /// 111 | /// Whether the purchaser included a gift message. 112 | /// 113 | [JsonPropertyName("has_gift_message")] 114 | public bool? HasGiftMessage { get; init; } 115 | 116 | /// 117 | /// Returns a string that represents the current object. 118 | /// 119 | /// A string that represents the current object. 120 | public override string ToString() 121 | { 122 | return 123 | $"Amount: {Amount}, Currency: {Currency}, DiscountCode: {DiscountCode}, AffiliateId: {AffiliateId}, SubaffiliateId: {SubaffiliateId}, ReferrerUri: {ReferrerUri}, IsGift: {IsGift}, HasGiftMessage: {HasGiftMessage}"; 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/Request/Shipping.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Util; 2 | using System.Runtime.Serialization; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MaxMind.MinFraud.Request 6 | { 7 | /// 8 | /// Enumeration of shipping speeds supported by minFraud. 9 | /// 10 | public enum ShippingDeliverySpeed 11 | { 12 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member 13 | [EnumMember(Value = "same_day")] SameDay, 14 | 15 | [EnumMember(Value = "overnight")] Overnight, 16 | 17 | [EnumMember(Value = "expedited")] Expedited, 18 | 19 | [EnumMember(Value = "standard")] Standard 20 | #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member 21 | } 22 | 23 | /// 24 | /// The shipping information for the transaction being sent to the 25 | /// web service. 26 | /// 27 | public sealed class Shipping : Location 28 | { 29 | /// 30 | /// Constructor. 31 | /// 32 | /// The first name of the end user as provided in their shipping information. 33 | /// The last name of the end user as provided in their shipping information. 34 | /// The company of the end user as provided in their shipping information. 35 | /// The first line of the user’s shipping address. 36 | /// The second line of the user’s shipping address. 37 | /// The city of the user’s shipping address. 38 | /// The ISO 3166-2 39 | /// subdivision code for the user’s shipping address. 40 | /// The two character ISO 41 | /// 3166-1 alpha-2 country code of the user’s shipping address. 42 | /// The postal code of the user’s shipping address. 43 | /// The phone number without the country code for the user’s shipping address. 44 | /// The country code for phone number associated with the user’s shipping address. 45 | /// The shipping delivery speed for the order. 46 | public Shipping( 47 | string? firstName = null, 48 | string? lastName = null, 49 | string? company = null, 50 | string? address = null, 51 | string? address2 = null, 52 | string? city = null, 53 | string? region = null, 54 | string? country = null, 55 | string? postal = null, 56 | string? phoneNumber = null, 57 | string? phoneCountryCode = null, 58 | ShippingDeliverySpeed? deliverySpeed = null 59 | ) : base( 60 | firstName: firstName, 61 | lastName: lastName, 62 | company: company, 63 | address: address, 64 | address2: address2, 65 | city: city, 66 | region: region, 67 | country: country, 68 | postal: postal, 69 | phoneNumber: phoneNumber, 70 | phoneCountryCode: phoneCountryCode 71 | ) 72 | { 73 | DeliverySpeed = deliverySpeed; 74 | } 75 | 76 | /// 77 | /// The shipping delivery speed for the order. 78 | /// 79 | [JsonConverter(typeof(EnumMemberValueConverter))] 80 | [JsonPropertyName("delivery_speed")] 81 | public ShippingDeliverySpeed? DeliverySpeed { get; init; } 82 | 83 | /// 84 | /// Returns a string that represents the current object. 85 | /// 86 | /// A string that represents the current object. 87 | public override string ToString() 88 | { 89 | return $"{base.ToString()}, DeliverySpeed: {DeliverySpeed}"; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Request/ShoppingCartItem.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MaxMind.MinFraud.Request 4 | { 5 | /// 6 | /// Information for an item in the shopping cart for the transaction 7 | /// being sent to the web service. 8 | /// 9 | public sealed class ShoppingCartItem 10 | { 11 | /// 12 | /// Constructor 13 | /// 14 | /// The category of the item. 15 | /// Your internal ID for the item. 16 | /// The quantity of the item in the shopping cart. 17 | /// This must be positive 18 | /// The price of the item in the shopping cart. This 19 | /// should be the same currency as the order currency. 20 | public ShoppingCartItem( 21 | string? category = null, 22 | string? itemId = null, 23 | int? quantity = null, 24 | decimal? price = null 25 | ) 26 | { 27 | Category = category; 28 | ItemId = itemId; 29 | Quantity = quantity; 30 | Price = price; 31 | } 32 | 33 | /// 34 | /// The category of the item. 35 | /// 36 | [JsonPropertyName("category")] 37 | public string? Category { get; init; } 38 | 39 | /// 40 | /// Your internal ID for the item. 41 | /// 42 | [JsonPropertyName("item_id")] 43 | public string? ItemId { get; init; } 44 | 45 | /// 46 | /// The quantity of the item in the shopping cart. 47 | /// 48 | [JsonPropertyName("quantity")] 49 | public int? Quantity { get; init; } 50 | 51 | /// 52 | /// The per-unit price of the item in the shopping cart. This should 53 | /// use the same currency as the order currency. 54 | /// 55 | [JsonPropertyName("price")] 56 | public decimal? Price { get; init; } 57 | 58 | /// 59 | /// Returns a string that represents the current object. 60 | /// 61 | /// A string that represents the current object. 62 | public override string ToString() 63 | { 64 | return $"Category: {Category}, ItemId: {ItemId}, Quantity: {Quantity}, Price: {Price}"; 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/Request/Transaction.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace MaxMind.MinFraud.Request 5 | { 6 | /// 7 | /// The transaction to be sent to the web service. 8 | /// 9 | public sealed class Transaction 10 | { 11 | /// 12 | /// Constructor. See 13 | /// 14 | /// the minFraud documentation 15 | /// 16 | /// for a general overview of the request sent to the web 17 | /// service. 18 | /// 19 | /// Information about the device used in the transaction. 20 | /// Information about the account used in the transaction. 21 | /// Billing information used in the transaction. 22 | /// Information about the credit card used in the transaction. 23 | /// Custom inputs as configured on your account portal. 24 | /// Information about the email used in the transaction. 25 | /// Details about the event such as the time. 26 | /// Details about the order. 27 | /// Information about the payment processing. 28 | /// Shipping information used in the transaction. 29 | /// List of shopping items in the transaction. 30 | public Transaction( 31 | Device? device = null, 32 | Account? account = null, 33 | Billing? billing = null, 34 | CreditCard? creditCard = null, 35 | CustomInputs? customInputs = null, 36 | Email? email = null, 37 | Event? userEvent = null, 38 | Order? order = null, 39 | Payment? payment = null, 40 | Shipping? shipping = null, 41 | IList? shoppingCart = default(List) 42 | ) 43 | { 44 | Device = device; 45 | Account = account; 46 | Billing = billing; 47 | CreditCard = creditCard; 48 | CustomInputs = customInputs; 49 | Email = email; 50 | Event = userEvent; 51 | Order = order; 52 | Payment = payment; 53 | Shipping = shipping; 54 | ShoppingCart = shoppingCart; 55 | } 56 | 57 | /// 58 | /// Account information for the transaction. 59 | /// 60 | [JsonPropertyName("account")] 61 | public Account? Account { get; init; } 62 | 63 | /// 64 | /// Information about the account used in the transaction. 65 | /// 66 | [JsonPropertyName("billing")] 67 | public Billing? Billing { get; init; } 68 | 69 | /// 70 | /// Information about the credit card used in the transaction. 71 | /// 72 | [JsonPropertyName("credit_card")] 73 | public CreditCard? CreditCard { get; init; } 74 | 75 | /// 76 | /// Custom inputs as configured on your account portal. 77 | /// 78 | [JsonPropertyName("custom_inputs")] 79 | public CustomInputs? CustomInputs { get; init; } 80 | 81 | /// 82 | /// Information about the device used in the transaction. 83 | /// 84 | [JsonPropertyName("device")] 85 | public Device? Device { get; init; } 86 | 87 | /// 88 | /// Information about the email used in the transaction. 89 | /// 90 | [JsonPropertyName("email")] 91 | public Email? Email { get; init; } 92 | 93 | /// 94 | /// Details about the event such as the time. 95 | /// 96 | [JsonPropertyName("event")] 97 | public Event? Event { get; init; } 98 | 99 | /// 100 | /// Details about the order. 101 | /// 102 | [JsonPropertyName("order")] 103 | public Order? Order { get; init; } 104 | 105 | /// 106 | /// Information about the payment processing. 107 | /// 108 | [JsonPropertyName("payment")] 109 | public Payment? Payment { get; init; } 110 | 111 | /// 112 | /// Shipping information used in the transaction. 113 | /// 114 | [JsonPropertyName("shipping")] 115 | public Shipping? Shipping { get; init; } 116 | 117 | /// 118 | /// List of shopping items in the transaction. 119 | /// 120 | [JsonPropertyName("shopping_cart")] 121 | public IList? ShoppingCart { get; init; } 122 | 123 | /// 124 | /// Returns a string that represents the current object. 125 | /// 126 | /// A string that represents the current object. 127 | public override string ToString() 128 | { 129 | return 130 | $"Account: {Account}, Billing: {Billing}, CreditCard: {CreditCard}, Device: {Device}, Email: {Email}, Event: {Event}, Order: {Order}, Payment: {Payment}, Shipping: {Shipping}, ShoppingCart: {ShoppingCart}"; 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/Address.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MaxMind.MinFraud.Response 4 | { 5 | /// 6 | /// General address response data. 7 | /// 8 | public abstract class Address 9 | { 10 | /// 11 | /// This property is true if the address is in the 12 | /// IP country. The property is false when the address is not in the IP 13 | /// country. If the address could not be parsed or was not provided or if the 14 | /// IP address could not be geolocated, the property will be null. 15 | /// 16 | [JsonPropertyName("is_in_ip_country")] 17 | public bool? IsInIPCountry { get; init; } 18 | 19 | /// 20 | /// This property is true if the postal code 21 | /// provided with the address is in the city for the address. The property is 22 | /// false when the postal code is not in the city. If the address was 23 | /// not provided or could not be parsed, the property will be null. 24 | /// 25 | [JsonPropertyName("is_postal_in_city")] 26 | public bool? IsPostalInCity { get; init; } 27 | 28 | /// 29 | /// The latitude associated with the address. 30 | /// 31 | [JsonPropertyName("latitude")] 32 | public double? Latitude { get; init; } 33 | 34 | /// 35 | /// The longitude associated with the address. 36 | /// 37 | [JsonPropertyName("longitude")] 38 | public double? Longitude { get; init; } 39 | 40 | /// 41 | /// The distance in kilometers from the address to the IP location. 42 | /// 43 | [JsonPropertyName("distance_to_ip_location")] 44 | public int? DistanceToIPLocation { get; init; } 45 | 46 | /// 47 | /// Returns a string that represents the current object. 48 | /// 49 | /// A string that represents the current object. 50 | public override string ToString() 51 | { 52 | return 53 | $"IsInIPCountry: {IsInIPCountry}, IsPostalInCity: {IsPostalInCity}, Latitude: {Latitude}, Longitude: {Longitude}, DistanceToIPLocation: {DistanceToIPLocation}"; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/BillingAddress.cs: -------------------------------------------------------------------------------- 1 | namespace MaxMind.MinFraud.Response 2 | { 3 | /// 4 | /// Information about the billing address. 5 | /// 6 | public sealed class BillingAddress : Address 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/CreditCard.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MaxMind.MinFraud.Response 4 | { 5 | /// 6 | /// Information about the credit card based on the issuer ID number. 7 | /// 8 | public sealed class CreditCard 9 | { 10 | /// 11 | /// The credit card brand. 12 | /// 13 | [JsonPropertyName("brand")] 14 | public string? Brand { get; init; } 15 | 16 | /// 17 | /// The two letter 18 | /// ISO 3166-1 alpha-2 country code associated with the location 19 | /// of the majority of customers using this credit card as determined 20 | /// by their billing address. In cases where the location of customers 21 | /// is highly mixed, this defaults to the country of the bank issuing 22 | /// the card. 23 | /// 24 | [JsonPropertyName("country")] 25 | public string? Country { get; init; } 26 | 27 | /// 28 | /// This property is true if the card is a business card. 29 | /// 30 | [JsonPropertyName("is_business")] 31 | public bool? IsBusiness { get; init; } 32 | 33 | /// 34 | /// This field is true if the country of the billing address 35 | /// matches the country of the majority of customers using that IIN. 36 | /// In cases where the location of customers is highly mixed, the 37 | /// match is to the country of the bank issuing the card. 38 | /// 39 | [JsonPropertyName("is_issued_in_billing_address_country")] 40 | public bool? IsIssuedInBillingAddressCountry { get; init; } 41 | 42 | /// 43 | /// This property is true if the card is a prepaid card. 44 | /// 45 | [JsonPropertyName("is_prepaid")] 46 | public bool? IsPrepaid { get; init; } 47 | 48 | /// 49 | /// This property is true if the card is a virtual card. 50 | /// 51 | [JsonPropertyName("is_virtual")] 52 | public bool? IsVirtual { get; init; } 53 | 54 | /// 55 | /// An object containing information about the credit card issuer. 56 | /// 57 | [JsonPropertyName("issuer")] 58 | public Issuer Issuer { get; init; } = new Issuer(); 59 | 60 | /// 61 | /// The credit card type. 62 | /// 63 | [JsonPropertyName("type")] 64 | public string? Type { get; init; } 65 | 66 | /// 67 | /// Returns a string that represents the current object. 68 | /// 69 | /// A string that represents the current object. 70 | public override string ToString() 71 | { 72 | return $"{nameof(Brand)}: {Brand}, {nameof(Country)}: {Country}, {nameof(IsBusiness)}: {IsBusiness}, {nameof(IsIssuedInBillingAddressCountry)}: {IsIssuedInBillingAddressCountry}, {nameof(IsPrepaid)}: {IsPrepaid}, {nameof(IsVirtual)}: {IsVirtual}, {nameof(Issuer)}: {Issuer}, {nameof(Type)}: {Type}"; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/Device.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace MaxMind.MinFraud.Response 5 | { 6 | /// 7 | /// This object contains information about the device that MaxMind 8 | /// believes is associated with the IP address passed in the request. 9 | /// In order to receive device output from minFraud Insights or 10 | /// minFraud Factors, you must be using the 11 | /// Device 12 | /// Tracking Add-on. 13 | /// 14 | public sealed class Device 15 | { 16 | /// 17 | /// A number representing the confidence that the DeviceId 18 | /// refers to a unique device as opposed to a cluster of similar 19 | /// devices. A confidence of 0.01 indicates very low confidence that 20 | /// the device is unique, whereas 99 indicates very high confidence. 21 | /// 22 | [JsonPropertyName("confidence")] 23 | public double? Confidence { get; init; } 24 | 25 | /// 26 | /// A UUID that MaxMind uses for the device associated with this IP 27 | /// address. 28 | /// 29 | [JsonPropertyName("id")] 30 | public Guid? Id { get; init; } 31 | 32 | /// 33 | /// The date and time of the last sighting of the device. 34 | /// 35 | [JsonPropertyName("last_seen")] 36 | public DateTimeOffset? LastSeen { get; init; } 37 | 38 | /// 39 | /// The local date and time of the transaction in the time zone of 40 | /// the device. This is determined by using the UTC offset associated 41 | /// with the device. This is an RFC 3339 date-time 42 | /// 43 | [JsonPropertyName("local_time")] 44 | public DateTimeOffset? LocalTime { get; init; } 45 | 46 | /// 47 | /// Returns a string that represents the current object. 48 | /// 49 | public override string ToString() 50 | { 51 | return $"Confidence: {Confidence}, Id: {Id}, LastSeen: {LastSeen}, LocalTime: {LocalTime}"; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/Disposition.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MaxMind.MinFraud.Response 4 | { 5 | /// 6 | /// This object contains the disposition set by custom rules. 7 | /// 8 | public sealed class Disposition 9 | { 10 | /// 11 | /// The action to take on the transaction as defined by your custom 12 | /// rules. The current set of values are "accept", "manual_review", 13 | /// "reject" and "test". If you do not have custom rules set up, 14 | /// null will be returned. 15 | /// 16 | [JsonPropertyName("action")] 17 | public string? Action { get; init; } 18 | 19 | /// 20 | /// The reason for the action. The current possible values are 21 | /// "custom_rule" and "default". If you do not have custom rules set 22 | /// up, null will be returned. 23 | /// 24 | [JsonPropertyName("reason")] 25 | public string? Reason { get; init; } 26 | 27 | /// 28 | /// The label of the custom rule that was triggered. If you do not have 29 | /// custom rules set up, the triggered custom rule does not have a 30 | /// label, or no custom rule was triggered, null will be 31 | /// returned. 32 | /// 33 | [JsonPropertyName("rule_label")] 34 | public string? RuleLabel { get; init; } 35 | 36 | /// 37 | /// Returns a string that represents the current object. 38 | /// 39 | public override string ToString() 40 | { 41 | return $"Action: {Action}, Reason: {Reason}, Rule Label: {RuleLabel}"; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/Email.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Util; 2 | using System; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MaxMind.MinFraud.Response 6 | { 7 | /// 8 | /// This object contains information about the email address passed in the request. 9 | /// 10 | public sealed class Email 11 | { 12 | /// 13 | /// An object containing information about the email address domain. 14 | /// 15 | [JsonPropertyName("domain")] 16 | public EmailDomain Domain { get; init; } = new EmailDomain(); 17 | 18 | /// 19 | /// The date the email address was first seen by MaxMind. 20 | /// 21 | [JsonPropertyName("first_seen")] 22 | [JsonConverter(typeof(DateConverter))] 23 | public DateTimeOffset? FirstSeen { get; init; } 24 | 25 | /// 26 | /// This property incidates whether the email is from a disposable 27 | /// email provider. The value will be null if no email address 28 | /// or email domain was passed as an input. 29 | /// 30 | [JsonPropertyName("is_disposable")] 31 | public bool? IsDisposable { get; init; } 32 | 33 | /// 34 | /// This property is true if MaxMind believes that this email is hosted by a free 35 | /// email provider such as Gmail or Yahoo! Mail. 36 | /// 37 | [JsonPropertyName("is_free")] 38 | public bool? IsFree { get; init; } 39 | 40 | /// 41 | /// This property is true if MaxMind believes that this email is likely to be used 42 | /// for fraud. Note that this is also factored into the overall risk_score in the 43 | /// response as well. 44 | /// 45 | [JsonPropertyName("is_high_risk")] 46 | public bool? IsHighRisk { get; init; } 47 | 48 | /// 49 | /// Returns a string that represents the current object. 50 | /// 51 | public override string ToString() 52 | { 53 | return $"Domain: {Domain}, FirstSeen: {FirstSeen}, IsDisposable: {IsDisposable}, IsFree: {IsFree}, IsHighRiskFree: {IsHighRisk}"; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/EmailDomain.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Util; 2 | using System; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MaxMind.MinFraud.Response 6 | { 7 | /// 8 | /// This object contains information about the email address domain passed 9 | /// in the request. 10 | /// 11 | public sealed class EmailDomain 12 | { 13 | /// 14 | /// The date the email address domain was first seen by MaxMind. 15 | /// 16 | [JsonPropertyName("first_seen")] 17 | [JsonConverter(typeof(DateConverter))] 18 | public DateTimeOffset? FirstSeen { get; init; } 19 | 20 | /// 21 | /// Returns a string that represents the current object. 22 | /// 23 | public override string ToString() 24 | { 25 | return $"FirstSeen: {FirstSeen}"; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/Factors.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MaxMind.MinFraud.Response 6 | { 7 | /// 8 | /// Model for Insights response. 9 | /// 10 | public sealed class Factors : Insights 11 | { 12 | /// 13 | /// This list contains objects that describe risk score reasons for a given transaction 14 | /// that change the risk score significantly. Risk score reasons are usually only 15 | /// returned for medium to high risk transactions. If there were no significant 16 | /// changes to the risk score due to these reasons, then this list will be empty. 17 | /// 18 | [JsonPropertyName("risk_score_reasons")] 19 | public IReadOnlyList RiskScoreReasons { get; init; } 20 | = new List().AsReadOnly(); 21 | 22 | /// 23 | /// An object containing the risk factor scores for many of the 24 | /// individual components that are used in calculating the overall 25 | /// risk score. 26 | /// 27 | [JsonPropertyName("subscores")] 28 | [Obsolete("Replaced by RiskScoreReasons")] 29 | public Subscores Subscores { get; init; } = new Subscores(); 30 | 31 | /// 32 | /// Returns a string that represents the current object. 33 | /// 34 | /// A string that represents the current object. 35 | public override string ToString() 36 | { 37 | #pragma warning disable 0618 38 | return $"{base.ToString()}, Subscores: {Subscores}, RiskScoreReasons: {RiskScoreReasons}"; 39 | #pragma warning restore 0618 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/GeoIP2Location.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.GeoIP2.Model; 2 | using System; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MaxMind.MinFraud.Response 6 | { 7 | /// 8 | /// A subclass of the GeoIP2 Location model with minFraud-specific 9 | /// additions. 10 | /// 11 | public sealed class GeoIP2Location : Location 12 | { 13 | /// 14 | /// The date and time of the transaction in the time 15 | /// zone associated with the IP address. 16 | /// 17 | [JsonPropertyName("local_time")] 18 | public DateTimeOffset? LocalTime { get; init; } 19 | 20 | /// 21 | /// Returns a string that represents the current object. 22 | /// 23 | /// A string that represents the current object. 24 | public override string ToString() 25 | { 26 | return $"{base.ToString()}, LocalTime: {LocalTime}"; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/IIPAddress.cs: -------------------------------------------------------------------------------- 1 | namespace MaxMind.MinFraud.Response 2 | { 3 | /// 4 | /// An interface for IP address risk classes. 5 | /// 6 | public interface IIPAddress 7 | { 8 | /// 9 | /// The risk associated with the IP address. The value ranges from 0.01 10 | /// to 99. A higher score indicates a higher risk. 11 | /// 12 | double? Risk { get; init; } 13 | } 14 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/IPAddress.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.GeoIP2.Responses; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text.Json.Serialization; 5 | 6 | namespace MaxMind.MinFraud.Response 7 | { 8 | /// 9 | /// Model for minFraud GeoIP2 Insights data. 10 | /// 11 | public sealed class IPAddress : InsightsResponse, IIPAddress 12 | { 13 | /// 14 | /// Location object for the requested IP address. 15 | /// 16 | [JsonPropertyName("location")] 17 | public new GeoIP2Location Location { get; init; } = new GeoIP2Location(); 18 | 19 | /// 20 | /// This property is not provided by minFraud. 21 | /// 22 | [JsonIgnore] 23 | [Obsolete("This is not provided in a minFraud response.")] 24 | public new GeoIP2.Model.MaxMind MaxMind { get; init; } = new GeoIP2.Model.MaxMind(); 25 | 26 | /// 27 | /// The risk associated with the IP address. The value ranges from 0.01 28 | /// to 99. A higher score indicates a higher risk. 29 | /// 30 | [JsonPropertyName("risk")] 31 | public double? Risk { get; init; } 32 | 33 | /// 34 | /// This list contains objects identifying the reasons why the IP 35 | /// address received the associated risk. This will be an empty list 36 | /// if there are no reasons. 37 | /// 38 | [JsonPropertyName("risk_reasons")] 39 | public IReadOnlyList RiskReasons { get; init; } 40 | = new List().AsReadOnly(); 41 | 42 | internal new void SetLocales(IReadOnlyList locales) 43 | { 44 | var l = new List(locales); 45 | base.SetLocales(l); 46 | } 47 | 48 | /// 49 | /// Returns a string that represents the current object. 50 | /// 51 | /// A string that represents the current object. 52 | public override string ToString() 53 | { 54 | return $"{base.ToString()}, Country: {Country}, Location: {Location}"; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/IPRiskReason.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MaxMind.MinFraud.Response 4 | { 5 | /// 6 | /// Reason for the IP risk. 7 | /// 8 | /// 9 | /// This class provides both a machine-readable code and a human-readable 10 | /// explanation of the reason for the IP risk score. 11 | /// 12 | public sealed class IPRiskReason 13 | { 14 | /// 15 | /// This property is a machine-readable code identifying the reason. 16 | /// 17 | /// 18 | /// Although more codes may be added in the future, the current codes are: 19 | /// 20 | /// 21 | /// 22 | /// ANONYMOUS_IP 23 | /// The IP address belongs to an anonymous network. See the 24 | /// object at .IPAddress.Traits for more details. 25 | /// 26 | /// 27 | /// BILLING_POSTAL_VELOCITY 28 | /// Many different billing postal codes have been seen on 29 | /// this IP address. 30 | /// 31 | /// 32 | /// EMAIL_VELOCITY 33 | /// Many different email addresses have been seen on this 34 | /// IP address. 35 | /// 36 | /// 37 | /// HIGH_RISK_DEVICE 38 | /// A high risk device was seen on this IP address. 39 | /// 40 | /// 41 | /// HIGH_RISK_EMAIL 42 | /// A high risk email address was seen on this IP address in 43 | /// your past transactions. 44 | /// 45 | /// 46 | /// ISSUER_ID_NUMBER_VELOCITY 47 | /// Many different issuer ID numbers have been seen on this 48 | /// IP address. 49 | /// 50 | /// 51 | /// MINFRAUD_NETWORK_ACTIVITY 52 | /// Suspicious activity has been seen on this IP address 53 | /// across minFraud customers. 54 | /// 55 | /// 56 | /// 57 | [JsonPropertyName("code")] 58 | public string? Code { get; init; } 59 | 60 | /// 61 | /// This property provides a human-readable explanation of the reason. 62 | /// The description may change at any time and should not be matched 63 | /// against. 64 | /// 65 | [JsonPropertyName("reason")] 66 | public string? Reason { get; init; } 67 | 68 | /// 69 | /// Returns a string that represents the current object. 70 | /// 71 | /// A string that represents the current object. 72 | public override string ToString() 73 | { 74 | return $"Code: {Code}, Reason: {Reason}"; 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/Insights.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MaxMind.MinFraud.Response 4 | { 5 | /// 6 | /// Model for Insights response. 7 | /// 8 | public class Insights : Score 9 | { 10 | /// 11 | /// An object containing GeoIP2 and minFraud Insights information about 12 | /// the IP address. 13 | /// 14 | [JsonPropertyName("ip_address")] 15 | public new IPAddress IPAddress { get; init; } = new IPAddress(); 16 | 17 | /// 18 | /// An object containing minFraud data about the credit card used in 19 | /// the transaction. 20 | /// 21 | [JsonPropertyName("credit_card")] 22 | public CreditCard CreditCard { get; init; } = new CreditCard(); 23 | 24 | /// 25 | /// This object contains information about the device that MaxMind 26 | /// believes is associated with the IP address passed in the request. 27 | /// 28 | [JsonPropertyName("device")] 29 | public Device Device { get; init; } = new Device(); 30 | 31 | /// 32 | /// This object contains information about the email address passed in 33 | /// the request. 34 | /// 35 | [JsonPropertyName("email")] 36 | public Email Email { get; init; } = new Email(); 37 | 38 | /// 39 | /// An object containing minFraud data related to the shipping address 40 | /// used in the transaction. 41 | /// 42 | [JsonPropertyName("shipping_address")] 43 | public ShippingAddress ShippingAddress { get; init; } = new ShippingAddress(); 44 | 45 | /// 46 | /// An object containing minFraud data related to the shipping phone 47 | /// used in the transaction. 48 | /// 49 | [JsonPropertyName("shipping_phone")] 50 | public Phone ShippingPhone { get; init; } = new Phone(); 51 | 52 | /// 53 | /// An object containing minFraud data related to the billing address 54 | /// used in the transaction. 55 | /// 56 | [JsonPropertyName("billing_address")] 57 | public BillingAddress BillingAddress { get; init; } = new BillingAddress(); 58 | 59 | /// 60 | /// An object containing minFraud data related to the billing phone 61 | /// used in the transaction. 62 | /// 63 | [JsonPropertyName("billing_phone")] 64 | public Phone BillingPhone { get; init; } = new Phone(); 65 | 66 | /// 67 | /// Returns a string that represents the current object. 68 | /// 69 | /// A string that represents the current object. 70 | public override string ToString() 71 | { 72 | return $"{base.ToString()}, IPAddress: {IPAddress}, CreditCard: {CreditCard}, Device: {Device}, Email: {Email}, ShippingAddress: {ShippingAddress}, ShippingPhone: {ShippingPhone}, BillingAddress: {BillingAddress}, BillingPhone: {BillingPhone}"; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/Issuer.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MaxMind.MinFraud.Response 4 | { 5 | /// 6 | /// Model for the credit card issuer data from minFraud. 7 | /// 8 | public sealed class Issuer 9 | { 10 | /// 11 | /// The name of the bank which issued the credit card. 12 | /// 13 | [JsonPropertyName("name")] 14 | public string? Name { get; init; } 15 | 16 | /// 17 | /// This property is true if the name matches the name 18 | /// provided in the request for the card issuer. It is false 19 | /// if the name does not match. The property is null if 20 | /// either no name or no issuer ID number (IIN) was provided in the 21 | /// request or if MaxMind does not have a name associated with the IIN. 22 | /// 23 | [JsonPropertyName("matches_provided_name")] 24 | public bool? MatchesProvidedName { get; init; } 25 | 26 | /// 27 | /// The phone number of the bank which issued the credit card. In some 28 | /// cases the phone number we return may be out of date. 29 | /// 30 | [JsonPropertyName("phone_number")] 31 | public string? PhoneNumber { get; init; } 32 | 33 | /// 34 | /// This property is true if the phone number matches 35 | /// the number provided in the request for the card issuer. It is 36 | /// false if the number does not match. It is 37 | /// null if either no phone number or no issuer ID 38 | /// number(IIN) was provided in the request or if MaxMind does not 39 | /// have a phone number associated with the IIN. 40 | /// 41 | [JsonPropertyName("matches_provided_phone_number")] 42 | public bool? MatchesProvidedPhoneNumber { get; init; } 43 | 44 | /// 45 | /// Returns a string that represents the current object. 46 | /// 47 | /// A string that represents the current object. 48 | public override string ToString() 49 | { 50 | return 51 | $"Name: {Name}, MatchesProvidedName: {MatchesProvidedName}, PhoneNumber: {PhoneNumber}, MatchesProvidedPhoneNumber: {MatchesProvidedPhoneNumber}"; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/Phone.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MaxMind.MinFraud.Response 4 | { 5 | /// 6 | /// This object contains information about the billing or shipping phone. 7 | /// 8 | public sealed class Phone 9 | { 10 | /// 11 | /// A two-character ISO 3166-1 country code for the country associated 12 | /// with the phone number. 13 | /// 14 | [JsonPropertyName("country")] 15 | public string? Country { get; init; } 16 | 17 | /// 18 | /// This is true if the phone number is a Voice over Internet 19 | /// Protocol (VoIP) number allocated by a regulator. It is false 20 | /// if the phone number is not a VoIP number allocated by a regulator. 21 | /// The property is null if a valid phone number has not been provided 22 | /// or if we have data for the phone number. 23 | /// 24 | [JsonPropertyName("is_voip")] 25 | public bool? IsVoip { get; init; } 26 | 27 | /// 28 | /// This is true if the phone number's prefix is commonly 29 | /// associated with the postal code. It is false if the prefix 30 | /// is not associated with the postal code. It is non-null only 31 | /// when the phone number is in the US, the number prefix is in our 32 | /// database, and the postal code and country 33 | /// are provided in the request. 34 | /// 35 | [JsonPropertyName("matches_postal")] 36 | public bool? MatchesPostal { get; init; } 37 | 38 | /// 39 | /// The name of the original network operator associated with the 40 | /// phone number. This property does not reflect phone numbers that 41 | /// have been ported from the original operator to another, nor does 42 | /// it identify mobile virtual network operators. 43 | /// 44 | [JsonPropertyName("network_operator")] 45 | public string? NetworkOperator { get; init; } 46 | 47 | /// 48 | /// One of the following values: fixed or mobile. 49 | /// Additional values may be added in the future. 50 | /// 51 | [JsonPropertyName("number_type")] 52 | public string? NumberType { get; init; } 53 | 54 | /// 55 | /// Returns a string that represents the current object. 56 | /// 57 | public override string ToString() 58 | { 59 | return $"Country: {Country}, IsVoip: {IsVoip}, MatchesPostal: {MatchesPostal}" + 60 | $"NetworkOperator: {NetworkOperator}, NumberType: {NumberType}"; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/RiskScoreReason.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace MaxMind.MinFraud.Response 5 | { 6 | /// 7 | /// Model for a risk score multiplier and reasons for that multiplier. 8 | /// 9 | public sealed class RiskScoreReason 10 | { 11 | /// 12 | /// The factor by which the risk score is increased (if the value is greater than 1) 13 | /// or decreased (if the value is less than 1) for given risk reason(s). 14 | /// Multipliers greater than 1.5 and less than 0.66 are considered 15 | /// significant and lead to risk reason(s) being present. 16 | /// 17 | [JsonPropertyName("multiplier")] 18 | public double? Multiplier { get; init; } 19 | 20 | /// 21 | /// This list contains objects that describe one of the reasons for the multiplier. 22 | /// 23 | [JsonPropertyName("reasons")] 24 | public IReadOnlyList Reasons { get; init; } 25 | = new List().AsReadOnly(); 26 | 27 | /// 28 | /// Returns a string that represents the current object. 29 | /// 30 | /// A string that represents the current object. 31 | public override string ToString() 32 | { 33 | return $"Multiplier: {Multiplier}, Reasons: {Reasons}"; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/Score.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.MinFraud.Util; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text.Json.Serialization; 6 | 7 | namespace MaxMind.MinFraud.Response 8 | { 9 | /// 10 | /// Model class for Score response. 11 | /// 12 | public class Score 13 | { 14 | /// 15 | /// This object contains information about the disposition set by 16 | /// custom rules. 17 | /// 18 | [JsonPropertyName("disposition")] 19 | public Disposition Disposition { get; init; } = new Disposition(); 20 | 21 | /// 22 | /// The approximate US dollar value of the funds remaining on your 23 | /// MaxMind account. 24 | /// 25 | [JsonPropertyName("funds_remaining")] 26 | public decimal? FundsRemaining { get; init; } 27 | 28 | /// 29 | /// This is a UUID that identifies the minFraud request. Please use 30 | /// this ID in support requests to MaxMind so that we can easily 31 | /// identify a particular request. 32 | /// 33 | [JsonPropertyName("id")] 34 | public Guid? Id { get; init; } 35 | 36 | /// 37 | /// An object containing information about the IP address's risk. 38 | /// 39 | [JsonPropertyName("ip_address")] 40 | [JsonConverter(typeof(ScoreIPAddressConverter))] 41 | public IIPAddress IPAddress { get; init; } = new ScoreIPAddress(); 42 | 43 | /// 44 | /// The approximate number of queries remaining for this service 45 | /// before your account runs out of funds. 46 | /// 47 | [JsonPropertyName("queries_remaining")] 48 | public long? QueriesRemaining { get; init; } 49 | 50 | /// 51 | /// This property contains the risk score, from 0.01 to 99. A 52 | /// higher score indicates a higher risk of fraud.For example, a score of 20 53 | /// indicates a 20% chance that a transaction is fraudulent.We never return a 54 | /// risk score of 0, since all transactions have the possibility of being 55 | /// fraudulent.Likewise we never return a risk score of 100. 56 | /// 57 | [JsonPropertyName("risk_score")] 58 | public double? RiskScore { get; init; } 59 | 60 | /// 61 | /// This list contains objects detailing issues with the request that 62 | /// was sent such as invalid or unknown inputs. It is highly 63 | /// recommended that you check this array for issues when integrating 64 | /// the web service. 65 | /// 66 | [JsonPropertyName("warnings")] 67 | public IReadOnlyList Warnings { get; init; } = new List().AsReadOnly(); 68 | 69 | /// 70 | /// Returns a string that represents the current object. 71 | /// 72 | /// A string that represents the current object. 73 | public override string ToString() 74 | { 75 | var warnings = string.Join("; ", Warnings.Select(x => x.Message)); 76 | return $"Warnings: [{warnings}], Disposition: {Disposition}, FundsRemaining: {FundsRemaining}, Id: {Id}, QueriesRemaining: {QueriesRemaining}, RiskScore: {RiskScore}"; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/ScoreIPAddress.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MaxMind.MinFraud.Response 4 | { 5 | /// 6 | /// The IP addresses risk. 7 | /// 8 | public sealed class ScoreIPAddress : IIPAddress 9 | { 10 | /// 11 | /// The risk associated with the IP address. The value ranges from 0.01 12 | /// to 99. A higher score indicates a higher risk. 13 | /// 14 | [JsonPropertyName("risk")] 15 | public double? Risk { get; init; } 16 | 17 | /// 18 | /// Returns a string that represents the current object. 19 | /// 20 | /// A string that represents the current object. 21 | public override string ToString() 22 | { 23 | return $"Risk: {Risk}"; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/ShippingAddress.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MaxMind.MinFraud.Response 4 | { 5 | /// 6 | /// Information about the shipping address. 7 | /// 8 | public sealed class ShippingAddress : Address 9 | { 10 | /// 11 | /// This property is true if the shipping address is in 12 | /// the IP country. The property is false when the address 13 | /// is not in the IP country. If the shipping address could not be 14 | /// parsed or was not provided or the IP address could not be 15 | /// geolocated, then the property is null. 16 | /// 17 | [JsonPropertyName("is_high_risk")] 18 | public bool? IsHighRisk { get; init; } 19 | 20 | /// 21 | /// The distance in kilometers from the shipping address to billing 22 | /// address. 23 | /// 24 | [JsonPropertyName("distance_to_billing_address")] 25 | public int? DistanceToBillingAddress { get; init; } 26 | 27 | /// 28 | /// Returns a string that represents the current object. 29 | /// 30 | /// A string that represents the current object. 31 | public override string ToString() 32 | { 33 | return $"{base.ToString()}, IsHighRisk: {IsHighRisk}, DistanceToBillingAddress: {DistanceToBillingAddress}"; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/Subscores.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace MaxMind.MinFraud.Response 5 | { 6 | /// 7 | /// This class contains scores for many of the individual risk 8 | /// factors that are used to calculate the overall risk score. 9 | /// 10 | [Obsolete("Replaced by RiskScoreReason")] 11 | public sealed class Subscores 12 | { 13 | /// 14 | /// The risk associated with the AVS result. If present, this is a 15 | /// value in the range 0.01 to 99. 16 | /// 17 | [JsonPropertyName("avs_result")] 18 | public double? AvsResult { get; init; } 19 | 20 | /// 21 | /// The risk associated with the billing address. If present, this is 22 | /// a value in the range 0.01 to 99. 23 | /// 24 | [JsonPropertyName("billing_address")] 25 | public double? BillingAddress { get; init; } 26 | 27 | /// 28 | /// The risk associated with the distance between the billing address 29 | /// and the location for the given IP address. If present, this is a 30 | /// value in the range 0.01 to 99. 31 | /// 32 | [JsonPropertyName("billing_address_distance_to_ip_location")] 33 | public double? BillingAddressDistanceToIPLocation { get; init; } 34 | 35 | /// 36 | /// The risk associated with the browser attributes such as the 37 | /// User-Agent and Accept-Language. If present, this is a value in the 38 | /// range 0.01 to 99. 39 | /// 40 | [JsonPropertyName("browser")] 41 | public double? Browser { get; init; } 42 | 43 | /// 44 | /// Individualized risk of chargeback for the given IP address on 45 | /// your account and shop ID. This is only available to users sending 46 | /// chargeback data to MaxMind. If present, this is a value in the 47 | /// range 0.01 to 99. 48 | /// 49 | [JsonPropertyName("chargeback")] 50 | public double? Chargeback { get; init; } 51 | 52 | /// 53 | /// The risk associated with the country the transaction originated 54 | /// from. If present, this is a value in the range 0.01 to 99. 55 | /// 56 | [JsonPropertyName("country")] 57 | public double? Country { get; init; } 58 | 59 | /// 60 | /// The risk associated with the combination of IP country, card 61 | /// issuer country, billing country, and shipping country. If present, 62 | /// this is a value in the range 0.01 to 99. 63 | /// 64 | [JsonPropertyName("country_mismatch")] 65 | public double? CountryMismatch { get; init; } 66 | 67 | /// 68 | /// The risk associated with the CVV result. If present, this is a 69 | /// value in the range 0.01 to 99. 70 | /// 71 | [JsonPropertyName("cvv_result")] 72 | public double? CvvResult { get; init; } 73 | 74 | /// 75 | /// The risk associated with the device. If present, this is a value 76 | /// in the range 0.01 to 99. 77 | /// 78 | [JsonPropertyName("device")] 79 | public double? Device { get; init; } 80 | 81 | /// 82 | /// The risk associated with the particular email address. If 83 | /// present, this is a value in the range 0.01 to 99. 84 | /// 85 | [JsonPropertyName("email_address")] 86 | public double? EmailAddress { get; init; } 87 | 88 | /// 89 | /// The general risk associated with the email domain. If present, 90 | /// this is a value in the range 0.01 to 99. 91 | /// 92 | [JsonPropertyName("email_domain")] 93 | public double? EmailDomain { get; init; } 94 | 95 | /// 96 | /// The risk associated with the email address local part (the part of 97 | /// the email address before the @ symbol). If present, this is a 98 | /// value in the range 0.01 to 99. 99 | /// 100 | [JsonPropertyName("email_local_part")] 101 | public double? EmailLocalPart { get; init; } 102 | 103 | /// 104 | /// The risk associated with the particular issuer ID number (IIN) 105 | /// given the billing location and the history of usage of the IIN on 106 | /// your account and shop ID. If present, this is a value in the range 107 | /// 0.01 to 99. 108 | /// 109 | [JsonPropertyName("issuer_id_number")] 110 | public double? IssuerIdNumber { get; init; } 111 | 112 | /// 113 | /// The risk associated with the particular order amount for your 114 | /// account and shop ID. If present, this is a value in the range 115 | /// 0.01 to 99. 116 | /// 117 | [JsonPropertyName("order_amount")] 118 | public double? OrderAmount { get; init; } 119 | 120 | /// 121 | /// The risk associated with the particular phone number. If present, 122 | /// this is a value in the range 0.01 to 99. 123 | /// 124 | [JsonPropertyName("phone_number")] 125 | public double? PhoneNumber { get; init; } 126 | 127 | /// 128 | /// The risk associated with the shipping address. If present, this is 129 | /// a value in the range 0.01 to 99. 130 | /// 131 | [JsonPropertyName("shipping_address")] 132 | public double? ShippingAddress { get; init; } 133 | 134 | /// 135 | /// The risk associated with the distance between the shipping address 136 | /// and the location for the given IP address. If present, this is a 137 | /// value in the range 0.01 to 99. 138 | /// 139 | [JsonPropertyName("shipping_address_distance_to_ip_location")] 140 | public double? ShippingAddressDistanceToIPLocation { get; init; } 141 | 142 | /// 143 | /// The risk associated with the local time of day of the transaction 144 | /// in the IP address location. If present, this is a value in the range 145 | /// 0.01 to 99. 146 | /// 147 | [JsonPropertyName("time_of_day")] 148 | public double? TimeOfDay { get; init; } 149 | 150 | /// 151 | /// Returns a string that represents the current object. 152 | /// 153 | /// A string that represents the current object. 154 | public override string ToString() 155 | { 156 | return $"AvsResult: {AvsResult}, BillingAddress: {BillingAddress}, BillingAddressDistanceToIPLocation: {BillingAddressDistanceToIPLocation}, Browser: {Browser}, Chargeback: {Chargeback}, Country: {Country}, CountryMismatch: {CountryMismatch}, CvvResult: {CvvResult}, EmailAddress: {EmailAddress}, EmailDomain: {EmailDomain}, IssuerIdMumber: {IssuerIdNumber}, OrderAmount: {OrderAmount}, PhoneNumber: {PhoneNumber}, ShippingAddressDistanceToIPLocation: {ShippingAddressDistanceToIPLocation}, TimeOfDay: {TimeOfDay}"; 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Response/Warning.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MaxMind.MinFraud.Response 4 | { 5 | /// 6 | /// A warning returned by the web service. 7 | /// 8 | public sealed class Warning 9 | { 10 | /// 11 | /// This value is a machine-readable code identifying the 12 | /// warning. See the 13 | /// web service documentation for the current list of warning 14 | /// codes. 15 | /// 16 | [JsonPropertyName("code")] 17 | public string? Code { get; init; } 18 | 19 | /// 20 | /// This property provides a human-readable explanation of the 21 | /// warning. The description may change at any time and should not be 22 | /// matched against. 23 | /// 24 | [JsonPropertyName("warning")] 25 | public string? Message { get; init; } 26 | 27 | /// 28 | /// A JSON Pointer to the input field that the warning is associated with. 29 | /// For instance, if the warning was about the billing city, this would be 30 | /// /billing/city. If it was for the price in the second shopping 31 | /// cart item, it would be /shopping_cart/1/price. 32 | /// 33 | [JsonPropertyName("input_pointer")] 34 | public string? InputPointer { get; init; } 35 | 36 | /// 37 | /// Returns a string that represents the current object. 38 | /// 39 | /// A string that represents the current object. 40 | public override string ToString() 41 | { 42 | return $"Code: {Code}, Message: {Message}, InputPointer: {InputPointer}"; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /MaxMind.MinFraud/Util/DateConverter.cs: -------------------------------------------------------------------------------- 1 | #region 2 | using System; 3 | using System.Globalization; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | #endregion 7 | 8 | namespace MaxMind.MinFraud.Util 9 | { 10 | internal class DateConverter : JsonConverter 11 | { 12 | private const string format = "yyyy-MM-dd"; 13 | 14 | public override DateTimeOffset? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 15 | { 16 | var v = reader.GetString(); 17 | if (v == null) 18 | { 19 | return null; 20 | } 21 | return DateTimeOffset.ParseExact( 22 | v, 23 | format, 24 | DateTimeFormatInfo.InvariantInfo, 25 | DateTimeStyles.None); 26 | } 27 | 28 | public override void Write(Utf8JsonWriter writer, DateTimeOffset? value, JsonSerializerOptions options) 29 | { 30 | writer.WriteStringValue(value?.ToString(format)); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/Util/EnumMemberValueConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Runtime.Serialization; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | 7 | namespace MaxMind.MinFraud.Util 8 | { 9 | // See https://github.com/dotnet/runtime/issues/31081. We could also switch 10 | // to a snakecase naming policy of that is added: 11 | // https://github.com/dotnet/runtime/issues/782 12 | internal class EnumMemberValueConverter : JsonConverter where T : Enum 13 | { 14 | public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) 15 | { 16 | var memInfo = typeof(T).GetMember(value.ToString()); 17 | var attr = memInfo[0].GetCustomAttributes(false).OfType().FirstOrDefault(); 18 | if (attr != null) 19 | { 20 | writer.WriteStringValue(attr.Value); 21 | return; 22 | } 23 | 24 | writer.WriteNullValue(); 25 | } 26 | 27 | public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 28 | { 29 | throw new NotImplementedException("Read is not implemented"); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/Util/IPAddressConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | 6 | namespace MaxMind.MinFraud.Util 7 | { 8 | internal class IPAddressConverter : JsonConverter 9 | { 10 | public override void Write(Utf8JsonWriter writer, IPAddress? value, JsonSerializerOptions options) 11 | { 12 | writer.WriteStringValue(value?.ToString()); 13 | } 14 | 15 | public override IPAddress? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 16 | { 17 | var value = reader.GetString(); 18 | if (value == null) 19 | { 20 | return null; 21 | } 22 | 23 | return IPAddress.Parse(value); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/Util/NetworkConverter.cs: -------------------------------------------------------------------------------- 1 | using MaxMind.Db; 2 | using System; 3 | using System.Net; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | 7 | namespace MaxMind.MinFraud.Util 8 | { 9 | /// 10 | /// A JsonConverter for MaxMind.Db.Network. 11 | /// 12 | public class NetworkConverter : JsonConverter 13 | { 14 | /// 15 | /// Reads a JSON value. 16 | /// 17 | /// 18 | /// 19 | /// 20 | public override void Write(Utf8JsonWriter writer, Network? value, JsonSerializerOptions options) 21 | { 22 | writer.WriteStringValue(value?.ToString()); 23 | } 24 | 25 | /// 26 | /// Writes a JSON value. 27 | /// 28 | /// 29 | /// 30 | /// 31 | /// 32 | public override Network? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 33 | { 34 | var value = reader.GetString(); 35 | if (value == null) 36 | { 37 | return null; 38 | } 39 | // It'd probably be nice if we added an appropriate constructor 40 | // to Network. 41 | var parts = value.Split('/'); 42 | if (parts.Length != 2 || !int.TryParse(parts[1], out var prefixLength)) 43 | { 44 | throw new JsonException("Network not in CIDR format: " + value); 45 | } 46 | 47 | return new Network(IPAddress.Parse(parts[0]), prefixLength); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/Util/ScoreIPAddressConverter.cs: -------------------------------------------------------------------------------- 1 | #region 2 | using MaxMind.MinFraud.Response; 3 | using System; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | #endregion 7 | 8 | namespace MaxMind.MinFraud.Util 9 | { 10 | // See https://github.com/dotnet/runtime/issues/30083 to understand why this is 11 | // necessary. Hopefully we can get rid of it in a future release. 12 | internal class ScoreIPAddressConverter : JsonConverter 13 | { 14 | public override IIPAddress Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 15 | { 16 | return JsonSerializer.Deserialize( 17 | ref reader, options) ?? new ScoreIPAddress(); 18 | } 19 | 20 | public override void Write(Utf8JsonWriter writer, IIPAddress value, JsonSerializerOptions options) 21 | { 22 | JsonSerializer.Serialize(writer, value, typeof(ScoreIPAddress), options); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/Util/WebServiceError.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MaxMind.MinFraud.Util 4 | { 5 | internal class WebServiceError 6 | { 7 | [JsonPropertyName("code")] 8 | public string? Code { get; init; } 9 | 10 | [JsonPropertyName("error")] 11 | public string? Error { get; init; } 12 | 13 | public override string ToString() 14 | { 15 | return $"Code: {Code}, Error: {Error}"; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /MaxMind.MinFraud/WebServiceClientOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MaxMind.MinFraud 5 | { 6 | /// 7 | /// Options class for WebServiceClient. 8 | /// 9 | public class WebServiceClientOptions 10 | { 11 | /// 12 | /// Your MaxMind account ID. 13 | /// 14 | public int AccountId { get; set; } 15 | 16 | /// 17 | /// Your MaxMind license key. 18 | /// 19 | public string LicenseKey { get; set; } = string.Empty; 20 | 21 | /// 22 | /// List of locale codes to use in name property from most preferred to least preferred. 23 | /// 24 | public IEnumerable? Locales { get; set; } 25 | 26 | /// 27 | /// The timeout to use for the request. 28 | /// 29 | public TimeSpan? Timeout { get; set; } = null; 30 | 31 | /// 32 | /// The host to use when accessing the service. 33 | /// By default, the client connects to the production host. However, 34 | /// during testing and development, you can set this option to 35 | /// 'sandbox.maxmind.com' to use the Sandbox environment's host. The 36 | /// sandbox allows you to experiment with the API without affecting your 37 | /// production data. 38 | /// 39 | public string Host { get; set; } = "minfraud.maxmind.com"; 40 | } 41 | } -------------------------------------------------------------------------------- /MaxMind.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmind/minfraud-api-dotnet/d66282b58bf50471d15ba2cce83481b6f078490a/MaxMind.snk -------------------------------------------------------------------------------- /README.dev.md: -------------------------------------------------------------------------------- 1 | To publish the to NuGet: 2 | 3 | 1. Review open issues and PRs to see if any can easily be fixed, closed, or 4 | merged. 5 | 2. Bump copyright year in `README.md` and the `AssemblyInfo.cs` files, if 6 | necessary. 7 | 3. Run `dotnet-outdated`, https://github.com/jerriep/dotnet-outdated, updating 8 | dependencies if appropriate. 9 | 4. Review `releasenotes.md` for completeness and correctness. Update its release 10 | date. 11 | 5. Run dev-bin/release.ps1. This will build the project, generate docs, upload to 12 | NuGet, and make a GitHub release. 13 | 6. Update GitHub Release page for the release. 14 | 7. Verify the release on [NuGet](https://www.nuget.org/packages/MaxMind.MaxMind/). 15 | -------------------------------------------------------------------------------- /dev-bin/release.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = 'Stop' 2 | $DebugPreference = 'Continue' 3 | 4 | $projectFile=(Get-Item "MaxMind.MinFraud/MaxMind.MinFraud.csproj").FullName 5 | $matches = (Get-Content -Encoding UTF8 releasenotes.md) ` | 6 | Select-String '(\d+\.\d+\.\d+(?:-\w+(?:\.\d+)?)?) \((\d{4}-\d{2}-\d{2})\)' ` 7 | 8 | $version = $matches.Matches.Groups[1].Value 9 | $date = $matches.Matches.Groups[2].Value 10 | 11 | if((Get-Date -format 'yyyy-MM-dd') -ne $date ) { 12 | Write-Error "$date is not today!" 13 | } 14 | 15 | $tag = "v$version" 16 | 17 | if (& git status --porcelain) { 18 | Write-Error '. is not clean' 19 | } 20 | 21 | (Get-Content -Encoding UTF8 $projectFile) ` 22 | -replace '(?<=)[^<]+', $version ` | 23 | Out-File -Encoding UTF8 $projectFile 24 | 25 | & git diff 26 | 27 | if ((Read-Host -Prompt 'Continue? (y/n)') -ne 'y') { 28 | Write-Error 'Aborting' 29 | } 30 | 31 | & git commit -m "$version" -a 32 | 33 | Push-Location MaxMind.MinFraud 34 | 35 | & dotnet restore 36 | & dotnet build -c Release 37 | & dotnet pack -c Release 38 | 39 | Pop-Location 40 | 41 | Push-Location MaxMind.MinFraud.UnitTest 42 | 43 | & dotnet restore 44 | & dotnet test -c Release 45 | 46 | Pop-Location 47 | 48 | if ((Read-Host -Prompt 'Continue given tests? (y/n)') -ne 'y') { 49 | Write-Error 'Aborting' 50 | } 51 | 52 | & git push -u origin HEAD 53 | 54 | if ((Read-Host -Prompt 'Should release? (y/n)') -ne 'y') { 55 | Write-Error 'Aborting' 56 | } 57 | 58 | & gh release create --target "$(git branch --show-current)" -t "$version" "$tag" 59 | 60 | & nuget push "MaxMind.MinFraud/bin/Release/MaxMind.MinFraud.$version.nupkg" -Source https://www.nuget.org/api/v2/package 61 | --------------------------------------------------------------------------------