diff --git a/lib/easypost/client.rb b/lib/easypost/client.rb index a44c44bb..da829db1 100644 --- a/lib/easypost/client.rb +++ b/lib/easypost/client.rb @@ -49,6 +49,7 @@ def initialize(api_key:, read_timeout: 60, open_timeout: 30, api_base: 'https:// EasyPost::Services::Embeddable, EasyPost::Services::EndShipper, EasyPost::Services::Event, + EasyPost::Services::FedexRegistration, EasyPost::Services::Insurance, EasyPost::Services::Luma, EasyPost::Services::Order, diff --git a/lib/easypost/services.rb b/lib/easypost/services.rb index 0f0e3862..b54f0c9c 100644 --- a/lib/easypost/services.rb +++ b/lib/easypost/services.rb @@ -21,6 +21,7 @@ module EasyPost::Services require_relative 'services/embeddable' require_relative 'services/end_shipper' require_relative 'services/event' +require_relative 'services/fedex_registration' require_relative 'services/insurance' require_relative 'services/luma' require_relative 'services/order' diff --git a/lib/easypost/services/fedex_registration.rb b/lib/easypost/services/fedex_registration.rb new file mode 100644 index 00000000..f1d9e7d5 --- /dev/null +++ b/lib/easypost/services/fedex_registration.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require 'securerandom' + +class EasyPost::Services::FedexRegistration < EasyPost::Services::Service + # Register the billing address for a FedEx account. + def register_address(fedex_account_number, params = {}) + wrapped_params = wrap_address_validation(params) + endpoint = "fedex_registrations/#{fedex_account_number}/address" + + response = @client.make_request(:post, endpoint, wrapped_params) + + EasyPost::InternalUtilities::Json.convert_json_to_object(response, EasyPost::Models::EasyPostObject) + end + + # Request a PIN for FedEx account verification. + def request_pin(fedex_account_number, pin_method_option) + wrapped_params = { + pin_method: { + option: pin_method_option, + }, + } + endpoint = "fedex_registrations/#{fedex_account_number}/pin" + + response = @client.make_request(:post, endpoint, wrapped_params) + + EasyPost::InternalUtilities::Json.convert_json_to_object(response, EasyPost::Models::EasyPostObject) + end + + # Validate the PIN entered by the user for FedEx account verification. + def validate_pin(fedex_account_number, params = {}) + wrapped_params = wrap_pin_validation(params) + endpoint = "fedex_registrations/#{fedex_account_number}/pin/validate" + + response = @client.make_request(:post, endpoint, wrapped_params) + + EasyPost::InternalUtilities::Json.convert_json_to_object(response, EasyPost::Models::EasyPostObject) + end + + # Submit invoice information to complete FedEx account registration. + def submit_invoice(fedex_account_number, params = {}) + wrapped_params = wrap_invoice_validation(params) + endpoint = "fedex_registrations/#{fedex_account_number}/invoice" + + response = @client.make_request(:post, endpoint, wrapped_params) + + EasyPost::InternalUtilities::Json.convert_json_to_object(response, EasyPost::Models::EasyPostObject) + end + + private + + # Wraps address validation parameters and ensures the "name" field exists. + # If not present, generates a UUID (with hyphens removed) as the name. + def wrap_address_validation(params) + wrapped_params = {} + + if params.key?(:address_validation) + address_validation = params[:address_validation].dup + ensure_name_field(address_validation) + wrapped_params[:address_validation] = address_validation + end + + wrapped_params[:easypost_details] = params[:easypost_details] if params.key?(:easypost_details) + + wrapped_params + end + + # Wraps PIN validation parameters and ensures the "name" field exists. + # If not present, generates a UUID (with hyphens removed) as the name. + def wrap_pin_validation(params) + wrapped_params = {} + + if params.key?(:pin_validation) + pin_validation = params[:pin_validation].dup + ensure_name_field(pin_validation) + wrapped_params[:pin_validation] = pin_validation + end + + wrapped_params[:easypost_details] = params[:easypost_details] if params.key?(:easypost_details) + + wrapped_params + end + + # Wraps invoice validation parameters and ensures the "name" field exists. + # If not present, generates a UUID (with hyphens removed) as the name. + def wrap_invoice_validation(params) + wrapped_params = {} + + if params.key?(:invoice_validation) + invoice_validation = params[:invoice_validation].dup + ensure_name_field(invoice_validation) + wrapped_params[:invoice_validation] = invoice_validation + end + + wrapped_params[:easypost_details] = params[:easypost_details] if params.key?(:easypost_details) + + wrapped_params + end + + # Ensures the "name" field exists in the provided hash. + # If not present, generates a UUID (with hyphens removed) as the name. + # This follows the pattern used in the web UI implementation. + def ensure_name_field(hash) + return if hash.key?(:name) && !hash[:name].nil? + + uuid = SecureRandom.uuid.delete('-') + hash[:name] = uuid + end +end diff --git a/spec/fedex_registration_spec.rb b/spec/fedex_registration_spec.rb new file mode 100644 index 00000000..84b9364e --- /dev/null +++ b/spec/fedex_registration_spec.rb @@ -0,0 +1,162 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe EasyPost::Services::FedexRegistration do + let(:client) { EasyPost::Client.new(api_key: ENV['EASYPOST_PROD_API_KEY']) } + + describe '.register_address' do + it 'registers a billing address' do + fedex_account_number = '123456789' + address_validation = { + name: 'BILLING NAME', + street1: '1234 BILLING STREET', + city: 'BILLINGCITY', + state: 'ST', + postal_code: '12345', + country_code: 'US', + } + easypost_details = { + carrier_account_id: 'ca_123', + } + params = { + address_validation: address_validation, + easypost_details: easypost_details, + } + + json_response = { + 'email_address' => nil, + 'options' => %w[SMS CALL INVOICE], + 'phone_number' => '***-***-9721', + } + + allow(client).to receive(:make_request).with( + :post, + "fedex_registrations/#{fedex_account_number}/address", + params, + ).and_return(json_response) + + response = client.fedex_registration.register_address(fedex_account_number, params) + + expect(response).to be_an_instance_of(EasyPost::Models::EasyPostObject) + expect(response.email_address).to be_nil + expect(response.options).to include('SMS') + expect(response.options).to include('CALL') + expect(response.options).to include('INVOICE') + expect(response.phone_number).to eq('***-***-9721') + end + end + + describe '.request_pin' do + it 'requests a pin' do + fedex_account_number = '123456789' + wrapped_params = { + pin_method: { + option: 'SMS', + }, + } + + json_response = { + 'message' => 'sent secured Pin', + } + + allow(client).to receive(:make_request).with( + :post, + "fedex_registrations/#{fedex_account_number}/pin", + wrapped_params, + ).and_return(json_response) + + response = client.fedex_registration.request_pin(fedex_account_number, 'SMS') + + expect(response).to be_an_instance_of(EasyPost::Models::EasyPostObject) + expect(response.message).to eq('sent secured Pin') + end + end + + describe '.validate_pin' do + it 'validates a pin' do + fedex_account_number = '123456789' + pin_validation = { + pin_code: '123456', + name: 'BILLING NAME', + } + easypost_details = { + carrier_account_id: 'ca_123', + } + params = { + pin_validation: pin_validation, + easypost_details: easypost_details, + } + + json_response = { + 'id' => 'ca_123', + 'object' => 'CarrierAccount', + 'type' => 'FedexAccount', + 'credentials' => { + 'account_number' => '123456789', + 'mfa_key' => '123456789-XXXXX', + }, + } + + allow(client).to receive(:make_request).with( + :post, + "fedex_registrations/#{fedex_account_number}/pin/validate", + params, + ).and_return(json_response) + + response = client.fedex_registration.validate_pin(fedex_account_number, params) + + expect(response).to be_an_instance_of(EasyPost::Models::EasyPostObject) + expect(response.id).to eq('ca_123') + expect(response.object).to eq('CarrierAccount') + expect(response.type).to eq('FedexAccount') + expect(response.credentials['account_number']).to eq('123456789') + expect(response.credentials['mfa_key']).to eq('123456789-XXXXX') + end + end + + describe '.submit_invoice' do + it 'submits details about an invoice' do + fedex_account_number = '123456789' + invoice_validation = { + name: 'BILLING NAME', + invoice_number: 'INV-12345', + invoice_date: '2025-12-08', + invoice_amount: '100.00', + invoice_currency: 'USD', + } + easypost_details = { + carrier_account_id: 'ca_123', + } + params = { + invoice_validation: invoice_validation, + easypost_details: easypost_details, + } + + json_response = { + 'id' => 'ca_123', + 'object' => 'CarrierAccount', + 'type' => 'FedexAccount', + 'credentials' => { + 'account_number' => '123456789', + 'mfa_key' => '123456789-XXXXX', + }, + } + + allow(client).to receive(:make_request).with( + :post, + "fedex_registrations/#{fedex_account_number}/invoice", + params, + ).and_return(json_response) + + response = client.fedex_registration.submit_invoice(fedex_account_number, params) + + expect(response).to be_an_instance_of(EasyPost::Models::EasyPostObject) + expect(response.id).to eq('ca_123') + expect(response.object).to eq('CarrierAccount') + expect(response.type).to eq('FedexAccount') + expect(response.credentials['account_number']).to eq('123456789') + expect(response.credentials['mfa_key']).to eq('123456789-XXXXX') + end + end +end