@@ -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
8261106For ** Direct Carriers** , edit:
0 commit comments