From 16c2ceb43fb3881cf16eee61b6dd70cf1ad206ec Mon Sep 17 00:00:00 2001 From: Anil Purohit Date: Mon, 17 Mar 2025 12:19:11 +0530 Subject: [PATCH 1/7] @W-17966639 B2B DelvieryService apex code changes --- .../classes/B2BDeliverySample.cls | 337 +++++++++++++----- 1 file changed, 254 insertions(+), 83 deletions(-) diff --git a/examples/b2b/checkout/integrations/classes/B2BDeliverySample.cls b/examples/b2b/checkout/integrations/classes/B2BDeliverySample.cls index 9775648e..1ce5507c 100644 --- a/examples/b2b/checkout/integrations/classes/B2BDeliverySample.cls +++ b/examples/b2b/checkout/integrations/classes/B2BDeliverySample.cls @@ -5,14 +5,17 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin // and add the host in Setup | Security | Remote site settings. private static String httpHost = 'https://example.com'; private static Boolean useHTTPService = false; + private String className = String.valueOf(this).split(':')[0]; + ApexClass apexClass = [SELECT ApiVersion FROM ApexClass WHERE Name = :className]; global sfdc_checkout.IntegrationStatus startCartProcessAsync(sfdc_checkout.IntegrationInfo jobInfo, Id cartId) { sfdc_checkout.IntegrationStatus integStatus = new sfdc_checkout.IntegrationStatus(); try { + String siteLanguage = jobInfo.siteLanguage; // We need to get the ID of the cart delivery group in order to create the order delivery groups. - Id cartDeliveryGroupId = [SELECT Id FROM CartDeliveryGroup WHERE CartId = :cartId WITH SECURITY_ENFORCED][0].Id; - + Id cartDeliveryGroupId = [SELECT Id FROM CartDeliveryGroup WHERE CartId = :cartId][0].Id; + // Used to increase the cost by a multiple of the number of items in the cart (useful for testing but should not be done in the final code) - Integer numberOfUniqueItems = [SELECT count() from cartItem WHERE CartId = :cartId WITH SECURITY_ENFORCED]; + Integer numberOfUniqueItems = [SELECT count() from cartItem WHERE CartId = :cartId]; // Following snippet of code fetches a static json response with 2 mocked sample shipping methods. // Another example that demonstrates how to call a live 3rd party HTTP Service to fetch the desired response is implemented @@ -23,25 +26,47 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin if(useHTTPService) { shippingOptionsAndRatesFromExternalService = getShippingOptionsAndRatesFromExternalService(numberOfUniqueItems); } else { - shippingOptionsAndRatesFromExternalService = getShippingOptionsAndRatesFromMockedService(numberOfUniqueItems); + shippingOptionsAndRatesFromExternalService = getShippingOptionsAndRatesFromMockedService(siteLanguage); } // On re-entry of the checkout flow delete all previous CartDeliveryGroupMethods for the given cartDeliveryGroupId - delete [SELECT Id FROM CartDeliveryGroupMethod WHERE CartDeliveryGroupId = :cartDeliveryGroupId WITH SECURITY_ENFORCED]; + //delete [SELECT Id FROM CartDeliveryGroupMethod WHERE CartDeliveryGroupId = :cartDeliveryGroupId]; // Create orderDeliveryMethods given your shipping options or fetch existing ones. 2 should be returned. List orderDeliveryMethodIds = getOrderDeliveryMethods(shippingOptionsAndRatesFromExternalService); + // We need to get the ID of the cart delivery group in order to create the cart delivery group methods. + List cartDeliveryGroups = new List([SELECT Id FROM CartDeliveryGroup WHERE CartId = :cartId]); + + System.debug('all options '+ shippingOptionsAndRatesFromExternalService); // Create a CartDeliveryGroupMethod record for every shipping option returned from the external service Integer i = 0; - for (Id orderDeliveryMethodId: orderDeliveryMethodIds) { - populateCartDeliveryGroupMethodWithShippingOptions(shippingOptionsAndRatesFromExternalService[i], + for(CartDeliveryGroup curCartDeliveryGroup : cartDeliveryGroups){ + // get selected Delivery method id for cart delivery groupId + ID previousSelectDeliveryMethodId = [SELECT SelectedDeliveryMethodId FROM CartDeliveryGroup WHERE Id = :curCartDeliveryGroup.Id][0].SelectedDeliveryMethodId; + + if(previousSelectDeliveryMethodId != null) { + System.debug('previousselecteddeliverymerhodId '+previousSelectDeliveryMethodId); + // delete all the cart delivery group method id except selected one + delete [SELECT Id FROM CartDeliveryGroupMethod WHERE CartDeliveryGroupId = :curCartDeliveryGroup.Id and Id!= :previousSelectDeliveryMethodId]; + } else { + System.debug('previousselecteddeliverymerhodId is null'); + delete [SELECT Id FROM CartDeliveryGroupMethod WHERE CartDeliveryGroupId = :curCartDeliveryGroup.Id]; + } + System.debug('orderDeliveryMethodIds '+orderDeliveryMethodIds); + for (Id orderDeliveryMethodId: orderDeliveryMethodIds) { + + if(!isShippingOptionMatchingWithSelectedDM(shippingOptionsAndRatesFromExternalService[i],previousSelectDeliveryMethodId)){ + System.debug('populating '); + populateCartDeliveryGroupMethodWithShippingOptions(shippingOptionsAndRatesFromExternalService[i], cartDeliveryGroupId, orderDeliveryMethodId, cartId); - i+=1; + } + i+=1; + } } - + // If everything works well, the charge is added to the cart and our integration has been successfully completed. integStatus.status = sfdc_checkout.IntegrationStatus.Status.SUCCESS; @@ -75,36 +100,106 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin } return integStatus; } +/** + This method compares previous Selected Delivery method with current shipping options and if both matches returns ture + */ + private boolean isShippingOptionMatchingWithSelectedDM(ShippingOptionsAndRatesFromExternalService shippingOption, String previousSelectDeliveryMethodId) { + if(previousSelectDeliveryMethodId != null && !previousSelectDeliveryMethodId.equals('')) { + // get delivery group method for seletctedDMId + CartDeliveryGroupMethod previousSelectDeliveryMethod = [SELECT Name, ShippingFee, WebCartId, Carrier, ClassOfService, ExternalProvider, ProductId, ReferenceNumber, IsActive, TransitTimeMin, TransitTimeMax, TransitTimeUnit, ProcessTime, ProcessTimeUnit FROM CartDeliveryGroupMethod WHERE Id= :previousSelectDeliveryMethodId]; + System.debug('previousSelectDeliveryMethod ' + previousSelectDeliveryMethod); + System.debug('shippingOption ' + shippingOption); + // return if all fields of shipping option matches with selectedDM else return false + return (previousSelectDeliveryMethod.Name.equals(shippingOption.getName()) && + previousSelectDeliveryMethod.IsActive.equals(shippingOption.isActive()) && + + previousSelectDeliveryMethod.ShippingFee.equals(shippingOption.getRate()) && + + isNullOrEquals(previousSelectDeliveryMethod.ProcessTime, shippingOption.getProcessTime()) && + isNullOrEquals(previousSelectDeliveryMethod.ProcessTimeUnit, shippingOption.getProcessTimeUnit()) && + + // ideally reference number should match but in this sample we are generating random string so won't match + //previousSelectDeliveryMethod.ReferenceNumber.equals(shippingOption.getReferenceNumber()) && + previousSelectDeliveryMethod.Carrier.equals(shippingOption.getCarrier()) && + previousSelectDeliveryMethod.ClassOfService.equals(shippingOption.getClassOfService()) && + previousSelectDeliveryMethod.ExternalProvider.equals(shippingOption.getProvider()) && + previousSelectDeliveryMethod.Carrier.equals(shippingOption.getCarrier()) && + + isNullOrEquals(previousSelectDeliveryMethod.TransitTimeMax, shippingOption.getTransitTimeMax()) && + isNullOrEquals(previousSelectDeliveryMethod.TransitTimeMin, shippingOption.getTransitTimeMin()) && + isNullOrEquals(previousSelectDeliveryMethod.TransitTimeUnit, shippingOption.getTransitTimeUnit())); + } + return false; + } + /** + This method compares two objects, if both are null or equals returns true + */ + private boolean isNullOrEquals(Object o1, Object o2) { + return (o1 == null && o2 == null) || (o1 != null && o1.equals(o2)); + } /** This method provides an alternative to retrieve Shipping Options if http call needs to be bypassed. This method uses a hardcoded sample response and MUST not be used in production systems. */ - private ShippingOptionsAndRatesFromExternalService[] getShippingOptionsAndRatesFromMockedService (Integer numberOfUniqueItems) { - ShippingOptionsAndRatesFromExternalService[] shippingOptions = new List(); - String responseBody = getShippingOptionsResponse(); - List results = (List) JSON.deserializeUntyped(responseBody); - for (Object result: results) { - Map subresult = (Map) result; - Map providerAndRate = (Map) subresult.get('rate'); - shippingOptions.add( new ShippingOptionsAndRatesFromExternalService( - (String) providerAndRate.get('name'), - (String) providerAndRate.get('serviceCode'), - (Decimal) providerAndRate.get('shipmentCost') * numberOfUniqueItems, - (Decimal) providerAndRate.get('otherCost'), - (String) providerAndRate.get('serviceName') - )); + private ShippingOptionsAndRatesFromExternalService[] getShippingOptionsAndRatesFromMockedService (String siteLanguage) { + ShippingOptionsAndRatesFromExternalService[] shippingOptions = new List(); + String responseBody = getShippingOptionsResponse(siteLanguage); + List results = (List) JSON.deserializeUntyped(responseBody); + for (Object result: results) { + Map subresult = (Map) result; + Map providerAndRate = (Map) subresult.get('rate'); + shippingOptions.add(new ShippingOptionsAndRatesFromExternalService( + (String) providerAndRate.get('name'), + (String) providerAndRate.get('serviceCode'), + (Decimal) providerAndRate.get('shipmentCost'), + (Decimal) providerAndRate.get('otherCost'), + (String) providerAndRate.get('serviceName'), + (String) providerAndRate.get('serviceName'), + (String) providerAndRate.get('serviceCode'), + generateRandomString(10), + true, + (Integer) providerAndRate.get('transitTimeMin'), + (Integer) providerAndRate.get('transitTimeMax'), + (String) providerAndRate.get('transitTimeUnit'), + (Integer) providerAndRate.get('processTime'), + (String) providerAndRate.get('processTimeUnit') + )); + } + return shippingOptions; } - return shippingOptions; - } + private static String generateRandomString(Integer len) { + final String chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz'; + String randStr = ''; + while (randStr.length() < len) { + Integer idx = Math.mod(Math.abs(Crypto.getRandomInteger()), chars.length()); + randStr += chars.substring(idx, idx+1); + } + return randStr; + } + private String getShippingOptionsResponse(String siteLanguage) { + String name1, name2, serviceName1, serviceName2; + Integer transitTimeMin, transitTimeMax, processTime; + String transitTimeUnit, processTimeUnit; + if(siteLanguage == 'de') { + name1 = 'Liefermethode 1'; + name2 = 'Liefermethode 2'; + serviceName1 = 'Testträger 1'; + serviceName2 = 'Testträger 2'; + } else if(siteLanguage == 'ja') { + name1 = '配送方法1'; + name2 = '配送方法2'; + serviceName1 = 'テストキャリア1'; + serviceName2 = 'テストキャリア2'; + } else { + name1 = 'Delivery Method 1'; + name2 = 'Delivery Method 2'; + serviceName1 = 'Test Carrier 1'; + serviceName2 = 'Test Carrier 2'; + } - private String getShippingOptionsResponse() { - String name1 = 'Delivery Method 1'; - String name2 = 'Delivery Method 2'; - String serviceName1 = 'Test Carrier 1'; - String serviceName2 = 'Test Carrier 2'; - return '[{"status":"calculated","rate":{"name":"'+name1+'","serviceName":"'+serviceName1+'","serviceCode":"SNC9600","shipmentCost":11.99,"otherCost":5.99}},{"status":"calculated","rate":{"name":"'+name2+'","serviceName":"'+serviceName2+'","serviceCode":"SNC9600","shipmentCost":15.99,"otherCost":6.99}}]'; - } + return '[{"status":"calculated","rate":{"name":"'+name1+'","serviceName":"'+serviceName1+'","serviceCode":"SNC9600","shipmentCost":11.99,"otherCost":5.99,"transitTimeMin":1,"transitTimeMax":2,"transitTimeUnit":"Days","processTime":1,"processTimeUnit":"Days"}},{"status":"calculated","rate":{"name":"'+name2+'","serviceName":"'+serviceName2+'","serviceCode":"SNC9600","shipmentCost":15.99,"otherCost":6.99,"transitTimeMin":2,"transitTimeMax":3,"transitTimeUnit":"Days","processTime":1,"processTimeUnit":"Days"}}]'; + } private ShippingOptionsAndRatesFromExternalService[] getShippingOptionsAndRatesFromExternalService (Integer numberOfUniqueItems) { final Integer successfulHttpRequest = 200; @@ -129,9 +224,18 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin shippingOptions.add( new ShippingOptionsAndRatesFromExternalService( (String) providerAndRate.get('name'), (String) providerAndRate.get('serviceCode'), - (Decimal) providerAndRate.get('shipmentCost') * numberOfUniqueItems, // Multiply so shipping costs can change; remove when using a real shipping provider + (Decimal) providerAndRate.get('shipmentCost'), (Decimal) providerAndRate.get('otherCost'), - (String) providerAndRate.get('serviceName') + (String) providerAndRate.get('serviceName'), + (String) providerAndRate.get('serviceName'), + (String) providerAndRate.get('serviceCode'), + generateRandomString(10), + true, + (Integer) providerAndRate.get('transitTimeMin'), + (Integer) providerAndRate.get('transitTimeMax'), + (String) providerAndRate.get('transitTimeUnit'), + (Integer) providerAndRate.get('processTime'), + (String) providerAndRate.get('processTimeUnit') )); } return shippingOptions; @@ -144,61 +248,128 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin // Structure to store the shipping options retrieved from external service. Class ShippingOptionsAndRatesFromExternalService { - private String name; - private String provider; - private Decimal rate; - private Decimal otherCost; - private String serviceName; - - public ShippingOptionsAndRatesFromExternalService(String someName, String someProvider, Decimal someRate, Decimal someOtherCost, String someServiceName) { - name = someName; - provider = someProvider; - rate = someRate; - otherCost = someOtherCost; - serviceName = someServiceName; - } - - public String getProvider() { - return provider; - } + private String name; + private String provider; + private Decimal rate; + private Decimal otherCost; + private String serviceName; + private String carrier; + private String classOfService; + private String referenceNumber; + private Boolean isActive; + private Integer transitTimeMin; + private Integer transitTimeMax; + private String transitTimeUnit; + private Integer processTime; + private String processTimeUnit; - public Decimal getRate() { - return rate; - } - - public Decimal getOtherCost() { - return otherCost; - } + public ShippingOptionsAndRatesFromExternalService() { + name = ''; + provider = ''; + rate = 0.0; + serviceName = ''; + otherCost = 0.0; + carrier = ''; + classOfService = ''; + referenceNumber = ''; + isActive = true; + transitTimeMin = 0; + transitTimeMax = 0; + transitTimeUnit = ''; + processTime = 0; + processTimeUnit = ''; + } - public String getServiceName() { - return serviceName; - } + public ShippingOptionsAndRatesFromExternalService(String someName, String someProvider, Decimal someRate, Decimal someOtherCost, String someServiceName, + String someCarrier, String someClassOfService, String someReferenceNumber, Boolean someIsActive, Integer someTransitTimeMin, Integer someTransitTimeMax, + String someTransitTimeUnit, Integer someProcessTime, String someProcessTimeUnit) { + name = someName; + provider = someProvider; + rate = someRate; + otherCost = someOtherCost; + serviceName = someServiceName; + carrier = someCarrier; + classOfService = someClassOfService; + referenceNumber = someReferenceNumber; + isActive = someIsActive; + transitTimeMin = someTransitTimeMin; + transitTimeMax = someTransitTimeMax; + transitTimeUnit = someTransitTimeUnit; + processTime = someProcessTime; + processTimeUnit = someProcessTimeUnit; + } - public String getName() { - return name; + public String getProvider() { return provider; } + public Decimal getRate() { return rate; } + public Decimal getOtherCost() { return otherCost; } + public String getServiceName() { return serviceName; } + public String getName() { return name; } + public String getCarrier() { return carrier; } + public String getClassOfService() { return classOfService; } + public String getReferenceNumber() { return referenceNumber; } + public Boolean isActive() { return isActive; } + public Integer getTransitTimeMin() { return transitTimeMin; } + public Integer getTransitTimeMax() { return transitTimeMax; } + public String getTransitTimeUnit() { return transitTimeUnit; } + public Integer getProcessTime() { return processTime; } + public String getProcessTimeUnit() { return processTimeUnit; } } - } // Create a CartDeliveryGroupMethod record for every shipping option returned from the external service private void populateCartDeliveryGroupMethodWithShippingOptions(ShippingOptionsAndRatesFromExternalService shippingOption, Id cartDeliveryGroupId, Id deliveryMethodId, Id webCartId){ - // When inserting a new CartDeliveryGroupMethod, the following fields have to be populated: - // CartDeliveryGroupId: Id of the delivery group of this shipping option - // DeliveryMethodId: Id of the delivery method for this shipping option - // ExternalProvider: Unique identifier of shipping provider - // Name: Name of the CartDeliveryGroupMethod record - // ShippingFee: The cost of shipping for the delivery group - // WebCartId: Id if the cart that the delivery group belongs to - CartDeliveryGroupMethod cartDeliveryGroupMethod = new CartDeliveryGroupMethod( - CartDeliveryGroupId = cartDeliveryGroupId, - DeliveryMethodId = deliveryMethodId, - ExternalProvider = shippingOption.getProvider(), - Name = shippingOption.getName(), - ShippingFee = shippingOption.getRate(), - WebCartId = webCartId - ); + // When inserting a new CartDeliveryGroupMethod, the following fields have to be populated: + // CartDeliveryGroupId: Id of the delivery group of this shipping option + // ExternalProvider: Unique identifier of shipping provider + // Name: Name of the CartDeliveryGroupMethod record + // ShippingFee: The cost of shipping for the delivery group + // WebCartId: Id if the cart that the delivery group belongs to + // Carrier: Shipping Carrier e.g. UPS, FedEx etc. + // ClassOfService: Service e.g. 2 Day Ground, Overnight etc. + // Product: Product Id for this Shipping Charge + // ReferenceNumber: Reference Number from External Service + // IsActive: If this Option is Active + + // Below fields are available only for api version >= 61 + + // TransitTimeMin: Minimum Transit Time + // TransitTimeMax: Maximum Transit Time + // TransitTimeUnit: Time Unit for Transit Time (Valid Values are "Hours", "Days", "Weeks") + // ProcessTime: Process Time + // ProcessTimeUnit: Time Unit for Process Time (Valid Values are "Hours", "Days", "Weeks") + + Id productId = getDefaultShippingChargeProduct2Id(); + + CartDeliveryGroupMethod cartDeliveryGroupMethod = new CartDeliveryGroupMethod(); + cartDeliveryGroupMethod.put('CartDeliveryGroupId', cartDeliveryGroupId); + cartDeliveryGroupMethod.put('DeliveryMethodId', deliveryMethodId); + cartDeliveryGroupMethod.put('ExternalProvider', shippingOption.getProvider()); + cartDeliveryGroupMethod.put('Name', shippingOption.getName()); + cartDeliveryGroupMethod.put('ShippingFee', shippingOption.getRate()); + cartDeliveryGroupMethod.put('WebCartId', webCartId); + cartDeliveryGroupMethod.put('Carrier', shippingOption.getCarrier()); + cartDeliveryGroupMethod.put('ClassOfService', shippingOption.getClassOfService()); + //cartDeliveryGroupMethod.put('ProductId', productId); + cartDeliveryGroupMethod.put('ReferenceNumber', shippingOption.getReferenceNumber()); + cartDeliveryGroupMethod.put('IsActive', shippingOption.isActive()); + + if(apexClass.ApiVersion >= 61) { + cartDeliveryGroupMethod.put('TransitTimeMin', shippingOption.getTransitTimeMin()); + cartDeliveryGroupMethod.put('TransitTimeMax', shippingOption.getTransitTimeMax()); + cartDeliveryGroupMethod.put('TransitTimeUnit', shippingOption.getTransitTimeUnit()); + cartDeliveryGroupMethod.put('ProcessTime', shippingOption.getProcessTime()); + cartDeliveryGroupMethod.put('ProcessTimeUnit', shippingOption.getProcessTimeUnit()); + } + + Boolean multiCurrencyEnabled = UserInfo.isMultiCurrencyOrganization(); + if(multiCurrencyEnabled) { + String cartDeliveryGroupQuery = 'SELECT CurrencyIsoCode FROM CartDeliveryGroup WHERE Id = :cartDeliveryGroupId LIMIT 1'; + CartDeliveryGroup cartDeliveryGroup = (CartDeliveryGroup) Database.query(cartDeliveryGroupQuery); + String cartDeliveryGroupMethodCurrency = (String) cartDeliveryGroup.get('CurrencyIsoCode'); + cartDeliveryGroupMethod.put('CurrencyIsoCode', cartDeliveryGroupMethodCurrency); + } insert(cartDeliveryGroupMethod); } @@ -229,7 +400,7 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin private Id getShippingChargeProduct2Id(Id orderDeliveryMethodId) { // The Order Delivery Method should have a Product2 associated with it, because we added that in getDefaultOrderDeliveryMethod if it didn't exist. - List orderDeliveryMethods = [SELECT ProductId FROM OrderDeliveryMethod WHERE Id = :orderDeliveryMethodId WITH SECURITY_ENFORCED]; + List orderDeliveryMethods = [SELECT ProductId FROM OrderDeliveryMethod WHERE Id = :orderDeliveryMethodId]; return orderDeliveryMethods[0].ProductId; } @@ -245,7 +416,7 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin for (ShippingOptionsAndRatesFromExternalService shippingOption: shippingOptions) { String shippingOptionNumber = String.valueOf(i); String name = defaultDeliveryMethodName + shippingOptionNumber; - List odms = [SELECT Id, ProductId, Carrier, ClassOfService FROM OrderDeliveryMethod WHERE Name = :name WITH SECURITY_ENFORCED]; + List odms = [SELECT Id, ProductId, Carrier, ClassOfService FROM OrderDeliveryMethod WHERE Name = :name]; // This is the case in which an Order Delivery method does not exist. if (odms.isEmpty()) { OrderDeliveryMethod defaultOrderDeliveryMethod = new OrderDeliveryMethod( @@ -284,7 +455,7 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin // Check to see if a Product2 with that name already exists. // If it doesn't exist, create one. String shippingChargeProduct2Name = 'Shipping Charge for this delivery method'; - List shippingChargeProducts = [SELECT Id FROM Product2 WHERE Name = :shippingChargeProduct2Name WITH SECURITY_ENFORCED]; + List shippingChargeProducts = [SELECT Id FROM Product2 WHERE Name = :shippingChargeProduct2Name]; if (shippingChargeProducts.isEmpty()) { Product2 shippingChargeProduct = new Product2( isActive = true, @@ -297,4 +468,4 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin return shippingChargeProducts[0].Id; } } -} \ No newline at end of file +} From 9abf3ae0637a9d967fe3441b82cd6402fc9f1576 Mon Sep 17 00:00:00 2001 From: Anil Purohit Date: Mon, 17 Mar 2025 12:26:04 +0530 Subject: [PATCH 2/7] Safe check added --- .../b2b/checkout/integrations/classes/B2BDeliverySample.cls | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/b2b/checkout/integrations/classes/B2BDeliverySample.cls b/examples/b2b/checkout/integrations/classes/B2BDeliverySample.cls index 1ce5507c..5a1d1980 100644 --- a/examples/b2b/checkout/integrations/classes/B2BDeliverySample.cls +++ b/examples/b2b/checkout/integrations/classes/B2BDeliverySample.cls @@ -26,7 +26,7 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin if(useHTTPService) { shippingOptionsAndRatesFromExternalService = getShippingOptionsAndRatesFromExternalService(numberOfUniqueItems); } else { - shippingOptionsAndRatesFromExternalService = getShippingOptionsAndRatesFromMockedService(siteLanguage); + shippingOptionsAndRatesFromExternalService = getShippingOptionsAndRatesFromMockedService(siteLanguage, numberOfUniqueItems); } // On re-entry of the checkout flow delete all previous CartDeliveryGroupMethods for the given cartDeliveryGroupId @@ -142,7 +142,7 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin This method provides an alternative to retrieve Shipping Options if http call needs to be bypassed. This method uses a hardcoded sample response and MUST not be used in production systems. */ - private ShippingOptionsAndRatesFromExternalService[] getShippingOptionsAndRatesFromMockedService (String siteLanguage) { + private ShippingOptionsAndRatesFromExternalService[] getShippingOptionsAndRatesFromMockedService (String siteLanguage, Integer numberOfUniqueItems) { ShippingOptionsAndRatesFromExternalService[] shippingOptions = new List(); String responseBody = getShippingOptionsResponse(siteLanguage); List results = (List) JSON.deserializeUntyped(responseBody); @@ -152,7 +152,7 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin shippingOptions.add(new ShippingOptionsAndRatesFromExternalService( (String) providerAndRate.get('name'), (String) providerAndRate.get('serviceCode'), - (Decimal) providerAndRate.get('shipmentCost'), + (Decimal) providerAndRate.get('shipmentCost') * numberOfUniqueItems, // Multiply so shipping costs can change; remove when using a real shipping provider (Decimal) providerAndRate.get('otherCost'), (String) providerAndRate.get('serviceName'), (String) providerAndRate.get('serviceName'), From b343e931cfdd3a6e0385530dbdf77c499e88320a Mon Sep 17 00:00:00 2001 From: Anil Purohit Date: Mon, 17 Mar 2025 12:49:46 +0530 Subject: [PATCH 3/7] Adding comments --- .../classes/B2BDeliverySample.cls | 43 ++++++++----------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/examples/b2b/checkout/integrations/classes/B2BDeliverySample.cls b/examples/b2b/checkout/integrations/classes/B2BDeliverySample.cls index 5a1d1980..e732d7c8 100644 --- a/examples/b2b/checkout/integrations/classes/B2BDeliverySample.cls +++ b/examples/b2b/checkout/integrations/classes/B2BDeliverySample.cls @@ -29,16 +29,12 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin shippingOptionsAndRatesFromExternalService = getShippingOptionsAndRatesFromMockedService(siteLanguage, numberOfUniqueItems); } - // On re-entry of the checkout flow delete all previous CartDeliveryGroupMethods for the given cartDeliveryGroupId - //delete [SELECT Id FROM CartDeliveryGroupMethod WHERE CartDeliveryGroupId = :cartDeliveryGroupId]; - // Create orderDeliveryMethods given your shipping options or fetch existing ones. 2 should be returned. List orderDeliveryMethodIds = getOrderDeliveryMethods(shippingOptionsAndRatesFromExternalService); // We need to get the ID of the cart delivery group in order to create the cart delivery group methods. List cartDeliveryGroups = new List([SELECT Id FROM CartDeliveryGroup WHERE CartId = :cartId]); - System.debug('all options '+ shippingOptionsAndRatesFromExternalService); // Create a CartDeliveryGroupMethod record for every shipping option returned from the external service Integer i = 0; for(CartDeliveryGroup curCartDeliveryGroup : cartDeliveryGroups){ @@ -46,18 +42,15 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin ID previousSelectDeliveryMethodId = [SELECT SelectedDeliveryMethodId FROM CartDeliveryGroup WHERE Id = :curCartDeliveryGroup.Id][0].SelectedDeliveryMethodId; if(previousSelectDeliveryMethodId != null) { - System.debug('previousselecteddeliverymerhodId '+previousSelectDeliveryMethodId); // delete all the cart delivery group method id except selected one delete [SELECT Id FROM CartDeliveryGroupMethod WHERE CartDeliveryGroupId = :curCartDeliveryGroup.Id and Id!= :previousSelectDeliveryMethodId]; } else { - System.debug('previousselecteddeliverymerhodId is null'); + // if there is no selected Delivery method then we can delete all the existing delivery methods associated with cart delivery group id delete [SELECT Id FROM CartDeliveryGroupMethod WHERE CartDeliveryGroupId = :curCartDeliveryGroup.Id]; } - System.debug('orderDeliveryMethodIds '+orderDeliveryMethodIds); for (Id orderDeliveryMethodId: orderDeliveryMethodIds) { - + // we will populate cart delivery method only when shipping option does not match with previous selected delivery method if(!isShippingOptionMatchingWithSelectedDM(shippingOptionsAndRatesFromExternalService[i],previousSelectDeliveryMethodId)){ - System.debug('populating '); populateCartDeliveryGroupMethodWithShippingOptions(shippingOptionsAndRatesFromExternalService[i], cartDeliveryGroupId, orderDeliveryMethodId, @@ -100,35 +93,33 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin } return integStatus; } -/** + /** This method compares previous Selected Delivery method with current shipping options and if both matches returns ture - */ + */ private boolean isShippingOptionMatchingWithSelectedDM(ShippingOptionsAndRatesFromExternalService shippingOption, String previousSelectDeliveryMethodId) { if(previousSelectDeliveryMethodId != null && !previousSelectDeliveryMethodId.equals('')) { // get delivery group method for seletctedDMId CartDeliveryGroupMethod previousSelectDeliveryMethod = [SELECT Name, ShippingFee, WebCartId, Carrier, ClassOfService, ExternalProvider, ProductId, ReferenceNumber, IsActive, TransitTimeMin, TransitTimeMax, TransitTimeUnit, ProcessTime, ProcessTimeUnit FROM CartDeliveryGroupMethod WHERE Id= :previousSelectDeliveryMethodId]; - System.debug('previousSelectDeliveryMethod ' + previousSelectDeliveryMethod); - System.debug('shippingOption ' + shippingOption); // return if all fields of shipping option matches with selectedDM else return false - return (previousSelectDeliveryMethod.Name.equals(shippingOption.getName()) && - previousSelectDeliveryMethod.IsActive.equals(shippingOption.isActive()) && + return (previousSelectDeliveryMethod.Name.equals(shippingOption.getName()) && // compare name + previousSelectDeliveryMethod.IsActive.equals(shippingOption.isActive()) && // both should be active status - previousSelectDeliveryMethod.ShippingFee.equals(shippingOption.getRate()) && + previousSelectDeliveryMethod.ShippingFee.equals(shippingOption.getRate()) && // compare shipping fee - isNullOrEquals(previousSelectDeliveryMethod.ProcessTime, shippingOption.getProcessTime()) && - isNullOrEquals(previousSelectDeliveryMethod.ProcessTimeUnit, shippingOption.getProcessTimeUnit()) && + isNullOrEquals(previousSelectDeliveryMethod.ProcessTime, shippingOption.getProcessTime()) && // compare processing time + isNullOrEquals(previousSelectDeliveryMethod.ProcessTimeUnit, shippingOption.getProcessTimeUnit()) && // compare time processing unit // ideally reference number should match but in this sample we are generating random string so won't match //previousSelectDeliveryMethod.ReferenceNumber.equals(shippingOption.getReferenceNumber()) && - previousSelectDeliveryMethod.Carrier.equals(shippingOption.getCarrier()) && - previousSelectDeliveryMethod.ClassOfService.equals(shippingOption.getClassOfService()) && - previousSelectDeliveryMethod.ExternalProvider.equals(shippingOption.getProvider()) && - previousSelectDeliveryMethod.Carrier.equals(shippingOption.getCarrier()) && - - isNullOrEquals(previousSelectDeliveryMethod.TransitTimeMax, shippingOption.getTransitTimeMax()) && - isNullOrEquals(previousSelectDeliveryMethod.TransitTimeMin, shippingOption.getTransitTimeMin()) && - isNullOrEquals(previousSelectDeliveryMethod.TransitTimeUnit, shippingOption.getTransitTimeUnit())); + previousSelectDeliveryMethod.Carrier.equals(shippingOption.getCarrier()) && //compare carrier method + previousSelectDeliveryMethod.ClassOfService.equals(shippingOption.getClassOfService()) && // comapre class of service + previousSelectDeliveryMethod.ExternalProvider.equals(shippingOption.getProvider()) && // compare external provider + + isNullOrEquals(previousSelectDeliveryMethod.TransitTimeMax, shippingOption.getTransitTimeMax()) && // compare transit time max + isNullOrEquals(previousSelectDeliveryMethod.TransitTimeMin, shippingOption.getTransitTimeMin()) && // compare transit time min + isNullOrEquals(previousSelectDeliveryMethod.TransitTimeUnit, shippingOption.getTransitTimeUnit())); // compare transit time unit } + // if previousSelectDeliveryMethod is null then we can return null so DM can be created return false; } From 82cde8fe1464645deea000068eb0ab3592729000 Mon Sep 17 00:00:00 2001 From: Anil Purohit Date: Tue, 18 Mar 2025 10:23:34 +0530 Subject: [PATCH 4/7] added SEE --- .../integrations/classes/B2BDeliverySample.cls | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/b2b/checkout/integrations/classes/B2BDeliverySample.cls b/examples/b2b/checkout/integrations/classes/B2BDeliverySample.cls index e732d7c8..3e0e7ac0 100644 --- a/examples/b2b/checkout/integrations/classes/B2BDeliverySample.cls +++ b/examples/b2b/checkout/integrations/classes/B2BDeliverySample.cls @@ -12,10 +12,10 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin try { String siteLanguage = jobInfo.siteLanguage; // We need to get the ID of the cart delivery group in order to create the order delivery groups. - Id cartDeliveryGroupId = [SELECT Id FROM CartDeliveryGroup WHERE CartId = :cartId][0].Id; + Id cartDeliveryGroupId = [SELECT Id FROM CartDeliveryGroup WHERE CartId = :cartId WITH SECURITY_ENFORCED][0].Id; // Used to increase the cost by a multiple of the number of items in the cart (useful for testing but should not be done in the final code) - Integer numberOfUniqueItems = [SELECT count() from cartItem WHERE CartId = :cartId]; + Integer numberOfUniqueItems = [SELECT count() from cartItem WHERE CartId = :cartId WITH SECURITY_ENFORCED]; // Following snippet of code fetches a static json response with 2 mocked sample shipping methods. // Another example that demonstrates how to call a live 3rd party HTTP Service to fetch the desired response is implemented @@ -33,20 +33,20 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin List orderDeliveryMethodIds = getOrderDeliveryMethods(shippingOptionsAndRatesFromExternalService); // We need to get the ID of the cart delivery group in order to create the cart delivery group methods. - List cartDeliveryGroups = new List([SELECT Id FROM CartDeliveryGroup WHERE CartId = :cartId]); + List cartDeliveryGroups = new List([SELECT Id FROM CartDeliveryGroup WHERE CartId = :cartId WITH SECURITY_ENFORCED]); // Create a CartDeliveryGroupMethod record for every shipping option returned from the external service Integer i = 0; for(CartDeliveryGroup curCartDeliveryGroup : cartDeliveryGroups){ // get selected Delivery method id for cart delivery groupId - ID previousSelectDeliveryMethodId = [SELECT SelectedDeliveryMethodId FROM CartDeliveryGroup WHERE Id = :curCartDeliveryGroup.Id][0].SelectedDeliveryMethodId; + ID previousSelectDeliveryMethodId = [SELECT SelectedDeliveryMethodId FROM CartDeliveryGroup WHERE Id = :curCartDeliveryGroup.Id WITH SECURITY_ENFORCED][0].SelectedDeliveryMethodId; if(previousSelectDeliveryMethodId != null) { // delete all the cart delivery group method id except selected one - delete [SELECT Id FROM CartDeliveryGroupMethod WHERE CartDeliveryGroupId = :curCartDeliveryGroup.Id and Id!= :previousSelectDeliveryMethodId]; + delete [SELECT Id FROM CartDeliveryGroupMethod WHERE CartDeliveryGroupId = :curCartDeliveryGroup.Id and Id!= :previousSelectDeliveryMethodId WITH SECURITY_ENFORCED]; } else { // if there is no selected Delivery method then we can delete all the existing delivery methods associated with cart delivery group id - delete [SELECT Id FROM CartDeliveryGroupMethod WHERE CartDeliveryGroupId = :curCartDeliveryGroup.Id]; + delete [SELECT Id FROM CartDeliveryGroupMethod WHERE CartDeliveryGroupId = :curCartDeliveryGroup.Id WITH SECURITY_ENFORCED]; } for (Id orderDeliveryMethodId: orderDeliveryMethodIds) { // we will populate cart delivery method only when shipping option does not match with previous selected delivery method @@ -391,7 +391,7 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin private Id getShippingChargeProduct2Id(Id orderDeliveryMethodId) { // The Order Delivery Method should have a Product2 associated with it, because we added that in getDefaultOrderDeliveryMethod if it didn't exist. - List orderDeliveryMethods = [SELECT ProductId FROM OrderDeliveryMethod WHERE Id = :orderDeliveryMethodId]; + List orderDeliveryMethods = [SELECT ProductId FROM OrderDeliveryMethod WHERE Id = :orderDeliveryMethodId WITH SECURITY_ENFORCED]; return orderDeliveryMethods[0].ProductId; } @@ -407,7 +407,7 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin for (ShippingOptionsAndRatesFromExternalService shippingOption: shippingOptions) { String shippingOptionNumber = String.valueOf(i); String name = defaultDeliveryMethodName + shippingOptionNumber; - List odms = [SELECT Id, ProductId, Carrier, ClassOfService FROM OrderDeliveryMethod WHERE Name = :name]; + List odms = [SELECT Id, ProductId, Carrier, ClassOfService FROM OrderDeliveryMethod WHERE Name = :name WITH SECURITY_ENFORCED]; // This is the case in which an Order Delivery method does not exist. if (odms.isEmpty()) { OrderDeliveryMethod defaultOrderDeliveryMethod = new OrderDeliveryMethod( @@ -446,7 +446,7 @@ global with sharing class B2BDeliverySample implements sfdc_checkout.CartShippin // Check to see if a Product2 with that name already exists. // If it doesn't exist, create one. String shippingChargeProduct2Name = 'Shipping Charge for this delivery method'; - List shippingChargeProducts = [SELECT Id FROM Product2 WHERE Name = :shippingChargeProduct2Name]; + List shippingChargeProducts = [SELECT Id FROM Product2 WHERE Name = :shippingChargeProduct2Name WITH SECURITY_ENFORCED]; if (shippingChargeProducts.isEmpty()) { Product2 shippingChargeProduct = new Product2( isActive = true, From fbb57b8c90e2acb9f72736f305efc5ed05e1d91b Mon Sep 17 00:00:00 2001 From: Anil Purohit Date: Thu, 20 Mar 2025 16:13:54 +0530 Subject: [PATCH 5/7] Added UT --- .../classes/B2BDeliverySampleTest.cls | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/examples/b2b/checkout/integrations/classes/B2BDeliverySampleTest.cls b/examples/b2b/checkout/integrations/classes/B2BDeliverySampleTest.cls index 1abeee35..a5345e8f 100644 --- a/examples/b2b/checkout/integrations/classes/B2BDeliverySampleTest.cls +++ b/examples/b2b/checkout/integrations/classes/B2BDeliverySampleTest.cls @@ -31,4 +31,53 @@ private class B2BDeliverySampleTest { System.assertEquals(sfdc_checkout.IntegrationStatus.Status.SUCCESS, integrationResult.status); Test.stopTest(); } + + @isTest static void testIntegrationRunsSuccessfully_toRetainPrevSelectedDeliveryMethod() { + Test.startTest(); + // Test: execute the integration for the test cart ID. + B2BDeliverySample apexSample = new B2BDeliverySample(); + sfdc_checkout.IntegrationInfo integInfo = new sfdc_checkout.IntegrationInfo(); + WebCart webCart = [SELECT Id FROM WebCart WHERE Name='Cart' LIMIT 1]; + integInfo.jobId = null; + sfdc_checkout.IntegrationStatus integrationResult = apexSample.startCartProcessAsync(integInfo, webCart.Id); + // Verify: the integration executed successfully + System.assertEquals(sfdc_checkout.IntegrationStatus.Status.SUCCESS, integrationResult.status); + + //Till here first run completed and we got some selected delivery method for each Delivery group + //now we will update our selected delivery method and rerun the calculations and check even post completion of intgration our selected DM should remain same + + //fetch all cart delivery groups + List cartDeliveryGroups = new List([SELECT Id FROM CartDeliveryGroup WHERE CartId = :webCart.Id]); + + //fetch selected delivery method id for first cdg + Id previousSelectDeliveryMethodId = [SELECT SelectedDeliveryMethodId FROM CartDeliveryGroup WHERE Id = :cartDeliveryGroups[0].Id][0].SelectedDeliveryMethodId; + + //find delivery method for same delivery group where id does not match with previous selected DM + List cartDeliveryGroupMethods = new List([SELECT Id FROM cartDeliveryGroupMethod WHERE CartDeliveryGroupId = :cartDeliveryGroups[0].Id]); + String nonSelectedId = null; + for( CartDeliveryGroupMethod cdgm : cartDeliveryGroupMethods) { + if(cdgm.Id != previousSelectDeliveryMethodId) { + nonSelectedId = cdgm.Id; + break; + } + } + + //update cart delivery group with updated DM + CartDeliveryGroup cdgToUpdate = cartDeliveryGroups[0]; + cdgToUpdate.SelectedDeliveryMethodId = nonSelectedId; + update cdgToUpdate; + + // run integratio again + integInfo.jobId = null; + integrationResult = apexSample.startCartProcessAsync(integInfo, webCart.Id); + // Verify: the integration executed successfully + System.assertEquals(sfdc_checkout.IntegrationStatus.Status.SUCCESS, integrationResult.status); + + // verify that newPreviousSelectDeliveryMethodId should match with our selected DM id + Id newPreviousSelectDeliveryMethodId = [SELECT SelectedDeliveryMethodId FROM CartDeliveryGroup WHERE Id = :cartDeliveryGroups[0].Id][0].SelectedDeliveryMethodId; + System.assertEquals(nonSelectedId, newPreviousSelectDeliveryMethodId); + + Test.stopTest(); + } + } From f3fcb3f3f3a8b75ffe47f256d6af6c0178b0e7ff Mon Sep 17 00:00:00 2001 From: Anil Purohit Date: Fri, 21 Mar 2025 11:47:19 +0530 Subject: [PATCH 6/7] Udated UT --- .../integrations/classes/B2BDeliverySampleTest.cls | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/examples/b2b/checkout/integrations/classes/B2BDeliverySampleTest.cls b/examples/b2b/checkout/integrations/classes/B2BDeliverySampleTest.cls index a5345e8f..f770e629 100644 --- a/examples/b2b/checkout/integrations/classes/B2BDeliverySampleTest.cls +++ b/examples/b2b/checkout/integrations/classes/B2BDeliverySampleTest.cls @@ -73,6 +73,20 @@ private class B2BDeliverySampleTest { // Verify: the integration executed successfully System.assertEquals(sfdc_checkout.IntegrationStatus.Status.SUCCESS, integrationResult.status); + //fetch all cart delivery groups + cartDeliveryGroups = new List([SELECT Id FROM CartDeliveryGroup WHERE CartId = :webCart.Id]); + //update cart delivery group with updated DM + cdgToUpdate = cartDeliveryGroups[0]; + //final Schema.Address address = (Schema.Address) cdgToUpdate.DeliverToAddress; + //address.Country = 'US'; + cdgToUpdate.DeliverToPostalCode = '100001'; + update cdgToUpdate; + + // run integratio again + integInfo.jobId = null; + integrationResult = apexSample.startCartProcessAsync(integInfo, webCart.Id); + // Verify: the integration executed successfully + System.assertEquals(sfdc_checkout.IntegrationStatus.Status.SUCCESS, integrationResult.status); // verify that newPreviousSelectDeliveryMethodId should match with our selected DM id Id newPreviousSelectDeliveryMethodId = [SELECT SelectedDeliveryMethodId FROM CartDeliveryGroup WHERE Id = :cartDeliveryGroups[0].Id][0].SelectedDeliveryMethodId; System.assertEquals(nonSelectedId, newPreviousSelectDeliveryMethodId); From 0db97b9bf35ccce099b1f5ff4615d14095c79521 Mon Sep 17 00:00:00 2001 From: Anil Purohit Date: Fri, 21 Mar 2025 11:52:57 +0530 Subject: [PATCH 7/7] Updated UT --- .../checkout/integrations/classes/B2BDeliverySampleTest.cls | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/b2b/checkout/integrations/classes/B2BDeliverySampleTest.cls b/examples/b2b/checkout/integrations/classes/B2BDeliverySampleTest.cls index f770e629..8438ffe9 100644 --- a/examples/b2b/checkout/integrations/classes/B2BDeliverySampleTest.cls +++ b/examples/b2b/checkout/integrations/classes/B2BDeliverySampleTest.cls @@ -67,7 +67,7 @@ private class B2BDeliverySampleTest { cdgToUpdate.SelectedDeliveryMethodId = nonSelectedId; update cdgToUpdate; - // run integratio again + // run integration again integInfo.jobId = null; integrationResult = apexSample.startCartProcessAsync(integInfo, webCart.Id); // Verify: the integration executed successfully @@ -77,12 +77,10 @@ private class B2BDeliverySampleTest { cartDeliveryGroups = new List([SELECT Id FROM CartDeliveryGroup WHERE CartId = :webCart.Id]); //update cart delivery group with updated DM cdgToUpdate = cartDeliveryGroups[0]; - //final Schema.Address address = (Schema.Address) cdgToUpdate.DeliverToAddress; - //address.Country = 'US'; cdgToUpdate.DeliverToPostalCode = '100001'; update cdgToUpdate; - // run integratio again + // run integration again integInfo.jobId = null; integrationResult = apexSample.startCartProcessAsync(integInfo, webCart.Id); // Verify: the integration executed successfully