Skip to content

Commit 8c12357

Browse files
authored
Merge pull request #367 from EasyPost/feature/fedex-2fa-support
feat(fedex): add FedEx Multi-Factor Authentication endpoints
1 parent 3850550 commit 8c12357

File tree

9 files changed

+438
-13
lines changed

9 files changed

+438
-13
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
# CHANGELOG
22

3+
## v8.5.0 (2025-12-09)
4+
5+
- Adds the following functions:
6+
- `fedexRegistration.registerAddress`
7+
- `fedexRegistration.requestPin`
8+
- `fedexRegistration.validatePin`
9+
- `fedexRegistration.submitInvoice`
10+
- Adds `details` property of a `Rate` objec
11+
312
## v8.4.1 (2025-12-01)
413

514
- Adds missing `apiKeys` field to `BaseUser`
6-
- Removes unecessary `SerializedName` entries in `StatelessRate`
715

816
## v8.4.0 (2025-11-24)
917

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Add this to your project's POM:
1616
<dependency>
1717
<groupId>com.easypost</groupId>
1818
<artifactId>easypost-api-client</artifactId>
19-
<version>8.4.1</version>
19+
<version>8.5.0</version>
2020
</dependency>
2121
```
2222

@@ -25,7 +25,7 @@ Add this to your project's POM:
2525
Add this to your project's build file:
2626

2727
```groovy
28-
implementation "com.easypost:easypost-api-client:8.4.1"
28+
implementation "com.easypost:easypost-api-client:8.5.0"
2929
```
3030

3131
**NOTE:** [Google Gson](http://code.google.com/p/google-gson/) is required.

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
8.4.1
1+
8.5.0

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<groupId>com.easypost</groupId>
77
<artifactId>easypost-api-client</artifactId>
88

9-
<version>8.4.1</version>
9+
<version>8.5.0</version>
1010
<packaging>jar</packaging>
1111

1212
<name>com.easypost:easypost-api-client</name>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.easypost.model;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
import lombok.Getter;
6+
7+
@Getter
8+
public final class FedExAccountValidationResponse {
9+
// If the response contains the following, one must complete pin or invoice validation next
10+
private String emailAddress;
11+
private List<String> options;
12+
private String phoneNumber;
13+
14+
// If the response contains the following, pre-validation has been completed
15+
private String id;
16+
private String object;
17+
private String type;
18+
private Map<String, String> credentials;
19+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.easypost.model;
2+
3+
import lombok.Getter;
4+
5+
@Getter
6+
public class FedExRequestPinResponse {
7+
private String message;
8+
}

src/main/java/com/easypost/service/EasyPostClient.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
package com.easypost.service;
22

3-
import lombok.Getter;
3+
import java.util.function.Function;
44

55
import com.easypost.Constants;
66
import com.easypost.exception.General.MissingParameterError;
7-
import com.easypost.hooks.ResponseHook;
8-
import com.easypost.hooks.ResponseHookResponses;
97
import com.easypost.hooks.RequestHook;
108
import com.easypost.hooks.RequestHookResponses;
9+
import com.easypost.hooks.ResponseHook;
10+
import com.easypost.hooks.ResponseHookResponses;
1111

12-
import java.util.function.Function;
12+
import lombok.Getter;
1313

1414
public class EasyPostClient {
1515
private final int connectTimeoutMilliseconds;
@@ -26,6 +26,7 @@ public class EasyPostClient {
2626
public final BillingService billing = new BillingService(this);
2727
public final CarrierAccountService carrierAccount = new CarrierAccountService(this);
2828
public final CarrierMetadataService carrierMetadata = new CarrierMetadataService(this);
29+
public final FedExRegistrationService fedexRegistration = new FedExRegistrationService(this);
2930
public final CarrierTypeService carrierType = new CarrierTypeService(this);
3031
public final ClaimService claim = new ClaimService(this);
3132
public final CustomerPortalService customerPortal = new CustomerPortalService(this);
@@ -82,7 +83,7 @@ public EasyPostClient(String apiKey, String apiBase) throws MissingParameterErro
8283
*
8384
* @param apiKey API key for API calls.
8485
* @param connectTimeoutMilliseconds Timeout for connection.
85-
* @throws MissingParameterError When the request fails.
86+
* @throws MissingParameterError When the request fails.
8687
*/
8788
public EasyPostClient(String apiKey, int connectTimeoutMilliseconds) throws MissingParameterError {
8889
this(apiKey, connectTimeoutMilliseconds, Constants.Http.API_BASE);
@@ -94,7 +95,7 @@ public EasyPostClient(String apiKey, int connectTimeoutMilliseconds) throws Miss
9495
* @param apiKey API key for API calls.
9596
* @param connectTimeoutMilliseconds Timeout for connection.
9697
* @param apiBase API base for API calls.
97-
* @throws MissingParameterError When the request fails.
98+
* @throws MissingParameterError When the request fails.
9899
*/
99100
public EasyPostClient(String apiKey, int connectTimeoutMilliseconds, String apiBase) throws MissingParameterError {
100101
this(apiKey, connectTimeoutMilliseconds, Constants.Http.DEFAULT_READ_TIMEOUT_MILLISECONDS, apiBase);
@@ -106,7 +107,7 @@ public EasyPostClient(String apiKey, int connectTimeoutMilliseconds, String apiB
106107
* @param apiKey API key for API calls.
107108
* @param connectTimeoutMilliseconds Timeout for connection.
108109
* @param readTimeoutMilliseconds Timeout for read.
109-
* @throws MissingParameterError When the request fails.
110+
* @throws MissingParameterError When the request fails.
110111
*/
111112
public EasyPostClient(String apiKey, int connectTimeoutMilliseconds, int readTimeoutMilliseconds)
112113
throws MissingParameterError {
@@ -120,7 +121,7 @@ public EasyPostClient(String apiKey, int connectTimeoutMilliseconds, int readTim
120121
* @param connectTimeoutMilliseconds Timeout for connection.
121122
* @param readTimeoutMilliseconds Timeout for read.
122123
* @param apiBase API base for API calls.
123-
* @throws MissingParameterError When the request fails.
124+
* @throws MissingParameterError When the request fails.
124125
*/
125126
public EasyPostClient(String apiKey, int connectTimeoutMilliseconds, int readTimeoutMilliseconds, String apiBase)
126127
throws MissingParameterError {
@@ -136,6 +137,7 @@ public EasyPostClient(String apiKey, int connectTimeoutMilliseconds, int readTim
136137

137138
/**
138139
* Subscribes to a request hook from the given function.
140+
*
139141
* @param function The function to be subscribed to the request hook
140142
*/
141143
public void subscribeToRequestHook(Function<RequestHookResponses, Object> function) {
@@ -144,6 +146,7 @@ public void subscribeToRequestHook(Function<RequestHookResponses, Object> functi
144146

145147
/**
146148
* Unsubscribes to a request hook from the given function.
149+
*
147150
* @param function The function to be unsubscribed from the request hook
148151
*/
149152
public void unsubscribeFromRequestHook(Function<RequestHookResponses, Object> function) {
@@ -152,6 +155,7 @@ public void unsubscribeFromRequestHook(Function<RequestHookResponses, Object> fu
152155

153156
/**
154157
* Subscribes to a response hook from the given function.
158+
*
155159
* @param function The function to be subscribed to the response hook
156160
*/
157161
public void subscribeToResponseHook(Function<ResponseHookResponses, Object> function) {
@@ -160,6 +164,7 @@ public void subscribeToResponseHook(Function<ResponseHookResponses, Object> func
160164

161165
/**
162166
* Unubscribes to a response hook from the given function.
167+
*
163168
* @param function The function to be unsubscribed from the response hook
164169
*/
165170
public void unsubscribeFromResponseHook(Function<ResponseHookResponses, Object> function) {
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package com.easypost.service;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
import java.util.UUID;
6+
7+
import com.easypost.exception.EasyPostException;
8+
import com.easypost.http.Requestor;
9+
import com.easypost.http.Requestor.RequestMethod;
10+
import com.easypost.model.FedExAccountValidationResponse;
11+
import com.easypost.model.FedExRequestPinResponse;
12+
13+
public class FedExRegistrationService {
14+
private final EasyPostClient client;
15+
16+
/**
17+
* FedExRegistrationService constructor.
18+
*
19+
* @param client The client object.
20+
*/
21+
FedExRegistrationService(EasyPostClient client) {
22+
this.client = client;
23+
}
24+
25+
/**
26+
* Register the billing address for a FedEx account.
27+
* Advanced method for custom parameter structures.
28+
*
29+
* @param fedexAccountNumber The FedEx account number.
30+
* @param params Map of parameters.
31+
* @return FedExAccountValidationResponse object with next steps (PIN or invoice
32+
* validation).
33+
* @throws EasyPostException when the request fails.
34+
*/
35+
public FedExAccountValidationResponse registerAddress(final String fedexAccountNumber,
36+
final Map<String, Object> params) throws EasyPostException {
37+
Map<String, Object> wrappedParams = wrapAddressValidation(params);
38+
String endpoint = String.format("fedex_registrations/%s/address", fedexAccountNumber);
39+
40+
return Requestor.request(RequestMethod.POST, endpoint, wrappedParams, FedExAccountValidationResponse.class,
41+
client);
42+
}
43+
44+
/**
45+
* Request a PIN for FedEx account verification.
46+
*
47+
* @param fedexAccountNumber The FedEx account number.
48+
* @param pinMethodOption The PIN delivery method: "SMS", "CALL", or "EMAIL".
49+
* @return FedExRequestPinResponse object confirming PIN was sent.
50+
* @throws EasyPostException when the request fails.
51+
*/
52+
public FedExRequestPinResponse requestPin(final String fedexAccountNumber, final String pinMethodOption)
53+
throws EasyPostException {
54+
Map<String, Object> wrappedParams = new HashMap<>();
55+
Map<String, Object> pinMethodMap = new HashMap<>();
56+
pinMethodMap.put("option", pinMethodOption);
57+
wrappedParams.put("pin_method", pinMethodMap);
58+
String endpoint = String.format("fedex_registrations/%s/pin", fedexAccountNumber);
59+
60+
return Requestor.request(RequestMethod.POST, endpoint, wrappedParams, FedExRequestPinResponse.class, client);
61+
}
62+
63+
/**
64+
* Validate the PIN entered by the user for FedEx account verification.
65+
*
66+
* @param fedexAccountNumber The FedEx account number.
67+
* @param params Map of parameters.
68+
* @return FedExAccountValidationResponse object.
69+
* @throws EasyPostException when the request fails.
70+
*/
71+
public FedExAccountValidationResponse validatePin(final String fedexAccountNumber, final Map<String, Object> params)
72+
throws EasyPostException {
73+
Map<String, Object> wrappedParams = wrapPinValidation(params);
74+
String endpoint = String.format("fedex_registrations/%s/pin/validate", fedexAccountNumber);
75+
76+
return Requestor.request(RequestMethod.POST, endpoint, wrappedParams, FedExAccountValidationResponse.class,
77+
client);
78+
}
79+
80+
/**
81+
* Submit invoice information to complete FedEx account registration.
82+
*
83+
* @param fedexAccountNumber The FedEx account number.
84+
* @param params Map of parameters.
85+
* @return FedExAccountValidationResponse object.
86+
* @throws EasyPostException when the request fails.
87+
*/
88+
public FedExAccountValidationResponse submitInvoice(final String fedexAccountNumber,
89+
final Map<String, Object> params)
90+
throws EasyPostException {
91+
Map<String, Object> wrappedParams = wrapInvoiceValidation(params);
92+
String endpoint = String.format("fedex_registrations/%s/invoice", fedexAccountNumber);
93+
94+
return Requestor.request(RequestMethod.POST, endpoint, wrappedParams, FedExAccountValidationResponse.class,
95+
client);
96+
}
97+
98+
/**
99+
* Wraps address validation parameters and ensures the "name" field exists.
100+
* If not present, generates a UUID (with hyphens removed) as the name.
101+
*
102+
* @param params The original parameters map.
103+
* @return A new map with properly wrapped address_validation and
104+
* easypost_details.
105+
*/
106+
@SuppressWarnings("unchecked")
107+
private Map<String, Object> wrapAddressValidation(final Map<String, Object> params) {
108+
Map<String, Object> wrappedParams = new HashMap<>();
109+
110+
if (params.containsKey("address_validation")) {
111+
Map<String, Object> addressValidation = new HashMap<>(
112+
(Map<String, Object>) params.get("address_validation"));
113+
ensureNameField(addressValidation);
114+
wrappedParams.put("address_validation", addressValidation);
115+
}
116+
117+
if (params.containsKey("easypost_details")) {
118+
wrappedParams.put("easypost_details", params.get("easypost_details"));
119+
}
120+
121+
return wrappedParams;
122+
}
123+
124+
/**
125+
* Wraps PIN validation parameters and ensures the "name" field exists.
126+
* If not present, generates a UUID (with hyphens removed) as the name.
127+
*
128+
* @param params The original parameters map.
129+
* @return A new map with properly wrapped pin_validation and easypost_details.
130+
*/
131+
@SuppressWarnings("unchecked")
132+
private Map<String, Object> wrapPinValidation(final Map<String, Object> params) {
133+
Map<String, Object> wrappedParams = new HashMap<>();
134+
135+
if (params.containsKey("pin_validation")) {
136+
Map<String, Object> pinValidation = new HashMap<>(
137+
(Map<String, Object>) params.get("pin_validation"));
138+
ensureNameField(pinValidation);
139+
wrappedParams.put("pin_validation", pinValidation);
140+
}
141+
142+
if (params.containsKey("easypost_details")) {
143+
wrappedParams.put("easypost_details", params.get("easypost_details"));
144+
}
145+
146+
return wrappedParams;
147+
}
148+
149+
/**
150+
* Wraps invoice validation parameters and ensures the "name" field exists.
151+
* If not present, generates a UUID (with hyphens removed) as the name.
152+
*
153+
* @param params The original parameters map.
154+
* @return A new map with properly wrapped invoice_validation and
155+
* easypost_details.
156+
*/
157+
@SuppressWarnings("unchecked")
158+
private Map<String, Object> wrapInvoiceValidation(final Map<String, Object> params) {
159+
Map<String, Object> wrappedParams = new HashMap<>();
160+
161+
if (params.containsKey("invoice_validation")) {
162+
Map<String, Object> invoiceValidation = new HashMap<>(
163+
(Map<String, Object>) params.get("invoice_validation"));
164+
ensureNameField(invoiceValidation);
165+
wrappedParams.put("invoice_validation", invoiceValidation);
166+
}
167+
168+
if (params.containsKey("easypost_details")) {
169+
wrappedParams.put("easypost_details", params.get("easypost_details"));
170+
}
171+
172+
return wrappedParams;
173+
}
174+
175+
/**
176+
* Ensures the "name" field exists in the provided map.
177+
* If not present, generates a UUID (with hyphens removed) as the name.
178+
* This follows the pattern used in the web UI implementation.
179+
*
180+
* @param map The map to ensure the "name" field in.
181+
*/
182+
private void ensureNameField(final Map<String, Object> map) {
183+
if (!map.containsKey("name") || map.get("name") == null) {
184+
String uuid = UUID.randomUUID().toString().replace("-", "");
185+
map.put("name", uuid);
186+
}
187+
}
188+
}

0 commit comments

Comments
 (0)