karrio-amazon-shipping 2025.5rc30__py3-none-any.whl → 2026.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. karrio/mappers/amazon_shipping/mapper.py +47 -48
  2. karrio/mappers/amazon_shipping/proxy.py +156 -26
  3. karrio/mappers/amazon_shipping/settings.py +22 -9
  4. karrio/plugins/amazon_shipping/__init__.py +5 -2
  5. karrio/providers/amazon_shipping/error.py +22 -13
  6. karrio/providers/amazon_shipping/rate.py +111 -50
  7. karrio/providers/amazon_shipping/shipment/cancel.py +32 -19
  8. karrio/providers/amazon_shipping/shipment/create.py +126 -69
  9. karrio/providers/amazon_shipping/tracking.py +93 -22
  10. karrio/providers/amazon_shipping/units.py +134 -5
  11. karrio/providers/amazon_shipping/utils.py +46 -73
  12. karrio/schemas/amazon_shipping/cancel_shipment_response.py +8 -0
  13. karrio/schemas/amazon_shipping/one_click_shipment_request.py +61 -0
  14. karrio/schemas/amazon_shipping/one_click_shipment_response.py +60 -0
  15. karrio/schemas/amazon_shipping/purchase_shipment_request.py +15 -64
  16. karrio/schemas/amazon_shipping/purchase_shipment_response.py +18 -23
  17. karrio/schemas/amazon_shipping/rate_request.py +38 -24
  18. karrio/schemas/amazon_shipping/rate_response.py +70 -7
  19. karrio/schemas/amazon_shipping/tracking_response.py +24 -1
  20. {karrio_amazon_shipping-2025.5rc30.dist-info → karrio_amazon_shipping-2026.1.dist-info}/METADATA +2 -2
  21. karrio_amazon_shipping-2026.1.dist-info/RECORD +29 -0
  22. {karrio_amazon_shipping-2025.5rc30.dist-info → karrio_amazon_shipping-2026.1.dist-info}/top_level.txt +1 -0
  23. karrio/schemas/amazon_shipping/create_shipment_request.py +0 -69
  24. karrio/schemas/amazon_shipping/create_shipment_response.py +0 -37
  25. karrio/schemas/amazon_shipping/purchase_label_request.py +0 -15
  26. karrio/schemas/amazon_shipping/purchase_label_response.py +0 -56
  27. karrio/schemas/amazon_shipping/shipping_label.py +0 -15
  28. karrio_amazon_shipping-2025.5rc30.dist-info/RECORD +0 -31
  29. {karrio_amazon_shipping-2025.5rc30.dist-info → karrio_amazon_shipping-2026.1.dist-info}/WHEEL +0 -0
  30. {karrio_amazon_shipping-2025.5rc30.dist-info → karrio_amazon_shipping-2026.1.dist-info}/entry_points.txt +0 -0
@@ -1,67 +1,66 @@
1
- from typing import List, Tuple
2
- from karrio.api.mapper import Mapper as BaseMapper
3
- from karrio.mappers.amazon_shipping.settings import Settings
4
- from karrio.core.utils.serializable import Deserializable, Serializable
5
- from karrio.core.models import (
6
- RateRequest,
7
- ShipmentRequest,
8
- ShipmentDetails,
9
- ShipmentCancelRequest,
10
- RateDetails,
11
- Message,
12
- ConfirmationDetails,
13
- TrackingDetails,
14
- TrackingRequest,
15
- )
16
- from karrio.providers.amazon_shipping import (
17
- parse_shipment_cancel_response,
18
- parse_tracking_response,
19
- parse_shipment_response,
20
- parse_rate_response,
21
- shipment_cancel_request,
22
- tracking_request,
23
- shipment_request,
24
- rate_request,
25
- )
1
+ """Karrio Amazon Shipping mapper."""
26
2
 
3
+ import typing
4
+ import karrio.lib as lib
5
+ import karrio.api.mapper as mapper
6
+ import karrio.core.models as models
7
+ import karrio.providers.amazon_shipping as provider
8
+ import karrio.mappers.amazon_shipping.settings as provider_settings
27
9
 
28
- class Mapper(BaseMapper):
29
- settings: Settings
10
+
11
+ class Mapper(mapper.Mapper):
12
+ """Amazon Shipping API mapper."""
13
+
14
+ settings: provider_settings.Settings
30
15
 
31
16
  # Request Mappers
32
17
 
33
- def create_rate_request(self, payload: RateRequest) -> Serializable:
34
- return rate_request(payload, self.settings)
18
+ def create_rate_request(
19
+ self,
20
+ payload: models.RateRequest,
21
+ ) -> lib.Serializable:
22
+ return provider.rate_request(payload, self.settings)
35
23
 
36
- def create_shipment_request(self, payload: ShipmentRequest) -> Serializable:
37
- return shipment_request(payload, self.settings)
24
+ def create_shipment_request(
25
+ self,
26
+ payload: models.ShipmentRequest,
27
+ ) -> lib.Serializable:
28
+ return provider.shipment_request(payload, self.settings)
38
29
 
39
30
  def create_cancel_shipment_request(
40
- self, payload: ShipmentCancelRequest
41
- ) -> Serializable:
42
- return shipment_cancel_request(payload, self.settings)
31
+ self,
32
+ payload: models.ShipmentCancelRequest,
33
+ ) -> lib.Serializable:
34
+ return provider.shipment_cancel_request(payload, self.settings)
43
35
 
44
- def create_tracking_request(self, payload: TrackingRequest) -> Serializable:
45
- return tracking_request(payload, self.settings)
36
+ def create_tracking_request(
37
+ self,
38
+ payload: models.TrackingRequest,
39
+ ) -> lib.Serializable:
40
+ return provider.tracking_request(payload, self.settings)
46
41
 
47
42
  # Response Parsers
48
43
 
49
44
  def parse_rate_response(
50
- self, response: Deserializable
51
- ) -> Tuple[List[RateDetails], List[Message]]:
52
- return parse_rate_response(response, self.settings)
45
+ self,
46
+ response: lib.Deserializable,
47
+ ) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
48
+ return provider.parse_rate_response(response, self.settings)
53
49
 
54
50
  def parse_shipment_response(
55
- self, response: Deserializable
56
- ) -> Tuple[ShipmentDetails, List[Message]]:
57
- return parse_shipment_response(response, self.settings)
51
+ self,
52
+ response: lib.Deserializable,
53
+ ) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]:
54
+ return provider.parse_shipment_response(response, self.settings)
58
55
 
59
56
  def parse_cancel_shipment_response(
60
- self, response: Deserializable
61
- ) -> Tuple[ConfirmationDetails, List[Message]]:
62
- return parse_shipment_cancel_response(response, self.settings)
57
+ self,
58
+ response: lib.Deserializable,
59
+ ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
60
+ return provider.parse_shipment_cancel_response(response, self.settings)
63
61
 
64
62
  def parse_tracking_response(
65
- self, response: Deserializable
66
- ) -> Tuple[List[TrackingDetails], List[Message]]:
67
- return parse_tracking_response(response, self.settings)
63
+ self,
64
+ response: lib.Deserializable,
65
+ ) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]:
66
+ return provider.parse_tracking_response(response, self.settings)
@@ -1,65 +1,195 @@
1
+ """Karrio Amazon Shipping API proxy."""
2
+
1
3
  import typing
4
+ import datetime
2
5
  import karrio.lib as lib
3
6
  import karrio.api.proxy as proxy
7
+ import karrio.core.errors as errors
8
+ import karrio.core.models as models
9
+ import karrio.providers.amazon_shipping.error as provider_error
4
10
  from karrio.mappers.amazon_shipping.settings import Settings
5
11
 
6
12
 
7
13
  class Proxy(proxy.Proxy):
14
+ """Amazon Shipping SP-API proxy."""
15
+
8
16
  settings: Settings
9
17
 
18
+ def authenticate(self, _=None) -> lib.Deserializable[str]:
19
+ """Obtain access token using LWA OAuth2 refresh token flow.
20
+
21
+ The token is cached and refreshed when expired.
22
+ """
23
+ cache_key = f"{self.settings.carrier_name}|{self.settings.client_id}"
24
+
25
+ def get_token():
26
+ result = lib.request(
27
+ url=self.settings.token_url,
28
+ trace=self.trace_as("json"),
29
+ method="POST",
30
+ headers={"Content-Type": "application/x-www-form-urlencoded"},
31
+ data=lib.to_query_string(dict(
32
+ grant_type="refresh_token",
33
+ refresh_token=self.settings.refresh_token,
34
+ client_id=self.settings.client_id,
35
+ client_secret=self.settings.client_secret,
36
+ )),
37
+ max_retries=2,
38
+ )
39
+
40
+ response = lib.to_dict(result)
41
+
42
+ if "error" in response:
43
+ raise errors.ParsedMessagesError(
44
+ messages=[
45
+ models.Message(
46
+ carrier_name=self.settings.carrier_name,
47
+ carrier_id=self.settings.carrier_id,
48
+ message=response.get("error_description", response["error"]),
49
+ code=response.get("error"),
50
+ )
51
+ ]
52
+ )
53
+
54
+ access_token = response.get("access_token")
55
+ if not access_token:
56
+ raise errors.ParsedMessagesError(
57
+ messages=[
58
+ models.Message(
59
+ carrier_name=self.settings.carrier_name,
60
+ carrier_id=self.settings.carrier_id,
61
+ message="Authentication failed: No access token received",
62
+ code="AUTH_ERROR",
63
+ )
64
+ ]
65
+ )
66
+
67
+ expiry = datetime.datetime.now() + datetime.timedelta(
68
+ seconds=float(response.get("expires_in", 3600))
69
+ )
70
+
71
+ return {
72
+ **response,
73
+ "expiry": lib.fdatetime(expiry),
74
+ "access_token": access_token,
75
+ }
76
+
77
+ token_state = self.settings.connection_cache.thread_safe(
78
+ refresh_func=get_token,
79
+ cache_key=cache_key,
80
+ buffer_minutes=5,
81
+ token_field="access_token",
82
+ )
83
+
84
+ # Handle both Token object and direct dict from cache
85
+ state = (
86
+ token_state.get_state()
87
+ if hasattr(token_state, "get_state")
88
+ else token_state
89
+ )
90
+ access_token = (
91
+ state.get("access_token")
92
+ if isinstance(state, dict)
93
+ else state
94
+ )
95
+
96
+ return lib.Deserializable(access_token)
97
+
10
98
  def get_rates(self, request: lib.Serializable) -> lib.Deserializable:
99
+ """Get shipping rates using the v2 getRates API."""
11
100
  response = self._send_request(
12
- path="/shipping/v1/rates",
13
- request=lib.Serializable(request, lib.to_json),
101
+ path="/shipping/v2/shipments/rates",
102
+ request=request,
14
103
  )
15
104
 
16
105
  return lib.Deserializable(response, lib.to_dict)
17
106
 
18
107
  def create_shipment(self, request: lib.Serializable) -> lib.Deserializable:
108
+ """Create shipment using the oneClickShipment API for combined rate + purchase."""
19
109
  response = self._send_request(
20
- path="/shipping/v1/purchaseShipment",
21
- request=lib.Serializable(request, lib.to_json),
110
+ path="/shipping/v2/oneClickShipment",
111
+ request=request,
22
112
  )
23
113
 
24
114
  return lib.Deserializable(response, lib.to_dict)
25
115
 
26
116
  def cancel_shipment(self, request: lib.Serializable) -> lib.Deserializable:
117
+ """Cancel shipment using the v2 cancelShipment API."""
118
+ shipment_id = request.serialize()
27
119
  response = self._send_request(
28
- path=f"/shipping/v1/shipments/{request.serialize()}/cancel",
120
+ path=f"/shipping/v2/shipments/{shipment_id}/cancel",
121
+ request=None,
122
+ method="PUT",
29
123
  )
30
124
 
31
- return lib.Deserializable(response if any(response) else "{}", lib.to_dict)
125
+ return lib.Deserializable(
126
+ response if response.strip() else "{}",
127
+ lib.to_dict,
128
+ )
32
129
 
33
130
  def get_tracking(self, request: lib.Serializable) -> lib.Deserializable:
34
- track = lambda trackingId: (
35
- trackingId,
36
- self._send_request(
37
- path=f"/shipping/v1/tracking/{trackingId}",
38
- method="GET",
39
- ),
40
- )
131
+ """Get tracking information using the v2 getTracking API."""
132
+ access_token = self.authenticate().deserialize()
133
+ tracking_data = request.serialize()
134
+
135
+ def track(data: dict) -> typing.Tuple[str, str]:
136
+ tracking_id = data.get("tracking_id")
137
+ carrier_id = data.get("carrier_id", "AMZN_US")
138
+
139
+ return (
140
+ tracking_id,
141
+ lib.request(
142
+ url=f"{self.settings.server_url}/shipping/v2/tracking",
143
+ trace=self.trace_as("json"),
144
+ method="GET",
145
+ headers=self._get_headers(access_token),
146
+ params=dict(
147
+ trackingId=tracking_id,
148
+ carrierId=carrier_id,
149
+ ),
150
+ ),
151
+ )
41
152
 
42
153
  responses: typing.List[typing.Tuple[str, str]] = lib.run_asynchronously(
43
- track, request.serialize()
154
+ track, tracking_data
44
155
  )
156
+
45
157
  return lib.Deserializable(
46
158
  responses,
47
159
  lambda res: [(key, lib.to_dict(response)) for key, response in res],
48
160
  )
49
161
 
50
162
  def _send_request(
51
- self, path: str, request: lib.Serializable = None, method: str = "POST"
163
+ self,
164
+ path: str,
165
+ request: lib.Serializable = None,
166
+ method: str = "POST",
52
167
  ) -> str:
53
- data: dict = dict(data=request.serialize()) if request is not None else dict()
168
+ """Send request to Amazon Shipping API."""
169
+ access_token = self.authenticate().deserialize()
170
+ data = dict(data=request.serialize()) if request is not None else {}
171
+
54
172
  return lib.request(
55
- **{
56
- "url": f"{self.settings.server_url}{path}",
57
- "trace": self.trace_as("json"),
58
- "method": method,
59
- "headers": {
60
- "Content-Type": "application/json",
61
- "x-amz-access-token": self.settings.access_token,
62
- },
63
- **data,
64
- }
173
+ url=f"{self.settings.server_url}{path}",
174
+ trace=self.trace_as("json"),
175
+ method=method,
176
+ headers=self._get_headers(access_token),
177
+ **data,
65
178
  )
179
+
180
+ def _get_headers(self, access_token: str) -> dict:
181
+ """Get request headers with authentication and business ID."""
182
+ headers = {
183
+ "Content-Type": "application/json",
184
+ "x-amz-access-token": access_token,
185
+ }
186
+
187
+ # Add shipping business ID if configured
188
+ business_id = (
189
+ self.settings.shipping_business_id
190
+ or self.settings.connection_config.shipping_business_id.state
191
+ )
192
+ if business_id:
193
+ headers["x-amzn-shipping-business-id"] = business_id
194
+
195
+ return headers
@@ -1,22 +1,35 @@
1
1
  """Karrio Amazon Shipping connection settings."""
2
2
 
3
3
  import attr
4
- import jstruct
5
- import karrio.lib as lib
6
- from karrio.providers.amazon_shipping.utils import Settings as BaseSettings
4
+ import karrio.providers.amazon_shipping.utils as provider_utils
7
5
 
8
6
 
9
7
  @attr.s(auto_attribs=True)
10
- class Settings(BaseSettings):
11
- """Amazon Shipping connection settings."""
8
+ class Settings(provider_utils.Settings):
9
+ """Amazon Shipping SP-API connection settings.
12
10
 
13
- seller_id: str # type:ignore
14
- developer_id: str # type:ignore
15
- mws_auth_token: str # type:ignore
11
+ Credentials for Login with Amazon (LWA) OAuth2 authentication:
12
+ - client_id: LWA application client ID
13
+ - client_secret: LWA application client secret
14
+ - refresh_token: LWA refresh token for the authorized seller
15
+
16
+ Optional configuration:
17
+ - aws_region: AWS region (us-east-1, eu-west-1, us-west-2)
18
+ - shipping_business_id: Amazon business ID header (e.g., AmazonShipping_US)
19
+ """
20
+
21
+ # Required SP-API credentials
22
+ client_id: str
23
+ client_secret: str
24
+ refresh_token: str
25
+
26
+ # Optional configuration
16
27
  aws_region: str = "us-east-1"
28
+ shipping_business_id: str = None
17
29
 
18
- carrier_id: str = "amazon_shipping"
30
+ # Standard karrio settings
19
31
  account_country_code: str = None
32
+ carrier_id: str = "amazon_shipping"
20
33
  test_mode: bool = False
21
34
  metadata: dict = {}
22
35
  config: dict = {}
@@ -1,3 +1,5 @@
1
+ """Karrio Amazon Shipping plugin."""
2
+
1
3
  import karrio.core.metadata as metadata
2
4
  import karrio.mappers.amazon_shipping as mappers
3
5
  import karrio.providers.amazon_shipping.units as units
@@ -6,12 +8,13 @@ import karrio.providers.amazon_shipping.units as units
6
8
  METADATA = metadata.PluginMetadata(
7
9
  status="beta",
8
10
  id="amazon_shipping",
9
- label="AmazonShipping",
11
+ label="Amazon Shipping",
10
12
  # Integrations
11
13
  Mapper=mappers.Mapper,
12
14
  Proxy=mappers.Proxy,
13
15
  Settings=mappers.Settings,
14
16
  # Data Units
15
- services=units.Service,
17
+ services=units.ShippingService,
18
+ options=units.ShippingOption,
16
19
  has_intl_accounts=True,
17
20
  )
@@ -1,4 +1,5 @@
1
- import karrio.schemas.amazon_shipping.error_response as amazon
1
+ """Karrio Amazon Shipping error parser."""
2
+
2
3
  import typing
3
4
  import karrio.lib as lib
4
5
  import karrio.core.models as models
@@ -6,24 +7,32 @@ import karrio.providers.amazon_shipping.utils as provider_utils
6
7
 
7
8
 
8
9
  def parse_error_response(
9
- response: dict, settings: provider_utils.Settings, details: dict = None
10
+ response: dict,
11
+ settings: provider_utils.Settings,
12
+ **kwargs,
10
13
  ) -> typing.List[models.Message]:
11
- errors = (
12
- lib.to_object(amazon.ErrorResponse, response)
13
- if response.get("errors") is not None
14
- else amazon.ErrorResponse(errors=[])
15
- )
14
+ """Parse error response from Amazon Shipping API.
15
+
16
+ The v2 API returns errors in the format:
17
+ {
18
+ "errors": [
19
+ {"code": "InvalidRequest", "message": "...", "details": "..."}
20
+ ]
21
+ }
22
+ """
23
+ errors = response.get("errors") or []
16
24
 
17
25
  return [
18
26
  models.Message(
19
27
  carrier_id=settings.carrier_id,
20
28
  carrier_name=settings.carrier_name,
21
- code=error.code,
22
- message=error.message,
29
+ code=error.get("code"),
30
+ message=error.get("message"),
23
31
  details={
24
- **(details or {}),
25
- **({} if error.details is None else {"note": error.details}),
26
- },
32
+ **kwargs,
33
+ **({"note": error.get("details")} if error.get("details") else {}),
34
+ } or None,
27
35
  )
28
- for error in errors.errors
36
+ for error in errors
37
+ if error.get("code") or error.get("message")
29
38
  ]
@@ -1,104 +1,165 @@
1
- import karrio.schemas.amazon_shipping.rate_request as amazon
2
- from karrio.schemas.amazon_shipping.rate_response import ServiceRate
1
+ """Karrio Amazon Shipping rating implementation."""
3
2
 
4
3
  import typing
5
4
  import karrio.lib as lib
6
- import karrio.core.units as units
7
5
  import karrio.core.models as models
8
- import karrio.core.errors as errors
9
- import karrio.providers.amazon_shipping.error as provider_error
10
- import karrio.providers.amazon_shipping.units as provider_units
6
+ import karrio.providers.amazon_shipping.error as error
11
7
  import karrio.providers.amazon_shipping.utils as provider_utils
8
+ import karrio.providers.amazon_shipping.units as provider_units
9
+ import karrio.schemas.amazon_shipping.rate_response as amazon
12
10
 
13
11
 
14
12
  def parse_rate_response(
15
- _response: lib.Deserializable[dict], settings: provider_utils.Settings
13
+ _response: lib.Deserializable[dict],
14
+ settings: provider_utils.Settings,
16
15
  ) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
16
+ """Parse rate response from Amazon Shipping API."""
17
17
  response = _response.deserialize()
18
- errors: typing.List[models.Message] = sum(
19
- [
20
- provider_error.parse_error_response(data, settings)
21
- for data in response.get("errors", [])
22
- ],
23
- [],
24
- )
18
+ messages = error.parse_error_response(response, settings)
19
+
25
20
  rates = [
26
- _extract_details(data, settings) for data in response.get("serviceRates", [])
21
+ _extract_details(rate, settings)
22
+ for rate in response.get("rates") or []
27
23
  ]
28
24
 
29
- return rates, errors
25
+ return rates, messages
30
26
 
31
27
 
32
28
  def _extract_details(
33
29
  data: dict,
34
30
  settings: provider_utils.Settings,
35
31
  ) -> models.RateDetails:
36
- rate = lib.to_object(ServiceRate, data)
37
- transit = (
38
- lib.to_date(rate.promise.deliveryWindow.start, "%Y-%m-%dT%H:%M:%S.%fZ").date()
39
- - lib.to_date(rate.promise.receiveWindow.end, "%Y-%m-%dT%H:%M:%S.%fZ").date()
40
- ).days
32
+ """Extract rate details from API response."""
33
+ rate = lib.to_object(amazon.Rate, data)
34
+
35
+ # Calculate transit days from delivery window
36
+ transit_days = lib.failsafe(
37
+ lambda: (
38
+ lib.to_date(rate.promise.deliveryWindow.start).date()
39
+ - lib.to_date(rate.promise.pickupWindow.start).date()
40
+ ).days
41
+ )
42
+
43
+ # Extract rate items as extra charges
44
+ extra_charges = [
45
+ models.ChargeDetails(
46
+ name=item.rateItemNameLocalization or item.rateItemID,
47
+ amount=lib.to_money(item.rateItemCharge.value),
48
+ currency=item.rateItemCharge.unit,
49
+ )
50
+ for item in rate.rateItemList or []
51
+ ]
41
52
 
42
53
  return models.RateDetails(
43
54
  carrier_id=settings.carrier_id,
44
55
  carrier_name=settings.carrier_name,
45
- service=provider_units.Service.map(rate.serviceType).name_or_key,
46
- total_charge=lib.to_decimal(rate.totalCharge.value),
56
+ service=provider_units.ShippingService.map(rate.serviceId).name_or_key,
57
+ total_charge=lib.to_money(rate.totalCharge.value),
47
58
  currency=rate.totalCharge.unit,
48
- transit_days=transit,
59
+ transit_days=transit_days,
60
+ extra_charges=extra_charges,
49
61
  meta=dict(
50
- service_name=rate.serviceType,
62
+ rate_id=rate.rateId,
63
+ carrier_id=rate.carrierId,
64
+ carrier_name=rate.carrierName,
65
+ service_id=rate.serviceId,
66
+ service_name=rate.serviceName,
51
67
  ),
52
68
  )
53
69
 
54
70
 
55
- def rate_request(payload: models.RateRequest, _) -> lib.Serializable:
71
+ def rate_request(
72
+ payload: models.RateRequest,
73
+ settings: provider_utils.Settings,
74
+ ) -> lib.Serializable:
75
+ """Create Amazon Shipping rate request."""
56
76
  shipper = lib.to_address(payload.shipper)
57
77
  recipient = lib.to_address(payload.recipient)
58
- packages = lib.to_packages(payload.parcels)
59
- options = lib.to_shipping_options(payload.options)
60
- services = lib.to_services(payload.services, provider_units.Service)
78
+ packages = lib.to_packages(payload.parcels, required=["weight"])
79
+ services = lib.to_services(payload.services, provider_units.ShippingService)
80
+ options = lib.to_shipping_options(
81
+ payload.options,
82
+ package_options=packages.options,
83
+ initializer=provider_units.shipping_options_initializer,
84
+ )
61
85
 
62
- request = amazon.RateRequest(
63
- shipFrom=amazon.Ship(
64
- name=shipper.person_name,
65
- city=shipper.city,
86
+ # Determine label format from options or settings
87
+ label_format = (
88
+ options.amazon_shipping_label_format.state
89
+ or settings.connection_config.label_format.state
90
+ or "PNG"
91
+ )
92
+
93
+ request = dict(
94
+ shipFrom=dict(
95
+ name=shipper.company_name or shipper.person_name,
66
96
  addressLine1=shipper.street,
67
97
  addressLine2=shipper.address_line2,
98
+ addressLine3=None,
99
+ companyName=shipper.company_name,
68
100
  stateOrRegion=shipper.state_code,
101
+ city=shipper.city,
102
+ countryCode=shipper.country_code,
103
+ postalCode=shipper.postal_code,
69
104
  email=shipper.email,
70
- copyEmails=lib.join(shipper.email),
71
105
  phoneNumber=shipper.phone_number,
72
106
  ),
73
- shipTo=amazon.Ship(
74
- name=recipient.person_name,
75
- city=recipient.city,
107
+ shipTo=dict(
108
+ name=recipient.company_name or recipient.person_name,
76
109
  addressLine1=recipient.street,
77
110
  addressLine2=recipient.address_line2,
111
+ addressLine3=None,
112
+ companyName=recipient.company_name,
78
113
  stateOrRegion=recipient.state_code,
114
+ city=recipient.city,
115
+ countryCode=recipient.country_code,
116
+ postalCode=recipient.postal_code,
79
117
  email=recipient.email,
80
- copyEmails=lib.join(recipient.email),
81
118
  phoneNumber=recipient.phone_number,
82
119
  ),
83
- serviceTypes=list(services),
84
120
  shipDate=lib.fdatetime(
85
- options.shipment_date.state, "%Y-%m-%d", "%Y-%m-%dT%H:%M:%S.%fZ"
86
- ),
87
- containerSpecifications=[
88
- amazon.ContainerSpecification(
89
- dimensions=amazon.Dimensions(
90
- height=package.height.IN,
121
+ options.shipment_date.state,
122
+ current_format="%Y-%m-%d",
123
+ output_format="%Y-%m-%dT%H:%M:%SZ",
124
+ ) if options.shipment_date.state else None,
125
+ packages=[
126
+ dict(
127
+ dimensions=dict(
91
128
  length=package.length.IN,
92
129
  width=package.width.IN,
93
- unit="IN",
94
- ),
95
- weight=amazon.Weight(
130
+ height=package.height.IN,
131
+ unit="INCH",
132
+ ) if package.has_dimensions else None,
133
+ weight=dict(
96
134
  value=package.weight.LB,
97
- unit="LB",
135
+ unit="POUND",
98
136
  ),
137
+ insuredValue=dict(
138
+ value=lib.to_money(package.options.declared_value.state),
139
+ unit=package.options.currency.state or "USD",
140
+ ) if package.options.declared_value.state else None,
141
+ packageClientReferenceId=package.parcel.id or str(index),
99
142
  )
100
- for package in packages
143
+ for index, package in enumerate(packages, 1)
101
144
  ],
145
+ channelDetails=dict(
146
+ channelType=options.amazon_shipping_channel_type.state or "EXTERNAL",
147
+ ),
148
+ labelSpecifications=dict(
149
+ format=label_format,
150
+ size=dict(
151
+ length=settings.connection_config.label_size_length.state or 6,
152
+ width=settings.connection_config.label_size_width.state or 4,
153
+ unit=settings.connection_config.label_size_unit.state or "INCH",
154
+ ),
155
+ dpi=300,
156
+ pageLayout="DEFAULT",
157
+ needFileJoining=False,
158
+ requestedDocumentTypes=["LABEL"],
159
+ ),
160
+ serviceSelection=dict(
161
+ serviceId=list(services) if any(services) else None,
162
+ ) if any(services) else None,
102
163
  )
103
164
 
104
165
  return lib.Serializable(request, lib.to_dict)