Search code examples
pythonsoapfedex

How to add customs value in python-fedex for international shipments?


I'm using the python-fedex module as a light wrapper for the FedEx SOAP API. As part of this, I'm trying to set up a basic example of an international shipment, but I'm getting stuck with the following error message:

fedex.base_service.FedexError: Customs Value is required. (Error code: 2033)

I believe I need to add the products I ship as commodities, incl. their customs value - but I struggle to get this to work. I found this link with some guidance (from C#), but I was unable to get it to work in Python. Any inputs are appreciated!

My code is below:


# !/usr/bin/env python
"""
This example shows how to create a shipment and generate a waybill as output. The variables populated below
represents the minimum required values. You will need to fill all of these, or
risk seeing a SchemaValidationError exception thrown.

Near the bottom of the module, you'll see some different ways to handle the
label data that is returned with the reply.
"""

import logging
import binascii
import datetime
import sys, os

from example_config import CONFIG_OBJ

from fedex.services.ship_service import FedexProcessShipmentRequest


# What kind of file do you want this example to generate?
# Valid choices for this example are PDF, PNG
GENERATE_IMAGE_TYPE = 'PDF'

# Un-comment to see the response from Fedex printed in stdout.
logging.basicConfig(stream=sys.stdout, level=logging.INFO)

# This is the object that will be handling our shipment request.
# We're using the FedexConfig object from example_config.py in this dir.
customer_transaction_id = "*** ShipService Request v17 using Python ***"  # Optional transaction_id
shipment = FedexProcessShipmentRequest(CONFIG_OBJ, customer_transaction_id=customer_transaction_id)

# This is very generalized, top-level information.
# REGULAR_PICKUP, REQUEST_COURIER, DROP_BOX, BUSINESS_SERVICE_CENTER or STATION
shipment.RequestedShipment.DropoffType = 'BUSINESS_SERVICE_CENTER'

# See page 355 in WS_ShipService.pdf for a full list. Here are the common ones:
# STANDARD_OVERNIGHT, PRIORITY_OVERNIGHT, FEDEX_GROUND, FEDEX_EXPRESS_SAVER,
# FEDEX_2_DAY, INTERNATIONAL_PRIORITY, SAME_DAY, INTERNATIONAL_ECONOMY
shipment.RequestedShipment.ServiceType = 'INTERNATIONAL_PRIORITY'

# What kind of package this will be shipped in.
# FEDEX_BOX, FEDEX_PAK, FEDEX_TUBE, YOUR_PACKAGING, FEDEX_ENVELOPE
shipment.RequestedShipment.PackagingType = 'FEDEX_ENVELOPE'

# Shipper contact info.
shipment.RequestedShipment.Shipper.Contact.PersonName = 'Shipper Name'
shipment.RequestedShipment.Shipper.Contact.CompanyName = 'Shipper Company'
shipment.RequestedShipment.Shipper.Contact.PhoneNumber = '004512345678'

# Shipper address.
shipment.RequestedShipment.Shipper.Address.StreetLines = ['Shipper Address']
shipment.RequestedShipment.Shipper.Address.City = 'City'
shipment.RequestedShipment.Shipper.Address.StateOrProvinceCode = ''
shipment.RequestedShipment.Shipper.Address.PostalCode = '8270'
shipment.RequestedShipment.Shipper.Address.CountryCode = 'DK'
shipment.RequestedShipment.Shipper.Address.Residential = False

# Recipient contact info.
shipment.RequestedShipment.Recipient.Contact.PersonName = 'US customer X'
shipment.RequestedShipment.Recipient.Contact.CompanyName = 'US company X'
shipment.RequestedShipment.Recipient.Contact.PhoneNumber = '0123456789'

# Recipient address
shipment.RequestedShipment.Recipient.Address.StreetLines = ['668 MURRAY AVE SE']
shipment.RequestedShipment.Recipient.Address.City = 'ROANOKE'
shipment.RequestedShipment.Recipient.Address.StateOrProvinceCode = 'VA'
shipment.RequestedShipment.Recipient.Address.PostalCode = '24013'
shipment.RequestedShipment.Recipient.Address.CountryCode = 'US'
# This is needed to ensure an accurate rate quote with the response. Use AddressValidation to get ResidentialStatus
shipment.RequestedShipment.Recipient.Address.Residential = False
shipment.RequestedShipment.EdtRequestType = 'NONE'

# Senders account information
shipment.RequestedShipment.ShippingChargesPayment.Payor.ResponsibleParty.AccountNumber = CONFIG_OBJ.account_number

# Who pays for the shipment?
# RECIPIENT, SENDER or THIRD_PARTY
shipment.RequestedShipment.ShippingChargesPayment.PaymentType = 'SENDER'

# Specifies the label type to be returned.
# LABEL_DATA_ONLY or COMMON2D
shipment.RequestedShipment.LabelSpecification.LabelFormatType = 'COMMON2D'

# Specifies which format the label file will be sent to you in.
# DPL, EPL2, PDF, PNG, ZPLII
shipment.RequestedShipment.LabelSpecification.ImageType = GENERATE_IMAGE_TYPE

# To use doctab stocks, you must change ImageType above to one of the
# label printer formats (ZPLII, EPL2, DPL).
# See documentation for paper types, there quite a few.
shipment.RequestedShipment.LabelSpecification.LabelStockType = 'PAPER_7X4.75'

# This indicates if the top or bottom of the label comes out of the
# printer first.
# BOTTOM_EDGE_OF_TEXT_FIRST or TOP_EDGE_OF_TEXT_FIRST
# Timestamp in YYYY-MM-DDThh:mm:ss format, e.g. 2002-05-30T09:00:00
shipment.RequestedShipment.ShipTimestamp = datetime.datetime.now().replace(microsecond=0).isoformat()

# BOTTOM_EDGE_OF_TEXT_FIRST, TOP_EDGE_OF_TEXT_FIRST
shipment.RequestedShipment.LabelSpecification.LabelPrintingOrientation = 'TOP_EDGE_OF_TEXT_FIRST'

# Delete the flags we don't want.
# Can be SHIPPING_LABEL_FIRST, SHIPPING_LABEL_LAST or delete
if hasattr(shipment.RequestedShipment.LabelSpecification, 'LabelOrder'):
    del shipment.RequestedShipment.LabelSpecification.LabelOrder  # Delete, not using.

# Create Weight, in pounds.
package1_weight = shipment.create_wsdl_object_of_type('Weight')
package1_weight.Value = 1.0
package1_weight.Units = "LB"

# Create PackageLineItem
package1 = shipment.create_wsdl_object_of_type('RequestedPackageLineItem')
# BAG, BARREL, BASKET, BOX, BUCKET, BUNDLE, CARTON, CASE, CONTAINER, ENVELOPE etc..
package1.PhysicalPackaging = 'ENVELOPE'
package1.Weight = package1_weight


# Add a signature option for the package using SpecialServicesRequested or comment out.
# SpecialServiceTypes can be APPOINTMENT_DELIVERY, COD, DANGEROUS_GOODS, DRY_ICE, SIGNATURE_OPTION etc..
package1.SpecialServicesRequested.SpecialServiceTypes = 'SIGNATURE_OPTION'
# SignatureOptionType can be ADULT, DIRECT, INDIRECT, NO_SIGNATURE_REQUIRED, SERVICE_DEFAULT
package1.SpecialServicesRequested.SignatureOptionDetail.OptionType = 'SERVICE_DEFAULT'


# This adds the RequestedPackageLineItem WSDL object to the shipment. It
# increments the package count and total weight of the shipment for you.
shipment.add_package(package1)


# If you want to make sure that all of your entered details are valid, you
# can call this and parse it just like you would via send_request(). If
# shipment.response.HighestSeverity == "SUCCESS", your shipment is valid.
# print(shipment.send_validation_request())

# Fires off the request, sets the 'response' attribute on the object.
shipment.send_request()

Solution

  • I have added a full international shipment example below, which solves this:

    """
    This example shows how to create an international shipment and generate a waybill as output.
    The example takes outset in a real practical use case, where electronic trade documents are
    used and an existing PDF commercial invoice is added along with product descriptions via ETD.
    Further, it adds event notifications to allow for emails to be sent to the end recipient.
    The script is comprised of a FedExLabelHelper class with all core functions, and a use case
    example with minimal dummy data
    """
    from example_config import CONFIG_OBJ
    from pathlib import Path
    import binascii
    import datetime
    from fedex.services.ship_service import FedexProcessShipmentRequest
    
    # ----------------------------------------------------
    # FedEx class for creating shipments
    class FedexLabelHelper:
        mCommodities = []
    
        def __init__(self):
            pass
    
        # ----------------------------------------------------
        # set overall shipment configuration
        def setShipmentConfig(
            self,
            CONFIG_OBJ,
            invoice_info,
            cust_tran_id="*** ShipService Request v17 using Python ***",
            dropoffType="BUSINESS_SERVICE_CENTER",
            shippingPaymentType="SENDER",
            labelFormatType="COMMON2D",
            labelSpecificationImageType="PDF",
            labelSpecificationStockType="PAPER_7X4.75",
            labelPrintingOrientation="TOP_EDGE_OF_TEXT_FIRST",
            LabelOrder="SHIPPING_LABEL_FIRST",
        ):
            self.invoice_info = invoice_info
            self.dropoffType = dropoffType
            self.serviceType = "INTERNATIONAL_PRIORITY" if invoice_info["ShippingExpress"] == True else "INTERNATIONAL_ECONOMY"
            self.mCommodities.clear()
            self.CONFIG_OBJ = CONFIG_OBJ
            self.shipment = FedexProcessShipmentRequest(CONFIG_OBJ, customer_transaction_id=cust_tran_id)
    
            self.shipment.RequestedShipment.DropoffType = dropoffType
            self.shipment.RequestedShipment.ServiceType = self.serviceType
            self.shipment.RequestedShipment.ShippingChargesPayment.Payor.ResponsibleParty.AccountNumber = CONFIG_OBJ.account_number
            self.shipment.RequestedShipment.ShippingChargesPayment.Payor.ResponsibleParty.Address.CountryCode = "DK"
            self.shipment.RequestedShipment.ShippingChargesPayment.PaymentType = shippingPaymentType
    
            labelSpecification = self.shipment.create_wsdl_object_of_type("LabelSpecification")
            labelSpecification.LabelFormatType = labelFormatType
            labelSpecification.LabelStockType = labelSpecificationStockType
            labelSpecification.ImageType = labelSpecificationImageType
            labelSpecification.LabelOrder = LabelOrder
            labelSpecification.LabelPrintingOrientation = labelPrintingOrientation
            self.shipment.RequestedShipment.LabelSpecification = labelSpecification
    
        # ----------------------------------------------------
        # set sender information
        def setSenderInfo(self, sender):
    
            self.shipment.RequestedShipment.Shipper.Contact.PersonName = sender["Name"]
            self.shipment.RequestedShipment.Shipper.Contact.CompanyName = sender["Company"]
            self.shipment.RequestedShipment.Shipper.Contact.PhoneNumber = sender["Phone"]
            self.shipment.RequestedShipment.Shipper.Contact.EMailAddress = sender["Email"]
            self.shipment.RequestedShipment.Shipper.Address.StreetLines = sender["Address"]
            self.shipment.RequestedShipment.Shipper.Address.City = sender["City"]
            self.shipment.RequestedShipment.Shipper.Address.StateOrProvinceCode = sender["Region"]
            self.shipment.RequestedShipment.Shipper.Address.PostalCode = sender["Zip"]
            self.shipment.RequestedShipment.Shipper.Address.CountryCode = sender["CountryCode"]
            self.shipment.RequestedShipment.Shipper.Address.Residential = sender["Residential"]
    
            ti = self.shipment.create_wsdl_object_of_type("TaxpayerIdentification")
            ti.Number = sender["VAT"]
            ti.TinType = "BUSINESS_NATIONAL"
            self.shipment.RequestedShipment.Shipper.Tins = ti
    
        # ----------------------------------------------------
        # upload all documents (invoice and product information)
        def upload_all_documents(self):
            doc_ids = []
            doc_ids.append(self.upload_document(self.invoice_info["InvoicePath"], "COMMERCIAL_INVOICE"))
    
            for pdf in self.invoice_info["Pdfs"]:
                doc_ids.append(self.upload_document(pdf, "OTHER"))
    
            return doc_ids
    
        # ----------------------------------------------------
        # function for uploading documents as electronic trade documents and getting the response doc IDs
        def upload_document(self, path, type):
            from fedex.services.document_service import FedexDocumentServiceRequest
    
            # specify prefix for use in attachment naming
            if type == "COMMERCIAL_INVOICE":
                prefix = "invoice_"
            else:
                prefix = "product_description_"
    
            uploadRequest = FedexDocumentServiceRequest(self.CONFIG_OBJ)
            uploadRequest.OriginCountryCode = "DK"
            uploadRequest.DestinationCountryCode = self.shipment.RequestedShipment.Recipient.Address.CountryCode
            uploadRequest.Usage = "ELECTRONIC_TRADE_DOCUMENTS"
    
            clientdetails = uploadRequest.create_wsdl_object_of_type("ClientDetail")
            clientdetails.AccountNumber = self.CONFIG_OBJ.account_number
            clientdetails.MeterNumber = self.CONFIG_OBJ.meter_number
            uploadRequest.ClientDetail = clientdetails
    
            webAuthDetails = uploadRequest.create_wsdl_object_of_type("WebAuthenticationDetail")
            webAuthDetails.ParentCredential.Key = self.CONFIG_OBJ.key
            webAuthDetails.ParentCredential.Password = self.CONFIG_OBJ.password
            webAuthDetails.UserCredential.Key = self.CONFIG_OBJ.key
            webAuthDetails.UserCredential.Password = self.CONFIG_OBJ.password
            uploadRequest.WebAuthenticationDetail = webAuthDetails
    
            docdetails = uploadRequest.create_wsdl_object_of_type("UploadDocumentDetail")
            docdetails.LineNumber = 1
            docdetails.DocumentType = type
            docdetails.FileName = prefix + path
            fileContent = open(path, "rb").read()
            fileBase64 = binascii.b2a_base64(fileContent)
            docdetails.DocumentContent = fileBase64.decode("cp1250")
            uploadRequest.Documents = docdetails
    
            uploadRequest.send_request()
    
            doc_id = uploadRequest.response.DocumentStatuses[0].DocumentId
    
            return doc_id
    
        # ----------------------------------------------------
        # set recipient information
        def setRecipientInfo(self, recipient):
            self.shipment.RequestedShipment.Recipient.Contact.PersonName = recipient["Name"]
            self.shipment.RequestedShipment.Recipient.Contact.CompanyName = recipient["Company"]
            self.shipment.RequestedShipment.Recipient.Contact.PhoneNumber = recipient["Phone"]
            self.shipment.RequestedShipment.Recipient.Contact.EMailAddress = recipient["Email"]
            self.shipment.RequestedShipment.Recipient.Address.StreetLines = recipient["Address"]
            self.shipment.RequestedShipment.Recipient.Address.City = recipient["City"]
            self.shipment.RequestedShipment.Recipient.Address.StateOrProvinceCode = recipient["Region"]
            self.shipment.RequestedShipment.Recipient.Address.PostalCode = recipient["Zip"]
            self.shipment.RequestedShipment.Recipient.Address.CountryCode = recipient["CountryCode"]
            self.shipment.RequestedShipment.Recipient.Address.Residential = recipient["Residential"]
    
            ti = self.shipment.create_wsdl_object_of_type("TaxpayerIdentification")
            ti.Number = recipient["VAT"]
            ti.TinType = "BUSINESS_NATIONAL"
            self.shipment.RequestedShipment.Recipient.Tins = ti
    
        # ----------------------------------------------------
        # add "commercial invoice" reference as the only commodity
        def add_ci_commodity(self):
    
            self.addCommodity(
                cCustomsValueAmnt=self.invoice_info["Value"],
                cCustomsValueCurrency=self.invoice_info["Currency"],
                cWeightValue=self.invoice_info["Weight"],
                cDescription="See attached commercial invoice",
                cQuantity=self.invoice_info["Quantity"],
                cExportLicenseNumber=self.shipment.RequestedShipment.Shipper.Tins.Number,
                cPartNumber=1,
            )
    
        # ----------------------------------------------------
        # add commodity to shipment (for now, just add 1 commodity to refer to attached CI)
        def addCommodity(
            self, cCustomsValueAmnt, cCustomsValueCurrency, cWeightValue, cDescription, cQuantity, cExportLicenseNumber, cPartNumber,
        ):
    
            commodity = self.shipment.create_wsdl_object_of_type("Commodity")
            commodity.NumberOfPieces = str(cQuantity)
            commodity.Description = cDescription
            commodity.Quantity = cQuantity
            commodity.QuantityUnits = "EA"
            commodity.ExportLicenseNumber = cExportLicenseNumber
            commodity.PartNumber = cPartNumber
            commodity.CountryOfManufacture = "DK"
    
            mCustomsValue = self.shipment.create_wsdl_object_of_type("Money")
            mCustomsValue.Amount = cCustomsValueAmnt
            mCustomsValue.Currency = cCustomsValueCurrency
            commodity.CustomsValue = mCustomsValue
    
            commodity_weight = self.shipment.create_wsdl_object_of_type("Weight")
            commodity_weight.Value = cWeightValue
            commodity_weight.Units = "KG"
            commodity.Weight = commodity_weight
    
            munitPrice = self.shipment.create_wsdl_object_of_type("Money")
            munitPrice.Amount = float(round((cCustomsValueAmnt / cQuantity), 2))
            munitPrice.Currency = cCustomsValueCurrency
            commodity.UnitPrice = munitPrice
    
            self.mCommodities.append(commodity)
    
        # ----------------------------------------------------
        # add package to shipment
        def set_packaging_info(self):
            weight = self.invoice_info["Weight"]
    
            type = "BOX" if weight > 0.5 else "ENVELOPE"
            weight_final = float(round(weight + 0.2, 2)) if weight > 0.5 else 0.4
    
            self.addShippingPackage(packageWeight=weight_final, physicalPackagingType=type, packagingType=f"FEDEX_{type}")
    
        # ----------------------------------------------------
        # add package to shipment
        def addShippingPackage(self, packageWeight, physicalPackagingType, packagingType, packageWeightUnit="KG"):
            package_weight = self.shipment.create_wsdl_object_of_type("Weight")
            package_weight.Value = packageWeight
            package_weight.Units = packageWeightUnit
    
            package = self.shipment.create_wsdl_object_of_type("RequestedPackageLineItem")
            package.PhysicalPackaging = physicalPackagingType
            package.Weight = package_weight
    
            self.shipment.add_package(package)
            self.shipment.RequestedShipment.TotalWeight = package_weight
            self.shipment.RequestedShipment.PackagingType = packagingType
    
        # ----------------------------------------------------
        # add information on duties
        def setDutiesPaymentInfo(self):
            mParty = self.shipment.create_wsdl_object_of_type("Party")
            mParty.AccountNumber = self.CONFIG_OBJ.account_number
            mParty.Address = self.shipment.RequestedShipment.Recipient.Address
    
            mPayor = self.shipment.create_wsdl_object_of_type("Payor")
            mPayor.ResponsibleParty = mParty
    
            mPayment = self.shipment.create_wsdl_object_of_type("Payment")
            mPayment.PaymentType = "RECIPIENT"  # change if sender should pay duties
            mPayment.Payor = mPayor
    
            mCustomsValue = self.shipment.create_wsdl_object_of_type("Money")
            mCustomsValue.Amount = self.invoice_info["Value"]
            mCustomsValue.Currency = self.invoice_info["Currency"]
    
            ccd = self.shipment.create_wsdl_object_of_type("CustomsClearanceDetail")
            ccd.Commodities = self.mCommodities
            ccd.CustomsValue = mCustomsValue
            ccd.DutiesPayment = mPayment
            self.shipment.RequestedShipment.CustomsClearanceDetail = ccd
    
        # ----------------------------------------------------
        # Set ETD (electronic trade documents) settings
        def setSpecialServices(self, doc_ids):
            # construct objects
            ssr = self.shipment.create_wsdl_object_of_type("ShipmentSpecialServicesRequested")
            ssr.SpecialServiceTypes.append("ELECTRONIC_TRADE_DOCUMENTS")
            ssr.SpecialServiceTypes.append("EVENT_NOTIFICATION")
    
            # set up ETD details
            etd = self.shipment.create_wsdl_object_of_type("EtdDetail")
            etd.RequestedDocumentCopies = "COMMERCIAL INVOICE"
    
            for i, doc_id in enumerate(doc_ids, start=0):
                udrd = self.shipment.create_wsdl_object_of_type("UploadDocumentReferenceDetail")
                udrd.DocumentType = "COMMERCIAL_INVOICE" if i == 0 else "OTHER"
                udrd.DocumentId = doc_id
                udrd.Description = "Commercial_Invoice" if i == 0 else "Product_Description"
                udrd.DocumentIdProducer = "CUSTOMER"
                ssr.EtdDetail.DocumentReferences.append(udrd)
    
            self.shipment.RequestedShipment.SpecialServicesRequested = ssr
    
            # set Event Notification details
            send = self.shipment.create_wsdl_object_of_type("ShipmentEventNotificationDetail")
            send.AggregationType = "PER_SHIPMENT"
    
            sens = self.shipment.create_wsdl_object_of_type("ShipmentEventNotificationSpecification")
            sens.NotificationDetail.NotificationType = "EMAIL"
            sens.NotificationDetail.EmailDetail.EmailAddress = self.shipment.RequestedShipment.Recipient.Contact.EMailAddress
            sens.NotificationDetail.EmailDetail.Name = self.shipment.RequestedShipment.Recipient.Contact.PersonName
            sens.NotificationDetail.Localization.LanguageCode = "EN"
            sens.Role = "SHIPPER"
            sens.Events.append("ON_SHIPMENT")
            sens.Events.append("ON_EXCEPTION")
            sens.Events.append("ON_DELIVERY")
            sens.FormatSpecification.Type = "HTML"
            send.EventNotifications = sens
            self.shipment.RequestedShipment.SpecialServicesRequested.EventNotificationDetail = send
    
        # ----------------------------------------------------
        # process the shipment
        def processInternationalShipment(self):
            from shutil import copyfile
    
            self.shipment.RequestedShipment.ShipTimestamp = datetime.datetime.now().replace(microsecond=0).isoformat()
    
            # print(" ---- **** DETAILS ---- ****")
            # print(self.shipment.RequestedShipment)
            # print(self.shipment.ClientDetail)
            # print(self.shipment.TransactionDetail)
            # print("REQUESTED SHIPMENT\n\n", self.shipment.RequestedShipment)
    
            self.shipment.send_request()
    
            # print("RESPONSE\n\n", self.shipment.response)
    
            status = self.shipment.response.HighestSeverity
    
            if status == "SUCCESS" and "CompletedShipmentDetail" in self.shipment.response:
                shipment_details = self.shipment.response.CompletedShipmentDetail
                package_details = shipment_details.CompletedPackageDetails[0]
                tracking_id = package_details.TrackingIds[0].TrackingNumber
                email = self.shipment.RequestedShipment.Recipient.Contact.EMailAddress
                fedex_cost = "N/A"
    
                if hasattr(package_details, "PackageRating"):
                    fedex_cost = package_details.PackageRating.PackageRateDetails[0].NetCharge.Amount
    
                # create the shipping PDF label
                ascii_label_data = package_details.Label.Parts[0].Image
                label_binary_data = binascii.a2b_base64(ascii_label_data)
                out_path = self.invoice_info["InvoiceId"] + f"_shipment_label_{tracking_id}.pdf"
    
                out_file = open(out_path, "wb")
                out_file.write(label_binary_data)
                out_file.close()
    
                # print output information
                print(
                    f"- SUCCESS: Created FedEx label for invoice {self.invoice_info['InvoiceId']}\n     tracking ID: {tracking_id}\n     email: {email}\n     FedEx cost: {fedex_cost}\n     Customs value: {self.invoice_info['Value']} {self.invoice_info['Currency']}\n     Weight: {self.invoice_info['Weight']}\n     output path: {out_path}"
                )
    
    
    # ----------------------------------------------------
    # main script
    commercial_invoice_path = "commercial_invoice_test.pdf"
    product_description_1_path = "product_description_test.pdf"
    
    sender = {
        "Company": "Sender Company",
        "Name": "Mr Smith",
        "Address": ["Address 1", "Address 2"],
        "Region": "",
        "Zip": "8230",
        "City": "Abyhoj",
        "Country": "Denmark",
        "Phone": "12345678",
        "Email": "mail@mail.com",
        "CountryCode": "DK",
        "Currency": "EUR",
        "VAT": "DK12345678",
        "Residential": False,
    }
    
    recipient = {
        "Company": "Recipient Co",
        "Name": "Contact Name",
        "Address": ["Adr1, Adr2"],
        "Region": "MN",
        "Zip": "55420",
        "City": "Bloomington",
        "Country": "United States",
        "Phone": "0123456789",
        "Email": "mail@mail.com",
        "CountryCode": "US",
        "Currency": "EUR",
        "VAT": "",
        "Residential": False,
    }
    
    invoice_info = {
        "InvoiceId": "14385",
        "Weight": 0.11,
        "Quantity": 2,
        "Value": 20.0,
        "Shipping": 25.0,
        "ShippingExpress": True,
        "Currency": "EUR",
        "InvoicePath": commercial_invoice_path,
        "Pdfs": [product_description_1_path],
    }
    
    # print output
    print(f"\n- recipient: {recipient}\n- invoice_info: {invoice_info}\n")
    
    # create FedEx Label Helper and set configuration
    flh = FedexLabelHelper()
    flh.setShipmentConfig(CONFIG_OBJ=CONFIG_OBJ, invoice_info=invoice_info)
    
    # add sender & recipient info to FedEx shipment
    flh.setSenderInfo(sender)
    flh.setRecipientInfo(recipient)
    
    # set packaging based on weight
    flh.set_packaging_info()
    
    # add reference to CI as only commodity info
    flh.add_ci_commodity()
    
    # set duties payment information
    flh.setDutiesPaymentInfo()
    
    # upload documents
    doc_ids = flh.upload_all_documents()
    
    # link uploaded documents as ETD and setup event notifications
    flh.setSpecialServices(doc_ids)
    
    # process shipments and create shipping labels
    flh.processInternationalShipment()