Skip to content

Commit 30410fd

Browse files
Justintime50claude
andcommitted
feat(fedex): add FedEx Multi-Factor Authentication endpoints
Adds native support for FedEx 2FA registration endpoints, eliminating the need for customers to use curl commands. This implementation enables automated FedEx registration workflows through the PHP client library. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 1fc514c commit 30410fd

10 files changed

Lines changed: 469 additions & 12 deletions

File tree

lib/EasyPost/EasyPostClient.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use EasyPost\Service\EmbeddableService;
2323
use EasyPost\Service\EndShipperService;
2424
use EasyPost\Service\EventService;
25+
use EasyPost\Service\FedExRegistrationService;
2526
use EasyPost\Service\InsuranceService;
2627
use EasyPost\Service\LumaService;
2728
use EasyPost\Service\OrderService;
@@ -58,6 +59,7 @@
5859
* @property EmbeddableService $embeddable
5960
* @property EndShipperService $endShipper
6061
* @property EventService $event
62+
* @property FedExRegistrationService $fedexRegistration
6163
* @property InsuranceService $insurance
6264
* @property LumaService $luma
6365
* @property OrderService $order
@@ -134,6 +136,7 @@ public function __get(string $serviceName)
134136
'embeddable' => EmbeddableService::class,
135137
'endShipper' => EndShipperService::class,
136138
'event' => EventService::class,
139+
'fedexRegistration' => FedExRegistrationService::class,
137140
'insurance' => InsuranceService::class,
138141
'luma' => LumaService::class,
139142
'order' => OrderService::class,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace EasyPost;
4+
5+
/**
6+
* @package EasyPost
7+
* @property string|null $email_address
8+
* @property string[]|null $options
9+
* @property string|null $phone_number
10+
* @property string|null $id
11+
* @property string|null $object
12+
* @property string|null $type
13+
* @property array<string, string>|null $credentials
14+
*/
15+
class FedExAccountValidationResponse extends EasyPostObject
16+
{
17+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace EasyPost;
4+
5+
/**
6+
* @package EasyPost
7+
* @property string|null $message
8+
*/
9+
class FedExRequestPinResponse extends EasyPostObject
10+
{
11+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
<?php
2+
3+
namespace EasyPost\Service;
4+
5+
use EasyPost\FedExAccountValidationResponse;
6+
use EasyPost\FedExRequestPinResponse;
7+
use EasyPost\Http\Requestor;
8+
use EasyPost\Util\InternalUtil;
9+
10+
/**
11+
* FedExRegistration service containing all the logic to make API calls.
12+
*/
13+
class FedExRegistrationService extends BaseService
14+
{
15+
/**
16+
* Register the billing address for a FedEx account.
17+
* Advanced method for custom parameter structures.
18+
*
19+
* @param string $fedexAccountNumber
20+
* @param mixed $params
21+
* @return mixed
22+
*/
23+
public function registerAddress(string $fedexAccountNumber, mixed $params = null): mixed
24+
{
25+
$wrappedParams = $this->wrapAddressValidation($params);
26+
$url = "/fedex_registrations/{$fedexAccountNumber}/address";
27+
28+
$response = Requestor::request($this->client, 'post', $url, $wrappedParams);
29+
30+
return InternalUtil::convertToEasyPostObject($this->client, $response);
31+
}
32+
33+
/**
34+
* Request a PIN for FedEx account verification.
35+
*
36+
* @param string $fedexAccountNumber
37+
* @param string $pinMethodOption
38+
* @return mixed
39+
*/
40+
public function requestPin(string $fedexAccountNumber, string $pinMethodOption): mixed
41+
{
42+
$wrappedParams = [
43+
'pin_method' => [
44+
'option' => $pinMethodOption,
45+
],
46+
];
47+
$url = "/fedex_registrations/{$fedexAccountNumber}/pin";
48+
49+
$response = Requestor::request($this->client, 'post', $url, $wrappedParams);
50+
51+
return InternalUtil::convertToEasyPostObject($this->client, $response);
52+
}
53+
54+
/**
55+
* Validate the PIN entered by the user for FedEx account verification.
56+
*
57+
* @param string $fedexAccountNumber
58+
* @param mixed $params
59+
* @return mixed
60+
*/
61+
public function validatePin(string $fedexAccountNumber, mixed $params = null): mixed
62+
{
63+
$wrappedParams = $this->wrapPinValidation($params);
64+
$url = "/fedex_registrations/{$fedexAccountNumber}/pin/validate";
65+
66+
$response = Requestor::request($this->client, 'post', $url, $wrappedParams);
67+
68+
return InternalUtil::convertToEasyPostObject($this->client, $response);
69+
}
70+
71+
/**
72+
* Submit invoice information to complete FedEx account registration.
73+
*
74+
* @param string $fedexAccountNumber
75+
* @param mixed $params
76+
* @return mixed
77+
*/
78+
public function submitInvoice(string $fedexAccountNumber, mixed $params = null): mixed
79+
{
80+
$wrappedParams = $this->wrapInvoiceValidation($params);
81+
$url = "/fedex_registrations/{$fedexAccountNumber}/invoice";
82+
83+
$response = Requestor::request($this->client, 'post', $url, $wrappedParams);
84+
85+
return InternalUtil::convertToEasyPostObject($this->client, $response);
86+
}
87+
88+
/**
89+
* Wraps address validation parameters and ensures the "name" field exists.
90+
* If not present, generates a UUID (with hyphens removed) as the name.
91+
*
92+
* @param mixed $params
93+
* @return array<string, mixed>
94+
*/
95+
private function wrapAddressValidation(mixed $params): array
96+
{
97+
$wrappedParams = [];
98+
99+
if (isset($params['address_validation'])) {
100+
$addressValidation = $params['address_validation'];
101+
$this->ensureNameField($addressValidation);
102+
$wrappedParams['address_validation'] = $addressValidation;
103+
}
104+
105+
if (isset($params['easypost_details'])) {
106+
$wrappedParams['easypost_details'] = $params['easypost_details'];
107+
}
108+
109+
return $wrappedParams;
110+
}
111+
112+
/**
113+
* Wraps PIN validation parameters and ensures the "name" field exists.
114+
* If not present, generates a UUID (with hyphens removed) as the name.
115+
*
116+
* @param mixed $params
117+
* @return array<string, mixed>
118+
*/
119+
private function wrapPinValidation(mixed $params): array
120+
{
121+
$wrappedParams = [];
122+
123+
if (isset($params['pin_validation'])) {
124+
$pinValidation = $params['pin_validation'];
125+
$this->ensureNameField($pinValidation);
126+
$wrappedParams['pin_validation'] = $pinValidation;
127+
}
128+
129+
if (isset($params['easypost_details'])) {
130+
$wrappedParams['easypost_details'] = $params['easypost_details'];
131+
}
132+
133+
return $wrappedParams;
134+
}
135+
136+
/**
137+
* Wraps invoice validation parameters and ensures the "name" field exists.
138+
* If not present, generates a UUID (with hyphens removed) as the name.
139+
*
140+
* @param mixed $params
141+
* @return array<string, mixed>
142+
*/
143+
private function wrapInvoiceValidation(mixed $params): array
144+
{
145+
$wrappedParams = [];
146+
147+
if (isset($params['invoice_validation'])) {
148+
$invoiceValidation = $params['invoice_validation'];
149+
$this->ensureNameField($invoiceValidation);
150+
$wrappedParams['invoice_validation'] = $invoiceValidation;
151+
}
152+
153+
if (isset($params['easypost_details'])) {
154+
$wrappedParams['easypost_details'] = $params['easypost_details'];
155+
}
156+
157+
return $wrappedParams;
158+
}
159+
160+
/**
161+
* Ensures the "name" field exists in the provided array.
162+
* If not present, generates a UUID (with hyphens removed) as the name.
163+
* This follows the pattern used in the web UI implementation.
164+
*
165+
* @param array<string, mixed> &$array
166+
* @return void
167+
*/
168+
private function ensureNameField(array &$array): void
169+
{
170+
if (!isset($array['name'])) {
171+
$uuid = sprintf(
172+
'%04x%04x%04x%04x%04x%04x%04x%04x',
173+
mt_rand(0, 0xffff),
174+
mt_rand(0, 0xffff),
175+
mt_rand(0, 0xffff),
176+
mt_rand(0, 0x0fff) | 0x4000,
177+
mt_rand(0, 0x3fff) | 0x8000,
178+
mt_rand(0, 0xffff),
179+
mt_rand(0, 0xffff),
180+
mt_rand(0, 0xffff)
181+
);
182+
$array['name'] = $uuid;
183+
}
184+
}
185+
}

lib/EasyPost/Util/InternalUtil.php

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
use EasyPost\Event;
1919
use EasyPost\Exception\General\EasyPostException;
2020
use EasyPost\Exception\General\FilteringException;
21+
use EasyPost\FedExAccountValidationResponse;
22+
use EasyPost\FedExRequestPinResponse;
2123
use EasyPost\Fee;
2224
use EasyPost\Insurance;
2325
use EasyPost\Order;
@@ -38,18 +40,20 @@
3840
use EasyPost\Webhook;
3941

4042
const OBJECT_MAPPING = [
41-
'Address' => Address::class,
42-
'ApiKey' => ApiKey::class,
43-
'Batch' => Batch::class,
44-
'Brand' => Brand::class,
45-
'CarrierAccount' => CarrierAccount::class,
46-
'CarrierDetail' => CarrierDetail::class,
47-
'Claim' => Claim::class,
48-
'CustomsInfo' => CustomsInfo::class,
49-
'CustomsItem' => CustomsItem::class,
50-
'EndShipper' => EndShipper::class,
51-
'Event' => Event::class,
52-
'Fee' => Fee::class,
43+
'Address' => Address::class,
44+
'ApiKey' => ApiKey::class,
45+
'Batch' => Batch::class,
46+
'Brand' => Brand::class,
47+
'CarrierAccount' => CarrierAccount::class,
48+
'CarrierDetail' => CarrierDetail::class,
49+
'Claim' => Claim::class,
50+
'CustomsInfo' => CustomsInfo::class,
51+
'CustomsItem' => CustomsItem::class,
52+
'EndShipper' => EndShipper::class,
53+
'Event' => Event::class,
54+
'FedExAccountValidationResponse' => FedExAccountValidationResponse::class,
55+
'FedExRequestPinResponse' => FedExRequestPinResponse::class,
56+
'Fee' => Fee::class,
5357
'Insurance' => Insurance::class,
5458
'Order' => Order::class,
5559
'Parcel' => Parcel::class,

0 commit comments

Comments
 (0)