The intention of this document is to explain some dynamic aspects of the basket and checkout REST API that are not covered by the REST API reference.
Term | Description |
---|---|
payment method | Configured instance of a payment connector implementation to a specific provider. Also known as payment service. |
payment instrument | A saved set of data for a payment service at a basket. |
payment | Payment instrument set at the basket to pay with. |
A new version of the Basket REST API had to be created since the new version (v1) is incompatible with the previous (legacy) Basket REST interface for all requests. Hence, media-type-based versioning has been introduced to distinguish between legacy- and v1-related REST requests. A REST client must set the following HTTP Accept
header in case of v1 requests:
Accept: application/vnd.intershop.basket.v1+json
In contrast, the standard media type header for JSON-based requests must be used to target requests of legacy Basket REST API:
Accept: application/json
This way requests of the legacy and v1 Basket REST API can be used side by side by a REST client at the same time.
The resource implementation is the main entry point for requests in the Basket REST API. The following diagram shows the class hierarchy of Basket REST API related classes:
The resource class for requests of the legacy Basket REST API inherits from V1-item resource implementation to ensure that legacy and V1 requests can be used side by side.
Resource class implementation of the legacy Basket REST API (blue) contains much functionality for request processing, like determining the current user's basket or triggering basket calculation, etc. In contrast, v1-related resource classes (green) are quite simple:
basket
's list and entity resources.invoke
-method.This way, the functionality for different requests is clearly separated instead of scattered over a single resource class implementation as done in the implementation of the legacy basket REST API. All request class implementations use a basket handler class that works on top of the Business Objects (BO)-layer. This means that no pipelines are called by the handler implementation except the ProcessBasket-Calculate
pipeline.
Note
Custom pipelines, triggered by pipeline extension points of other sub-pipelines of ProcessBasket
are not called by the v1 Basket REST functionality at all.
Method implementations of REST requests processing database updates must be annotated with the com.intershop.component.rest.capi.transaction.Transactional
annotation. This annotation provides automatic transaction support for committing database changes after completion of request processing.
If request processing fails for any reason, e.g., in the handler class, the transaction can be rolled back as shown below:
// trigger roll back in case of an error if (result.isError()) { Transaction tx = transactionMgr.getCurrentTransaction(); tx.markRollbackOnly(); }
Error handling exclusively based on HTTP status codes is not sufficient for the majority of the REST clients. Therefore the v1 Basket REST API introduces a unique handling for errors as well as other information to provide a REST client with as much information as possible about the results of processing a specific request.
... "errors": [ { "causes": [ { "code": "basket.address.create_address_duplicate_address.error", "message": "An address with this data already exists." } ], "code": "basket.address.creation.error", "message": "The address could not be added to your basket.", "status": "422" }], "infos": [ { "code": "basket.validation.shipping_method_was_set.info", "message": "The selected shipping method is not valid anymore. A new shipping method has been selected for the basket." }] ...
Besides the data
section containing the response's payload data, a response might contain an errors
and an infos
section that can include one or multiple entries. These sections contain at least a unique key for the error or the info and a localized message describing the issue or info. The locale of the message follows the one specified in the request URL. Furthermore, there is a fallback to the standard locale if no localization is found for the request locale. Additionally, an info or error message may contain an embedded cause
element. A cause
element has the same structure as its surrounding error or info entry and provides further information to isolate the issue.
An entry of the infos
or errors
section may contain additional parameters if these help to further describe the issue. Furthermore, an infos
or errors
section may contain a paths
attribute whose value points to the origin of the error or info in the request's JSON payload. The syntax of the path corresponds to the JSON Path DSL.
... "infos": [ { "causes": [ { "code": "basket.line_item.add_item_added_to_new_line_item_with_adjusted_quantity.info", "message": "This product has limited inventory. We have adjusted the quantity to reflect the number of products we have available and added the product to your cart.", "parameters": { "requested": "110", "granted": "100" }, "paths": [ "$[0].quantity" ] }, { "code": "basket.line_item.add_item_max_item_quantity_exceeded.info", "message": "The quantity you entered is invalid. We have adjusted the quantity to meet our Maximum Purchasing Policy." } ], "code": "basket.line_item.creation.info", "message": "This product has been added to your basket.", "paths": [ "$[0]" ], "status": "201" } ], ...
See Concept - REST API Error and Info Messages (v1) for details regarding the errors
and infos
sections.
In general, every changing request (POST
, PUT
, PATCH
, DELETE
) that manipulates the basket's
resource or one of its sub-resources may affect results of the previous basket calculation by invalidating these results. This way the basket has to be re-calculated after a single or several changing REST requests to get valid amounts for prices and costs again. There are two basic use cases:
For this reason the responsibility for basket calculation is handed over to the REST client in the new basket and the checkout REST API. A REST client can control the basket calculation behavior with the calculated
attribute that is available at the following item resources and sub-resources:
whereas
calculated=false
means that no calculation should be triggered by the server,calculated=true
means that (re-)calculation should be triggered by the server. This is the default if neither is specified.If the client is not certain about the calculation state of the basket, it can trigger the following request:
{ "calculated": true }
There is no performance impact in this case since a basket with a valid calculation state will not be (re-)calculated as a result of this request.
Basket's purchase currency, i.e. the currency the basket is calculated with, is controlled by the optional cur
REST request matrix parameter only:
https://.../-;loc=en_US;cur=USD/baskets/...
The purchaseCurrency
attribute of the basket item resource is read-only and informative only, means setting the value of this attribute in the context of POST/PUT
/PATCH
requests has no effect to basket calculation at all.
GET
requests to a basket item resource ignore the value of the cur
matrix parameter completely. This way, a GET
request with the cur
matrix parameter value EUR is answered with the value USD for the purchaseCurrency
attribute if the basket is currently calculated with the purchase currency USD. Furthermore there is an info message that basket's purchase currency differs from requested currency.
{ "data" : { ... "purchaseCurrency": "USD", ... } "infos": [ { "code": "request.parameter.currency.differs_from_basket_purchase_currency.", "message": "Basket purchase currency differs from requested currency specified for the 'cur' matrix parameter. All money values are returned in basket's purchase currency." } ] }
However, the REST interface does not respond with an error in this case. A client has to note this behavior to avoid confusion related to basket's purchase currency.
In case that a REST client demands for changing basket's purchase currency without changing the basket otherwise, it must send a PATCH
request with the new currency and at least the calculated
attribute with a value equal to true
:
{ "calculated": true }
The request explicitly forces invalidation of basket calculation and a subsequent re-calculation. This behavior works for all subresources of the basket item resource that contain the calculated
attribute. This way, the purchase currency can be changed implicitly with every request that manipulates the basket in any way (including the calculated
attribute with a value equal to true
) or explicitly by sending a PATCH
request that only contains the calculated
attribute with a value equal to true
.
A validation's
subresource has been introduced for the basket item resource to give a REST client the possibility to validate all or several basket settings for correctness.
{ "scopes": ["All"], "adjustmentsAllowed": false, "errorBehavior": "NeverStop" }
Validations could not be processed in the course of the basket's PATCH
request, as done for basket calculation. The reason for this is that the payload of the PATCH
request's response would be mixed with payload origin from basket validation, especially in the errors
and infos
section. Furthermore, a client was not able to differentiate whether a returned HTTP error status is issued by PATCH
request processing or by validation errors.
See Concept - Basket Handling: Validate Basket for details regarding validation scopes, allowance for adjustments and error behavior.
This way the POST
request to the basket's validations
subresource responds with a data section that contains, besides requested validation parameters, error and info messages resulting from validation processing.
{ "data": { "adjustmentsAllowed": false, "basket": "L9kKAEsBWAgAAAFuARZXSAMd", "errorBehavior": "NeverStop", "results": { "adjusted": false, "errors": [ { "code": "basket.validation.payment_missing.error", "message": "No payment method has been selected.", "parameters": { "scopes": "Payment" }, "paths": [ "$.payments" ] }, { "code": "basket.validation.basket_not_covered.error", "message": "The value of the basket is not covered by the selected payment instruments.", "parameters": { "scopes": "Payment" }, "paths": [ "$.payments" ] }, { "code": "basket.validation.invoice_to_address_missing.error", "message": "No invoice-to address has been selected.", "parameters": { "scopes": "InvoiceAddress,Addresses" }, "paths": [ "$.invoiceToAddress" ] } ], "valid": false }, "scopes": [ "All" ] } }
There are several concepts to extend the basket REST API v1.
The extension concept of Attribute Subresource is available for REST resources and subresources that represent custom attributes of a business object, which has a com.intershop.beehive.core.capi.domain.ExtensibleObjectPO
representation. The reason for this limitation is that these business objects can store the values provided to an attribute subresource by a REST client in their Attribute Value (AV) tables in the database.
In the Basket REST API v1 the following resources and sub-resources support this extension mechanism:
One advantage of the extensibility via Attribute Subresource is the fact that this mechanism allows extensibility while retaining the type of additional data. As these attributes are stored as custom attributes in ICM's AV database tables, all data types are supported that can be stored there:
An attributes
sub-resource is introduced for every resource/subresource that supports this extension concept. This way additional data of different types can simply be provided and retrieved in the REST API.
{ "name": "testDate", "type": "Date", "value": "2019-01-31T18:23:00+02:00" }
{ "data": { "name": "testDate", "type": "Date", "value": "2019-01-31T18:23:00+02:00" }, "links": { "self": "http://localhost/INTERSHOP/rest/WFS/inSPIRED-inTRONICS-Site/-;loc=en_US&cur=USD/baskets/3hcKAEsBBxoAAAFt0A4aeEja/attributes/testDate" } }
Another advantage of this extension mechanism is that no implementation is required at all. REST handler and object mapper of attributes
subresource handles storage of attribute data behind the scenes.
Customizable Requests and Responses of REST requests and responses is available for REST resources and subresources that mainly represent domain objects, like basket, basket totals, discounts, etc. . The extension concept of Customizable Requests and Responses differs from Attribute Subresources in the following points:
String
were supported. Starting with ICM 7.10.27, the value type is now Object
.This extension mechanism is mainly based on the concepts of:
com.fasterxml.jackson.annotation.JsonAnyGetter
com.fasterxml.jackson.annotation.JsonAnySetter
which allow to specify additional fields in the REST request's and the response's JSON body.
The following JSON snippet of updating a basket contains the two regular fields commonShipToAddress
and commonShippingMethod
. Furthermore, JSON content contains the fields string
, integer
and custom
that are not part of the Basket REST API. Since these fields cannot be assigned to regular attributes of the corresponding Java resource object class that represents the basket item resource during JSON unmarshalling, they are put in a map.
{ "commonShipToAddress": "urn:address:customer:7aEKAEsBn7EAAAFsnihVfB17:cWMKAEsBn7UAAAFsnihVfB17", "commonShippingMethod": "STD_GROUND", "string" : "foo", "integer" : 42, "custom" : { "custom_string" : "bar", "custom_integer" : 42 } }
Afterwards the map entries can be retrieved from the basket resource object (com.intershop.sellside.rest.basket.v1.capi.resourceobject.basket.BasketRO
). The prerequisite for this is that the related resource object extends the class com.intershop.sellside.rest.common.v1.capi.resourceobject.common.CustomizableRO
with its method:
+getAnyFields(): Map<String,Object>
Note
All field values are of type Object
and are not automatically converted. Type casting and conversion must be done manually instead.
Customizablity of REST client requests, however, does not only affect the Resource Objects (RO). Data provided with ROs have to be set on ICM's Business Objects (BO) in any way. For this reason several REST handler implementations call registered implementations of the interface ObjectHandlerExtension<S,T>
.
public interface ObjectHandlerExtension<S, T> { /** * Checks if this extension is applicable for the current context. If other context objects * (e.g. ApplicationBO) are required for deciding if the handler has to be executed, it has to be injected. * * @param source The source object handed in within the request. * @param target The business object to be updated. * @return <code>true</code> is the extension is applicable for the given context, otherwise <code>false</code>. */ default boolean isApplicable(S source, T target) { return true; } /** * Updates the given target with the data specified in the source. * * @param source * The data object to get data from. * @param target * The target object that should be updated. * @param result * The result to record feedback data representing the current status of an update * handler execution as well as collecting all error and info messages. */ void update(S source, T target, MethodInvocationResult<T> result); }
The following example shows how custom data provided with a request to the baskets
item resource can be processed in the REST framework of the server:
// Basket handler implementation calls registered custom handlers public class BasketHandlerImpl implements BasketHandler { @Inject private Set<ObjectHandlerExtension<BasketRO, BasketBO>> handlers; ... private void invokeExtendedBasketUpdateHandlers(BasketRO basketRO, BasketBO basketBO, MethodInvocationResult<BasketBO> result) { handlers.stream().filter(h -> h.isApplicable(basketRO, basketBO)) .forEach(h -> h.update(basketRO, basketBO, result)); } ... } // Custom basket REST handler can do its own processing of data provided in request body's custom fields. public class MyCustomBasketDataHandler implements ObjectHandlerExtension<BasketRO, BasketBO> { @Override public boolean isApplicable(BasketRO basketRO, BasketBO basketBO) { // Decide if there are any further conditions, whether the update method should be called or not! } @Override public void update(BasketRO basketRO, BasketBO basketBO, MethodInvocationResult<BasketBO> result) { Map<String, Object> myCustomData = basketRO.getAnyFields(); // e.g. cast/convert and process/set value of an above's map entry on your own BasketBOExtension implementation } } // Guice registration of the REST handler extension. public class MyHandlerModule extends AbstractModule { @Override protected void configure() { Multibinder<ObjectHandlerExtension<BasketRO, BasketBO>> enhanceBasketHandlers = Multibinder .newSetBinder(binder(), new TypeLiteral<ObjectHandlerExtension<BasketRO, BasketBO>>() {}); enhanceBasketHandlers.addBinding().to(MyCustomBasketDataHandler.class); } }
The following handler implementations call handler extensions by default:
com.intershop.sellside.rest.basket.v1.internal.handler.AddressHandlerImpl
com.intershop.sellside.rest.basket.v1.internal.handler.BasketHandlerImpl
com.intershop.sellside.rest.basket.v1.internal.handler.LineItemHandlerImpl
com.intershop.sellside.rest.basket.v1.internal.handler.ShippingHandlerImpl
com.intershop.sellside.rest.order.v1.internal.handler.OrderHandlerImpl
The following JSON snippet of getting a basket resource contains the fields string,
integer
and custom
in the response body's data
section that are not part of the Basket REST API:
{ "data": { "buckets": [ "1648241666" ], "calculated": true, "commonShipToAddress": "urn:address:customer:7aEKAEsBn7EAAAFsnihVfB17:cWMKAEsBn7UAAAFsnihVfB17", "commonShippingMethod": "STD_GROUND", "customer": "Patricia", ... "string" : "foo", "integer" : "42", "custom" : { "custom_string" : "bar", "custom_integer" : 42 } ... } }
The prerequisite for this is that the related resource object extends the class com.intershop.sellside.rest.common.v1.capi.resourceobject.common.CustomizableRO
:
+setAnyFields(String fieldName, Object fieldValue): void
Customizablity of REST client responses, however, does not only affect the Resource Objects (RO), but also custom data that have to be mapped to responses RO(s). For this reason several REST object mappers extend the abstract class ExtensibleFunction<S,T>
that calls registered implementations of the interface FunctionExtension<S,T>
.
/** * Base class for all mappings based on a {@link Function} which need to be extensible. * * For instance the basket resource object is completed with B2C data by default, but in B2B context additional data are * set too. */ @Beta public abstract class ExtensibleFunction<S, T> implements Function<S, T> { @Inject private Set<FunctionExtension<S, T>> extensions; @Override public T apply(S source) { T target = createTarget(source); target = applyExtensions(source, target); return target; } /** * Creates the initial target object and fills in the first data. * * @param source * The source object to create and fill a new target object for. * @return The fresh and enriched target object. Must not be <code>null</code>. */ protected abstract T createTarget(S source); /** * Iterates over all extension registered in the Guice module for the same source/target object combination. For all * applicable extensions the function is invoked. * * @param source * The source object to read the data from. * @param target * The target object to be completed. */ protected T applyExtensions(S source, T target) { Objects.requireNonNull(target); if (extensions != null) { for (FunctionExtension<S, T> ext : extensions) { if (ext.isApplicable(source, target)) { target = ext.apply(source, target); } } } return target; } }
The following example shows how custom data can be provided with a response to a request of the baskets
item resource in the REST framework of the server:
// Custom basket resource object mapper that writes its own data to response body's custom fields. public class CustomRO { private String custom_string; private Integer custom_integer; public CustomRO() {} public CustomRO(String custom_string, Integer custom_integer) { this.custom_string = custom_string; this.custom_integer = custom_integer; } // Getter and Setter } @Priority(80) public class MyCustomBasketROMapper implements FunctionExtension<BasketBO, BasketRO> { @Override public BasketRO apply(BasketBO source, BasketRO target) { target.setAnyField("string", "foo"); target.setAnyField("integer", "42"); // Object type only valid for ICM starting with 7.10.27 target.setAnyField("custom", new CustomRO("bar", 42)) return target; } } // Guice registration of the resource object mapper extension. public class MyMapperModule extends AbstractModule { @Override protected void configure() { Multibinder<FunctionExtension<BasketBO, BasketRO>> enhanceBasketMappers = Multibinder .newSetBinder(binder(), new TypeLiteral<FunctionExtension<BasketBO, BasketRO>>() {}); enhanceBasketMappers.addBinding().to(MyCustomBasketROMapper.class); } }
Starting with ICM 7.10.17, FunctionExtensions
are processed in the order of their @Priority
annotations (beginning with the highest).
The following resource objects and their mapper implementations support mapper extensions by default:
Resource Object | Mapper Implementation |
---|---|
com.intershop.sellside.rest.basket.v1.capi.resourceobject.basket.BasketRO | com.intershop.sellside.rest.basket.v1.internal.mapper.basket.BasketROMapper |
com.intershop.sellside.rest.basket.v1.capi.resourceobject.basket.BasketTotalsRO | com.intershop.sellside.rest.basket.v1.internal.mapper.basket.BasketTotalsROMapper |
com.intershop.sellside.rest.basket.v1.capi.resourceobject.basket.RecurrenceRO | com.intershop.sellside.rest.basket.v1.internal.mapper.basket.RecurrenceROMapper |
com.intershop.sellside.rest.basket.v1.capi.resourceobject.lineitem.GiftMessageRO | com.intershop.sellside.rest.basket.v1.internal.mapper.lineitem.GiftMessageROMapper |
com.intershop.sellside.rest.basket.v1.capi.resourceobject.lineitem.GiftWrapRO | com.intershop.sellside.rest.basket.v1.internal.mapper.lineitem.GiftWrapROMapper |
com.intershop.sellside.rest.basket.v1.capi.resourceobject.lineitem.LineItemRO | com.intershop.sellside.rest.basket.v1.internal.mapper.lineitem.LineItemROMapper |
com.intershop.sellside.rest.basket.v1.capi.resourceobject.lineitem.LineItemPricingRO | com.intershop.sellside.rest.basket.v1.internal.mapper.lineitem.LineItemPricingROMapper |
com.intershop.sellside.rest.basket.v1.capi.resourceobject.lineitem.WarrantyRO | com.intershop.sellside.rest.basket.v1.internal.mapper.lineitem.WarrantyROMapper |
com.intershop.sellside.rest.basket.v1.capi.resourceobject.product.ProductRO | com.intershop.sellside.rest.basket.v1.internal.mapper.product.ProductROMapper |
com.intershop.sellside.rest.basket.v1.capi.resourceobject.promotion.DiscountRO | com.intershop.sellside.rest.basket.v1.internal.mapper.promortion.DiscountROMapper |
com.intershop.sellside.rest.basket.v1.capi.resourceobject.promotion.PromotionCodeRO | com.intershop.sellside.rest.basket.v1.internal.mapper.promotion.PromotionCodeROMapper |
com.intershop.sellside.rest.basket.v1.capi.resourceobject.promotion.PromotionRO | com.intershop.sellside.rest.basket.v1.internal.mapper.promotion.PromotionROMapper |
com.intershop.sellside.rest.basket.v1.capi.resourceobject.shipping.PackSlipMessageRO | com.intershop.sellside.rest.basket.v1.internal.mapper.shipping.PackSlipMessageROMapper |
com.intershop.sellside.rest.basket.v1.capi.resourceobject.shipping.ShippingBucketRO | com.intershop.sellside.rest.basket.v1.internal.mapper.shipping.ShippingBucketROMapper |
com.intershop.sellside.rest.basket.v1.capi.resourceobject.shipping.ShippingMethodRO | com.intershop.sellside.rest.basket.v1.internal.mapper.shipping.PackMethodROMapper |
{ "data": [ { "calculated": false, "customer": "Patricia", "discounts": {}, "id": "3hcKAEsBBxoAAAFt0A4aeEja", "invoiceToAddress": "urn:address:customer:7aEKAEsBn7EAAAFsnihVfB17:ljwKAEsBn7YAAAFsnihVfB17", "totalProductQuantity": 0, "totals": { "grandTotal": {}, "paymentCostsTotal": {}, "undiscountedItemTotal": {}, "undiscountedShippingTotal": {} }, "user": "patricia@test.intershop.de" }, { "buckets": [ "1648241666" ], "calculated": true, "commonShipToAddress": "urn:address:customer:7aEKAEsBn7EAAAFsnihVfB17:cWMKAEsBn7UAAAFsnihVfB17", "commonShippingMethod": "STD_GROUND", "customer": "Patricia", "discounts": {}, "id": "A68KAEsBTuEAAAFt0VQaeEjX", "invoiceToAddress": "urn:address:customer:7aEKAEsBn7EAAAFsnihVfB17:ljwKAEsBn7YAAAFsnihVfB17", "lineItems": [ "PcEKAEsBJZwAAAFtklYaeEjX" ], "purchaseCurrency": "USD", "totalProductQuantity": 1, "totals": { "grandTotal": { "gross": { "currency": "USD", "value": 211.840000 }, "net": { "currency": "USD", "value": 178.020000 }, "tax": { "currency": "USD", "value": 33.82 } }, "paymentCostsTotal": {}, "undiscountedItemTotal": { "gross": { "currency": "USD", "value": 208.25 }, "net": { "currency": "USD", "value": 175.00 } }, "undiscountedShippingTotal": { "gross": { "currency": "USD", "value": 3.59 }, "net": { "currency": "USD", "value": 3.02 }, "tax": { "currency": "USD", "value": 0.57 } }, "itemTotal": { "gross": { "currency": "USD", "value": 208.250000 }, "net": { "currency": "USD", "value": 175.000000 } }, "salesTaxTotalsByTaxRate": [ { "calculatedTax": { "currency": "USD", "value": 33.25 }, "effectiveTaxRate": 19.000000, "taxableAmount": { "currency": "USD", "value": 33.25 } } ], "shippingTaxTotalsByTaxRate": [ { "calculatedTax": { "currency": "USD", "value": 0.57 }, "effectiveTaxRate": 19.000000, "taxableAmount": { "currency": "USD", "value": 3.02 } } ], "shippingTotal": { "gross": { "currency": "USD", "value": 3.59 }, "net": { "currency": "USD", "value": 3.02 } }, "taxTotalsByTaxRate": [ { "calculatedTax": { "currency": "USD", "value": 33.82 }, "effectiveTaxRate": 19.000000, "taxableAmount": { "currency": "USD", "value": 178.020000 } } ] }, "user": "patricia@test.intershop.de" } ], "links": { "self": { "3hcKAEsBBxoAAAFt0A4aeEja": "http://localhost/INTERSHOP/rest/WFS/inSPIRED-inTRONICS-Site/-;loc=en_US&cur=USD/baskets/3hcKAEsBBxoAAAFt0A4aeEja", "A68KAEsBTuEAAAFt0VQaeEjX": "http://localhost/INTERSHOP/rest/WFS/inSPIRED-inTRONICS-Site/-;loc=en_US&cur=USD/baskets/A68KAEsBTuEAAAFt0VQaeEjX" } } }
The request returns a list of active baskets for the specified user. The following has to be considered:
A basket can be deleted using the following request:
{ "infos": [ { "code": "basket.deletion.info", "message": "The basket will be deleted.", "status": "200" } ] }
Note
The basket is not physically removed from the database as a result of this request, but only set to basket state INVALID
. However, the basket cannot be reactivated and is not contained in the list of active baskets any longer.
To add a product to the basket, use the following POST
request:
[ { "product": "5777062", "quantity": { "value": "2" } }, { "product": "5920217", "quantity": { "value": "1" } } ]
This request is designed to add multiple products to the basket in one request. Adding multiple products to the basket with a single request can sometimes be a performance challenge. For example, you may want to add all the products in a wishlist or product list with a single request, rather than adding all the products with separate requests.
{ "data": { "basket": "K9TAqLIj1PMAAAGJYoVkDC9g", "calculated": true, "freeGift": false, "hiddenGift": false, "id": "W7HAqLIjVtUAAAGJeFVkDC9i", "position": 1, "pricing": { "giftingTotal": {}, "price": { "gross": { "currency": "USD", "value": 661.64 }, "net": { "currency": "USD", "value": 556.00 } }, "salesTaxTotal": { "currency": "USD", "value": 105.64 }, "shippingTaxTotal": { "currency": "USD", "value": 5.29 }, "singleBasePrice": { "gross": { "currency": "USD", "value": 165.41 }, "net": { "currency": "USD", "value": 139.00 } }, "undiscountedPrice": { "gross": { "currency": "USD", "value": 661.64 }, "net": { "currency": "USD", "value": 556.00 } }, "undiscountedShippingTotal": { "gross": { "currency": "USD", "value": 33.15 }, "net": { "currency": "USD", "value": 27.86 } }, "undiscountedSingleBasePrice": { "gross": { "currency": "USD", "value": 165.41 }, "net": { "currency": "USD", "value": 139.00 } } }, "product": "5777062", "quantity": { "value": 2 }, "shipToAddress": "urn:address:customer:DRnAqLIjTP0AAAGIc5xXICm4:WUbAqLIjs_MAAAGIeJxXICm4", "shippingMethod": "STD_GROUND" } }
This feature is available from ICM version 11.3.0 (Basket REST API version 1.4.0) and higher.
In the previous releases, the REST API endpoint for adding products to the basket processed additions one at a time, which could result in performance bottlenecks when adding a large number of products at once. Each individual addition incurred some overhead, such as individual processing and database operations, resulting in slower response times and potential scalability issues.
To address the above issue, new functionality has been introduced to perform line item bulk processing by using a new URL parameter, Boolean useBulkProcessing
.
By using the useBulkProcessing
parameter in the URL, developers can enhance their interaction with our REST API endpoint. By including this parameter in requests, developers can efficiently manage large shopping baskets without the need for complex code changes.
To demonstrate the usage, the following URL serves as example:
https:/{baseUrl}/baskets/{basketID}/items
In the above URL example, append the new parameter useBulkProcessing
to the URL with the value true
to enable the bulk processing functionality:
https:/{baseUrl}/baskets/{basketID}/items?useBulkProcessing=true
By incorporating the parameter into the requests, developers can take full advantage of the new large basket approach and optimize the management of large collections of line items within their online shops.
For more information on the execution of single and multiple processing handlers, see Concept - Basket Handling | Concept BasketHandling Add to BasketOperation (bulkoperations).
This provides all payment methods (also known as payment services) that are eligible for the current user and basket. An include section containing all related payment instruments is provided on demand (URL query parameter include=paymentInstruments
). This section contains all payment instruments created for all eligible payment methods, i.e. the ones created for the user as well as for the basket. Payment instruments of payment methods that are not eligible are not returned.
In case a payment method is not usable at the moment, but would have minor changes in the basket, it is listed with the flag "restricted". In case the service connector provides a reason, it is available in the list of "restrictions".
For payment methods without parameters that currently have no related payment instrument instance, a payment instrument with a synthetic ID is provided by default. The paymentInstruments
attribute contains one entry with the same value as the id
attribute in this case. The specific payment instrument instance will be created on the fly by the server if this payment instrument is assigned to the basket.
The first example shows a list of eligible payment methods: one without restrictions, the other one with the limitation that the defined maximum order amount for this method was exceeded.
{ "data": [ { "default": false, "description": "Just pay your order directly when it gets delivered!", "displayName": "Cash on Delivery", "id": "ISH_CASH_ON_DELIVERY", "paymentInstruments": [ "ISH_CASH_ON_DELIVERY" ], "restricted": false, "saveAllowed": false }, { "default": false, "description": "Paying by invoice is easy and comfortable - give it a try!", "displayName": "Invoice", "id": "ISH_INVOICE", "maxOrderAmount": { "gross": { "currency": "USD", "value": 1000 } }, "minOrderAmount": { "gross": { "currency": "USD", "value": 500 } }, "paymentCostsThreshold": { "net": { "currency": "USD", "value": 0 } }, "paymentInstruments": [ "ISH_INVOICE" ], "restricted": true, "restrictions": [ { "code": "payment.restriction.MaxOrderAmount", "message": "The maximum order amount for this payment method has been exceeded." } ], "saveAllowed": false } ], "included": { "paymentInstruments": { "ISH_CASH_IN_ADVANCE": { "id": "ISH_CASH_IN_ADVANCE" }, "ISH_INVOICE": { "id": "ISH_INVOICE" } } } }
The details of the payment method have to include information (metadata) about the required parameters for the method. On connector side these data are defined by PropertyGroups.
The metadata have to include information about
The following code snippet gives an example of how the representation of the payment parameters to be provided by the user interface my look like. Normally that means that the user has to enter them. The only exception are parameters annotated with "hidden".
These are calculated in the UI or by the provider integration scripts. A prominent example for such a scenario are payment pages hosted by the payment service provider.
{ "data": [{ "restricted": false, "default": false, "description": "...", "displayName": "Direct Debit Transfer", "id": "ISH_DEBIT_TRANSFER", "saveAllowed": true, "parameters": [{ "name": "iban", "displayName": "IBAN", "description": "Enter your International Bank Account Number.", "type": "string", "placeholder": "DE12-3456-7890" "hidden": false, "constraints": [ { "required": { "message": "The IBAN is required." }, }, { "size": { "min": 15, "max": 34, "message": "The IBAN must have a length of 15 to 34 characters." }, }, { "pattern": { "regexp": "^[A-Z]{2}[0-9]{2}([\-\ ]{0,1}[0-9A-Z]{4}){4}[\-\ 0-9A-Z]{0,4}", "message": "The IBAN structure is invalid." } } ] } ] }] }
Java Bean Validation Annotation | Constraint Name | Comment |
---|---|---|
NotNull | required | |
Size | size | |
Pattern | pattern | Java regex pattern without any change is handed out |
Future | future | |
Past | past |
The REST API supports five basic - but most important - JavaBean annotations. In case more annotations are required, the functionality can easily be extended. To do so, a customer ConstraintRO
needs to be provided together with a Function
implementation to map the data from the annotation to the resource object.
// Provides a resource object for a custom Annotation "@MyMin" defining the lower limit of an integer public class MyMinConstraintRO extends ConstraintRO { public static final String TYPE_NAME = "my-min"; private final int minValue; public MyMinConstraintRO(String message, int minValue) { super(TYPE_NAME, message); this.minValue = minValue; } public int getMinValue() { return minValue; } } // Do the mapping in a special function public class MyMinConstraintROMapper implements Function<MyMin, MyMinConstraintRO> { @Inject @Constraint private Function<String, String> messageMapper; @Override public MyMinConstraintRO apply(MyMin mymin) { String message = messageMapper.apply(mymin.message()); return new MyMinConstraintRO(message, mymin.minimum()); } } // the new mapping needs to be registed public class AppSfRestCommonMapperModule extends AbstractModule { @Override protected void configure() { MapBinder<Class<?>, Function<? extends Annotation, ? extends ConstraintRO>> constraintMappers = MapBinder .newMapBinder(binder(), new TypeLiteral<Class<?>>() {}, new TypeLiteral<Function<? extends Annotation, ? extends ConstraintRO>>() {}); constraintMappers.addBinding(MyMin.class).to(MyMinConstraintROMapper.class); } }
Using the request, the client can resolve the list of active (limited and open tender) payments assigned to the current basket. The request supports includes to retrieve also the related payment methods and payment instruments in a single request.
{ "data": [ { "baseAmount": { "gross": { "currency": "USD", "value": 13055.07 } }, "id": "open-tender", "paymentCosts": { "gross": { "currency": "USD", "value": 0 }, "net": { "currency": "USD", "value": 0 }, "tax": { "currency": "USD", "value": 0 } }, "paymentInstrument": "ISH_CASH_ON_DELIVERY", "paymentMethod": "ISH_CASH_ON_DELIVERY", "totalAmount": { "gross": { "currency": "USD", "value": 13055.07 } } } ], "included": { "paymentMethod": { "ISH_CASH_ON_DELIVERY": { "default": false, "description": "Just pay your order directly when it gets delivered!", "displayName": "Cash on Delivery", "id": "ISH_CASH_ON_DELIVERY", "paymentInstruments": [ "ISH_CASH_ON_DELIVERY" ], "restricted": false, "saveAllowed": true } }, "paymentInstrument": { "ISH_CASH_ON_DELIVERY": { "id": "ISH_CASH_ON_DELIVERY" } } } }
The request is intended to allow saving the customer entered data at the basket. It is not required in case the payment method does not have data to be saved (technically: no PropertyGroup
returned by getPaymentParameterDescriptors(PaymentContext)
).
{ "paymentMethod" : "ISH_DIRECT_DEBIT", "parameters" : [ { "name": "IBAN", "value": "DE12345678901234" }, { "name": "holder", "value": "Patricia Miller" } ] }
{ "data": { "accountIdentifier": "************1234", "id": "3EQKAB2_MmMAAAFqY89pwU8_", "parameters": [ { "name": "holder", "value": "Patricia Miller" }, { "name": "IBAN", "value": "************1234" } ] } }
This assigns a new payment to the basket. In case the new payment is an open tender payment and there is already such a payment assigned to the basket, the request will fail. The old open tender payment needs to be removed first. In case the payment is a limited tender payment, an existing open tender may be removed automatically if the limited tender completely covers the basket. If there is a limited tender which completely covers the basket, then an open tender payment cannot be created without removing the limited tender payment first.
{ "paymentInstrument" : "4j0KAB2_28MAAAFuueJdhmPp" }
{ "data": { "baseAmount": { "currency": "USD", "value": 628.34 }, "id": "6vYKAB2_Qa0AAAFuTnNdhmPq", "openTender": true, "paymentCosts": { "gross": { "currency": "USD", "value": 0.00 }, "net": { "currency": "USD", "value": 0.0 }, "tax": { "currency": "USD", "value": 0.00 } }, "paymentInstrument": "d3EKAB2_dhUAAAFu_iJdhmPn", "paymentMethod": "ISH_DEBIT_TRANSFER", "redirectRequired": false, "status": "Unprocessed", "totalAmount": { "currency": "USD", "value": 628.34 } } }
Only a single open tender payment can be assigned to the basket. In order to reduce the effort for the client for deleting the old open tender payment and assigning a new one, a shortcut is possible. The API provides an alias for the active open tender payment at the basket, which can be used to create or replace it with a single request. Similar to the POST /baskets/<basketID>/payments/
request, an open tender payment can only be created if there is no limited tender payment that fully covers the basket. In that case the limited tender payment would have to be removed first.
{ "paymentInstrument" : "3EQKAB2_MmMAAAFqY89pwU8_" }
{ "data": { "baseAmount": { "gross": { "currency": "USD", "value": 628.34 } }, "id": "open-tender", "paymentCosts": { "gross": { "currency": "USD", "value": 0 }, "net": { "currency": "USD", "value": 0 }, "tax": { "currency": "USD", "value": 0 } }, "paymentInstrument": "3EQKAB2_MmMAAAFqY89pwU8_", "paymentMethod": "ISH_DEBIT_TRANSFER", "redirectRequired": false, "totalAmount": { "gross": { "currency": "USD", "value": 628.34 } } }, "included": { "paymentInstrument": { "3EQKAB2_MmMAAAFqY89pwU8_": { "accountIdentifier": "************1234", "id": "3EQKAB2_MmMAAAFqY89pwU8_", "parameters": [ { "name": "holder", "value": "Patricia Miller" }, { "name": "IBAN", "value": "************1234" } ] } } } }
This removes the payment from the basket. See the following example for details:
{ "infos": [ { "code": "payment.deletion.info", "message": "The payment has been deleted.", "status": "200" } ] }
This removes the payment instrument from the basket. In case it was currently assigned as active payment, the payment is deleted, too.
{ "infos": [ { "code": "payment-instrument.deletion.info", "message": "The payment instrument has been deleted.", "status": "200" } ] }
These requests are only supported if the payment method supports a kind of redirecting workflow. The request could be used for all kinds of redirect.
One possibility to provide the three required URLs is the Create Payment request.
{ "instrument": "ThePaymentInstrumentUUID", "redirect": { "successUrl": "http://storefront.url/success", "cancelUrl": "http://storefront.url/cancel", "failureUrl": "http://storefront.url/failure" } }
{ "data": { "baseAmount": { "gross": { "currency": "USD", "value": 13055.07 } }, "id": "open-tender", "paymentCosts": { "gross": { "currency": "USD", "value": 0 }, "net": { "currency": "USD", "value": 0 }, "tax": { "currency": "USD", "value": 0 } }, "paymentInstrument": "ISH_CASH_ON_DELIVERY", "paymentMethod": "ISH_CASH_ON_DELIVERY", "totalAmount": { "gross": { "currency": "USD", "value": 13055.07 } }, "redirect": { "redirectUrl": "https://server.payment-provider.com/" } } }
The same approach can be used if POST /baskets/<id>/payments
is supported.
Alternatively, a separate request to a dedicated resource is possible. The structure is basically the same. The difference is that only the content of the "redirect"
section is used in the request. By providing this alternative request, the client can choose the best option for its implementation. In most cases the ID will be open-tender
(LimitedTender
payments would have to use the ID, but these kinds of payment methods usually do not use redirect.)
{ "redirect": { "successUrl": "http://storefront.url/successPage?paymentID=0815", "cancelUrl": "http://storefront.url/cancel", "failureUrl": "http://storefront.url/failure" } }
The response is the same as in the section above (POST /.../payments
).
In case of "redirect before checkout" there is only one single option: The redirect must be completed before the order can be created. That means that there is no need for a special handling for closed browser windows. In such a case the basket is still available and no special handling is required. This is completely different in case of a redirect after checkout and will be described at the order resource.
{ "redirect": { "parameters" : [ { "name": "transactionID", "value": "id from provider" } ], "status": "success" // map to the redirect status (valid values: success, cancel, failure) mapped by the client from the called URL to these values } }
{ "data": { "baseAmount": { "gross": { "currency": "USD", "value": 1046.4 } }, "id": "open-tender", "paymentCosts": { "gross": { "currency": "USD", "value": 0 }, "net": { "currency": "USD", "value": 0 }, "tax": { "currency": "USD", "value": 0 } }, "paymentInstrument": "ifUKAB2_yPIAAAFqGhxaH7rb", "paymentMethod": "ISH_CREDITCARD", "redirect": { "cancelUrl": "http://storefront.url/cancel", "failureUrl": "http://storefront.url/failure", "parameters": [ { "name": "Status", "value": "Success" }, { "name": "transactionID", "value": "id from provider" }, { "name": "RedirectAmount", "value": { "currencyMnemonic": "USD", "value": 1046.4, "available": true } } ], "redirectUrl": "http://icm-server/INTERSHOP/web/WFS/inSPIRED-inTRONICS-Site/en_US/-/USD/ISHPayRedirect-3DSecure;pgid=VWGQ3pi7ldqRpDkHwv2g8e9l0000UMdp5y8P;sid=0BQKAB2_FXEAAAFq3t8CvdWC?merchant=0815&password=intershop&service=in4KAB2_qLcAAAFp9fd4YuOd&amount=%24+1%2C046.40¤cy=USD&card_number=4111111111111111&card_type=vsa&successURL=http%3A%2F%2Fstorefront.url%2Fsuccess&failURL=http%3A%2F%2Fstorefront.url%2Ffailure&cancelURL=http%3A%2F%2Fstorefront.url%2Fcancel", "status": "SUCCESS", "successUrl": "http://storefront.url/success" }, "redirectRequired": false, "totalAmount": { "gross": { "currency": "USD", "value": 1046.4 } } } }
The REST sub-resource 'quotes' is available from ICM version 7.10.38.
.../baskets/<basket-id>/quotes
The endpoint allows to add/remove a quote instead of single items.
A quote can be added to the basket with the following request:
POST .../baskets/<basket-id>/quotes // Request payload { { "calculated" : true|false, "id" : "123", } } // Response payload - success { "data" : { "calculated" : true|false, "id" : "123", "displayName" : "For me only", "description" : "Best prices ever", "number" : "0123456789", "creationDate" : "1635428413969", "validFromDate" : "1635428400000", "validToDate" : "1636036800000", "sellerComment" : "I will go bankrupt!", "total" : { "net":{ "currency": "USD", "value": 514.68 } } }, "links" : [{ // link to .../baskets/<basket-id>/quotes/<quote-id> } ... ] "infos" : [{ }, ... ] } // Response payload - failure { "errors" : [{ }, ... ] }
The quotes contained in the basket can be retrieved by the following request:
GET .../baskets/<basket-id>/quotes // Response payload { [{ "calculated" : true|false, "id" : "123", "displayName" : "For me only", "description" : "Best prices ever", "number" : "0123456789", "creationDate" : "1635428413969", "validFromDate" : "1635428400000", "validToDate" : "1636036800000", "sellerComment" : "I will go bankrupt!", "total" : { "net":{ "currency": "USD", "value": 514.68 } } }, { "calculated" : true|false, "id" : "456", "displayName" : "For me only again", "description" : "Very best prices ever", "number" : "9876543210", "creationDate" : "1635428413969", "validFromDate" : "1635428400000", "validToDate" : "1636036800000", "sellerComment" : "I am bankrupt now!", "total" : { "net":{ "currency": "USD", "value": 25678.68 } } }] }
One or more quotes can be removed from the basket with the following request:
DELETE .../baskets/<basket-id>/quotes/<quote-id> // Response payload { "infos": [ { "code": "basket.delete_quote.info", "message": "The quote was deleted from the basket.", "status": "200" } ] }
There are no other requests for the basket-quote item resource currently.