diff --git a/EasyPost.Tests/ServicesTests/FedExRegistrationServiceTest.cs b/EasyPost.Tests/ServicesTests/FedExRegistrationServiceTest.cs new file mode 100644 index 00000000..e8a8a3b4 --- /dev/null +++ b/EasyPost.Tests/ServicesTests/FedExRegistrationServiceTest.cs @@ -0,0 +1,154 @@ +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(); + + Parameters.FedExRegistration.RegisterAddress parameters = new Parameters.FedExRegistration.RegisterAddress + { + 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(); + + Parameters.FedExRegistration.ValidatePin parameters = new Parameters.FedExRegistration.ValidatePin + { + 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(); + + Parameters.FedExRegistration.SubmitInvoice parameters = new Parameters.FedExRegistration.SubmitInvoice + { + Name = "test_name", + InvoiceNumber = "test_invoice", + InvoiceAmount = "100.00", + InvoiceDate = "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..dbaa2130 --- /dev/null +++ b/EasyPost/Models/API/FedExAccountValidationResponse.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using EasyPost._base; +using Newtonsoft.Json; + +namespace EasyPost.Models.API +{ + /// + /// Represents a FedEx account validation response. + /// + public class FedExAccountValidationResponse : EphemeralEasyPostObject + { + #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..11043469 --- /dev/null +++ b/EasyPost/Models/API/FedExRequestPinResponse.cs @@ -0,0 +1,21 @@ +using EasyPost._base; +using Newtonsoft.Json; + +namespace EasyPost.Models.API +{ + /// + /// Represents a FedEx request PIN response. + /// + public class FedExRequestPinResponse : EphemeralEasyPostObject + { + #region JSON Properties + + /// + /// Gets or sets the message. + /// + [JsonProperty("message")] + public string? Message { get; set; } + + #endregion + } +} 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 new file mode 100644 index 00000000..eb1d7291 --- /dev/null +++ b/EasyPost/Services/FedExRegistrationService.cs @@ -0,0 +1,93 @@ +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. + /// + /// The FedEx account number. + /// parameter set. + /// to use for the HTTP request. + /// object with next steps (PIN or invoice validation). + public async Task RegisterAddress(string fedexAccountNumber, Parameters.FedExRegistration.RegisterAddress parameters, CancellationToken cancellationToken = default) + { + Dictionary wrappedParams = parameters.ToDictionary(); + 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. + /// parameter set. + /// to use for the HTTP request. + /// object. + public async Task ValidatePin(string fedexAccountNumber, Parameters.FedExRegistration.ValidatePin parameters, CancellationToken cancellationToken = default) + { + Dictionary wrappedParams = parameters.ToDictionary(); + 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. + /// parameter set. + /// to use for the HTTP request. + /// object. + public async Task SubmitInvoice(string fedexAccountNumber, Parameters.FedExRegistration.SubmitInvoice parameters, CancellationToken cancellationToken = default) + { + Dictionary wrappedParams = parameters.ToDictionary(); + string endpoint = $"fedex_registrations/{fedexAccountNumber}/invoice"; + + return await RequestAsync(Method.Post, endpoint, cancellationToken, wrappedParams); + } + } +}