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

UPS Ship API Support #129

Open
pseudoclass opened this issue Jun 17, 2024 · 13 comments
Open

UPS Ship API Support #129

pseudoclass opened this issue Jun 17, 2024 · 13 comments

Comments

@pseudoclass
Copy link

What are you trying to do?

We would like to be able to fetch duties and taxes applied in the UPS shipping provider in Postie for international shipping. Currently the UPS shipping provider in Postie supports the Rates, Tracking and Labels API's but in order to get duties and taxes for international orders it would need to also support the UPS "Ship" API.

What's your proposed solution?

Add support in the UPS Shipping Provider for the "Ship" API

Additional context

No response

@engram-design
Copy link
Member

Just for clarity, do you mean the Shipping API? If so, that's the API we use to generate labels by creating a shipment.

Do you mean you want to use that API for rates-fetching? It's my understanding that taxes and rates are included in the quoted price. This "product" when selected by the user is then used to generate a shipment (and label) when it comes to fulfilment.

@pseudoclass
Copy link
Author

@engram-design

Sorry for not getting back sooner :)

Just for clarity, do you mean the Shipping API? If so, that's the API we use to generate labels by creating a shipment.

I believe so. Here is the response we got back from UPS regarding getting duties and taxes on international shipping:

"Based on the information, it appears to me that you are using the UPS rating API to get the rates for the shipments. Please note that the Rating API does not return the Duties and Taxes when requesting a rate for the shipment, the duties and taxes are returned when shipping a package using Ship API. A rate request will only return the rate for a specific UPS service or multiple."

Do you mean you want to use that API for rates-fetching? It's my understanding that taxes and rates are included in the quoted price. This "product" when selected by the user is then used to generate a shipment (and label) when it comes to fulfilment.

Yes. As per the comment from UPS, international duties and taxes aren't included in the quoted price from the Rating API, but if the Shipping API can be used for fetching the rates it would include them. Is that possible?

Thanks

@engram-design
Copy link
Member

Thanks for your response, that's quite interesting to hear. I'll look into this, but I believe it should be possible!

@sjcallender
Copy link

Hey @engram-design 👋. Is there anyway we can help expedite this? This is holding up a client from deploying UPS Worldwide. We eager to get their business shipping internationally.

@engram-design
Copy link
Member

Looking into it, this isn't going to be an easy addition, because the Shipment API requires you to specify a service to use. Whereas we "shop" for rates using api/rating/v1/Shop to return all available services.

Looping through each enabled service and checking rates with the api/shipments/v1/ship endpoint is not really what we need more. More to the point, this is going to generate shipments which cost your account to generate the labels, which you certainly don't want if you're just trying to get a quoted price.

I know your UPS contact @pseudoclass has mentioned Tax and Duties aren't included in the rating API, which seems backward to me. The TotalChargesWithTaxes item should be returning the value including any taxes, which would have thought be correct.

Having a bit of a search, I wonder if the Landed Cost Quote API would be suitable.

The Landed Cost Quote API allows you to estimate the all-inclusive cost of international shipments - including applicable duties, VAT, taxes, brokerage fees, and other fees. Required parameters include the currency and shipment details, such as the commodity ID, price, quantity, and country code of origin.

@johnnynotsolucky
Copy link
Contributor

The TotalChargesWithTaxes item should be returning the value including any taxes, which would have thought be correct.

I'm not seeing this item in the response from UPS at all.

The docs mention that including TaxInformationIndicator would add TotalChargesWithTaxes in the response, but it doesn't seem to work for me. I've tried with addresses in Canada and Mexico.

'Shipment' => [
    'Shipper' => $this->getContact($shipment->getFrom()),
    'ShipFrom' => $this->getContact($shipment->getFrom()),
    'ShipTo' => $this->getContact($shipment->getTo()),
    'NumOfPieces' => count($shipment->getPackages()),
    'Package' => $this->getPackages($shipment),
    'TaxInformationIndicator' => 'X',
],

Having a bit of a search, I wonder if the Landed Cost Quote API would be suitable.

How feasible would it be to use this API in the plugin or are you suggesting we implement this manually perhaps?

(btw I work with @pseudoclass and @sjcallender)

@engram-design
Copy link
Member

engram-design commented Aug 4, 2024

@johnnynotsolucky It'd be amazing if you could share your origin and destination address, along with a test dimension/weight values (even better if you're using the rates tester utility), so I can replicate your setup.

We would implement this Landed Cost Quote API as an addition to the standard one, so it'll be something you could opt into. I'd just need to ensure that it delivers the same information as the regular quote API.

@johnnynotsolucky
Copy link
Contributor

@engram-design can I email you those addresses + extra config we're using?

We would implement this Landed Cost Quote API as an addition to the standard one, so it'll be something you could opt into. I'd just need to ensure that it delivers the same information as the regular quote API.

Sounds great!

@engram-design
Copy link
Member

@johnnynotsolucky Of course, shoot through to support@verbb.io

@johnnynotsolucky
Copy link
Contributor

johnnynotsolucky commented Aug 5, 2024

@engram-design I can get the same results using the default rates tester address too. I'll just put it all here.

config.php:

return [
    '*' => [
        'hasCpSection' => true,
        'applyFreeShipping' => true,
        'enableCaching' => false,
        'enableRouteCheck' => false,
        'providers' => [
            'ups' => [
                'name' => 'UPS',
                'enabled' => true,
                'clientId' => '$UPS_API_CLIENT_ID',
                'clientSecret' => '$UPS_API_CLIENT_SECRET',
                'accountNumber' => '$UPS_API_ACCOUNT_NUMBER',
                'isProduction' => '$UPS_API_IS_PRODUCTION', // resolves to false
                'services' => [
                    '01' => [
                        'enabled' => true,
                    ],
                    '02' => [
                        'enabled' => true,
                    ],
                    '03' => [
                        'enabled' => true,
                    ],
                    '12' => [
                        'enabled' => true,
                    ],
                    '13' => [
                        'enabled' => false,
                    ],
                    '14' => [
                        'enabled' => false,
                    ],
                ],
            ],
        ],
    ]
];

Example request to UPS:

{
  "RateRequest": {
    "PickupType": {
      "Code": "01"
    },
    "Shipment": {
      "Shipper": {
        "Name": "Testing Sender",
        "AttentionName": "Testing Sender",
        "Address": {
          "AddressLine": [
            "1 Infinite Loop"
          ],
          "City": "Cupertino",
          "StateProvinceCode": "CA",
          "PostalCode": "95014",
          "CountryCode": "US"
        },
        "EMailAddress": "test@test.com",
        "ShipperNumber": "<redacted>"
      },
      "ShipFrom": {
        "Name": "Testing Sender",
        "AttentionName": "Testing Sender",
        "Address": {
          "AddressLine": [
            "1 Infinite Loop"
          ],
          "City": "Cupertino",
          "StateProvinceCode": "CA",
          "PostalCode": "95014",
          "CountryCode": "US"
        },
        "EMailAddress": "test@test.com"
      },
      "ShipTo": {
        "Name": "Testing Recipient",
        "AttentionName": "Testing Recipient",
        "Address": {
          "AddressLine": [
            "400 Bell Street"
          ],
          "City": "Ingersoll Ontario",
          "StateProvinceCode": "ON",
          "PostalCode": "N5C2P6",
          "CountryCode": "CA"
        },
        "EMailAddress": "test@test.com"
      },
      "NumOfPieces": 1,
      "Package": [
        {
          "PackagingType": {
            "Code": "02"
          },
          "Dimensions": {
            "UnitOfMeasurement": {
              "Code": "IN"
            },
            "Length": "5.12",
            "Width": "5.12",
            "Height": "0.79"
          },
          "PackageWeight": {
            "UnitOfMeasurement": {
              "Code": "LBS"
            },
            "Weight": "1.54"
          }
        }
      ],
      "TaxInformationIndicator": "X",
      "ShipmentRatingOptions": {
        "NegotiatedRatesIndicator": "Y"
      },
      "PaymentDetails": {
        "ShipmentCharge": {
          "Type": "01",
          "BillShipper": {
            "AccountNumber": "<redacted>"
          }
        }
      }
    }
  }
}

Both these tests use the default dimension and weight values.

Origin: 1 Infinite Loop, Cupertino, CA, US, 95014
Destination: 1600 Amphitheatre Parkway, Mountain View, CA, US, 94043
One of the RatedResponses

{
  "Disclaimer": {
    "Code": "05",
    "Description": "Rate excludes VAT. Rate includes a fuel surcharge, but excludes taxes, duties and other charges that may apply to the shipment."
  },
  "Service": {
    "Code": "01",
    "Description": ""
  },
  "RatedShipmentAlert": {
    "Code": "110971",
    "Description": "Your invoice may vary from the displayed reference rates"
  },
  "BillingWeight": {
    "UnitOfMeasurement": {
      "Code": "LBS",
      "Description": "Pounds"
    },
    "Weight": "2.0"
  },
  "TransportationCharges": {
    "CurrencyCode": "USD",
    "MonetaryValue": "47.79"
  },
  "ServiceOptionsCharges": {
    "CurrencyCode": "USD",
    "MonetaryValue": "0.00"
  },
  "TotalCharges": {
    "CurrencyCode": "USD",
    "MonetaryValue": "47.79"
  },
  "NegotiatedRateCharges": {
    "TotalCharge": {
      "CurrencyCode": "USD",
      "MonetaryValue": "47.31"
    }
  },
  "GuaranteedDelivery": {
    "BusinessDaysInTransit": "1",
    "DeliveryByTime": "10:30 A.M."
  },
  "RatedPackage": {
    "TransportationCharges": {
      "CurrencyCode": "USD",
      "MonetaryValue": "47.79"
    },
    "ServiceOptionsCharges": {
      "CurrencyCode": "USD",
      "MonetaryValue": "0.00"
    },
    "TotalCharges": {
      "CurrencyCode": "USD",
      "MonetaryValue": "47.79"
    },
    "Weight": "1.6",
    "BillingWeight": {
      "UnitOfMeasurement": {
        "Code": "LBS",
        "Description": "Pounds"
      },
      "Weight": "2.0"
    }
  }
}

Origin: 1 Infinite Loop, Cupertino, CA, US, 95014
Destination: 400 Bell Street, Ingersoll Ontario, ON, CA, N5C2P6
One of the RatedResponses:

{
  "Disclaimer": {
    "Code": "05",
    "Description": "Rate excludes VAT. Rate includes a fuel surcharge, but excludes taxes, duties and other charges that may apply to the shipment."
  },
  "Service": {
    "Code": "65",
    "Description": ""
  },
  "RatedShipmentAlert": [
    {
      "Code": "110971",
      "Description": "Your invoice may vary from the displayed reference rates"
    },
    {
      "Code": "110920",
      "Description": "Ship To Address Classification is changed from Commercial to Residential"
    }
  ],
  "BillingWeight": {
    "UnitOfMeasurement": {
      "Code": "LBS",
      "Description": "Pounds"
    },
    "Weight": "2.0"
  },
  "TransportationCharges": {
    "CurrencyCode": "USD",
    "MonetaryValue": "166.97"
  },
  "ItemizedCharges": [
    {
      "Code": "375",
      "CurrencyCode": "USD",
      "MonetaryValue": "27.25"
    },
    {
      "Code": "270",
      "CurrencyCode": "USD",
      "MonetaryValue": "6.20"
    }
  ],
  "ServiceOptionsCharges": {
    "CurrencyCode": "USD",
    "MonetaryValue": "0.00"
  },
  "TotalCharges": {
    "CurrencyCode": "USD",
    "MonetaryValue": "166.97"
  },
  "NegotiatedRateCharges": {
    "TotalCharge": {
      "CurrencyCode": "USD",
      "MonetaryValue": "165.30"
    }
  },
  "RatedPackage": {
    "TransportationCharges": {
      "CurrencyCode": "USD",
      "MonetaryValue": "0.00"
    },
    "ServiceOptionsCharges": {
      "CurrencyCode": "USD",
      "MonetaryValue": "0.00"
    },
    "TotalCharges": {
      "CurrencyCode": "USD",
      "MonetaryValue": "0.00"
    },
    "Weight": "1.6",
    "BillingWeight": {
      "UnitOfMeasurement": {
        "Code": "LBS",
        "Description": "Pounds"
      },
      "Weight": "2.0"
    }
  }
}

@engram-design
Copy link
Member

Thanks for that, indeed the TaxInformationIndicator setting doesn't seem to do anything. A TotalChargesWithTaxes should be present in the response from UPS. By all means according to their docs, that should be what we want.

Any taxes that may be applicable to a shipment would be returned in response.

That's frustrating. I've made a slight change to now handle tax-inclusive rates that come back in the response, but like your examples above, I'm not getting that in my responses.

I've been testing out the Landed Cost API, but I am getting a bit stuck with some more advanced concepts like Harmonized Tariff Schedule (HTS) Codes, which is starting to make things pretty overkill just to get some tax/duty-inclusive rates.

I'm not sure where that leaves us, I'm afraid. We can't use the Ship API to get rates inclusive of taxes, as that's going to generate a label and charge the account. It also doesn't allow us to "shop" rates (get multiple rates).

@johnnynotsolucky
Copy link
Contributor

@engram-design thanks for looking more into this, it's really appreciated!

I've been testing out the Landed Cost API, but I am getting a bit stuck with some more advanced concepts like Harmonized Tariff Schedule (HTS) Codes, which is starting to make things pretty overkill just to get some tax/duty-inclusive rates.

I had a look at that API when you mentioned it previously and I had wondered how much work it would be to implement alongside current implementations 😅

I'm not sure where that leaves us, I'm afraid

Hoping UPS tech support can shed some light on this - I'll let you know here if we get anything useful back 💪

@engram-design
Copy link
Member

Yeah, I got 99% of the way there, then it starts to complain about this missing data like tariffs, which is getting to be too complex for just being able to post something. I'll get in touch with my UPS contact as well and see how we go.

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

4 participants