diff --git a/CHANGELOG.md b/CHANGELOG.md index 35689be0..70177632 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Removes the deprecated, unusable `add_credit_card` function - Stripe has disabled the ability to pass plain credit card details over the wire and now requires using [Stripe.js/Elements/Checkout](https://support.stripe.com/questions/card-tokenization-restrictions-using-publishable-keys). Follow the [Decentralized (EasyPost-Manage Billing) Guide](https://docs.easypost.com/guides/get-started-with-forge/easypost-managed-billing-guide#referralcustomer-billing-management) for more details on the new flow to use. - Makes `referral_customer.retrieve_easypost_stripe_api_key` public to help facilitate adding credit cards using Stripe.js +- Adds support for `FedexDefaultAccount`, `GofoWalletAccount`, `UpsDapAccount`, `UniUniWalletAccount`, and `VehoWalletAccount` by properly routing carrier-account create requests to the `/carrier_accounts/open` endpoint. ## v10.7.0 (2026-06-25) diff --git a/easypost/constant.py b/easypost/constant.py index 07760d43..18a71b29 100644 --- a/easypost/constant.py +++ b/easypost/constant.py @@ -32,15 +32,24 @@ # Internal constants (user's should not use these) _TEST_FAILED_INTENTIONALLY_ERROR = "Test failed intentionally." -_CARRIER_ACCOUNT_TYPES_WITH_CUSTOM_WORKFLOWS = [ - "FedexAccount", - "FedexSmartpostAccount", -] -_CARRIER_ACCOUNT_TYPES_WITH_CUSTOM_OAUTH = [ - "AmazonShippingAccount", - "UpsAccount", - "UpsMailInnovationsAccount", - "UpsSurepostAccount", - "UspsShipAccount", -] +_CARRIER_ACCOUNT_CREATE_ENDPOINT_TYPES = { + "/carrier_accounts/open": ( + "FedexDefaultAccount", + "GofoWalletAccount", + "UpsDapAccount", + "UniUniWalletAccount", + "VehoWalletAccount", + ), + "/carrier_accounts/register": ( + "FedexAccount", + "FedexSmartpostAccount", + ), + "/carrier_accounts/register_oauth": ( + "AmazonShippingAccount", + "UpsAccount", + "UpsMailInnovationsAccount", + "UpsSurepostAccount", + "UspsShipAccount", + ), +} _FILTERS_KEY = "filters" diff --git a/easypost/services/carrier_account_service.py b/easypost/services/carrier_account_service.py index 201103db..9f5d9e1f 100644 --- a/easypost/services/carrier_account_service.py +++ b/easypost/services/carrier_account_service.py @@ -4,8 +4,7 @@ ) from easypost.constant import ( - _CARRIER_ACCOUNT_TYPES_WITH_CUSTOM_OAUTH, - _CARRIER_ACCOUNT_TYPES_WITH_CUSTOM_WORKFLOWS, + _CARRIER_ACCOUNT_CREATE_ENDPOINT_TYPES, MISSING_PARAMETER_ERROR, ) from easypost.easypost_object import convert_to_easypost_object @@ -31,7 +30,7 @@ def create(self, **params) -> CarrierAccount: raise MissingParameterError(MISSING_PARAMETER_ERROR.format("type")) url = self._select_carrier_account_creation_endpoint(carrier_account_type=carrier_account_type) - if carrier_account_type in _CARRIER_ACCOUNT_TYPES_WITH_CUSTOM_OAUTH: + if url == "/carrier_accounts/register_oauth": wrapped_params = {"carrier_account_oauth_registrations": params} else: wrapped_params = {self._snakecase_name(self._model_class): params} @@ -64,9 +63,8 @@ def types(self) -> list[dict[str, Any]]: def _select_carrier_account_creation_endpoint(self, carrier_account_type: Optional[Any]) -> str: """Determines which API endpoint to use for the creation call.""" - if carrier_account_type in _CARRIER_ACCOUNT_TYPES_WITH_CUSTOM_WORKFLOWS: - return "/carrier_accounts/register" - elif carrier_account_type in _CARRIER_ACCOUNT_TYPES_WITH_CUSTOM_OAUTH: - return "/carrier_accounts/register_oauth" + for endpoint, carrier_account_types in _CARRIER_ACCOUNT_CREATE_ENDPOINT_TYPES.items(): + if carrier_account_type in carrier_account_types: + return endpoint return "/carrier_accounts" diff --git a/tests/cassettes/test_carrier_account_create_url_routing_AmazonShippingAccount.yaml b/tests/cassettes/test_carrier_account_create_url_routing_AmazonShippingAccount.yaml new file mode 100644 index 00000000..ab9b0da5 --- /dev/null +++ b/tests/cassettes/test_carrier_account_create_url_routing_AmazonShippingAccount.yaml @@ -0,0 +1,142 @@ +interactions: +- request: + body: '{"carrier_account_oauth_registrations": {"type": "AmazonShippingAccount"}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '74' + Content-Type: + - application/json + authorization: + - + user-agent: + - + method: POST + uri: https://api.easypost.com/v2/carrier_accounts/register_oauth + response: + body: + string: '{"id": "ca_f5ee71bc1d49489d9f9c13d210f393fb", "object": "CarrierAccount", + "type": "AmazonShippingAccount", "clone": false, "created_at": "2026-10-15T20:04:32Z", + "updated_at": "2026-10-15T20:04:32Z", "description": null, "reference": null, + "billing_type": "carrier", "readable": "Amazon Shipping", "logo": null, "fields": + [], "credentials": {}, "test_credentials": {}}' + headers: + cache-control: + - private, no-cache, no-store + content-length: + - '1753' + content-type: + - application/json; charset=utf-8 + easypost-api-version: + - '2015-01-01' + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - EasyPost-Api-Version + x-backend: + - easypost + x-content-type-options: + - nosniff + x-download-options: + - noopen + x-ep-request-uuid: + - 7ff0b0716ad131d0e2ba14b7005c4820 + x-frame-options: + - SAMEORIGIN + x-node: + - web108azw + x-permitted-cross-domain-policies: + - none + x-proxied: + - intlb4azw f29267e751 + - extlb2azw ff4cfe6eb5 + x-runtime: + - '0.119564' + x-version-label: + - easypost-202610151849-c381b11f6c-main + x-xss-protection: + - 1; mode=block + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + authorization: + - + user-agent: + - + method: DELETE + uri: https://api.easypost.com/v2/carrier_accounts/ca_f5ee71bc1d49489d9f9c13d210f393fb + response: + body: + string: '{}' + headers: + cache-control: + - private, no-cache, no-store + content-length: + - '2' + content-type: + - application/json; charset=utf-8 + easypost-api-version: + - '2015-01-01' + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - EasyPost-Api-Version + x-backend: + - easypost + x-content-type-options: + - nosniff + x-download-options: + - noopen + x-ep-request-uuid: + - add87ec86ad131d1e2ba14b80058ad37 + x-frame-options: + - SAMEORIGIN + x-node: + - web124azw + x-permitted-cross-domain-policies: + - none + x-proxied: + - intlb1azw f29267e751 + - extlb3azw ff4cfe6eb5 + x-runtime: + - '0.076582' + x-version-label: + - easypost-202610151849-c381b11f6c-main + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_carrier_account_create_url_routing_FedexAccount.yaml b/tests/cassettes/test_carrier_account_create_url_routing_FedexAccount.yaml new file mode 100644 index 00000000..3af6f367 --- /dev/null +++ b/tests/cassettes/test_carrier_account_create_url_routing_FedexAccount.yaml @@ -0,0 +1,74 @@ +interactions: +- request: + body: '{"carrier_account": {"type": "FedexAccount", "registration_data": {}}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '70' + Content-Type: + - application/json + authorization: + - + user-agent: + - + method: POST + uri: https://api.easypost.com/v2/carrier_accounts/register + response: + body: + string: '{"error": {"code": "GONE", "message": "The requested resource is no + longer available at the server and no forwarding address is known.", "errors": + [{"message": "FedEx account registrations must use the multi-factor authentication + (MFA) registration workflow. See: https://support.easypost.com/hc/en-us/articles/35262738410253-FedEx-Multi-Factor-Authentication"}]}}' + headers: + cache-control: + - private, no-cache, no-store + content-length: + - '357' + content-type: + - application/json; charset=utf-8 + easypost-api-version: + - '2015-01-01' + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - EasyPost-Api-Version + x-backend: + - easypost + x-content-type-options: + - nosniff + x-download-options: + - noopen + x-ep-request-uuid: + - aebfacaf6ad131d0e2ba149e0058abce + x-frame-options: + - SAMEORIGIN + x-node: + - web107azw + x-permitted-cross-domain-policies: + - none + x-proxied: + - intlb4azw f29267e751 + - extlb3azw ff4cfe6eb5 + x-runtime: + - '0.042922' + x-version-label: + - easypost-202610151849-c381b11f6c-main + x-xss-protection: + - 1; mode=block + status: + code: 410 + message: Gone +version: 1 diff --git a/tests/cassettes/test_carrier_account_create_url_routing_FedexDefaultAccount.yaml b/tests/cassettes/test_carrier_account_create_url_routing_FedexDefaultAccount.yaml new file mode 100644 index 00000000..16a770ad --- /dev/null +++ b/tests/cassettes/test_carrier_account_create_url_routing_FedexDefaultAccount.yaml @@ -0,0 +1,78 @@ +interactions: +- request: + body: '{"carrier_account": {"type": "FedexDefaultAccount", "registration_data": + {}}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '77' + Content-Type: + - application/json + authorization: + - + user-agent: + - + method: POST + uri: https://api.easypost.com/v2/carrier_accounts/open + response: + body: + string: '{"error": {"code": "UNPROCESSABLE_ENTITY", "message": "The request + was understood, but cannot be processed.", "errors": [{"field": "name", "message": + "is missing"}, {"field": "street1", "message": "is missing"}, {"field": "city", + "message": "is missing"}, {"field": "state", "message": "is missing"}, {"field": + "postal_code", "message": "is missing"}, {"field": "country", "message": "is + missing"}, {"field": "phone", "message": "is missing"}, {"field": "email", + "message": "is missing"}]}}' + headers: + cache-control: + - private, no-cache, no-store + content-length: + - '453' + content-type: + - application/json; charset=utf-8 + easypost-api-version: + - '2015-01-01' + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - EasyPost-Api-Version + x-backend: + - easypost + x-content-type-options: + - nosniff + x-download-options: + - noopen + x-ep-request-uuid: + - 97548db56ad131cee2ba1496005e01b6 + x-frame-options: + - SAMEORIGIN + x-node: + - web107azw + x-permitted-cross-domain-policies: + - none + x-proxied: + - intlb3azw f29267e751 + - extlb1azw ff4cfe6eb5 + x-runtime: + - '0.027957' + x-version-label: + - easypost-202610151849-c381b11f6c-main + x-xss-protection: + - 1; mode=block + status: + code: 422 + message: Unprocessable Entity +version: 1 diff --git a/tests/cassettes/test_carrier_account_create_url_routing_FedexSmartpostAccount.yaml b/tests/cassettes/test_carrier_account_create_url_routing_FedexSmartpostAccount.yaml new file mode 100644 index 00000000..0248eccd --- /dev/null +++ b/tests/cassettes/test_carrier_account_create_url_routing_FedexSmartpostAccount.yaml @@ -0,0 +1,75 @@ +interactions: +- request: + body: '{"carrier_account": {"type": "FedexSmartpostAccount", "registration_data": + {}}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '79' + Content-Type: + - application/json + authorization: + - + user-agent: + - + method: POST + uri: https://api.easypost.com/v2/carrier_accounts/register + response: + body: + string: '{"error": {"code": "GONE", "message": "The requested resource is no + longer available at the server and no forwarding address is known.", "errors": + [{"message": "FedEx account registrations must use the multi-factor authentication + (MFA) registration workflow. See: https://support.easypost.com/hc/en-us/articles/35262738410253-FedEx-Multi-Factor-Authentication"}]}}' + headers: + cache-control: + - private, no-cache, no-store + content-length: + - '357' + content-type: + - application/json; charset=utf-8 + easypost-api-version: + - '2015-01-01' + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - EasyPost-Api-Version + x-backend: + - easypost + x-content-type-options: + - nosniff + x-download-options: + - noopen + x-ep-request-uuid: + - cb7ede806ad131d0e2ba149f005e0635 + x-frame-options: + - SAMEORIGIN + x-node: + - web127azw + x-permitted-cross-domain-policies: + - none + x-proxied: + - intlb3azw f29267e751 + - extlb1azw ff4cfe6eb5 + x-runtime: + - '0.042996' + x-version-label: + - easypost-202610151849-c381b11f6c-main + x-xss-protection: + - 1; mode=block + status: + code: 410 + message: Gone +version: 1 diff --git a/tests/cassettes/test_carrier_account_create_url_routing_GofoWalletAccount.yaml b/tests/cassettes/test_carrier_account_create_url_routing_GofoWalletAccount.yaml new file mode 100644 index 00000000..9c7a226b --- /dev/null +++ b/tests/cassettes/test_carrier_account_create_url_routing_GofoWalletAccount.yaml @@ -0,0 +1,143 @@ +interactions: +- request: + body: '{"carrier_account": {"type": "GofoWalletAccount", "registration_data": + {}}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '75' + Content-Type: + - application/json + authorization: + - + user-agent: + - + method: POST + uri: https://api.easypost.com/v2/carrier_accounts/open + response: + body: + string: '{"id": "ca_beb5806670fc4518b58f0ef6969616fd", "object": "CarrierAccount", + "type": "GofoWalletAccount", "clone": false, "created_at": "2026-10-15T20:04:31Z", + "updated_at": "2026-10-15T20:04:31Z", "description": "GOFO Wallet Account", + "reference": null, "billing_type": "easypost", "readable": "GOFO Wallet", + "logo": null, "fields": [], "credentials": {}, "test_credentials": {}}' + headers: + cache-control: + - private, no-cache, no-store + content-length: + - '460' + content-type: + - application/json; charset=utf-8 + easypost-api-version: + - '2015-01-01' + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - EasyPost-Api-Version + x-backend: + - easypost + x-content-type-options: + - nosniff + x-download-options: + - noopen + x-ep-request-uuid: + - 2cad6c3f6ad131cfe2ba1497005c43e8 + x-frame-options: + - SAMEORIGIN + x-node: + - web124azw + x-permitted-cross-domain-policies: + - none + x-proxied: + - intlb1azw f29267e751 + - extlb2azw ff4cfe6eb5 + x-runtime: + - '0.071176' + x-version-label: + - easypost-202610151849-c381b11f6c-main + x-xss-protection: + - 1; mode=block + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + authorization: + - + user-agent: + - + method: DELETE + uri: https://api.easypost.com/v2/carrier_accounts/ca_beb5806670fc4518b58f0ef6969616fd + response: + body: + string: '{}' + headers: + cache-control: + - private, no-cache, no-store + content-length: + - '2' + content-type: + - application/json; charset=utf-8 + easypost-api-version: + - '2015-01-01' + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - EasyPost-Api-Version + x-backend: + - easypost + x-content-type-options: + - nosniff + x-download-options: + - noopen + x-ep-request-uuid: + - add87ec56ad131cfe2ba14980058a8a4 + x-frame-options: + - SAMEORIGIN + x-node: + - web116azw + x-permitted-cross-domain-policies: + - none + x-proxied: + - intlb3azw f29267e751 + - extlb3azw ff4cfe6eb5 + x-runtime: + - '0.070445' + x-version-label: + - easypost-202610151849-c381b11f6c-main + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_carrier_account_create_url_routing_UniUniWalletAccount.yaml b/tests/cassettes/test_carrier_account_create_url_routing_UniUniWalletAccount.yaml new file mode 100644 index 00000000..b37551a8 --- /dev/null +++ b/tests/cassettes/test_carrier_account_create_url_routing_UniUniWalletAccount.yaml @@ -0,0 +1,143 @@ +interactions: +- request: + body: '{"carrier_account": {"type": "UniUniWalletAccount", "registration_data": + {}}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '77' + Content-Type: + - application/json + authorization: + - + user-agent: + - + method: POST + uri: https://api.easypost.com/v2/carrier_accounts/open + response: + body: + string: '{"id": "ca_057c0400d9e6445eaca363a46156e047", "object": "CarrierAccount", + "type": "UniUniWalletAccount", "clone": false, "created_at": "2026-10-15T20:04:31Z", + "updated_at": "2026-10-15T20:04:31Z", "description": "UniUni Wallet Account", + "reference": null, "billing_type": "easypost", "readable": "UniUni Wallet", + "logo": null, "fields": [], "credentials": {}, "test_credentials": {}}' + headers: + cache-control: + - private, no-cache, no-store + content-length: + - '600' + content-type: + - application/json; charset=utf-8 + easypost-api-version: + - '2015-01-01' + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - EasyPost-Api-Version + x-backend: + - easypost + x-content-type-options: + - nosniff + x-download-options: + - noopen + x-ep-request-uuid: + - 7ff0b07a6ad131cfe2ba149a005c455a + x-frame-options: + - SAMEORIGIN + x-node: + - web117azw + x-permitted-cross-domain-policies: + - none + x-proxied: + - intlb1azw f29267e751 + - extlb2azw ff4cfe6eb5 + x-runtime: + - '0.072914' + x-version-label: + - easypost-202610151849-c381b11f6c-main + x-xss-protection: + - 1; mode=block + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + authorization: + - + user-agent: + - + method: DELETE + uri: https://api.easypost.com/v2/carrier_accounts/ca_057c0400d9e6445eaca363a46156e047 + response: + body: + string: '{}' + headers: + cache-control: + - private, no-cache, no-store + content-length: + - '2' + content-type: + - application/json; charset=utf-8 + easypost-api-version: + - '2015-01-01' + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - EasyPost-Api-Version + x-backend: + - easypost + x-content-type-options: + - nosniff + x-download-options: + - noopen + x-ep-request-uuid: + - aebfacaf6ad131cfe2ba149b0058a9ee + x-frame-options: + - SAMEORIGIN + x-node: + - web114azw + x-permitted-cross-domain-policies: + - none + x-proxied: + - intlb1azw f29267e751 + - extlb3azw ff4cfe6eb5 + x-runtime: + - '0.082458' + x-version-label: + - easypost-202610151849-c381b11f6c-main + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_carrier_account_create_url_routing_UpsAccount.yaml b/tests/cassettes/test_carrier_account_create_url_routing_UpsAccount.yaml new file mode 100644 index 00000000..070a99f3 --- /dev/null +++ b/tests/cassettes/test_carrier_account_create_url_routing_UpsAccount.yaml @@ -0,0 +1,74 @@ +interactions: +- request: + body: '{"carrier_account_oauth_registrations": {"type": "UpsAccount"}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '63' + Content-Type: + - application/json + authorization: + - + user-agent: + - + method: POST + uri: https://api.easypost.com/v2/carrier_accounts/register_oauth + response: + body: + string: '{"error": {"code": "UNPROCESSABLE_ENTITY", "message": "The request + was understood, but cannot be processed.", "errors": [{"field": "account_number", + "message": "The provided Carrier Account''s Account Number is required but + missing."}]}}' + headers: + cache-control: + - private, no-cache, no-store + content-length: + - '227' + content-type: + - application/json; charset=utf-8 + easypost-api-version: + - '2015-01-01' + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - EasyPost-Api-Version + x-backend: + - easypost + x-content-type-options: + - nosniff + x-download-options: + - noopen + x-ep-request-uuid: + - cb7ede836ad131d1e2ba14b9005e07d0 + x-frame-options: + - SAMEORIGIN + x-node: + - web104azw + x-permitted-cross-domain-policies: + - none + x-proxied: + - intlb2azw f29267e751 + - extlb1azw ff4cfe6eb5 + x-runtime: + - '0.025475' + x-version-label: + - easypost-202610151849-c381b11f6c-main + x-xss-protection: + - 1; mode=block + status: + code: 422 + message: Unprocessable Entity +version: 1 diff --git a/tests/cassettes/test_carrier_account_create_url_routing_UpsDapAccount.yaml b/tests/cassettes/test_carrier_account_create_url_routing_UpsDapAccount.yaml new file mode 100644 index 00000000..05a3da8e --- /dev/null +++ b/tests/cassettes/test_carrier_account_create_url_routing_UpsDapAccount.yaml @@ -0,0 +1,73 @@ +interactions: +- request: + body: '{"carrier_account": {"type": "UpsDapAccount", "registration_data": {}}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '71' + Content-Type: + - application/json + authorization: + - + user-agent: + - + method: POST + uri: https://api.easypost.com/v2/carrier_accounts/open + response: + body: + string: '{"error": {"code": "PARAMETER.REQUIRED", "message": "Missing required + parameter.", "errors": [{"field": "postal_code", "message": "is required but + missing"}]}}' + headers: + cache-control: + - private, no-cache, no-store + content-length: + - '150' + content-type: + - application/json; charset=utf-8 + easypost-api-version: + - '2015-01-01' + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - EasyPost-Api-Version + x-backend: + - easypost + x-content-type-options: + - nosniff + x-download-options: + - noopen + x-ep-request-uuid: + - 97548db66ad131cfe2ba1499005e033b + x-frame-options: + - SAMEORIGIN + x-node: + - web127azw + x-permitted-cross-domain-policies: + - none + x-proxied: + - intlb3azw f29267e751 + - extlb1azw ff4cfe6eb5 + x-runtime: + - '0.058331' + x-version-label: + - easypost-202610151849-c381b11f6c-main + x-xss-protection: + - 1; mode=block + status: + code: 422 + message: Unprocessable Entity +version: 1 diff --git a/tests/cassettes/test_carrier_account_create_url_routing_UpsMailInnovationsAccount.yaml b/tests/cassettes/test_carrier_account_create_url_routing_UpsMailInnovationsAccount.yaml new file mode 100644 index 00000000..52af48a2 --- /dev/null +++ b/tests/cassettes/test_carrier_account_create_url_routing_UpsMailInnovationsAccount.yaml @@ -0,0 +1,76 @@ +interactions: +- request: + body: '{"carrier_account_oauth_registrations": {"type": "UpsMailInnovationsAccount"}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '78' + Content-Type: + - application/json + authorization: + - + user-agent: + - + method: POST + uri: https://api.easypost.com/v2/carrier_accounts/register_oauth + response: + body: + string: '{"error": {"code": "UNPROCESSABLE_ENTITY", "message": "The request + was understood, but cannot be processed.", "errors": [{"field": "account_number", + "message": "The provided Carrier Account''s Account Number is required but + missing."}]}}' + headers: + cache-control: + - private, no-cache, no-store + content-length: + - '227' + content-type: + - application/json; charset=utf-8 + easypost-api-version: + - '2015-01-01' + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - EasyPost-Api-Version + x-backend: + - easypost + x-canary: + - direct + x-content-type-options: + - nosniff + x-download-options: + - noopen + x-ep-request-uuid: + - 7ff0b0746ad131d1e2ba14ba005c49f9 + x-frame-options: + - SAMEORIGIN + x-node: + - web101azw + x-permitted-cross-domain-policies: + - none + x-proxied: + - intlb2azw f29267e751 + - extlb2azw ff4cfe6eb5 + x-runtime: + - '0.025996' + x-version-label: + - easypost-202610151955-b13716d895-main + x-xss-protection: + - 1; mode=block + status: + code: 422 + message: Unprocessable Entity +version: 1 diff --git a/tests/cassettes/test_carrier_account_create_url_routing_UpsSurepostAccount.yaml b/tests/cassettes/test_carrier_account_create_url_routing_UpsSurepostAccount.yaml new file mode 100644 index 00000000..cbbb67d1 --- /dev/null +++ b/tests/cassettes/test_carrier_account_create_url_routing_UpsSurepostAccount.yaml @@ -0,0 +1,74 @@ +interactions: +- request: + body: '{"carrier_account_oauth_registrations": {"type": "UpsSurepostAccount"}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '71' + Content-Type: + - application/json + authorization: + - + user-agent: + - + method: POST + uri: https://api.easypost.com/v2/carrier_accounts/register_oauth + response: + body: + string: '{"error": {"code": "UNPROCESSABLE_ENTITY", "message": "The request + was understood, but cannot be processed.", "errors": [{"field": "account_number", + "message": "The provided Carrier Account''s Account Number is required but + missing."}]}}' + headers: + cache-control: + - private, no-cache, no-store + content-length: + - '227' + content-type: + - application/json; charset=utf-8 + easypost-api-version: + - '2015-01-01' + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - EasyPost-Api-Version + x-backend: + - easypost + x-content-type-options: + - nosniff + x-download-options: + - noopen + x-ep-request-uuid: + - add87ec86ad131d1e2ba14bb0058aed1 + x-frame-options: + - SAMEORIGIN + x-node: + - web112azw + x-permitted-cross-domain-policies: + - none + x-proxied: + - intlb2azw f29267e751 + - extlb3azw ff4cfe6eb5 + x-runtime: + - '0.023379' + x-version-label: + - easypost-202610151849-c381b11f6c-main + x-xss-protection: + - 1; mode=block + status: + code: 422 + message: Unprocessable Entity +version: 1 diff --git a/tests/cassettes/test_carrier_account_create_url_routing_UspsShipAccount.yaml b/tests/cassettes/test_carrier_account_create_url_routing_UspsShipAccount.yaml new file mode 100644 index 00000000..91209a5b --- /dev/null +++ b/tests/cassettes/test_carrier_account_create_url_routing_UspsShipAccount.yaml @@ -0,0 +1,142 @@ +interactions: +- request: + body: '{"carrier_account_oauth_registrations": {"type": "UspsShipAccount"}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '68' + Content-Type: + - application/json + authorization: + - + user-agent: + - + method: POST + uri: https://api.easypost.com/v2/carrier_accounts/register_oauth + response: + body: + string: '{"id": "ca_fa253fbe72144d0dbe6817fc99d0c136", "object": "CarrierAccount", + "type": "UspsShipAccount", "clone": false, "created_at": "2026-10-15T20:04:33Z", + "updated_at": "2026-10-15T20:04:33Z", "description": null, "reference": null, + "billing_type": "carrier", "readable": "Usps Ship", "logo": null, "fields": + [], "credentials": {}, "test_credentials": {}}' + headers: + cache-control: + - private, no-cache, no-store + content-length: + - '2561' + content-type: + - application/json; charset=utf-8 + easypost-api-version: + - '2015-01-01' + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - EasyPost-Api-Version + x-backend: + - easypost + x-content-type-options: + - nosniff + x-download-options: + - noopen + x-ep-request-uuid: + - 97548db46ad131d1e2ba14bc005e092a + x-frame-options: + - SAMEORIGIN + x-node: + - web115azw + x-permitted-cross-domain-policies: + - none + x-proxied: + - intlb2azw f29267e751 + - extlb1azw ff4cfe6eb5 + x-runtime: + - '0.105050' + x-version-label: + - easypost-202610151849-c381b11f6c-main + x-xss-protection: + - 1; mode=block + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + authorization: + - + user-agent: + - + method: DELETE + uri: https://api.easypost.com/v2/carrier_accounts/ca_fa253fbe72144d0dbe6817fc99d0c136 + response: + body: + string: '{}' + headers: + cache-control: + - private, no-cache, no-store + content-length: + - '2' + content-type: + - application/json; charset=utf-8 + easypost-api-version: + - '2015-01-01' + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - EasyPost-Api-Version + x-backend: + - easypost + x-content-type-options: + - nosniff + x-download-options: + - noopen + x-ep-request-uuid: + - add87ec36ad131d1e2ba14d80058afe8 + x-frame-options: + - SAMEORIGIN + x-node: + - web127azw + x-permitted-cross-domain-policies: + - none + x-proxied: + - intlb2azw f29267e751 + - extlb3azw ff4cfe6eb5 + x-runtime: + - '0.086817' + x-version-label: + - easypost-202610151849-c381b11f6c-main + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_carrier_account_create_url_routing_VehoWalletAccount.yaml b/tests/cassettes/test_carrier_account_create_url_routing_VehoWalletAccount.yaml new file mode 100644 index 00000000..91a029da --- /dev/null +++ b/tests/cassettes/test_carrier_account_create_url_routing_VehoWalletAccount.yaml @@ -0,0 +1,143 @@ +interactions: +- request: + body: '{"carrier_account": {"type": "VehoWalletAccount", "registration_data": + {}}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '75' + Content-Type: + - application/json + authorization: + - + user-agent: + - + method: POST + uri: https://api.easypost.com/v2/carrier_accounts/open + response: + body: + string: '{"id": "ca_25a8211bea34436cb296a2a769497f0b", "object": "CarrierAccount", + "type": "VehoWalletAccount", "clone": false, "created_at": "2026-10-15T20:04:32Z", + "updated_at": "2026-10-15T20:04:32Z", "description": "Veho Account", "reference": + null, "billing_type": "easypost", "readable": "Veho", "logo": null, "fields": + [], "credentials": {}, "test_credentials": {}}' + headers: + cache-control: + - private, no-cache, no-store + content-length: + - '583' + content-type: + - application/json; charset=utf-8 + easypost-api-version: + - '2015-01-01' + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - EasyPost-Api-Version + x-backend: + - easypost + x-content-type-options: + - nosniff + x-download-options: + - noopen + x-ep-request-uuid: + - cb7ede836ad131d0e2ba149c005e0501 + x-frame-options: + - SAMEORIGIN + x-node: + - web124azw + x-permitted-cross-domain-policies: + - none + x-proxied: + - intlb3azw f29267e751 + - extlb1azw ff4cfe6eb5 + x-runtime: + - '0.091164' + x-version-label: + - easypost-202610151849-c381b11f6c-main + x-xss-protection: + - 1; mode=block + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + authorization: + - + user-agent: + - + method: DELETE + uri: https://api.easypost.com/v2/carrier_accounts/ca_25a8211bea34436cb296a2a769497f0b + response: + body: + string: '{}' + headers: + cache-control: + - private, no-cache, no-store + content-length: + - '2' + content-type: + - application/json; charset=utf-8 + easypost-api-version: + - '2015-01-01' + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - EasyPost-Api-Version + x-backend: + - easypost + x-content-type-options: + - nosniff + x-download-options: + - noopen + x-ep-request-uuid: + - 7ff0b0756ad131d0e2ba149d005c472c + x-frame-options: + - SAMEORIGIN + x-node: + - web113azw + x-permitted-cross-domain-policies: + - none + x-proxied: + - intlb2azw f29267e751 + - extlb2azw ff4cfe6eb5 + x-runtime: + - '0.071861' + x-version-label: + - easypost-202610151849-c381b11f6c-main + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_carrier_account_update_ups.yaml b/tests/cassettes/test_carrier_account_update_ups.yaml deleted file mode 100644 index 1cd438e2..00000000 --- a/tests/cassettes/test_carrier_account_update_ups.yaml +++ /dev/null @@ -1,204 +0,0 @@ -interactions: -- request: - body: '{"carrier_account_oauth_registrations": {"type": "UpsAccount", "account_number": - "123456789"}}' - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '94' - Content-Type: - - application/json - authorization: - - - user-agent: - - - method: POST - uri: https://api.easypost.com/v2/carrier_accounts/register_oauth - response: - body: - string: '{"id": "ca_6e690e7623ba4354b29d209fc19a725f", "object": "CarrierAccount", - "type": "UpsAccount", "clone": false, "created_at": "2025-05-23T19:13:53Z", - "updated_at": "2025-05-23T19:13:53Z", "description": null, "reference": null, - "billing_type": "carrier", "readable": "UPS", "logo": null, "fields": [], - "credentials": {}, "test_credentials": {}}' - headers: - cache-control: - - private, no-cache, no-store - content-length: - - '1404' - content-type: - - application/json; charset=utf-8 - expires: - - '0' - pragma: - - no-cache - referrer-policy: - - strict-origin-when-cross-origin - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - transfer-encoding: - - chunked - x-backend: - - easypost - x-content-type-options: - - nosniff - x-download-options: - - noopen - x-ep-request-uuid: - - 6be4a8f66830c8f1e787301d003e93e1 - x-frame-options: - - SAMEORIGIN - x-node: - - bigweb54nuq - x-permitted-cross-domain-policies: - - none - x-proxied: - - intlb4nuq 668d3fc830 - - extlb1nuq 99aac35317 - x-runtime: - - '0.097712' - x-version-label: - - easypost-202505231841-d89645a184-master - x-xss-protection: - - 1; mode=block - status: - code: 201 - message: Created -- request: - body: '{"carrier_account": {"account_number": "987654321"}}' - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '52' - Content-Type: - - application/json - authorization: - - - user-agent: - - - method: PATCH - uri: https://api.easypost.com/v2/carrier_accounts/ca_6e690e7623ba4354b29d209fc19a725f - response: - body: - string: '{"id": "ca_6e690e7623ba4354b29d209fc19a725f", "object": "CarrierAccount", - "type": "UpsAccount", "clone": false, "created_at": "2025-05-23T19:13:53Z", - "updated_at": "2025-05-23T19:13:54Z", "description": null, "reference": null, - "billing_type": "carrier", "readable": "UPS", "logo": null, "fields": [], - "credentials": {}, "test_credentials": {}}' - headers: - cache-control: - - private, no-cache, no-store - content-length: - - '1404' - content-type: - - application/json; charset=utf-8 - expires: - - '0' - pragma: - - no-cache - referrer-policy: - - strict-origin-when-cross-origin - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - transfer-encoding: - - chunked - x-backend: - - easypost - x-content-type-options: - - nosniff - x-download-options: - - noopen - x-ep-request-uuid: - - 6be4a8f66830c8f1e787301d003e9403 - x-frame-options: - - SAMEORIGIN - x-node: - - bigweb53nuq - x-permitted-cross-domain-policies: - - none - x-proxied: - - intlb4nuq 668d3fc830 - - extlb1nuq 99aac35317 - x-runtime: - - '0.073912' - x-version-label: - - easypost-202505231841-d89645a184-master - x-xss-protection: - - 1; mode=block - status: - code: 200 - message: OK -- request: - body: null - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '0' - authorization: - - - user-agent: - - - method: DELETE - uri: https://api.easypost.com/v2/carrier_accounts/ca_6e690e7623ba4354b29d209fc19a725f - response: - body: - string: '{}' - headers: - cache-control: - - private, no-cache, no-store - content-length: - - '2' - content-type: - - application/json; charset=utf-8 - expires: - - '0' - pragma: - - no-cache - referrer-policy: - - strict-origin-when-cross-origin - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - transfer-encoding: - - chunked - x-backend: - - easypost - x-content-type-options: - - nosniff - x-download-options: - - noopen - x-ep-request-uuid: - - 6be4a8f66830c8f2e787301d003e941e - x-frame-options: - - SAMEORIGIN - x-node: - - bigweb38nuq - x-permitted-cross-domain-policies: - - none - x-proxied: - - intlb4nuq 668d3fc830 - - extlb1nuq 99aac35317 - x-runtime: - - '0.067810' - x-version-label: - - easypost-202505231841-d89645a184-master - x-xss-protection: - - 1; mode=block - status: - code: 200 - message: OK -version: 1 diff --git a/tests/test_carrier_account.py b/tests/test_carrier_account.py index edf6f976..a708c665 100644 --- a/tests/test_carrier_account.py +++ b/tests/test_carrier_account.py @@ -1,7 +1,29 @@ import pytest +from easypost.constant import _CARRIER_ACCOUNT_CREATE_ENDPOINT_TYPES from easypost.errors.api.api_error import ApiError from easypost.models import CarrierAccount +from easypost.requestor import Requestor + + +@pytest.fixture +def vcr_cassette_name(request): + return request.node.name.replace("[", "_").replace("]", "") + + +def _flatten_custom_carrier_route_mapping(): + return [ + (carrier_account_type, endpoint) + for endpoint, carrier_account_types in _CARRIER_ACCOUNT_CREATE_ENDPOINT_TYPES.items() + for carrier_account_type in carrier_account_types + ] + + +def _carrier_create_payload_for_endpoint(endpoint: str) -> dict: + if endpoint in ("/carrier_accounts/open", "/carrier_accounts/register"): + return {"registration_data": {}} + + return {} @pytest.mark.vcr() @@ -75,76 +97,43 @@ def test_carrier_account_type(prod_client): @pytest.mark.vcr() -def test_carrier_account_create_with_custom_workflow(prod_client): - """Test registering a Carrier Account with custom workflow. - - We purposefully don't pass data here because real data is required for this endpoint - which we don't have in a test context, simply assert the error matches when no data is passed. - """ - carrier_account = { - "type": "FedexAccount", - "registration_data": {}, - } - - try: - prod_client.carrier_account.create(**carrier_account) - except ApiError as error: - assert error.http_status == 422 - assert any( - [error["field"] == "account_number" and error["message"] == "must be present and a string"] - for error in error.errors +@pytest.mark.parametrize( + "carrier_account_type,expected_endpoint,params", + [ + pytest.param( + carrier_account_type, + endpoint, + _carrier_create_payload_for_endpoint(endpoint), + id=carrier_account_type, ) + for carrier_account_type, endpoint in _flatten_custom_carrier_route_mapping() + ], +) +def test_carrier_account_create_url_routing(prod_client, monkeypatch, carrier_account_type, expected_endpoint, params): + payload = {"type": carrier_account_type, **params} + captured_request = {} + original_request = Requestor.request -@pytest.mark.vcr() -def test_carrier_account_create_ups(prod_client): - """Test registering a UPS Carrier Account which uses a different URL and schema.""" - params = { - "type": "UpsAccount", - "account_number": "123456789", - } - - carrier_account = prod_client.carrier_account.create(**params) - - assert isinstance(carrier_account, CarrierAccount) - assert str.startswith(carrier_account.id, "ca_") - assert carrier_account.type == "UpsAccount" - - prod_client.carrier_account.delete(carrier_account.id) # Delete the carrier account once it's done being tested. + def spy_request(self, method, url, params=None, *args, **kwargs): + if "url" not in captured_request and url.startswith("/carrier_accounts"): + captured_request["url"] = url + captured_request["params"] = params + return original_request(self, method, url, params, *args, **kwargs) + monkeypatch.setattr(Requestor, "request", spy_request) -@pytest.mark.vcr() -def test_carrier_account_update_ups(prod_client): - """Test updating a UPS Carrier Account which uses a different URL and schema.""" - params = { - "type": "UpsAccount", - "account_number": "123456789", - } - - carrier_account = prod_client.carrier_account.create(**params) - - updated_carrier_account = prod_client.carrier_account.update(carrier_account.id, account_number="987654321") - - assert isinstance(updated_carrier_account, CarrierAccount) - assert str.startswith(updated_carrier_account.id, "ca_") - assert updated_carrier_account.type == "UpsAccount" - - prod_client.carrier_account.delete( - updated_carrier_account.id - ) # Delete the carrier account once it's done being tested. - - -@pytest.mark.vcr() -def test_carrier_account_create_amazon_shipping(prod_client): - """Test registering an Amazon Shipping Carrier Account which uses a different URL and schema.""" - params = { - "type": "AmazonShippingAccount", - } - - carrier_account = prod_client.carrier_account.create(**params) - - assert isinstance(carrier_account, CarrierAccount) - assert str.startswith(carrier_account.id, "ca_") - assert carrier_account.type == "AmazonShippingAccount" + try: + carrier_account = prod_client.carrier_account.create(**payload) + except ApiError as error: + assert error.http_status not in (404, 500) + else: + assert isinstance(carrier_account, CarrierAccount) + assert str.startswith(carrier_account.id, "ca_") + prod_client.carrier_account.delete(carrier_account.id) - prod_client.carrier_account.delete(carrier_account.id) # Delete the carrier account once it's done being tested. + assert captured_request["url"] == expected_endpoint + if expected_endpoint == "/carrier_accounts/register_oauth": + assert "carrier_account_oauth_registrations" in captured_request["params"] + else: + assert "carrier_account" in captured_request["params"]