Concept - Basket REST API (v1)

1 Introduction

This concept is valid for ICM version 7.10.13.4 and higher.

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 documentation because of its nature describing the structure of requests and responses in a more static way.

1.1 Glossary

TermDescription
payment methodConfigured instance of a payment connector implementation to a specific provider. Also known as payment service.
payment instrumentA saved set of data for a payment service at a basket.
paymentPayment instrument set at the basket to pay with.

1.2 References

2 Basket Resource

2.1 General

2.1.1 Basic Architecture

2.1.1.1 Versioning

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:

HTTP Accept Header for V1 requests of Basket REST API
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:

HTTP Accept Header for legacy requests of 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.

2.1.1.2 Class Hierarchy

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:

Concept__Basket_REST_API_Basic_Class_Hierarchy

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:

  • v1 Basket REST API provides two different resource class implementations for the basket's list and entity resources.
  • Both resource class implementations are quite simple and mainly include annotations for Swagger API documentation and simple calls of the request classes' invoke-method.
  • Request class implementations

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.

2.1.2 Transaction Handling

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:

Mark transaction as rollback only
// trigger roll back in case of an error
if (result.isError())
{
    Transaction tx = transactionMgr.getCurrentTransaction();
    tx.markRollbackOnly();
}

2.1.3 Error Handling

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.

Info message with parameters and path attributes
...
    "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.

2.1.4 Basket Calculation

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:

  1. Every basket manipulation operation, like adding a new item to the basket, should trigger a basket (re-)calculation, so that the REST client can always be sure that the server provides a basket with correct calculation results.
  2. Several basket manipulation operations should be processed by the client without triggering a basket (re-)calculation after each request. An example might be adding various items - for any reason - by single requests to the basket. A (re-)calculation after each request might neither be necessary nor desired due to performance issues.

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:

  • basket
  • item
  • shipping bucket

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:

Request: PATCH /baskets/<basket_id>
{
	"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.

2.1.5 Basket Validation

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.

Request: POST /baskets/<basket_id>/validations
{
	"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.

Response: POST /baskets/<basket_id>/validations
{
    "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"
        ]
    }
}

2.1.6 Extensibility

There are several concepts to extend the basket REST API v1.

2.1.6.1 Attribute Subresource

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:

  • basket
  • line item
  • address

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:

  • boolean
  • date
  • decimal
  • double
  • integer
  • long
  • money
  • quantity
  • string
  • text
  • multiple boolean
  • multiple date
  • multiple decimal
  • multiple double
  • multiple integer
  • multiple long
  • multiple string
2.1.6.1.1 Implementation

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.

Request: POST /baskets/<basket_id>/attributes
{
	"name": "testDate",
	"type": "Date",
	"value": "2019-01-31T18:23:00+02:00"
}
Response: POST /baskets/<basket_id>/attributes
{
    "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.

2.1.6.2 Customizable Requests and Responses

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:

  • Only values of type String  are supported.
  • Programming effort is necessary by adding extension classes of REST handler and object mapper(s).

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.

2.1.6.2.1 Request

The following JSON snippet of updating a basket contains the two regular fields commonShipToAddress and commonShippingMethod . Furthermore, JSON content contains the two fields string and integer 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.

POST /baskets/<basket_id>
{
	"commonShipToAddress": "urn:address:customer:7aEKAEsBn7EAAAFsnihVfB17:cWMKAEsBn7UAAAFsnihVfB17",
    "commonShippingMethod": "STD_GROUND",
    "string" : "foo",
    "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,String>

Note

All field values are converted to the java.util.String type in the server, even if they are specified by other JSON data types, like number .

Customizablity of REST client's 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> .

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:

REST Handler Extensions
// 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, String> myCustomData = basketRO.getAnyFields();
        // e.g. 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
2.1.6.2.2 Response

The following JSON snippet of getting a basket resource contains the two fields string and integer in the response body's data section that are not part of the Basket REST API:

GET /baskets/<basket_id>
{
    "data": {
            "buckets": [
                "1648241666"
            ],
            "calculated": true,
            "commonShipToAddress": "urn:address:customer:7aEKAEsBn7EAAAFsnihVfB17:cWMKAEsBn7UAAAFsnihVfB17",
            "commonShippingMethod": "STD_GROUND",
            "customer": "Patricia",
            ...
            "string" : "foo",
            "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 . A limitation here is that field values are always of type String . The reason for this is the related method that allows to set values of type string only:

+setAnyFields(String fieldName, String fieldValue): void

Customizablity of REST client's 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> .

ExtensibleFunction<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:

REST Object Mapper Extensions
// Custom basket resource object mapper that writes its own data to response body's custom fields.

@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");
        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 ObjectMapper Implementation
com.intershop.sellside.rest.basket.v1.capi.resourceobject.basket.BasketROcom.intershop.sellside.rest.basket.v1.internal.mapper.basket.BasketROMapper
com.intershop.sellside.rest.basket.v1.capi.resourceobject.basket.BasketTotalsROcom.intershop.sellside.rest.basket.v1.internal.mapper.basket.BasketTotalsROMapper
com.intershop.sellside.rest.basket.v1.capi.resourceobject.basket.RecurrenceROcom.intershop.sellside.rest.basket.v1.internal.mapper.basket.RecurrenceROMapper
com.intershop.sellside.rest.basket.v1.capi.resourceobject.lineitem.GiftMessageROcom.intershop.sellside.rest.basket.v1.internal.mapper.lineitem.GiftMessageROMapper
com.intershop.sellside.rest.basket.v1.capi.resourceobject.lineitem.GiftWrapROcom.intershop.sellside.rest.basket.v1.internal.mapper.lineitem.GiftWrapROMapper
com.intershop.sellside.rest.basket.v1.capi.resourceobject.lineitem.LineItemROcom.intershop.sellside.rest.basket.v1.internal.mapper.lineitem.LineItemROMapper
com.intershop.sellside.rest.basket.v1.capi.resourceobject.lineitem.LineItemPricingROcom.intershop.sellside.rest.basket.v1.internal.mapper.lineitem.LineItemPricingROMapper
com.intershop.sellside.rest.basket.v1.capi.resourceobject.lineitem.WarrantyROcom.intershop.sellside.rest.basket.v1.internal.mapper.lineitem.WarrantyROMapper
com.intershop.sellside.rest.basket.v1.capi.resourceobject.product.ProductROcom.intershop.sellside.rest.basket.v1.internal.mapper.product.ProductROMapper
com.intershop.sellside.rest.basket.v1.capi.resourceobject.promotion.DiscountROcom.intershop.sellside.rest.basket.v1.internal.mapper.promortion.DiscountROMapper
com.intershop.sellside.rest.basket.v1.capi.resourceobject.promotion.PromotionCodeROcom.intershop.sellside.rest.basket.v1.internal.mapper.promotion.PromotionCodeROMapper
com.intershop.sellside.rest.basket.v1.capi.resourceobject.promotion.PromotionROcom.intershop.sellside.rest.basket.v1.internal.mapper.promotion.PromotionROMapper
com.intershop.sellside.rest.basket.v1.capi.resourceobject.shipping.PackSlipMessageROcom.intershop.sellside.rest.basket.v1.internal.mapper.shipping.PackSlipMessageROMapper
com.intershop.sellside.rest.basket.v1.capi.resourceobject.shipping.ShippingBucketROcom.intershop.sellside.rest.basket.v1.internal.mapper.shipping.ShippingBucketROMapper
com.intershop.sellside.rest.basket.v1.capi.resourceobject.shipping.ShippingMethodROcom.intershop.sellside.rest.basket.v1.internal.mapper.shipping.PackMethodROMapper


2.2 Get List of Active Baskets

Response: GET /baskets
{
    "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:

  • This list contains all active baskets of the specified user, there is no separation between baskets that are created via the inSPIRED Storefront and the ones created by the REST API.
  • The entries in the list are sorted in descending order according to the basket's creation date. This means that the user's current (most recently created) basket is the first element in the list.

2.3 Delete Basket

A basket can be deleted using the following request:

Response: DELETE /baskets/<basket_id>
{
    "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.

3 Items Resource

3.1 Add Product to Basket

A product can be added to the basket by using the following POST request:

Request: POST baskets/<basket_id>/items?include=product,shipToAddress,attributes,shippingMethod
[
	{
		"product": "5920217",
		"quantity": {
			"value": "2"
		}
	}
]

This request is designed to add multiple products to the basket in the course of one request. Adding multiple products to the basket with one request can sometimes be quite beneficial for the performance. One example is to add all products of a wish list or product list with a single call, instead of adding all products with single requests.

3.2 Get the List of Items

Response GET baskets/<basket_id>/items/<item_id>
{
    "data": [{
            "quantity": {
                "value": 1,
                "unit": ""
            },
            "shippingMethod": "STD_GROUND",
            "shipToAddressUrn": "kS4KABvHkwcAAAFCdL4J6CtB"
        }
    ],
    "id": "SLgKDACqC9IAAAFCiCdCMC0t",
    "included": {
        "product": {
            "name": "Acer Predator G3 G3610",
            "sku": "10809311",
            "longDescription": "The Acer Predator G3 Series has a sophisticated design that glows with gaming passion. Powered by the latest processors and high-end graphics solutions, this awesome machine delivers killer performance, dazzling HD multimedia, and enhanced media management to exceed your gaming expectations and more!<br/><br/><b>Premium design</b><br/>Easy-to-reach, top-side USB placement simplifies cable management and saves space. On the backside, an ergonomically-designed handle provides maximum comfort and leverage for moving the machine around. Up front, the easy-swap hard drive bay (optional) ensures flexible expandability whenever you want.<br/><br/><b>Advanced technology</b><br/>The Predator G3 Series runs a seriously powerful processor, along with ultra-fast DDR3 memory for rapid multitasking. A wide range of top-brand graphics options are available with Microsoft® DirectX® 11, which enhances multimedia and gaming performance for stunning realism.<br/><br/><b>Immersive entertainment</b><br/>Acer Arcade™ Deluxe 4.0 integrates HD entertainment and sharing, and a Blu-ray Disc™ drive rounds out this stunning personal home-theater experience. Furthermore, the HDMI® port (optional) can connect your PC to HD peripherals for yet more avenues to exciting high-definition entertainment!",
            "shortDescription": "Intel Core i5-2400 Processor (6M Cache, 3.10 GHz), 4GB DDR3, 1TB 5400RPM SATA",
            "thumbnail": "/INTERSHOP/static/WFS/PrimeTech-PrimeTechSpecials-Site/-/PrimeTech/en_US/1XS/10809311-9609.jpg",
            "uri": "PrimeTech-PrimeTechSpecials-Site/-/products/10809311"
            "bundleProduct": false,
            "variationProduct": false,
        },
        "inventory": {
            "inStock": true,
            "availability": true,
        },
        "shipToAddress": {
            "link": "https://...",
            "state": "",
            "country": "Germany",
            "id": "kS4KABvHkwcAAAFCdL4J6CtB",
            "email": "patricia@test.intershop.de",
            "city": "Potsdam",
            "street": "Berliner Str. 20",
            "street2": "",
            "street3": "",
            "mobile": "",
            "phoneHome": "049364112677",
            "firstName": "Patricia",
            "lastName": "Miller",
            "title": "Mrs.",
            "addressName": "Patricia Miller, Berliner Str. 20, Potsdam",
            "countryCode": "DE",
            "postalCode": "14482",
            "phoneBusiness": ""
        },
        "shippingMethod": {
            "id": "STD_GROUND"
            "name": "Standard Ground",
            "shippingTimeMin": 3,
            "shippingTimeMax": 7,
        },
        "singleBasePrice": {
            "value": 993,
            "currencyMnemonic": "USD"
        },
        "price": {
            "value": 993,
            "currencyMnemonic": "USD"
        },
        "salesTaxes": [
            {
                "type": "AppliedTax",
                "amount": {
                    "type": "Money",
                    "value": 343.94,
                    "currencyMnemonic": "USD"
                },
                "rate": 0.19
            }
        ],
        "totals": {
            "value": 993,
            "currencyMnemonic": "USD"
        },
    }
}

4 Basket Payment Resource

4.1 Get Eligible Payment Methods

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.

Response: GET /baskets/<basketID>/eligible-payment-methods?include=paymentInstruments
{
    "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"
            }
        }
    }
}

4.1.1 Provide Metadata for Payment Parameters

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

  • Needed fields (names, labels and type)
  • Required or optional
  • Type of display (hidden, text input, ...)
  • Syntactical validation via simple constraints

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.

Response: GET /baskets/<basketID>/eligible-payment-methods?include=paymentInstruments
{
    "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."
                          }
                    }
                ]
             }
        ]
    }]
}

4.1.1.1 Mapping of Annotations

Java Bean Validation AnnotationConstraint NameComment

NotNull

required
Sizesize
PatternpatternJava regex pattern without any change is handed out
Futurefuture
Pastpast

4.1.1.2 Extend Annotation Mapping

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.

JavaBean Annotation Mapper Extensions
// 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);
    }
}

4.2 Get Active Payments

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.

Response: GET /baskets/<basketID>/payments?include=paymentInstrument,paymentMethod
{
    "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"
            }
        }
    }
}

4.3 Create a Payment Instrument

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)).

Request: POST /baskets/<basketID>/payment-instruments?include=paymentMethod
{
    "paymentMethod" : "ISH_DIRECT_DEBIT",
    "parameters" : [
        {
            "name": "IBAN",
            "value": "DE12345678901234"
        },
		{
            "name": "holder",
            "value": "Patricia Miller"
        }
    ]
}
Response: POST /baskets/<basketID>/payment-instruments?include=paymentMethod
{
    "data": {
        "accountIdentifier": "************1234",
        "id": "3EQKAB2_MmMAAAFqY89pwU8_",
        "parameters": [
            {
                "name": "holder",
                "value": "Patricia Miller"
            },
            {
                "name": "IBAN",
                "value": "************1234"
            }
        ]
    }
}

4.4 Assign a Payment to the Basket

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.

Request: POST /baskets/<basketID>/payments/
{
    "paymentInstrument" : "4j0KAB2_28MAAAFuueJdhmPp"
}
Response: POST /baskets/<basketID>/payments/
{
    "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
        }
    }
}

4.5 Create/Replace Open Tender Payment

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.

Request: PUT /baskets/<basketID>/payments/open-tender?include=paymentInstrument,paymentMethod
{
    "paymentInstrument" : "3EQKAB2_MmMAAAFqY89pwU8_"
}
Response: PUT /baskets/<basketID>/payments/open-tender?include=paymentInstrument
{
    "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"
                    }
                ]
            }
        }
    }
}

4.6 Remove a Payment

This removes the payment from the basket. See the following example for details:

Response: DELETE /baskets/<basketID>/payments/<paymentID>
{
    "infos": [
        {
            "code": "payment.deletion.info",
            "message": "The payment has been deleted.",
            "status": "200"
        }
    ]
}

4.7 Remove a Payment Instrument

This removes the payment instrument from the basket. In case it was currently assigned as active payment, the payment is deleted, too.

Response: DELETE /baskets/<basketID>/payment-instruments/<paymentInstrumentID>
{
    "infos": [
        {
            "code": "payment-instrument.deletion.info",
            "message": "The payment instrument has been deleted.",
            "status": "200"
        }
    ]
}

4.8 Prepare and Handle Redirect

4.8.1 General Workflow

workflow redirect before checkout


4.8.2 Prepare Redirect While Creating Payment

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.

Request: PUT /baskets/<basketID>/payments/open-tender
{
    "instrument": "ThePaymentInstrumentUUID",
    "redirect": {
        "successUrl": "http://storefront.url/success",
        "cancelUrl": "http://storefront.url/cancel",
        "failureUrl": "http://storefront.url/failure"
    }
}
Response: PUT /baskets/<basketID>/payments/open-tender
{
    "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.

4.8.3 Prepare Redirect Using Separate Request

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.)

Request: PATCH /baskets/<basketID>/payments/<paymentID>
{
    "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).

4.8.4 Handle Callback From Redirect

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.

Request: PATCH /baskets/<basketID>/payments/<paymentID>
{
    "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
    }
}
Response: PATCH /baskets/<basketID>/payments/<paymentID>
{
    "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
            }
        }
    }
}

Disclaimer

The information provided in the Knowledge Base may not be applicable to all systems and situations. Intershop Communications will not be liable to any party for any direct or indirect damages resulting from the use of the Customer Support section of the Intershop Corporate Web site, including, without limitation, any lost profits, business interruption, loss of programs or other data on your information handling system.

Customer Support
Knowledge Base
Product Resources
Support Tickets