Skip to content

Commit 11b318d

Browse files
authored
Merge pull request #910 from karrioapi/karrio-release-2025.5
[release] karrio 2025.5
2 parents 0817f9d + 941d9eb commit 11b318d

File tree

210 files changed

+13501
-3882
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

210 files changed

+13501
-3882
lines changed

CARRIER_INTEGRATION_GUIDE.md

Lines changed: 281 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ Use the `sdk add-extension` command to generate the complete directory structure
7373
--confirm
7474
```
7575

76-
**Available Features**: `address`, `document`, `manifest`, `pickup`, `rating`, `shipping`, `tracking`
76+
**Available Features**: `address`, `callback`, `document`, `duties`, `insurance`, `manifest`, `pickup`, `rating`, `shipping`, `tracking`, `webhook`
7777
**Use**: `--no-is-xml-api` for JSON API and `--is-xml-api` for XML APIs
7878
**Try Help if you encounter an error**: `./bin/cli sdk add-extension --help` (at the root of the project and make sure the env var has been activated.)
7979

@@ -821,6 +821,286 @@ def shipment_request(
821821
return lib.Serializable(request, lib.to_dict) # Use lib.to_xml for XML APIs
822822
```
823823
824+
#### Manifest Implementation
825+
826+
**File**: `karrio/providers/[carrier_name]/manifest.py`
827+
828+
```python
829+
"""Karrio [Carrier Name] manifest creation implementation."""
830+
831+
import typing
832+
import karrio.lib as lib
833+
import karrio.core.models as models
834+
import karrio.providers.[carrier_name].error as error
835+
import karrio.providers.[carrier_name].utils as provider_utils
836+
import karrio.schemas.[carrier_name].manifest_request as [carrier_name]_req
837+
import karrio.schemas.[carrier_name].manifest_response as [carrier_name]_res
838+
839+
def parse_manifest_response(
840+
_response: lib.Deserializable[dict],
841+
settings: provider_utils.Settings,
842+
) -> typing.Tuple[models.ManifestDetails, typing.List[models.Message]]:
843+
response = _response.deserialize()
844+
messages = error.parse_error_response(response, settings)
845+
manifest = _extract_details(response, settings) if not messages else None
846+
847+
return manifest, messages
848+
849+
def _extract_details(
850+
data: dict,
851+
settings: provider_utils.Settings,
852+
) -> models.ManifestDetails:
853+
manifest = lib.to_object([carrier_name]_res.ManifestResponseType, data)
854+
855+
return models.ManifestDetails(
856+
carrier_id=settings.carrier_id,
857+
carrier_name=settings.carrier_name,
858+
manifest_id=manifest.manifestId if hasattr(manifest, 'manifestId') else "",
859+
doc=models.ManifestDocument(manifest=manifest.manifestData) if hasattr(manifest, 'manifestData') else None,
860+
meta=dict(
861+
status=manifest.status if hasattr(manifest, 'status') else "",
862+
),
863+
) if manifest else None
864+
865+
def manifest_request(
866+
payload: models.ManifestRequest,
867+
settings: provider_utils.Settings,
868+
) -> lib.Serializable:
869+
request = [carrier_name]_req.ManifestRequestType(
870+
accountNumber=settings.account_number,
871+
shipments=[
872+
{"trackingNumber": identifier}
873+
for identifier in payload.shipment_identifiers
874+
],
875+
)
876+
877+
return lib.Serializable(request, lib.to_dict)
878+
```
879+
880+
#### Duties & Taxes Implementation
881+
882+
**File**: `karrio/providers/[carrier_name]/duties.py`
883+
884+
```python
885+
"""Karrio [Carrier Name] duties and taxes calculation implementation."""
886+
887+
import typing
888+
import karrio.lib as lib
889+
import karrio.core.models as models
890+
import karrio.providers.[carrier_name].error as error
891+
import karrio.providers.[carrier_name].utils as provider_utils
892+
import karrio.schemas.[carrier_name].duties_taxes_request as [carrier_name]_req
893+
import karrio.schemas.[carrier_name].duties_taxes_response as [carrier_name]_res
894+
895+
def parse_duties_response(
896+
_response: lib.Deserializable[dict],
897+
settings: provider_utils.Settings,
898+
) -> typing.Tuple[models.DutiesDetails, typing.List[models.Message]]:
899+
response = _response.deserialize()
900+
messages = error.parse_error_response(response, settings)
901+
duties = _extract_details(response, settings) if not messages else None
902+
903+
return duties, messages
904+
905+
def _extract_details(
906+
data: dict,
907+
settings: provider_utils.Settings,
908+
) -> models.DutiesDetails:
909+
duties = lib.to_object([carrier_name]_res.DutiesTaxesResponseType, data)
910+
911+
return models.DutiesDetails(
912+
carrier_id=settings.carrier_id,
913+
carrier_name=settings.carrier_name,
914+
duties=lib.to_money(duties.dutiesAmount) if hasattr(duties, 'dutiesAmount') else 0,
915+
taxes=lib.to_money(duties.taxesAmount) if hasattr(duties, 'taxesAmount') else 0,
916+
total=lib.to_money(duties.totalAmount) if hasattr(duties, 'totalAmount') else 0,
917+
currency=duties.currency if hasattr(duties, 'currency') else "USD",
918+
meta=dict(
919+
breakdown=duties.breakdown if hasattr(duties, 'breakdown') else {},
920+
),
921+
) if duties else None
922+
923+
def duties_request(
924+
payload: models.DutiesRequest,
925+
settings: provider_utils.Settings,
926+
) -> lib.Serializable:
927+
shipper = lib.to_address(payload.shipper)
928+
recipient = lib.to_address(payload.recipient)
929+
930+
request = [carrier_name]_req.DutiesTaxesRequestType(
931+
originCountry=shipper.country_code,
932+
destinationCountry=recipient.country_code,
933+
items=[
934+
{
935+
"description": item.description,
936+
"quantity": item.quantity,
937+
"value": item.value,
938+
"hsCode": item.hs_code,
939+
}
940+
for item in (payload.commodities or [])
941+
],
942+
)
943+
944+
return lib.Serializable(request, lib.to_dict)
945+
```
946+
947+
#### Insurance Implementation
948+
949+
**File**: `karrio/providers/[carrier_name]/insurance.py`
950+
951+
```python
952+
"""Karrio [Carrier Name] insurance application implementation."""
953+
954+
import typing
955+
import karrio.lib as lib
956+
import karrio.core.models as models
957+
import karrio.providers.[carrier_name].error as error
958+
import karrio.providers.[carrier_name].utils as provider_utils
959+
import karrio.schemas.[carrier_name].insurance_request as [carrier_name]_req
960+
import karrio.schemas.[carrier_name].insurance_response as [carrier_name]_res
961+
962+
def parse_insurance_response(
963+
_response: lib.Deserializable[dict],
964+
settings: provider_utils.Settings,
965+
) -> typing.Tuple[models.InsuranceDetails, typing.List[models.Message]]:
966+
response = _response.deserialize()
967+
messages = error.parse_error_response(response, settings)
968+
insurance = _extract_details(response, settings) if not messages else None
969+
970+
return insurance, messages
971+
972+
def _extract_details(
973+
data: dict,
974+
settings: provider_utils.Settings,
975+
) -> models.InsuranceDetails:
976+
insurance = lib.to_object([carrier_name]_res.InsuranceResponseType, data)
977+
978+
return models.InsuranceDetails(
979+
carrier_id=settings.carrier_id,
980+
carrier_name=settings.carrier_name,
981+
insurance_id=insurance.policyId if hasattr(insurance, 'policyId') else "",
982+
premium=lib.to_money(insurance.premium) if hasattr(insurance, 'premium') else 0,
983+
coverage=lib.to_money(insurance.coverage) if hasattr(insurance, 'coverage') else 0,
984+
currency=insurance.currency if hasattr(insurance, 'currency') else "USD",
985+
meta=dict(
986+
policy_number=insurance.policyNumber if hasattr(insurance, 'policyNumber') else "",
987+
),
988+
) if insurance else None
989+
990+
def insurance_request(
991+
payload: models.InsuranceRequest,
992+
settings: provider_utils.Settings,
993+
) -> lib.Serializable:
994+
request = [carrier_name]_req.InsuranceRequestType(
995+
shipmentId=payload.shipment_identifier,
996+
coverage=payload.coverage_amount,
997+
currency=payload.currency or "USD",
998+
)
999+
1000+
return lib.Serializable(request, lib.to_dict)
1001+
```
1002+
1003+
#### Webhook Registration Implementation
1004+
1005+
**File**: `karrio/providers/[carrier_name]/webhook/register.py`
1006+
1007+
```python
1008+
"""Karrio [Carrier Name] webhook registration implementation."""
1009+
1010+
import typing
1011+
import karrio.lib as lib
1012+
import karrio.core.models as models
1013+
import karrio.providers.[carrier_name].error as error
1014+
import karrio.providers.[carrier_name].utils as provider_utils
1015+
import karrio.schemas.[carrier_name].webhook_request as [carrier_name]_req
1016+
import karrio.schemas.[carrier_name].webhook_response as [carrier_name]_res
1017+
1018+
def parse_webhook_registration_response(
1019+
_response: lib.Deserializable[dict],
1020+
settings: provider_utils.Settings,
1021+
) -> typing.Tuple[models.WebhookDetails, typing.List[models.Message]]:
1022+
response = _response.deserialize()
1023+
messages = error.parse_error_response(response, settings)
1024+
webhook = _extract_details(response, settings) if not messages else None
1025+
1026+
return webhook, messages
1027+
1028+
def _extract_details(
1029+
data: dict,
1030+
settings: provider_utils.Settings,
1031+
) -> models.WebhookDetails:
1032+
webhook = lib.to_object([carrier_name]_res.WebhookResponseType, data)
1033+
1034+
return models.WebhookDetails(
1035+
carrier_id=settings.carrier_id,
1036+
carrier_name=settings.carrier_name,
1037+
webhook_id=webhook.webhookId if hasattr(webhook, 'webhookId') else "",
1038+
url=webhook.url if hasattr(webhook, 'url') else "",
1039+
events=webhook.events if hasattr(webhook, 'events') else [],
1040+
meta=dict(
1041+
status=webhook.status if hasattr(webhook, 'status') else "",
1042+
),
1043+
) if webhook else None
1044+
1045+
def webhook_registration_request(
1046+
payload: models.WebhookRegistrationRequest,
1047+
settings: provider_utils.Settings,
1048+
) -> lib.Serializable:
1049+
request = [carrier_name]_req.WebhookRequestType(
1050+
url=payload.url,
1051+
events=payload.events or ["shipment.created", "shipment.delivered"],
1052+
)
1053+
1054+
return lib.Serializable(request, lib.to_dict)
1055+
```
1056+
1057+
#### Callback/OAuth Implementation (for carriers with OAuth flows)
1058+
1059+
**File**: `karrio/providers/[carrier_name]/callback/oauth.py`
1060+
1061+
```python
1062+
"""Karrio [Carrier Name] OAuth callback implementation."""
1063+
1064+
import typing
1065+
import karrio.lib as lib
1066+
import karrio.core.models as models
1067+
import karrio.providers.[carrier_name].error as error
1068+
import karrio.providers.[carrier_name].utils as provider_utils
1069+
1070+
def parse_oauth_callback_response(
1071+
_response: lib.Deserializable[dict],
1072+
settings: provider_utils.Settings,
1073+
) -> typing.Tuple[models.OAuthDetails, typing.List[models.Message]]:
1074+
response = _response.deserialize()
1075+
messages = error.parse_error_response(response, settings)
1076+
1077+
oauth = models.OAuthDetails(
1078+
carrier_id=settings.carrier_id,
1079+
carrier_name=settings.carrier_name,
1080+
access_token=response.get("access_token"),
1081+
refresh_token=response.get("refresh_token"),
1082+
expires_in=response.get("expires_in"),
1083+
token_type=response.get("token_type", "Bearer"),
1084+
) if not messages else None
1085+
1086+
return oauth, messages
1087+
1088+
def oauth_callback_request(
1089+
payload: models.OAuthCallbackRequest,
1090+
settings: provider_utils.Settings,
1091+
) -> lib.Serializable:
1092+
# Handle OAuth code exchange
1093+
request = dict(
1094+
grant_type="authorization_code",
1095+
code=payload.code,
1096+
redirect_uri=payload.redirect_uri,
1097+
client_id=settings.client_id,
1098+
client_secret=settings.client_secret,
1099+
)
1100+
1101+
return lib.Serializable(request, lib.to_dict)
1102+
```
1103+
8241104
### Step 12: Configure Plugin Metadata
8251105
8261106
For **Direct Carriers**, edit:

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
1+
# Karrio 2025.5rc46
2+
3+
## Changes
4+
5+
### Feat
6+
7+
- feat: add karrio hooks interface
8+
- feat: add karrio duties interface
9+
- feat: add karrio insurance interface
10+
- feat: add karrio webhook interface
11+
- feat: add standardized auth proxy interface
12+
- feat: add default service csv file for dhl parcel and rollback packet International service
13+
14+
### Docs
15+
16+
- docs: karrio plugins development guide
17+
18+
### Tests
19+
20+
- tests: consolidate new interface integrations with tests
21+
22+
### Chores
23+
24+
- chore: update website with karrio embed page
25+
- devX: add drf type stub for better IDE type suggestions
26+
127
# Karrio 2025.5rc45
228

329
## Changes

apps/api/karrio/server/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2025.5rc35
1+
2025.5rc36

0 commit comments

Comments
 (0)