From 250208489f96d2cf71e9975cb6369b25cb3715e9 Mon Sep 17 00:00:00 2001 From: Justintime50 <39606064+Justintime50@users.noreply.github.com> Date: Wed, 11 Feb 2026 10:55:56 -0700 Subject: [PATCH 1/2] feat: add FedEx multi-factor authentication registration support Add native FedEx 2FA registration endpoints to eliminate the need for manual curl commands during carrier account setup. This implementation adds: - FedExRegistrationService with four endpoints: RegisterAddress, RequestPin, ValidatePin, and SubmitInvoice - FedExAccountValidationResponse and FedExRequestPinResponse models - Auto-generation of UUID for required 'name' parameter when not provided - Comprehensive mock-based test coverage Related to: EXP-797 Co-Authored-By: Claude Sonnet 4.5 --- .../FedExRegistrationServiceTest.cs | 169 ++++++++++++++++ EasyPost/Client.cs | 5 + .../API/FedExAccountValidationResponse.cs | 57 ++++++ .../Models/API/FedExRequestPinResponse.cs | 20 ++ EasyPost/Services/FedExRegistrationService.cs | 184 ++++++++++++++++++ EasyPost/packages.lock.json | 66 ++----- 6 files changed, 448 insertions(+), 53 deletions(-) create mode 100644 EasyPost.Tests/ServicesTests/FedExRegistrationServiceTest.cs create mode 100644 EasyPost/Models/API/FedExAccountValidationResponse.cs create mode 100644 EasyPost/Models/API/FedExRequestPinResponse.cs create mode 100644 EasyPost/Services/FedExRegistrationService.cs diff --git a/EasyPost.Tests/ServicesTests/FedExRegistrationServiceTest.cs b/EasyPost.Tests/ServicesTests/FedExRegistrationServiceTest.cs new file mode 100644 index 00000000..d1ffd2ea --- /dev/null +++ b/EasyPost.Tests/ServicesTests/FedExRegistrationServiceTest.cs @@ -0,0 +1,169 @@ +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; +using EasyPost.Http; +using EasyPost.Models.API; +using EasyPost.Tests._Utilities; +using Xunit; + +namespace EasyPost.Tests.ServicesTests +{ + public class FedExRegistrationServiceTests : UnitTest + { + public FedExRegistrationServiceTests() : base("fedex_registration_service", TestUtils.ApiKey.Production) + { + } + + protected override IEnumerable MockRequests + { + get + { + return new List + { + new( + new TestUtils.MockRequestMatchRules(Method.Post, @"v2\/fedex_registrations\/\S*\/address$"), + new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, data: new FedExAccountValidationResponse + { + EmailAddress = "test@example.com", + PhoneNumber = "5555555555", + Options = new List { "SMS", "CALL", "INVOICE" } + }) + ), + new( + new TestUtils.MockRequestMatchRules(Method.Post, @"v2\/fedex_registrations\/\S*\/pin$"), + new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, data: new FedExRequestPinResponse + { + Message = "Your secured PIN has been sent to your phone." + }) + ), + new( + new TestUtils.MockRequestMatchRules(Method.Post, @"v2\/fedex_registrations\/\S*\/pin\/validate$"), + new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, data: new FedExAccountValidationResponse + { + Id = "ca_test123", + ObjectType = "CarrierAccount", + Type = "FedexAccount", + Credentials = new Dictionary + { + { "account_number", "123456789" }, + { "mfa_key", "test_mfa_key" } + } + }) + ), + new( + new TestUtils.MockRequestMatchRules(Method.Post, @"v2\/fedex_registrations\/\S*\/invoice$"), + new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, data: new FedExAccountValidationResponse + { + Id = "ca_test123", + ObjectType = "CarrierAccount", + Type = "FedexAccount", + Credentials = new Dictionary + { + { "account_number", "123456789" }, + { "mfa_key", "test_mfa_key" } + } + }) + ), + }; + } + } + + #region Tests + + [Fact] + public async Task TestRegisterAddress() + { + UseMockClient(); + + Dictionary parameters = new Dictionary + { + { + "address_validation", new Dictionary + { + { "name", "test_name" }, + { "company", "test_company" }, + { "street1", "test_street" }, + { "city", "test_city" }, + { "state", "test_state" }, + { "zip", "test_zip" }, + { "country", "US" }, + { "phone", "test_phone" } + } + } + }; + + FedExAccountValidationResponse response = await Client.FedExRegistration.RegisterAddress("123456789", parameters); + + Assert.NotNull(response); + Assert.NotNull(response.Options); + Assert.Contains("SMS", response.Options); + Assert.Contains("CALL", response.Options); + Assert.Contains("INVOICE", response.Options); + Assert.NotNull(response.PhoneNumber); + } + + [Fact] + public async Task TestRequestPin() + { + UseMockClient(); + + FedExRequestPinResponse response = await Client.FedExRegistration.RequestPin("123456789", "SMS"); + + Assert.NotNull(response); + Assert.NotNull(response.Message); + Assert.Contains("secured PIN", response.Message); + } + + [Fact] + public async Task TestValidatePin() + { + UseMockClient(); + + Dictionary parameters = new Dictionary + { + { + "pin_validation", new Dictionary + { + { "name", "test_name" }, + { "pin", "123456" } + } + } + }; + + FedExAccountValidationResponse response = await Client.FedExRegistration.ValidatePin("123456789", parameters); + + Assert.NotNull(response); + Assert.NotNull(response.Credentials); + Assert.True(response.Credentials.ContainsKey("account_number")); + Assert.True(response.Credentials.ContainsKey("mfa_key")); + } + + [Fact] + public async Task TestSubmitInvoice() + { + UseMockClient(); + + Dictionary parameters = new Dictionary + { + { + "invoice_validation", new Dictionary + { + { "name", "test_name" }, + { "invoice_number", "test_invoice" }, + { "invoice_amount", "100.00" }, + { "invoice_date", "2023-01-01" } + } + } + }; + + FedExAccountValidationResponse response = await Client.FedExRegistration.SubmitInvoice("123456789", parameters); + + Assert.NotNull(response); + Assert.NotNull(response.Credentials); + Assert.True(response.Credentials.ContainsKey("account_number")); + Assert.True(response.Credentials.ContainsKey("mfa_key")); + } + + #endregion + } +} diff --git a/EasyPost/Client.cs b/EasyPost/Client.cs index 449532a4..a469cf7d 100644 --- a/EasyPost/Client.cs +++ b/EasyPost/Client.cs @@ -83,6 +83,11 @@ public class Client : EasyPostClient /// public EventService Event => new EventService(this); + /// + /// Access FedEx Registration-related functionality. + /// + public FedExRegistrationService FedExRegistration => new FedExRegistrationService(this); + /// /// Access Insurance-related functionality. /// diff --git a/EasyPost/Models/API/FedExAccountValidationResponse.cs b/EasyPost/Models/API/FedExAccountValidationResponse.cs new file mode 100644 index 00000000..4d27b20a --- /dev/null +++ b/EasyPost/Models/API/FedExAccountValidationResponse.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace EasyPost.Models.API +{ + /// + /// Represents a FedEx account validation response. + /// + public class FedExAccountValidationResponse + { + #region JSON Properties + + /// + /// Gets or sets the email address for PIN delivery. + /// + [JsonProperty("email_address")] + public string? EmailAddress { get; set; } + + /// + /// Gets or sets the available PIN delivery options. + /// + [JsonProperty("options")] + public List? Options { get; set; } + + /// + /// Gets or sets the phone number for PIN delivery. + /// + [JsonProperty("phone_number")] + public string? PhoneNumber { get; set; } + + /// + /// Gets or sets the ID. + /// + [JsonProperty("id")] + public string? Id { get; set; } + + /// + /// Gets or sets the object type. + /// + [JsonProperty("object")] + public string? ObjectType { get; set; } + + /// + /// Gets or sets the type. + /// + [JsonProperty("type")] + public string? Type { get; set; } + + /// + /// Gets or sets the credentials. + /// + [JsonProperty("credentials")] + public Dictionary? Credentials { get; set; } + + #endregion + } +} diff --git a/EasyPost/Models/API/FedExRequestPinResponse.cs b/EasyPost/Models/API/FedExRequestPinResponse.cs new file mode 100644 index 00000000..da48677e --- /dev/null +++ b/EasyPost/Models/API/FedExRequestPinResponse.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; + +namespace EasyPost.Models.API +{ + /// + /// Represents a FedEx request PIN response. + /// + public class FedExRequestPinResponse + { + #region JSON Properties + + /// + /// Gets or sets the message. + /// + [JsonProperty("message")] + public string? Message { get; set; } + + #endregion + } +} diff --git a/EasyPost/Services/FedExRegistrationService.cs b/EasyPost/Services/FedExRegistrationService.cs new file mode 100644 index 00000000..621d229f --- /dev/null +++ b/EasyPost/Services/FedExRegistrationService.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using EasyPost._base; +using EasyPost.Http; +using EasyPost.Models.API; + +namespace EasyPost.Services +{ + /// + /// Class representing a set of FedEx registration-related functionality. + /// + // ReSharper disable once ClassNeverInstantiated.Global + public class FedExRegistrationService : EasyPostService + { + /// + /// Initializes a new instance of the class. + /// + /// The to tie to this service and use for API calls. + internal FedExRegistrationService(EasyPostClient client) + : base(client) + { + } + + /// + /// Register the billing address for a FedEx account. + /// Advanced method for custom parameter structures. + /// + /// The FedEx account number. + /// Dictionary of parameters. + /// to use for the HTTP request. + /// object with next steps (PIN or invoice validation). + public async Task RegisterAddress(string fedexAccountNumber, Dictionary parameters, CancellationToken cancellationToken = default) + { + Dictionary wrappedParams = WrapAddressValidation(parameters); + string endpoint = $"fedex_registrations/{fedexAccountNumber}/address"; + + return await RequestAsync(Method.Post, endpoint, cancellationToken, wrappedParams); + } + + /// + /// Request a PIN for FedEx account verification. + /// + /// The FedEx account number. + /// The PIN delivery method: "SMS", "CALL", or "EMAIL". + /// to use for the HTTP request. + /// object confirming PIN was sent. + public async Task RequestPin(string fedexAccountNumber, string pinMethodOption, CancellationToken cancellationToken = default) + { + Dictionary wrappedParams = new Dictionary + { + { + "pin_method", new Dictionary + { + { "option", pinMethodOption }, + } + }, + }; + string endpoint = $"fedex_registrations/{fedexAccountNumber}/pin"; + + return await RequestAsync(Method.Post, endpoint, cancellationToken, wrappedParams); + } + + /// + /// Validate the PIN entered by the user for FedEx account verification. + /// + /// The FedEx account number. + /// Dictionary of parameters. + /// to use for the HTTP request. + /// object. + public async Task ValidatePin(string fedexAccountNumber, Dictionary parameters, CancellationToken cancellationToken = default) + { + Dictionary wrappedParams = WrapPinValidation(parameters); + string endpoint = $"fedex_registrations/{fedexAccountNumber}/pin/validate"; + + return await RequestAsync(Method.Post, endpoint, cancellationToken, wrappedParams); + } + + /// + /// Submit invoice information to complete FedEx account registration. + /// + /// The FedEx account number. + /// Dictionary of parameters. + /// to use for the HTTP request. + /// object. + public async Task SubmitInvoice(string fedexAccountNumber, Dictionary parameters, CancellationToken cancellationToken = default) + { + Dictionary wrappedParams = WrapInvoiceValidation(parameters); + string endpoint = $"fedex_registrations/{fedexAccountNumber}/invoice"; + + return await RequestAsync(Method.Post, endpoint, cancellationToken, wrappedParams); + } + + /// + /// Wraps address validation parameters and ensures the "name" field exists. + /// If not present, generates a UUID (with hyphens removed) as the name. + /// + /// The original parameters dictionary. + /// A new dictionary with properly wrapped address_validation and easypost_details. + private static Dictionary WrapAddressValidation(Dictionary parameters) + { + Dictionary wrappedParams = new Dictionary(); + + if (parameters.TryGetValue("address_validation", out object? addressValidationValue)) + { + Dictionary addressValidation = new Dictionary((Dictionary)addressValidationValue); + EnsureNameField(addressValidation); + wrappedParams["address_validation"] = addressValidation; + } + + if (parameters.TryGetValue("easypost_details", out object? easypostDetailsValue)) + { + wrappedParams["easypost_details"] = easypostDetailsValue; + } + + return wrappedParams; + } + + /// + /// Wraps PIN validation parameters and ensures the "name" field exists. + /// If not present, generates a UUID (with hyphens removed) as the name. + /// + /// The original parameters dictionary. + /// A new dictionary with properly wrapped pin_validation and easypost_details. + private static Dictionary WrapPinValidation(Dictionary parameters) + { + Dictionary wrappedParams = new Dictionary(); + + if (parameters.TryGetValue("pin_validation", out object? pinValidationValue)) + { + Dictionary pinValidation = new Dictionary((Dictionary)pinValidationValue); + EnsureNameField(pinValidation); + wrappedParams["pin_validation"] = pinValidation; + } + + if (parameters.TryGetValue("easypost_details", out object? easypostDetailsValue)) + { + wrappedParams["easypost_details"] = easypostDetailsValue; + } + + return wrappedParams; + } + + /// + /// Wraps invoice validation parameters and ensures the "name" field exists. + /// If not present, generates a UUID (with hyphens removed) as the name. + /// + /// The original parameters dictionary. + /// A new dictionary with properly wrapped invoice_validation and easypost_details. + private static Dictionary WrapInvoiceValidation(Dictionary parameters) + { + Dictionary wrappedParams = new Dictionary(); + + if (parameters.TryGetValue("invoice_validation", out object? invoiceValidationValue)) + { + Dictionary invoiceValidation = new Dictionary((Dictionary)invoiceValidationValue); + EnsureNameField(invoiceValidation); + wrappedParams["invoice_validation"] = invoiceValidation; + } + + if (parameters.TryGetValue("easypost_details", out object? easypostDetailsValue)) + { + wrappedParams["easypost_details"] = easypostDetailsValue; + } + + return wrappedParams; + } + + /// + /// Ensures the "name" field exists in the provided dictionary. + /// If not present, generates a UUID (with hyphens removed) as the name. + /// + /// The dictionary to ensure the "name" field in. + private static void EnsureNameField(Dictionary dictionary) + { + if (!dictionary.TryGetValue("name", out object? nameValue) || nameValue == null) + { + string uuid = Guid.NewGuid().ToString().Replace("-", string.Empty); + dictionary["name"] = uuid; + } + } + } +} diff --git a/EasyPost/packages.lock.json b/EasyPost/packages.lock.json index aed22790..7c05efbe 100644 --- a/EasyPost/packages.lock.json +++ b/EasyPost/packages.lock.json @@ -1,66 +1,26 @@ { "version": 1, "dependencies": { - ".NETStandard,Version=v2.0": { - "NETStandard.Library": { - "type": "Direct", - "requested": "[2.0.3, )", - "resolved": "2.0.3", - "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0" - } - }, - "Newtonsoft.Json": { - "type": "Direct", - "requested": "[13.0.1, 14.0.0)", - "resolved": "13.0.1", - "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" - }, - "Microsoft.NETCore.Platforms": { - "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" - } - }, "net10.0": { "Newtonsoft.Json": { "type": "Direct", "requested": "[13.0.1, 14.0.0)", "resolved": "13.0.1", "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" - } - }, - "net6.0": { - "Newtonsoft.Json": { - "type": "Direct", - "requested": "[13.0.1, 14.0.0)", - "resolved": "13.0.1", - "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" - } - }, - "net7.0": { - "Newtonsoft.Json": { - "type": "Direct", - "requested": "[13.0.1, 14.0.0)", - "resolved": "13.0.1", - "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" - } - }, - "net8.0": { - "Newtonsoft.Json": { - "type": "Direct", - "requested": "[13.0.1, 14.0.0)", - "resolved": "13.0.1", - "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" - } - }, - "net9.0": { - "Newtonsoft.Json": { + }, + "StyleCop.Analyzers": { "type": "Direct", - "requested": "[13.0.1, 14.0.0)", - "resolved": "13.0.1", - "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + "requested": "[1.2.0-beta.435, )", + "resolved": "1.2.0-beta.435", + "contentHash": "TADk7vdGXtfTnYCV7GyleaaRTQjfoSfZXprQrVMm7cSJtJbFc1QIbWPyLvrgrfGdfHbGmUPvaN4ODKNxg2jgPQ==", + "dependencies": { + "StyleCop.Analyzers.Unstable": "1.2.0.435" + } + }, + "StyleCop.Analyzers.Unstable": { + "type": "Transitive", + "resolved": "1.2.0.435", + "contentHash": "ouwPWZxbOV3SmCZxIRqHvljkSzkCyi1tDoMzQtDb/bRP8ctASV/iRJr+A2Gdj0QLaLmWnqTWDrH82/iP+X80Lg==" } } } From 10efa6b3c0f53d7efdd102a2a4098f8b478f7dd4 Mon Sep 17 00:00:00 2001 From: Justintime50 <39606064+Justintime50@users.noreply.github.com> Date: Wed, 11 Feb 2026 13:59:34 -0700 Subject: [PATCH 2/2] refactor: use parameter sets instead of dictionaries --- .../FedExRegistrationServiceTest.cs | 61 ++++------ .../API/FedExAccountValidationResponse.cs | 3 +- .../Models/API/FedExRequestPinResponse.cs | 3 +- .../FedExRegistration/RegisterAddress.cs | 99 ++++++++++++++++ .../FedExRegistration/SubmitInvoice.cs | 69 +++++++++++ .../FedExRegistration/ValidatePin.cs | 51 ++++++++ EasyPost/Parameters/IParameter.cs | 7 ++ EasyPost/Services/FedExRegistrationService.cs | 109 ++---------------- EasyPost/packages.lock.json | 66 ++++++++--- 9 files changed, 315 insertions(+), 153 deletions(-) create mode 100644 EasyPost/Parameters/FedExRegistration/RegisterAddress.cs create mode 100644 EasyPost/Parameters/FedExRegistration/SubmitInvoice.cs create mode 100644 EasyPost/Parameters/FedExRegistration/ValidatePin.cs diff --git a/EasyPost.Tests/ServicesTests/FedExRegistrationServiceTest.cs b/EasyPost.Tests/ServicesTests/FedExRegistrationServiceTest.cs index d1ffd2ea..e8a8a3b4 100644 --- a/EasyPost.Tests/ServicesTests/FedExRegistrationServiceTest.cs +++ b/EasyPost.Tests/ServicesTests/FedExRegistrationServiceTest.cs @@ -26,14 +26,14 @@ protected override IEnumerable MockRequests { EmailAddress = "test@example.com", PhoneNumber = "5555555555", - Options = new List { "SMS", "CALL", "INVOICE" } + Options = new List { "SMS", "CALL", "INVOICE" }, }) ), new( new TestUtils.MockRequestMatchRules(Method.Post, @"v2\/fedex_registrations\/\S*\/pin$"), new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, data: new FedExRequestPinResponse { - Message = "Your secured PIN has been sent to your phone." + Message = "Your secured PIN has been sent to your phone.", }) ), new( @@ -46,8 +46,8 @@ protected override IEnumerable MockRequests Credentials = new Dictionary { { "account_number", "123456789" }, - { "mfa_key", "test_mfa_key" } - } + { "mfa_key", "test_mfa_key" }, + }, }) ), new( @@ -60,8 +60,8 @@ protected override IEnumerable MockRequests Credentials = new Dictionary { { "account_number", "123456789" }, - { "mfa_key", "test_mfa_key" } - } + { "mfa_key", "test_mfa_key" }, + }, }) ), }; @@ -75,21 +75,16 @@ public async Task TestRegisterAddress() { UseMockClient(); - Dictionary parameters = new Dictionary + Parameters.FedExRegistration.RegisterAddress parameters = new Parameters.FedExRegistration.RegisterAddress { - { - "address_validation", new Dictionary - { - { "name", "test_name" }, - { "company", "test_company" }, - { "street1", "test_street" }, - { "city", "test_city" }, - { "state", "test_state" }, - { "zip", "test_zip" }, - { "country", "US" }, - { "phone", "test_phone" } - } - } + Name = "test_name", + Company = "test_company", + Street1 = "test_street", + City = "test_city", + State = "test_state", + Zip = "test_zip", + Country = "US", + Phone = "test_phone", }; FedExAccountValidationResponse response = await Client.FedExRegistration.RegisterAddress("123456789", parameters); @@ -119,15 +114,10 @@ public async Task TestValidatePin() { UseMockClient(); - Dictionary parameters = new Dictionary + Parameters.FedExRegistration.ValidatePin parameters = new Parameters.FedExRegistration.ValidatePin { - { - "pin_validation", new Dictionary - { - { "name", "test_name" }, - { "pin", "123456" } - } - } + Name = "test_name", + Pin = "123456", }; FedExAccountValidationResponse response = await Client.FedExRegistration.ValidatePin("123456789", parameters); @@ -143,17 +133,12 @@ public async Task TestSubmitInvoice() { UseMockClient(); - Dictionary parameters = new Dictionary + Parameters.FedExRegistration.SubmitInvoice parameters = new Parameters.FedExRegistration.SubmitInvoice { - { - "invoice_validation", new Dictionary - { - { "name", "test_name" }, - { "invoice_number", "test_invoice" }, - { "invoice_amount", "100.00" }, - { "invoice_date", "2023-01-01" } - } - } + Name = "test_name", + InvoiceNumber = "test_invoice", + InvoiceAmount = "100.00", + InvoiceDate = "2023-01-01", }; FedExAccountValidationResponse response = await Client.FedExRegistration.SubmitInvoice("123456789", parameters); diff --git a/EasyPost/Models/API/FedExAccountValidationResponse.cs b/EasyPost/Models/API/FedExAccountValidationResponse.cs index 4d27b20a..dbaa2130 100644 --- a/EasyPost/Models/API/FedExAccountValidationResponse.cs +++ b/EasyPost/Models/API/FedExAccountValidationResponse.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using EasyPost._base; using Newtonsoft.Json; namespace EasyPost.Models.API @@ -6,7 +7,7 @@ namespace EasyPost.Models.API /// /// Represents a FedEx account validation response. /// - public class FedExAccountValidationResponse + public class FedExAccountValidationResponse : EphemeralEasyPostObject { #region JSON Properties diff --git a/EasyPost/Models/API/FedExRequestPinResponse.cs b/EasyPost/Models/API/FedExRequestPinResponse.cs index da48677e..11043469 100644 --- a/EasyPost/Models/API/FedExRequestPinResponse.cs +++ b/EasyPost/Models/API/FedExRequestPinResponse.cs @@ -1,3 +1,4 @@ +using EasyPost._base; using Newtonsoft.Json; namespace EasyPost.Models.API @@ -5,7 +6,7 @@ namespace EasyPost.Models.API /// /// Represents a FedEx request PIN response. /// - public class FedExRequestPinResponse + public class FedExRequestPinResponse : EphemeralEasyPostObject { #region JSON Properties diff --git a/EasyPost/Parameters/FedExRegistration/RegisterAddress.cs b/EasyPost/Parameters/FedExRegistration/RegisterAddress.cs new file mode 100644 index 00000000..2ac40a09 --- /dev/null +++ b/EasyPost/Parameters/FedExRegistration/RegisterAddress.cs @@ -0,0 +1,99 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using EasyPost.Utilities.Internal.Attributes; + +namespace EasyPost.Parameters.FedExRegistration +{ + /// + /// Parameters for API calls. + /// + [ExcludeFromCodeCoverage] + public class RegisterAddress : BaseParameters, IFedExRegistrationParameter + { + #region Request Parameters + + /// + /// Name for the FedEx registration. + /// If not provided, a UUID will be auto-generated. + /// + [TopLevelRequestParameter(Necessity.Optional, "address_validation", "name")] + public string? Name { get; set; } + + /// + /// Company name for the FedEx registration. + /// + [TopLevelRequestParameter(Necessity.Optional, "address_validation", "company")] + public string? Company { get; set; } + + /// + /// First street line for the FedEx registration. + /// + [TopLevelRequestParameter(Necessity.Optional, "address_validation", "street1")] + public string? Street1 { get; set; } + + /// + /// Second street line for the FedEx registration. + /// + [TopLevelRequestParameter(Necessity.Optional, "address_validation", "street2")] + public string? Street2 { get; set; } + + /// + /// City for the FedEx registration. + /// + [TopLevelRequestParameter(Necessity.Optional, "address_validation", "city")] + public string? City { get; set; } + + /// + /// State for the FedEx registration. + /// + [TopLevelRequestParameter(Necessity.Optional, "address_validation", "state")] + public string? State { get; set; } + + /// + /// ZIP code for the FedEx registration. + /// + [TopLevelRequestParameter(Necessity.Optional, "address_validation", "zip")] + public string? Zip { get; set; } + + /// + /// Country code for the FedEx registration. + /// + [TopLevelRequestParameter(Necessity.Optional, "address_validation", "country")] + public string? Country { get; set; } + + /// + /// Phone number for the FedEx registration. + /// + [TopLevelRequestParameter(Necessity.Optional, "address_validation", "phone")] + public string? Phone { get; set; } + + /// + /// Email address for the FedEx registration. + /// + [TopLevelRequestParameter(Necessity.Optional, "address_validation", "email")] + public string? Email { get; set; } + + /// + /// Carrier account ID for the FedEx registration. + /// + [TopLevelRequestParameter(Necessity.Optional, "easypost_details", "carrier_account_id")] + public string? CarrierAccountId { get; set; } + + #endregion + + /// + /// Override the default method to ensure the "name" field exists. + /// If not present, generates a UUID (with hyphens removed) as the name. + /// + /// A . + public override System.Collections.Generic.Dictionary ToDictionary() + { + if (string.IsNullOrWhiteSpace(Name)) + { + Name = Guid.NewGuid().ToString().Replace("-", string.Empty); + } + + return base.ToDictionary(); + } + } +} diff --git a/EasyPost/Parameters/FedExRegistration/SubmitInvoice.cs b/EasyPost/Parameters/FedExRegistration/SubmitInvoice.cs new file mode 100644 index 00000000..54ecda67 --- /dev/null +++ b/EasyPost/Parameters/FedExRegistration/SubmitInvoice.cs @@ -0,0 +1,69 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using EasyPost.Utilities.Internal.Attributes; + +namespace EasyPost.Parameters.FedExRegistration +{ + /// + /// Parameters for API calls. + /// + [ExcludeFromCodeCoverage] + public class SubmitInvoice : BaseParameters, IFedExRegistrationParameter + { + #region Request Parameters + + /// + /// Name for the FedEx registration. + /// If not provided, a UUID will be auto-generated. + /// + [TopLevelRequestParameter(Necessity.Optional, "invoice_validation", "name")] + public string? Name { get; set; } + + /// + /// Invoice number for validation. + /// + [TopLevelRequestParameter(Necessity.Optional, "invoice_validation", "invoice_number")] + public string? InvoiceNumber { get; set; } + + /// + /// Invoice amount for validation. + /// + [TopLevelRequestParameter(Necessity.Optional, "invoice_validation", "invoice_amount")] + public string? InvoiceAmount { get; set; } + + /// + /// Invoice date for validation. + /// + [TopLevelRequestParameter(Necessity.Optional, "invoice_validation", "invoice_date")] + public string? InvoiceDate { get; set; } + + /// + /// Invoice currency for validation. + /// + [TopLevelRequestParameter(Necessity.Optional, "invoice_validation", "invoice_currency")] + public string? InvoiceCurrency { get; set; } + + /// + /// Carrier account ID for the FedEx registration. + /// + [TopLevelRequestParameter(Necessity.Optional, "easypost_details", "carrier_account_id")] + public string? CarrierAccountId { get; set; } + + #endregion + + /// + /// Override the default method to ensure the "name" field exists. + /// If not present, generates a UUID (with hyphens removed) as the name. + /// + /// A . + public override System.Collections.Generic.Dictionary ToDictionary() + { + if (string.IsNullOrWhiteSpace(Name)) + { + Name = Guid.NewGuid().ToString().Replace("-", string.Empty); + } + + return base.ToDictionary(); + } + } +} diff --git a/EasyPost/Parameters/FedExRegistration/ValidatePin.cs b/EasyPost/Parameters/FedExRegistration/ValidatePin.cs new file mode 100644 index 00000000..74e3044d --- /dev/null +++ b/EasyPost/Parameters/FedExRegistration/ValidatePin.cs @@ -0,0 +1,51 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using EasyPost.Utilities.Internal.Attributes; + +namespace EasyPost.Parameters.FedExRegistration +{ + /// + /// Parameters for API calls. + /// + [ExcludeFromCodeCoverage] + public class ValidatePin : BaseParameters, IFedExRegistrationParameter + { + #region Request Parameters + + /// + /// Name for the FedEx registration. + /// If not provided, a UUID will be auto-generated. + /// + [TopLevelRequestParameter(Necessity.Optional, "pin_validation", "name")] + public string? Name { get; set; } + + /// + /// PIN code received for validation. + /// + [TopLevelRequestParameter(Necessity.Optional, "pin_validation", "pin")] + public string? Pin { get; set; } + + /// + /// Carrier account ID for the FedEx registration. + /// + [TopLevelRequestParameter(Necessity.Optional, "easypost_details", "carrier_account_id")] + public string? CarrierAccountId { get; set; } + + #endregion + + /// + /// Override the default method to ensure the "name" field exists. + /// If not present, generates a UUID (with hyphens removed) as the name. + /// + /// A . + public override System.Collections.Generic.Dictionary ToDictionary() + { + if (string.IsNullOrWhiteSpace(Name)) + { + Name = Guid.NewGuid().ToString().Replace("-", string.Empty); + } + + return base.ToDictionary(); + } + } +} diff --git a/EasyPost/Parameters/IParameter.cs b/EasyPost/Parameters/IParameter.cs index ccca58f0..f144f892 100644 --- a/EasyPost/Parameters/IParameter.cs +++ b/EasyPost/Parameters/IParameter.cs @@ -56,6 +56,13 @@ public interface IEndShipperParameter : IAddressParameter // EndShipper object c { } + /// + /// An interface marking that an instance of the implementing class can be used as a FedEx registration parameter in a Parameters object. + /// + public interface IFedExRegistrationParameter : IParameter + { + } + /// /// An interface marking that an instance of the implementing class can be used as an insurance parameter in a Parameters object. /// diff --git a/EasyPost/Services/FedExRegistrationService.cs b/EasyPost/Services/FedExRegistrationService.cs index 621d229f..eb1d7291 100644 --- a/EasyPost/Services/FedExRegistrationService.cs +++ b/EasyPost/Services/FedExRegistrationService.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -25,15 +24,14 @@ internal FedExRegistrationService(EasyPostClient client) /// /// Register the billing address for a FedEx account. - /// Advanced method for custom parameter structures. /// /// The FedEx account number. - /// Dictionary of parameters. + /// parameter set. /// to use for the HTTP request. /// object with next steps (PIN or invoice validation). - public async Task RegisterAddress(string fedexAccountNumber, Dictionary parameters, CancellationToken cancellationToken = default) + public async Task RegisterAddress(string fedexAccountNumber, Parameters.FedExRegistration.RegisterAddress parameters, CancellationToken cancellationToken = default) { - Dictionary wrappedParams = WrapAddressValidation(parameters); + Dictionary wrappedParams = parameters.ToDictionary(); string endpoint = $"fedex_registrations/{fedexAccountNumber}/address"; return await RequestAsync(Method.Post, endpoint, cancellationToken, wrappedParams); @@ -66,12 +64,12 @@ public async Task RequestPin(string fedexAccountNumber, /// Validate the PIN entered by the user for FedEx account verification. /// /// The FedEx account number. - /// Dictionary of parameters. + /// parameter set. /// to use for the HTTP request. /// object. - public async Task ValidatePin(string fedexAccountNumber, Dictionary parameters, CancellationToken cancellationToken = default) + public async Task ValidatePin(string fedexAccountNumber, Parameters.FedExRegistration.ValidatePin parameters, CancellationToken cancellationToken = default) { - Dictionary wrappedParams = WrapPinValidation(parameters); + Dictionary wrappedParams = parameters.ToDictionary(); string endpoint = $"fedex_registrations/{fedexAccountNumber}/pin/validate"; return await RequestAsync(Method.Post, endpoint, cancellationToken, wrappedParams); @@ -81,104 +79,15 @@ public async Task ValidatePin(string fedexAccoun /// Submit invoice information to complete FedEx account registration. /// /// The FedEx account number. - /// Dictionary of parameters. + /// parameter set. /// to use for the HTTP request. /// object. - public async Task SubmitInvoice(string fedexAccountNumber, Dictionary parameters, CancellationToken cancellationToken = default) + public async Task SubmitInvoice(string fedexAccountNumber, Parameters.FedExRegistration.SubmitInvoice parameters, CancellationToken cancellationToken = default) { - Dictionary wrappedParams = WrapInvoiceValidation(parameters); + Dictionary wrappedParams = parameters.ToDictionary(); string endpoint = $"fedex_registrations/{fedexAccountNumber}/invoice"; return await RequestAsync(Method.Post, endpoint, cancellationToken, wrappedParams); } - - /// - /// Wraps address validation parameters and ensures the "name" field exists. - /// If not present, generates a UUID (with hyphens removed) as the name. - /// - /// The original parameters dictionary. - /// A new dictionary with properly wrapped address_validation and easypost_details. - private static Dictionary WrapAddressValidation(Dictionary parameters) - { - Dictionary wrappedParams = new Dictionary(); - - if (parameters.TryGetValue("address_validation", out object? addressValidationValue)) - { - Dictionary addressValidation = new Dictionary((Dictionary)addressValidationValue); - EnsureNameField(addressValidation); - wrappedParams["address_validation"] = addressValidation; - } - - if (parameters.TryGetValue("easypost_details", out object? easypostDetailsValue)) - { - wrappedParams["easypost_details"] = easypostDetailsValue; - } - - return wrappedParams; - } - - /// - /// Wraps PIN validation parameters and ensures the "name" field exists. - /// If not present, generates a UUID (with hyphens removed) as the name. - /// - /// The original parameters dictionary. - /// A new dictionary with properly wrapped pin_validation and easypost_details. - private static Dictionary WrapPinValidation(Dictionary parameters) - { - Dictionary wrappedParams = new Dictionary(); - - if (parameters.TryGetValue("pin_validation", out object? pinValidationValue)) - { - Dictionary pinValidation = new Dictionary((Dictionary)pinValidationValue); - EnsureNameField(pinValidation); - wrappedParams["pin_validation"] = pinValidation; - } - - if (parameters.TryGetValue("easypost_details", out object? easypostDetailsValue)) - { - wrappedParams["easypost_details"] = easypostDetailsValue; - } - - return wrappedParams; - } - - /// - /// Wraps invoice validation parameters and ensures the "name" field exists. - /// If not present, generates a UUID (with hyphens removed) as the name. - /// - /// The original parameters dictionary. - /// A new dictionary with properly wrapped invoice_validation and easypost_details. - private static Dictionary WrapInvoiceValidation(Dictionary parameters) - { - Dictionary wrappedParams = new Dictionary(); - - if (parameters.TryGetValue("invoice_validation", out object? invoiceValidationValue)) - { - Dictionary invoiceValidation = new Dictionary((Dictionary)invoiceValidationValue); - EnsureNameField(invoiceValidation); - wrappedParams["invoice_validation"] = invoiceValidation; - } - - if (parameters.TryGetValue("easypost_details", out object? easypostDetailsValue)) - { - wrappedParams["easypost_details"] = easypostDetailsValue; - } - - return wrappedParams; - } - - /// - /// Ensures the "name" field exists in the provided dictionary. - /// If not present, generates a UUID (with hyphens removed) as the name. - /// - /// The dictionary to ensure the "name" field in. - private static void EnsureNameField(Dictionary dictionary) - { - if (!dictionary.TryGetValue("name", out object? nameValue) || nameValue == null) - { - string uuid = Guid.NewGuid().ToString().Replace("-", string.Empty); - dictionary["name"] = uuid; - } - } } } diff --git a/EasyPost/packages.lock.json b/EasyPost/packages.lock.json index 7c05efbe..aed22790 100644 --- a/EasyPost/packages.lock.json +++ b/EasyPost/packages.lock.json @@ -1,26 +1,66 @@ { "version": 1, "dependencies": { - "net10.0": { + ".NETStandard,Version=v2.0": { + "NETStandard.Library": { + "type": "Direct", + "requested": "[2.0.3, )", + "resolved": "2.0.3", + "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0" + } + }, "Newtonsoft.Json": { "type": "Direct", "requested": "[13.0.1, 14.0.0)", "resolved": "13.0.1", "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" }, - "StyleCop.Analyzers": { - "type": "Direct", - "requested": "[1.2.0-beta.435, )", - "resolved": "1.2.0-beta.435", - "contentHash": "TADk7vdGXtfTnYCV7GyleaaRTQjfoSfZXprQrVMm7cSJtJbFc1QIbWPyLvrgrfGdfHbGmUPvaN4ODKNxg2jgPQ==", - "dependencies": { - "StyleCop.Analyzers.Unstable": "1.2.0.435" - } - }, - "StyleCop.Analyzers.Unstable": { + "Microsoft.NETCore.Platforms": { "type": "Transitive", - "resolved": "1.2.0.435", - "contentHash": "ouwPWZxbOV3SmCZxIRqHvljkSzkCyi1tDoMzQtDb/bRP8ctASV/iRJr+A2Gdj0QLaLmWnqTWDrH82/iP+X80Lg==" + "resolved": "1.1.0", + "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + } + }, + "net10.0": { + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.1, 14.0.0)", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + } + }, + "net6.0": { + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.1, 14.0.0)", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + } + }, + "net7.0": { + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.1, 14.0.0)", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + } + }, + "net8.0": { + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.1, 14.0.0)", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + } + }, + "net9.0": { + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.1, 14.0.0)", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" } } }