@@ -2,10 +2,6 @@ import type {
22 CartSetAnonymousIdAction ,
33 CartSetCustomerIdAction ,
44 CartUpdateAction ,
5- CentPrecisionMoney ,
6- InvalidOperationError ,
7- MissingTaxRateForCountryError ,
8- ShippingMethodDoesNotMatchCartError ,
95} from "@commercetools/platform-sdk" ;
106import type {
117 Address ,
@@ -48,29 +44,23 @@ import type {
4844import type {
4945 CustomLineItem ,
5046 DirectDiscount ,
51- TaxPortion ,
52- TaxedItemPrice ,
5347} from "@commercetools/platform-sdk/dist/declarations/src/generated/models/cart" ;
5448import type { ShippingMethodResourceIdentifier } from "@commercetools/platform-sdk/dist/declarations/src/generated/models/shipping-method" ;
55- import { Decimal } from "decimal.js/decimal" ;
5649import { v4 as uuidv4 } from "uuid" ;
5750import { CommercetoolsError } from "~src/exceptions" ;
58- import { getShippingMethodsMatchingCart } from "~src/shipping" ;
5951import type { Writable } from "~src/types" ;
52+ import type { CartRepository } from "." ;
6053import type { UpdateHandlerInterface } from "../abstract" ;
6154import { AbstractUpdateHandler , type RepositoryContext } from "../abstract" ;
6255import {
6356 createAddress ,
6457 createCentPrecisionMoney ,
6558 createCustomFields ,
6659 createTypedMoney ,
67- getReferenceFromResourceIdentifier ,
68- roundDecimal ,
6960} from "../helpers" ;
7061import {
7162 calculateCartTotalPrice ,
7263 calculateLineItemTotalPrice ,
73- calculateTaxedPrice ,
7464 createCustomLineItemFromDraft ,
7565 selectPrice ,
7666} from "./helpers" ;
@@ -79,6 +69,12 @@ export class CartUpdateHandler
7969 extends AbstractUpdateHandler
8070 implements Partial < UpdateHandlerInterface < Cart , CartUpdateAction > >
8171{
72+ private repository : CartRepository ;
73+
74+ constructor ( storage : any , repository : CartRepository ) {
75+ super ( storage ) ;
76+ this . repository = repository ;
77+ }
8278 addItemShippingAddress (
8379 context : RepositoryContext ,
8480 resource : Writable < Cart > ,
@@ -769,166 +765,11 @@ export class CartUpdateHandler
769765 { shippingMethod } : CartSetShippingMethodAction ,
770766 ) {
771767 if ( shippingMethod ) {
772- if ( resource . taxMode === "External" ) {
773- throw new Error ( "External tax rate is not supported" ) ;
774- }
775-
776- const country = resource . shippingAddress ?. country ;
777-
778- if ( ! country ) {
779- throw new CommercetoolsError < InvalidOperationError > ( {
780- code : "InvalidOperation" ,
781- message : `The cart with ID '${ resource . id } ' does not have a shipping address set.` ,
782- } ) ;
783- }
784-
785- // Bit of a hack: calling this checks that the resource identifier is
786- // valid (i.e. id xor key) and that the shipping method exists.
787- this . _storage . getByResourceIdentifier < "shipping-method" > (
788- context . projectKey ,
789- shippingMethod ,
790- ) ;
791-
792- // getShippingMethodsMatchingCart does the work of determining whether the
793- // shipping method is allowed for the cart, and which shipping rate to use
794- const shippingMethods = getShippingMethodsMatchingCart (
768+ resource . shippingInfo = this . repository . createShippingInfo (
795769 context ,
796- this . _storage ,
797770 resource ,
798- {
799- expand : [ "zoneRates[*].zone" ] ,
800- } ,
801- ) ;
802-
803- const method = shippingMethods . results . find ( ( candidate ) =>
804- shippingMethod . id
805- ? candidate . id === shippingMethod . id
806- : candidate . key === shippingMethod . key ,
807- ) ;
808-
809- // Not finding the method in the results means it's not allowed, since
810- // getShippingMethodsMatchingCart only returns allowed methods and we
811- // already checked that the method exists.
812- if ( ! method ) {
813- throw new CommercetoolsError < ShippingMethodDoesNotMatchCartError > ( {
814- code : "ShippingMethodDoesNotMatchCart" ,
815- message : `The shipping method with ${ shippingMethod . id ? `ID '${ shippingMethod . id } '` : `key '${ shippingMethod . key } '` } is not allowed for the cart with ID '${ resource . id } '.` ,
816- } ) ;
817- }
818-
819- const taxCategory = this . _storage . getByResourceIdentifier < "tax-category" > (
820- context . projectKey ,
821- method . taxCategory ,
822- ) ;
823-
824- // TODO: match state in addition to country
825- const taxRate = taxCategory . rates . find (
826- ( rate ) => rate . country === country ,
827- ) ;
828-
829- if ( ! taxRate ) {
830- throw new CommercetoolsError < MissingTaxRateForCountryError > ( {
831- code : "MissingTaxRateForCountry" ,
832- message : `Tax category '${ taxCategory . id } ' is missing a tax rate for country '${ country } '.` ,
833- taxCategoryId : taxCategory . id ,
834- } ) ;
835- }
836-
837- // There should only be one zone rate matching the address, since
838- // Locations cannot be assigned to more than one zone.
839- // See https://docs.commercetools.com/api/projects/zones#location
840- const zoneRate = method . zoneRates . find ( ( rate ) =>
841- rate . zone . obj ?. locations . some ( ( loc ) => loc . country === country ) ,
842- ) ;
843-
844- if ( ! zoneRate ) {
845- // This shouldn't happen because getShippingMethodsMatchingCart already
846- // filtered out shipping methods without any zones matching the address
847- throw new Error ( "Zone rate not found" ) ;
848- }
849-
850- // Shipping rates are defined by currency, and getShippingMethodsMatchingCart
851- // also matches on currency, so there should only be one in the array.
852- // See https://docs.commercetools.com/api/projects/shippingMethods#zonerate
853- const shippingRate = zoneRate . shippingRates [ 0 ] ;
854- if ( ! shippingRate ) {
855- // This shouldn't happen because getShippingMethodsMatchingCart already
856- // filtered out shipping methods without any matching rates
857- throw new Error ( "Shipping rate not found" ) ;
858- }
859-
860- const shippingRateTier = shippingRate . tiers . find (
861- ( tier ) => tier . isMatching ,
771+ shippingMethod ,
862772 ) ;
863- if ( shippingRateTier && shippingRateTier . type !== "CartValue" ) {
864- throw new Error ( "Non-CartValue shipping rate tier is not supported" ) ;
865- }
866-
867- const shippingPrice = shippingRateTier
868- ? createCentPrecisionMoney ( shippingRateTier . price )
869- : shippingRate . price ;
870-
871- // TODO: handle freeAbove
872-
873- const totalGross : CentPrecisionMoney = taxRate . includedInPrice
874- ? shippingPrice
875- : {
876- ...shippingPrice ,
877- centAmount : roundDecimal (
878- new Decimal ( shippingPrice . centAmount ) . mul ( 1 + taxRate . amount ) ,
879- resource . taxRoundingMode ,
880- ) . toNumber ( ) ,
881- } ;
882-
883- const totalNet : CentPrecisionMoney = taxRate . includedInPrice
884- ? {
885- ...shippingPrice ,
886- centAmount : roundDecimal (
887- new Decimal ( shippingPrice . centAmount ) . div ( 1 + taxRate . amount ) ,
888- resource . taxRoundingMode ,
889- ) . toNumber ( ) ,
890- }
891- : shippingPrice ;
892-
893- const taxPortions : TaxPortion [ ] = [
894- {
895- name : taxRate . name ,
896- rate : taxRate . amount ,
897- amount : {
898- ...shippingPrice ,
899- centAmount : totalGross . centAmount - totalNet . centAmount ,
900- } ,
901- } ,
902- ] ;
903-
904- const totalTax : CentPrecisionMoney = {
905- ...shippingPrice ,
906- centAmount : taxPortions . reduce (
907- ( acc , portion ) => acc + portion . amount . centAmount ,
908- 0 ,
909- ) ,
910- } ;
911-
912- const taxedPrice : TaxedItemPrice = {
913- totalNet,
914- totalGross,
915- taxPortions,
916- totalTax,
917- } ;
918-
919- // @ts -ignore
920- resource . shippingInfo = {
921- shippingMethod : {
922- typeId : "shipping-method" ,
923- id : method . id ,
924- } ,
925- shippingMethodName : method . name ,
926- price : shippingPrice ,
927- shippingRate,
928- taxedPrice,
929- taxRate,
930- taxCategory : method . taxCategory ,
931- } ;
932773 } else {
933774 resource . shippingInfo = undefined ;
934775 }
0 commit comments