Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for Fedex Trade Documents Upload API #9

Open
bleepsandblops opened this issue Jan 18, 2024 · 7 comments
Open

Adding support for Fedex Trade Documents Upload API #9

bleepsandblops opened this issue Jan 18, 2024 · 7 comments

Comments

@bleepsandblops
Copy link

What are you trying to do?

Hi there,

Thanks a lot for adding the regular Fedex API. Turns out they have another API called Trade Documents Upload API and it has a different URL:
https://documentapi.prod.fedex.com/
and then endpoints:
for documents: documents/v1/etds/upload
for images: documents/v1/etds/upload

As far as I know, auth etc works like the other Fedex API.

Thank you!

What's your proposed solution?

Implement the Fedex Trade Upload API or add an option in the regular Fedex one

Additional context

No response

@engram-design
Copy link
Member

You can also override the baseUri of the provider, which is probably going to be the easiest thing here, rather than spinning up a whole new provider, or adding a conditional for which API to use. Particularly if the auth is all the same.

{% set data = craft.consume.fetchData('fedex', 'POST', 'documents/v1/etds/upload', {
    base_uri: 'https://documentapi.prod.fedex.com/',
}) %}

@bleepsandblops
Copy link
Author

bleepsandblops commented Jan 19, 2024

Hi @engram-design thanks a lot, so would you say something like this should work? Because server is sending me back a 500 (see 500 error below)

        $payload = [
            'base_uri' => 'https://documentapi.prod.fedex.com/',
            'multipart' => [
                [
                    'name' => 'attachment',
                    'contents' => fopen(Craft::getAlias('@root') . $filePath, 'r'),
                    'filename' => $fileName
                ],
                [
                    'name' => 'document',
                    'contents' => json_encode([
                        'workflowName' => 'ETDPostshipment',
                        'name' => $fileName,
                        'contentType' => 'application/pdf',
                        'carrierCode' => 'FDXE',
                        'meta' => [
                            'trackingNumber' => $trackingNumber,
                            'shipmentDate' => $shipmentDate,
                            'shipDocumentType' => 'COMMERCIAL_INVOICE',
                            'originCountryCode' => 'FR',
                            'destinationCountryCode' => $order->shippingAddress->countryCode
                        ]
                    ])
                ]
            ]
        ];

$data = Consume::$plugin->getService()->fetchData('fedexTradeUpload', 'POST', 'documents/v1/etds/upload', $payload);

(I've also tried without the json_encode.)

The 500 error:

    "message": "Failed to parse multipart servlet request; nested exception is javax.servlet.ServletException: org.apache.tomcat.util.http.fileupload.FileUploadBase$InvalidContentTypeException: the request doesn't contain a multipart/form-data or multipart/mixed stream, content type header is text/xml; charset=utf-8",

For reference, this works:

        $headers = [
            'Authorization' => 'Bearer ' . $token, 
            'X-locale' => 'en_US',
            'Content-Type' => 'multipart/form-data'
        ];

        $payload =
            [
                'workflowName' => 'ETDPostshipment',
                'name' => 'XXX.pdf',
                'contentType' => 'application/pdf',
                'carrierCode' => 'FDXE',
                'meta' => [
                    'trackingNumber' => $trackingNumber,
                    'shipmentDate' => $shipmentDate,
                    'shipDocumentType' => 'COMMERCIAL_INVOICE',
                    'originCountryCode' => 'FR',
                    'destinationCountryCode' => $order->shippingAddress->countryCode
                ]
            ];

        $jsonPayload = json_encode($payload);

        $filePath = '/storage/XXX.pdf';
        $fileName = 'XXX.pdf'; // The name of the form field

        try {
            // Create a multipart/form-data stream
            $multipartStream = new MultipartStream([
                [
                    'name' => 'attachment',
                    'contents' => fopen(Craft::getAlias('@root') . $filePath, 'r'),
                    'filename' => basename($filePath)
                ],
                [
                    'name' => 'document',
                    'contents' => $jsonPayload
                ]
            ]);


            $headers = [
                'Authorization' => 'Bearer ' . $token, 
                'X-locale' => 'en_US',
                'Content-Type' => 'multipart/form-data; boundary=' . $multipartStream->getBoundary()
            ];

            // Create a PSR-7 request with the multipart stream as the body
            $request = new Request(
                'POST',
                'https://documentapi.prod.fedex.com/documents/v1/etds/upload', // Replace with your API endpoint
                $headers,
                $multipartStream
            );

            // Send the request
            $response = $client->send($request);

            echo $response->getBody();
        } catch (GuzzleException $e) {
            dump($e->getResponse()->getBody()->getContents());
        }

@bleepsandblops
Copy link
Author

Ok so I had a dig in the code, and essentially this bit here is missing the 'multipart' right?
https://github.com/verbb/auth/blob/d8ca65e982c0b0658dfd36a67abba3710015db23/src/base/ProviderTrait.php#L109

If I add in these lines this bit of code:

            if ($multipart = ArrayHelper::remove($options, 'multipart')) {
                $options['body'] =  $multipart;
                $boundary = ArrayHelper::remove($options, 'boundary');
                $options['headers']['Content-Type'] = 'multipart/form-data; boundary='.$boundary;
            }

and in my call:

$multipartStream = new MultipartStream([
            [
                'name' => 'attachment',
                'contents' => fopen(Craft::getAlias('@root') . $filePath, 'r'),
                'filename' => basename($filePath)
            ],
            [
                'name' => 'document',
                'contents' => json_encode([
                    'workflowName' => 'ETDPostshipment',
                    'name' => $fileName,
                    'contentType' => 'application/pdf',
                    'carrierCode' => 'FDXE',
                    'meta' => [
                        'trackingNumber' => $trackingNumber,
                        'shipmentDate' => $shipmentDate,
                        'shipDocumentType' => 'COMMERCIAL_INVOICE',
                        'originCountryCode' => 'FR',
                        'destinationCountryCode' => $order->shippingAddress->countryCode
                    ]
                ])
            ]
        ]);

        $payload = [
            'base_uri' => 'https://documentapi.prod.fedex.com/',
            'multipart' => $multipartStream,
            'boundary' => $multipartStream->getBoundary()
        ];

        $data = Consume::$plugin->getService()->fetchData('fedexTradeUpload', 'POST', 'documents/v1/etds/upload', $payload);

then it seems to work!

@engram-design
Copy link
Member

Ah, good call. Yes we actually need to implement the shortcuts that Guzzle normally does, like json being auto-encoded as JSON, as technically it uses the league/oauth2-client package's getAuthenticatedRequest() method to do the authenticated request.

Just added that in verbb/auth@d5e9c1e

@bleepsandblops
Copy link
Author

@engram-design thanks! struggling with my composer would you mind sharing how I get that commit? I don't have auth as a standalone plugin, just through consume's requirements

@engram-design
Copy link
Member

Yeah, you'll need to manually include the auth package via composer require verbb/auth:"dev-craft-4 as 1.0.15" which you can (and should) remove once I've tagged a release

@bleepsandblops
Copy link
Author

This works great FYI - fine to close for me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants