karrio-teleship 2025.5rc35__py3-none-any.whl → 2025.5.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 (44) hide show
  1. karrio/mappers/teleship/__init__.py +2 -1
  2. karrio/mappers/teleship/hooks.py +27 -0
  3. karrio/mappers/teleship/mapper.py +60 -0
  4. karrio/mappers/teleship/proxy.py +191 -38
  5. karrio/plugins/teleship/__init__.py +4 -1
  6. karrio/providers/teleship/__init__.py +25 -0
  7. karrio/providers/teleship/duties.py +115 -0
  8. karrio/providers/teleship/hooks/__init__.py +5 -0
  9. karrio/providers/teleship/hooks/event.py +163 -0
  10. karrio/providers/teleship/hooks/oauth.py +103 -0
  11. karrio/providers/teleship/manifest.py +68 -0
  12. karrio/providers/teleship/pickup/__init__.py +8 -0
  13. karrio/providers/teleship/pickup/cancel.py +43 -0
  14. karrio/providers/teleship/pickup/schedule.py +66 -0
  15. karrio/providers/teleship/rate.py +210 -247
  16. karrio/providers/teleship/shipment/cancel.py +15 -58
  17. karrio/providers/teleship/shipment/create.py +238 -266
  18. karrio/providers/teleship/tracking.py +35 -50
  19. karrio/providers/teleship/units.py +67 -67
  20. karrio/providers/teleship/utils.py +16 -132
  21. karrio/providers/teleship/webhook/__init__.py +8 -0
  22. karrio/providers/teleship/webhook/deregister.py +47 -0
  23. karrio/providers/teleship/webhook/register.py +48 -0
  24. karrio/schemas/teleship/duties_taxes_request.py +82 -0
  25. karrio/schemas/teleship/duties_taxes_response.py +28 -0
  26. karrio/schemas/teleship/error_response.py +1 -0
  27. karrio/schemas/teleship/manifest_request.py +39 -0
  28. karrio/schemas/teleship/manifest_response.py +31 -0
  29. karrio/schemas/teleship/pickup_request.py +31 -0
  30. karrio/schemas/teleship/pickup_response.py +48 -0
  31. karrio/schemas/teleship/rate_request.py +16 -34
  32. karrio/schemas/teleship/rate_response.py +20 -3
  33. karrio/schemas/teleship/shipment_request.py +16 -34
  34. karrio/schemas/teleship/shipment_response.py +188 -29
  35. karrio/schemas/teleship/tracking_response.py +15 -5
  36. karrio/schemas/teleship/webhook_request.py +18 -0
  37. karrio/schemas/teleship/webhook_response.py +20 -0
  38. {karrio_teleship-2025.5rc35.dist-info → karrio_teleship-2025.5.1.dist-info}/METADATA +2 -2
  39. karrio_teleship-2025.5.1.dist-info/RECORD +49 -0
  40. karrio_teleship-2025.5.1.dist-info/entry_points.txt +2 -0
  41. karrio_teleship-2025.5rc35.dist-info/RECORD +0 -29
  42. karrio_teleship-2025.5rc35.dist-info/entry_points.txt +0 -2
  43. {karrio_teleship-2025.5rc35.dist-info → karrio_teleship-2025.5.1.dist-info}/WHEEL +0 -0
  44. {karrio_teleship-2025.5rc35.dist-info → karrio_teleship-2025.5.1.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,4 @@
1
1
  from karrio.mappers.teleship.mapper import Mapper
2
2
  from karrio.mappers.teleship.proxy import Proxy
3
- from karrio.mappers.teleship.settings import Settings
3
+ from karrio.mappers.teleship.settings import Settings
4
+ from karrio.mappers.teleship.hooks import Hooks
@@ -0,0 +1,27 @@
1
+ """Karrio Teleship client hooks."""
2
+
3
+ import typing
4
+ import karrio.lib as lib
5
+ import karrio.core.models as models
6
+ import karrio.api.hooks as hooks
7
+ import karrio.providers.teleship as provider
8
+ import karrio.mappers.teleship.settings as provider_settings
9
+
10
+
11
+ class Hooks(hooks.Hooks):
12
+ settings: provider_settings.Settings
13
+
14
+ def on_webhook_event(
15
+ self, payload: models.RequestPayload
16
+ ) -> typing.Tuple[models.WebhookEventDetails, typing.List[models.Message]]:
17
+ return provider.on_webhook_event(payload, self.settings)
18
+
19
+ def on_oauth_authorize(
20
+ self, payload: models.OAuthAuthorizePayload
21
+ ) -> typing.Tuple[models.OAuthAuthorizeRequest, typing.List[models.Message]]:
22
+ return provider.on_oauth_authorize(payload, self.settings)
23
+
24
+ def on_oauth_callback(
25
+ self, payload: models.RequestPayload
26
+ ) -> typing.Tuple[typing.List[typing.Dict], typing.List[models.Message]]:
27
+ return provider.on_oauth_callback(payload, self.settings)
@@ -51,4 +51,64 @@ class Mapper(mapper.Mapper):
51
51
  self, response: lib.Deserializable[str]
52
52
  ) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]:
53
53
  return provider.parse_tracking_response(response, self.settings)
54
+
55
+ def create_pickup_request(
56
+ self, payload: models.PickupRequest
57
+ ) -> lib.Serializable:
58
+ return provider.pickup_request(payload, self.settings)
59
+
60
+ def create_cancel_pickup_request(
61
+ self, payload: models.PickupCancelRequest
62
+ ) -> lib.Serializable:
63
+ return provider.cancel_pickup_request(payload, self.settings)
64
+
65
+ def parse_pickup_response(
66
+ self, response: lib.Deserializable[str]
67
+ ) -> typing.Tuple[models.PickupDetails, typing.List[models.Message]]:
68
+ return provider.parse_pickup_response(response, self.settings)
69
+
70
+ def parse_cancel_pickup_response(
71
+ self, response: lib.Deserializable[str]
72
+ ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
73
+ return provider.parse_cancel_pickup_response(response, self.settings)
74
+
75
+ def create_manifest_request(
76
+ self, payload: models.ManifestRequest
77
+ ) -> lib.Serializable:
78
+ return provider.manifest_request(payload, self.settings)
79
+
80
+ def parse_manifest_response(
81
+ self, response: lib.Deserializable[str]
82
+ ) -> typing.Tuple[models.ManifestDetails, typing.List[models.Message]]:
83
+ return provider.parse_manifest_response(response, self.settings)
84
+
85
+ def create_duties_calculation_request(
86
+ self, payload: models.DutiesCalculationRequest
87
+ ) -> lib.Serializable:
88
+ return provider.duties_calculation_request(payload, self.settings)
89
+
90
+ def parse_duties_calculation_response(
91
+ self, response: lib.Deserializable[str]
92
+ ) -> typing.Tuple[models.DutiesCalculationDetails, typing.List[models.Message]]:
93
+ return provider.parse_duties_calculation_response(response, self.settings)
94
+
95
+ def create_webhook_registration_request(
96
+ self, payload: models.WebhookRegistrationRequest
97
+ ) -> lib.Serializable:
98
+ return provider.webhook_registration_request(payload, self.settings)
99
+
100
+ def parse_webhook_registration_response(
101
+ self, response: lib.Deserializable[str]
102
+ ) -> typing.Tuple[models.WebhookRegistrationDetails, typing.List[models.Message]]:
103
+ return provider.parse_webhook_registration_response(response, self.settings)
104
+
105
+ def create_webhook_deregistration_request(
106
+ self, payload: models.WebhookDeregistrationRequest
107
+ ) -> lib.Serializable:
108
+ return provider.webhook_deregistration_request(payload, self.settings)
109
+
110
+ def parse_webhook_deregistration_response(
111
+ self, response: lib.Deserializable[str]
112
+ ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
113
+ return provider.parse_webhook_deregistration_response(response, self.settings)
54
114
 
@@ -1,77 +1,131 @@
1
1
  """Karrio Teleship client proxy."""
2
2
 
3
+ import datetime
3
4
  import karrio.lib as lib
4
5
  import karrio.api.proxy as proxy
6
+ import karrio.core.errors as errors
7
+ import karrio.core.models as models
8
+ import karrio.providers.teleship.error as provider_error
5
9
  import karrio.mappers.teleship.settings as provider_settings
6
10
 
7
11
 
8
12
  class Proxy(proxy.Proxy):
9
13
  settings: provider_settings.Settings
10
14
 
15
+ def authenticate(self, request: lib.Serializable) -> lib.Deserializable[str]:
16
+ """Retrieve access token using thread-safe token manager.
17
+
18
+ Returns the cached token if available and not expired,
19
+ otherwise fetches a new token from the OAuth endpoint.
20
+ """
21
+ cache_key = f"{self.settings.carrier_name}|{self.settings.client_id}|{self.settings.client_secret}"
22
+
23
+ def get_token():
24
+ response = lib.request(
25
+ url=f"{self.settings.server_url}/oauth/token",
26
+ method="POST",
27
+ headers={"content-Type": "application/x-www-form-urlencoded"},
28
+ data=lib.to_query_string(
29
+ dict(
30
+ grant_type="client_credentials",
31
+ clientId=self.settings.client_id,
32
+ clientSecret=self.settings.client_secret,
33
+ )
34
+ ),
35
+ decoder=lib.to_dict,
36
+ )
37
+
38
+ messages = provider_error.parse_error_response(response, self.settings)
39
+
40
+ if any(messages):
41
+ raise errors.ParsedMessagesError(messages)
42
+
43
+ expiry = datetime.datetime.now() + datetime.timedelta(
44
+ seconds=float(response.get("expiresIn", 0))
45
+ )
46
+
47
+ return {**response, "expiry": lib.fdatetime(expiry)}
48
+
49
+ token = self.settings.connection_cache.thread_safe(
50
+ refresh_func=get_token,
51
+ cache_key=cache_key,
52
+ buffer_minutes=30,
53
+ token_field="accessToken",
54
+ )
55
+
56
+ return lib.Deserializable(token.get_state())
57
+
11
58
  def get_rates(self, request: lib.Serializable) -> lib.Deserializable[str]:
12
- response = lib.request(
13
- url=f"{self.settings.server_url}/api/rates/quotes",
14
- data=lib.to_json(request.serialize()),
15
- trace=self.trace_as("json"),
16
- method="POST",
17
- headers={
18
- "Content-Type": "application/json",
19
- "Authorization": f"Bearer {self.settings.access_token}",
20
- **({
21
- "x-account-id": self.settings.connection_config.account_id.state
22
- } if self.settings.connection_config.account_id.state else {}),
23
- },
59
+ access_token = self.authenticate(request).deserialize()
60
+
61
+ responses = lib.run_asynchronously(
62
+ lambda payload: lib.request(
63
+ url=f"{self.settings.server_url}/api/rates/quotes",
64
+ data=lib.to_json(payload),
65
+ trace=self.trace_as("json"),
66
+ method="POST",
67
+ headers={
68
+ "Content-Type": "application/json",
69
+ "Authorization": f"Bearer {access_token}",
70
+ },
71
+ ),
72
+ request.serialize(),
24
73
  )
25
74
 
26
- return lib.Deserializable(response, lib.to_dict)
75
+ return lib.Deserializable(
76
+ responses,
77
+ lambda __: [lib.to_dict(_) for _ in __],
78
+ )
27
79
 
28
80
  def create_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
29
- response = lib.request(
30
- url=f"{self.settings.server_url}/api/shipments/labels",
31
- data=lib.to_json(request.serialize()),
32
- trace=self.trace_as("json"),
33
- method="POST",
34
- headers={
35
- "Content-Type": "application/json",
36
- "Authorization": f"Bearer {self.settings.access_token}",
37
- **({
38
- "x-account-id": self.settings.connection_config.account_id.state
39
- } if self.settings.connection_config.account_id.state else {}),
40
- },
81
+ access_token = self.authenticate(request).deserialize()
82
+
83
+ responses = lib.run_asynchronously(
84
+ lambda payload: lib.request(
85
+ url=f"{self.settings.server_url}/api/shipments/labels",
86
+ data=lib.to_json(payload),
87
+ trace=self.trace_as("json"),
88
+ method="POST",
89
+ headers={
90
+ "Content-Type": "application/json",
91
+ "Authorization": f"Bearer {access_token}",
92
+ },
93
+ ),
94
+ request.serialize(),
41
95
  )
42
96
 
43
- return lib.Deserializable(response, lib.to_dict)
97
+ return lib.Deserializable(
98
+ responses,
99
+ lambda __: [lib.to_dict(_) for _ in __],
100
+ )
44
101
 
45
- def cancel_shipment(
46
- self, request: lib.Serializable
47
- ) -> lib.Deserializable[str]:
102
+ def cancel_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
103
+ access_token = self.authenticate(request).deserialize()
48
104
  shipment_id = request.serialize().get("shipmentId")
49
105
 
50
106
  response = lib.request(
51
107
  url=f"{self.settings.server_url}/api/shipments/labels/{shipment_id}/void",
52
108
  trace=self.trace_as("json"),
53
- method="DELETE",
109
+ method="POST",
54
110
  headers={
55
- "Authorization": f"Bearer {self.settings.access_token}",
56
- **({
57
- "x-account-id": self.settings.connection_config.account_id.state
58
- } if self.settings.connection_config.account_id.state else {}),
111
+ "Content-Type": "application/json",
112
+ "Authorization": f"Bearer {access_token}",
59
113
  },
60
114
  )
61
115
 
62
116
  return lib.Deserializable(response, lib.to_dict)
63
117
 
64
118
  def get_tracking(self, request: lib.Serializable) -> lib.Deserializable:
119
+ access_token = self.authenticate(request).deserialize()
120
+
65
121
  def _get_tracking(tracking_number: str):
66
122
  return tracking_number, lib.request(
67
123
  url=f"{self.settings.server_url}/api/tracking/{tracking_number}",
68
124
  trace=self.trace_as("json"),
69
125
  method="GET",
70
126
  headers={
71
- "Authorization": f"Bearer {self.settings.access_token}",
72
- **({
73
- "x-account-id": self.settings.connection_config.account_id.state
74
- } if self.settings.connection_config.account_id.state else {}),
127
+ "Content-Type": "application/json",
128
+ "Authorization": f"Bearer {access_token}",
75
129
  },
76
130
  )
77
131
 
@@ -84,3 +138,102 @@ class Proxy(proxy.Proxy):
84
138
  (num, lib.to_dict(track)) for num, track in res if any(track.strip())
85
139
  ],
86
140
  )
141
+
142
+ def schedule_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
143
+ access_token = self.authenticate(request).deserialize()
144
+
145
+ response = lib.request(
146
+ url=f"{self.settings.server_url}/api/pickups",
147
+ data=lib.to_json(request.serialize()),
148
+ trace=self.trace_as("json"),
149
+ method="POST",
150
+ headers={
151
+ "Content-Type": "application/json",
152
+ "Authorization": f"Bearer {access_token}",
153
+ },
154
+ )
155
+
156
+ return lib.Deserializable(response, lib.to_dict)
157
+
158
+ def cancel_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
159
+ access_token = self.authenticate(request).deserialize()
160
+ pickup_id = request.serialize().get("pickupId")
161
+
162
+ response = lib.request(
163
+ url=f"{self.settings.server_url}/api/pickups/{pickup_id}/cancel",
164
+ trace=self.trace_as("json"),
165
+ method="POST",
166
+ headers={
167
+ "Content-Type": "application/json",
168
+ "Authorization": f"Bearer {access_token}",
169
+ },
170
+ )
171
+
172
+ return lib.Deserializable(response, lib.to_dict)
173
+
174
+ def create_manifest(self, request: lib.Serializable) -> lib.Deserializable[str]:
175
+ access_token = self.authenticate(request).deserialize()
176
+
177
+ response = lib.request(
178
+ url=f"{self.settings.server_url}/api/manifests",
179
+ data=lib.to_json(request.serialize()),
180
+ trace=self.trace_as("json"),
181
+ method="POST",
182
+ headers={
183
+ "Content-Type": "application/json",
184
+ "Authorization": f"Bearer {access_token}",
185
+ },
186
+ )
187
+
188
+ return lib.Deserializable(response, lib.to_dict)
189
+
190
+ def calculate_duties(self, request: lib.Serializable) -> lib.Deserializable[str]:
191
+ access_token = self.authenticate(request).deserialize()
192
+
193
+ response = lib.request(
194
+ url=f"{self.settings.server_url}/api/trade-engine/duties-taxes",
195
+ data=lib.to_json(request.serialize()),
196
+ trace=self.trace_as("json"),
197
+ method="POST",
198
+ headers={
199
+ "Content-Type": "application/json",
200
+ "Authorization": f"Bearer {access_token}",
201
+ },
202
+ )
203
+
204
+ return lib.Deserializable(response, lib.to_dict)
205
+
206
+ def register_webhook(self, request: lib.Serializable) -> lib.Deserializable[str]:
207
+ access_token = self.authenticate(request).deserialize()
208
+
209
+ response = lib.request(
210
+ url=f"{self.settings.server_url}/api/webhooks",
211
+ data=lib.to_json(request.serialize()),
212
+ trace=self.trace_as("json"),
213
+ method="POST",
214
+ headers={
215
+ "Content-Type": "application/json",
216
+ "Authorization": f"Bearer {access_token}",
217
+ },
218
+ )
219
+
220
+ return lib.Deserializable(response, lib.to_dict)
221
+
222
+ def deregister_webhook(self, request: lib.Serializable) -> lib.Deserializable[str]:
223
+ access_token = self.authenticate(request).deserialize()
224
+ webhook_id = request.serialize().get("webhookId")
225
+
226
+ response = lib.request(
227
+ url=f"{self.settings.server_url}/api/webhooks/{webhook_id}",
228
+ trace=self.trace_as("json"),
229
+ method="DELETE",
230
+ headers={
231
+ "Authorization": f"Bearer {access_token}",
232
+ },
233
+ )
234
+
235
+ # DELETE returns 204 No Content on success - handle empty response
236
+ return lib.Deserializable(
237
+ response,
238
+ lambda r: lib.to_dict(r) if r and r.strip() else {},
239
+ )
@@ -3,6 +3,7 @@ from karrio.core.metadata import PluginMetadata
3
3
  from karrio.mappers.teleship.mapper import Mapper
4
4
  from karrio.mappers.teleship.proxy import Proxy
5
5
  from karrio.mappers.teleship.settings import Settings
6
+ from karrio.mappers.teleship.hooks import Hooks
6
7
  import karrio.providers.teleship.units as units
7
8
  import karrio.providers.teleship.utils as utils
8
9
 
@@ -18,11 +19,13 @@ METADATA = PluginMetadata(
18
19
  Mapper=Mapper,
19
20
  Proxy=Proxy,
20
21
  Settings=Settings,
22
+ Hooks=Hooks,
21
23
  # Data Units
22
24
  is_hub=False,
23
25
  options=units.ShippingOption,
24
26
  services=units.ShippingService,
25
- connection_configs=utils.ConnectionConfig,
27
+ connection_configs=units.ConnectionConfig,
28
+ system_config=units.SYSTEM_CONFIG,
26
29
  # Extra info
27
30
  website="https://www.teleship.com",
28
31
  documentation="https://developers.teleship.com",
@@ -13,4 +13,29 @@ from karrio.providers.teleship.shipment import (
13
13
  from karrio.providers.teleship.tracking import (
14
14
  parse_tracking_response,
15
15
  tracking_request,
16
+ )
17
+ from karrio.providers.teleship.pickup import (
18
+ pickup_request,
19
+ parse_pickup_response,
20
+ cancel_pickup_request,
21
+ parse_cancel_pickup_response,
22
+ )
23
+ from karrio.providers.teleship.manifest import (
24
+ manifest_request,
25
+ parse_manifest_response,
26
+ )
27
+ from karrio.providers.teleship.duties import (
28
+ duties_calculation_request,
29
+ parse_duties_calculation_response,
30
+ )
31
+ from karrio.providers.teleship.webhook import (
32
+ webhook_registration_request,
33
+ parse_webhook_registration_response,
34
+ webhook_deregistration_request,
35
+ parse_webhook_deregistration_response,
36
+ )
37
+ from karrio.providers.teleship.hooks import (
38
+ on_webhook_event,
39
+ on_oauth_authorize,
40
+ on_oauth_callback,
16
41
  )
@@ -0,0 +1,115 @@
1
+ """Karrio Teleship duties calculation implementation."""
2
+
3
+ import typing
4
+ import karrio.schemas.teleship.duties_taxes_request as teleship
5
+ import karrio.schemas.teleship.duties_taxes_response as duties
6
+ import karrio.lib as lib
7
+ import karrio.core.models as models
8
+ import karrio.providers.teleship.error as error
9
+ import karrio.providers.teleship.utils as provider_utils
10
+
11
+
12
+ def parse_duties_calculation_response(
13
+ _response: lib.Deserializable[str],
14
+ settings: provider_utils.Settings,
15
+ ) -> typing.Tuple[models.DutiesCalculationDetails, typing.List[models.Message]]:
16
+ response = _response.deserialize()
17
+ messages = error.parse_error_response(response, settings)
18
+ details = lib.to_object(duties.DutiesTaxesResponseType, response)
19
+
20
+ duties_details = lib.identity(
21
+ models.DutiesCalculationDetails(
22
+ carrier_id=settings.carrier_id,
23
+ carrier_name=settings.carrier_name,
24
+ total_charge=lib.to_money(details.price),
25
+ currency=details.currency,
26
+ charges=[
27
+ models.ChargeDetails(
28
+ name=charge.name,
29
+ amount=lib.to_money(charge.amount),
30
+ currency=charge.currency,
31
+ )
32
+ for charge in (details.charges or [])
33
+ ],
34
+ meta=lib.to_dict(details),
35
+ )
36
+ if details and details.price
37
+ else None
38
+ )
39
+
40
+ return duties_details, messages
41
+
42
+
43
+ def duties_calculation_request(
44
+ payload: models.DutiesCalculationRequest,
45
+ settings: provider_utils.Settings,
46
+ ) -> lib.Serializable:
47
+ shipper = lib.to_address(payload.shipper)
48
+ recipient = lib.to_address(payload.recipient)
49
+ customs = lib.to_customs_info(payload.customs)
50
+ commodities = lib.to_commodities(customs.commodities or [])
51
+
52
+ request = teleship.DutiesTaxesRequestType(
53
+ shipTo=teleship.ShipType(
54
+ name=recipient.person_name,
55
+ email=recipient.email,
56
+ phone=recipient.phone_number,
57
+ company=recipient.company_name,
58
+ address=teleship.AddressType(
59
+ line1=recipient.address_line1,
60
+ line2=recipient.address_line2,
61
+ city=recipient.city,
62
+ state=recipient.state_code,
63
+ postcode=recipient.postal_code,
64
+ country=recipient.country_code,
65
+ ),
66
+ ),
67
+ shipFrom=teleship.ShipType(
68
+ name=shipper.person_name,
69
+ email=shipper.email,
70
+ phone=shipper.phone_number,
71
+ company=shipper.company_name,
72
+ address=teleship.AddressType(
73
+ line1=shipper.address_line1,
74
+ line2=shipper.address_line2,
75
+ city=shipper.city,
76
+ state=shipper.state_code,
77
+ postcode=shipper.postal_code,
78
+ country=shipper.country_code,
79
+ ),
80
+ ),
81
+ commodities=[
82
+ teleship.CommodityType(
83
+ quantity=commodity.quantity,
84
+ title=commodity.title or commodities.description,
85
+ description=lib.identity(
86
+ commodity.description if commodity.title else None
87
+ ),
88
+ hsCode=commodity.hs_code,
89
+ sku=commodity.sku,
90
+ value=teleship.ConsigneeChargesType(
91
+ amount=commodity.value_amount,
92
+ currency=commodity.value_currency,
93
+ ),
94
+ unitWeight=teleship.UnitWeightType(
95
+ unit=commodity.weight_unit or "kg",
96
+ value=commodity.weight,
97
+ ),
98
+ countryOfOrigin=commodity.origin_country,
99
+ category=commodity.category,
100
+ )
101
+ for commodity in commodities
102
+ ],
103
+ customs=teleship.CustomsType(
104
+ contentType=customs.content_type or "CommercialGoods",
105
+ incoterms=lib.failsafe(lambda: customs.options.incoterms.state),
106
+ invoiceNumber=customs.invoice,
107
+ invoiceDate=customs.invoice_date,
108
+ EORI=lib.failsafe(lambda: customs.options.eori_number.state),
109
+ VAT=lib.failsafe(lambda: customs.options.vat.state),
110
+ ),
111
+ currency=payload.options.get("currency") if payload.options else None,
112
+ orderTrackingReference=payload.reference,
113
+ )
114
+
115
+ return lib.Serializable(request, lib.to_dict)
@@ -0,0 +1,5 @@
1
+ from karrio.providers.teleship.hooks.event import on_webhook_event
2
+ from karrio.providers.teleship.hooks.oauth import (
3
+ on_oauth_authorize,
4
+ on_oauth_callback,
5
+ )