Migrated the FedEx carrier from SOAP to the REST API - #1193
Open
fballiano wants to merge 5 commits into
Open
Conversation
Replaces the three SOAP services (RateService_v10, ShipService_v10, TrackService_v5) and their shipped WSDLs with FedEx's REST API, following the USPS REST carrier: a thin OAuthClient plus RestClient on Symfony HttpClient, with no new Composer dependency. Credentials move from meter number / key / password to an OAuth2 client id and secret, both encrypted. The upgrade script deletes the obsolete rows so no stale encrypted SOAP secret is left behind. Config values that REST renamed are migrated rather than aliased at read time: the five SOAP dropoff types map onto REST pickup types, and INTERNATIONAL_PRIORITY becomes FEDEX_INTERNATIONAL_PRIORITY in allowed_methods and free_method. Aliasing would have collapsed FEDEX_INTERNATIONAL_PRIORITY and FEDEX_INTERNATIONAL_PRIORITY_EXPRESS onto one code, letting one differently-priced service overwrite the other. Adds a rate_endpoint setting because FedEx exposes two rate products: the standard Rates and Transit Times API, and the Comprehensive one that registered Integrator Providers are required to use and which answers 403 on the other path. Both take the same payload and return the same shape, so one builder and one parser serve both. Defaults to standard. Also fixes unit_of_measure having no default at all, which sent weight.units as null and made an unsaved FedEx config unquotable.
The shipment and cancel endpoints work against the sandbox once the request carries an account authorised for shipping, so both paths now have live coverage: a real label is created, asserted to be a PDF, and cancelled again. Testing that surfaced a bug in rollBack(). FedEx refuses a cancel with HTTP 200, no errors[], and output.cancelledShipment false, but rollBack() ignored the response and returned true unconditionally, so a label left live during a multi-package failure was silently reported as rolled back. It now checks the flag per package, logs the reason FedEx gave, and still attempts the remaining packages before returning false.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #918.
FedEx has been retiring its SOAP web services, so the carrier now talks REST. Mirrors the USPS REST carrier: a thin
Fedex/OAuthClientplusFedex/RestClienton Symfony HttpClient, no new Composer dependency. The three WSDLs are gone, along with_createSoapClient,getVersionInfo,_getAuthDetailsand the already-dead_parseXmlResponse.Credentials move from meter number / key / password to an OAuth2 client id and secret, both encrypted. Tokens are cached under a
fedex_oauthtag forexpires_in - 300.Decisions worth reviewing
Renamed config values are migrated, not aliased. The five SOAP dropoff types map onto REST pickup types, and
INTERNATIONAL_PRIORITYbecomesFEDEX_INTERNATIONAL_PRIORITYinallowed_methodsandfree_method. Aliasing at read time would have collapsedFEDEX_INTERNATIONAL_PRIORITYandFEDEX_INTERNATIONAL_PRIORITY_EXPRESSonto one code, letting one differently-priced service silently overwrite the other.New
rate_endpointsetting, because FedEx exposes two rate products:/rate/v1/rates/quotes, and/rate/v1/comprehensiverates/quoteswhich registered Integrator Providers are required to use and which 403s on the other path. Both take the same payload and return the same shape, so one builder and one parser serve both. Defaults to standard, which is what a normal shipping account wants.Freight codes dropped from the default
allowed_methods— the Rate API doesn't quote freight, and FedEx Freight became a separate company in June 2026. Labels stay ingetCode('method')so existing configs still render.Bugs found while testing
rollBack()claimed success on a refused cancel. FedEx refuses a cancellation with HTTP 200, noerrors[], andoutput.cancelledShipment: false. The old code ignored the response and returnedtrueregardless, so a label left live during a multi-package failure was reported as rolled back — a label the merchant still gets billed for. It now checks the flag per package, logs the reason, and still attempts the remaining packages before returning false.carriers/fedex/unit_of_measurehad no default inconfig.xml, so it sentweight.units: null, which FedEx rejects outright. An unsaved FedEx config was unquotable. Pre-existing, not introduced here; defaults toLBnow with a regression test.Testing
44 unit tests over payload building, response parsing and rollback, with the rate and tracking parsers checked against real captured sandbox responses committed as fixtures.
6 integration tests hit the live sandbox, gated on
FEDEX_SANDBOX_*org secrets so they skip where credentials are absent:collectRates()as checkout calls itrollBack()has to noticeThe sandbox returns
SERVICE.UNAVAILABLE.ERRORandSYSTEM.UNEXPECTED.ERRORat random on byte-identical payloads, roughly one call in three. The integration tests retry those two codes with backoff; every other error fails on the first attempt. Production code does not retry, to avoid doubling checkout latency.Migration verified against a seeded database: SOAP credential rows deleted,
dropoffand service codes rewritten,INTERNATIONAL_PRIORITY_FREIGHTandEUROPE_FIRST_INTERNATIONAL_PRIORITYcorrectly untouched.