/ This must implement the sfdc_checkout.CartShippingCharges interface
// in order to be processed by the checkout flow for the "Shipping" integration
global with sharing class B2CDeliverySample implements sfdc_checkout.CartShippingCharges {
global sfdc_checkout.IntegrationStatus startCartProcessAsync(sfdc_checkout.IntegrationInfo jobInfo, Id cartId) {
sfdc_checkout.IntegrationStatus integStatus = new sfdc_checkout.IntegrationStatus();
try {
// 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;
// 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 ];
// Get shipping options, including aspects like rates and carriers, from the external service.
ShippingOptionsAndRatesFromExternalService[] shippingOptionsAndRatesFromExternalService = getShippingOptionsAndRatesFromExternalService(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<Id> orderDeliveryMethodIds = getOrderDeliveryMethods();
// Create a CartDeliveryGroupMethod record for every shipping option returned from the external service
Integer i = 0;
for (Id orderDeliveryMethodId: orderDeliveryMethodIds) {
populateCartDeliveryGroupMethodWithShippingOptions(shippingOptionsAndRatesFromExternalService[i],
cartDeliveryGroupId,
orderDeliveryMethodId,
cartId);
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;
// For testing purposes, this example treats exceptions as user errors, which means they are displayed to the buyer user.
// In production you probably want this to be an admin-type error. In that case, throw the exception here
// and make sure that a notification system is in place to let the admin know that the error occurred.
// See the readme section about error handling for details about how to create that notification.
} catch (DmlException de) {
// Catch any exceptions thrown when trying to insert the shipping charge to the CartItems
Integer numErrors = de.getNumDml();
String errorMessage = 'There were ' + numErrors + ' errors when trying to insert the charge in the CartItem: ';
for(Integer errorIdx = 0; errorIdx < numErrors; errorIdx++) {
errorMessage += 'Field Names = ' + de.getDmlFieldNames(errorIdx);
errorMessage += 'Message = ' + de.getDmlMessage(errorIdx);
errorMessage += ' , ';
}
return integrationStatusFailedWithCartValidationOutputError(
integStatus,
errorMessage,
jobInfo,
cartId
);
} catch(Exception e) {
return integrationStatusFailedWithCartValidationOutputError(
integStatus,
'An exception of type ' + e.getTypeName() + ' has occurred: ' + e.getMessage(),
jobInfo,
cartId
);
}
return integStatus;
}
private ShippingOptionsAndRatesFromExternalService[] getShippingOptionsAndRatesFromExternalService (Integer numberOfUniqueItems) {
final Integer SuccessfulHttpRequest = 200;
ShippingOptionsAndRatesFromExternalService[] shippingOptions = new List<ShippingOptionsAndRatesFromExternalService>();
Http http = new Http();
HttpRequest request = new HttpRequest();
// To access the service below, you may need to add endpoint = https://b2b-commerce-test.herokuapp.com in Setup | Security | Remote site settings.
request.setEndpoint('https://b2b-commerce-test.herokuapp.com/calculate-shipping-rates-winter-21');
request.setMethod('GET');
HttpResponse response = http.send(request);
// If the request is successful, parse the JSON response.
// The response looks like this:
// [{"status":"calculated","rate":{"name":"Delivery Method 1","serviceName":"Test Carrier 1","serviceCode":"SNC9600","shipmentCost":11.99,"otherCost":5.99}},
// {"status":"calculated","rate":{"name":"Delivery Method 2","serviceName":"Test Carrier 2","serviceCode":"SNC9600","shipmentCost":15.99,"otherCost":6.99}}]
if (response.getStatusCode() == SuccessfulHttpRequest) {
List<Object> results = (List<Object>) JSON.deserializeUntyped(response.getBody());
for (Object result: results) {
Map<String, Object> subresult = (Map<String, Object>) result;
Map<String, Object> providerAndRate = (Map<String, Object>) subresult.get('rate');
shippingOptions.add( new ShippingOptionsAndRatesFromExternalService(
(String) providerAndRate.get('name'),
(String) providerAndRate.get('serviceCode'),
(Decimal) providerAndRate.get('shipmentCost') *0, // Multiply so shipping costs can change; remove when using a real shipping provider
(Decimal) providerAndRate.get('otherCost')* 0,
(String) providerAndRate.get('serviceName')
));
}
return shippingOptions;
}
else {
throw new CalloutException ('There was a problem with the request. Error: ' + response.getStatusCode());
}
}
// 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;
}
public Decimal getRate() {
return rate;
}
public Decimal getOtherCost() {
return otherCost;
}
public String getServiceName() {
return serviceName;
}
public String getName() {
return name;
}
}
// 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
);
insert(cartDeliveryGroupMethod);
}
private sfdc_checkout.IntegrationStatus integrationStatusFailedWithCartValidationOutputError(
sfdc_checkout.IntegrationStatus integrationStatus, String errorMessage, sfdc_checkout.IntegrationInfo jobInfo, Id cartId) {
integrationStatus.status = sfdc_checkout.IntegrationStatus.Status.FAILED;
// In order for the error to be propagated to the user, we need to add a new CartValidationOutput record.
// The following fields must be populated:
// BackgroundOperationId: Foreign Key to the BackgroundOperation
// CartId: Foreign key to the WebCart that this validation line is for
// Level (required): One of the following - Info, Error, or Warning
// Message (optional): Message displayed to the user
// Name (required): The name of this CartValidationOutput record. For example CartId:BackgroundOperationId
// RelatedEntityId (required): Foreign key to WebCart, CartItem, CartDeliveryGroup
// Type (required): One of the following - SystemError, Inventory, Taxes, Pricing, Shipping, Entitlement, Other
CartValidationOutput cartValidationError = new CartValidationOutput(
BackgroundOperationId = jobInfo.jobId,
CartId = cartId,
Level = 'Error',
Message = errorMessage.left(255),
Name = (String)cartId + ':' + jobInfo.jobId,
RelatedEntityId = cartId,
Type = 'Shipping'
);
insert(cartValidationError);
return integrationStatus;
}
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<OrderDeliveryMethod> orderDeliveryMethods = [SELECT ProductId FROM OrderDeliveryMethod WHERE Id = :orderDeliveryMethodId ];
return orderDeliveryMethods[0].ProductId;
}
private List<Id> getOrderDeliveryMethods() {
List<Id> orderDeliveryMethodIds = new List<Id>();
List<OrderDeliveryMethod> odms = [SELECT Id, ProductId, Carrier, ClassOfService, IsActive FROM OrderDeliveryMethod Limit 2 ];
for(OrderDeliveryMethod odm :odms){
orderDeliveryMethodIds.add(odm.id);
}
return orderDeliveryMethodIds;
}
private Id getDefaultShippingChargeProduct2Id() {
// In this example we will name the product representing shipping charges 'Shipping Charge for this delivery method'.
// 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<Product2> shippingChargeProducts = [SELECT Id FROM Product2 WHERE Name = :shippingChargeProduct2Name ];
if (shippingChargeProducts.isEmpty()) {
Product2 shippingChargeProduct = new Product2(
isActive = true,
Name = shippingChargeProduct2Name
);
insert(shippingChargeProduct);
return shippingChargeProduct.Id;
}
else {
return shippingChargeProducts[0].Id;
}
}
}
global with sharing class B2BTaxSample implements sfdc_checkout.CartTaxCalculations {
global sfdc_checkout.IntegrationStatus startCartProcessAsync(sfdc_checkout.IntegrationInfo jobInfo, Id cartId) {
sfdc_checkout.IntegrationStatus integStatus = new sfdc_checkout.IntegrationStatus();
integStatus.status = sfdc_checkout.IntegrationStatus.Status.SUCCESS;
return integStatus;
}
}
000393185

We use three kinds of cookies on our websites: required, functional, and advertising. You can choose whether functional and advertising cookies apply. Click on the different cookie categories to find out more about each category and to change the default settings.
Privacy Statement
Required cookies are necessary for basic website functionality. Some examples include: session cookies needed to transmit the website, authentication cookies, and security cookies.
Functional cookies enhance functions, performance, and services on the website. Some examples include: cookies used to analyze site traffic, cookies used for market research, and cookies used to display advertising that is not directed to a particular individual.
Advertising cookies track activity across websites in order to understand a viewer’s interests, and direct them specific marketing. Some examples include: cookies used for remarketing, or interest-based advertising.