Skip to main content

One post tagged with "deprecation"

View All Tags

ยท 11 min read
Markus

Minimum @springtree/eva-sdk-redux SDK version: 2.89.0

Context#

To help with the transition of dropping the SessionID a new set of helpers have been added to the SDK for working with the current cart/order. These helpers can be found on the getShoppingCartInfo reducer and they mimic the helpers as found in the eva-sdk-repo (recoil/redux). The removal of the SessionID will focus on these topics:

  • settings
  • cart operations
  • removal of services

We've used the POS app to test and verify the SDK (thanks Ruben for the assist). I will add the code of the POS order service (Angular based) to the end of this document. That should give you a reference for how to wire up the new SDK cart operations in your app. Note that the setting persistence mentioned below is not in that example code.

Settings#

These settings should be persisted by the application:

  • userToken
  • currentOrderId
  • applicationToken (anonymous cart usage only)

The sdks settings.changes$ observable can be used to update local storage on each change. On application startup you should restore these settings.

There is a new setting in the SDK you can use to disable the session ID from being sent. Set settings.disableSessionID to true to start working as if the deprecation is in effect.

userToken#

There is no change in handling user tokens.

currentOrderId#

This setting is new and will track the currently active order. The getShoppingCartInfo and getShoppingCart reducers will use this setting unless you specifically override it in the request payload. The request payload of getShoppingCartInfo will update the setting as needed. The cart operations (see below) will also update the setting if a new order was created. You should always start cart updates with getShoppingCartInfo as has always been the case.

applicationToken#

To access an anonymous cart/order without a SessionID you will need to supply the application token header. This is used so anonymous users cannot access other anonymous carts by guessing order ids. The backend will return a new application token in the EVA-Application-Token header which should be used on subsequent cart calls. The SDK manages this entire process for you you only need to persist and restore the applicationToken setting. There are interceptors in place that capture new tokens and the setting will ensure it is sent when needed. For apps that only work when logged in this setting can be skipped. I recommend implementing persisting and restoring regardless.

Cart operations#

The previous behaviour of the EVA.Core.GetShoppingCart service would automatically create new orders when a product was added on a session without a current cart/order. Without the session support the caller will need to first call EVA.Core.CreateOrder and use the returned order id. Also if the last line of an order was cancelled the order would be set to being completed disallowing any changes. Through a collection of helpers on the getShoppingCartInfo reducer the process of creating orders will be managed for you. These helpers do not require you to add the OrderID to the request as the current order id is used automatically. The 'add to cart' operation will automatically create and order for you. The cart modifying operations will throw an error if no current order exist and otherwise will use the current order id.

These are the add to cart operations available:

  • addProductToOrder
  • addServiceProductToOrder
  • addBundleProductToOrder
  • addWishListProductToOrder

These are the cart modification operations available:

  • cancelOrder
  • changeOrderLinesToCarryOut
  • changeOrderLinesToDelivery
  • changeOrderLinesToPickup
  • modifyQuantityOrdered
  • setRequestedDate
  • splitOrderLine
  • updateProductRequirementValuesForOrderLine
  • updateSerialNumber

Cart operation queue#

If you fire off multiple cart operations in rapid succession the SDK will queue them up and only fetch the new cart when all are done. This is useful for barcode scanning or application users running on multiple cans of Red Bull.

Cart operation result#

The cart operations themselves are done outside of the redux context. The operation result response provides access to the promise of the operation call and the cart update that was triggered because of it, Note that due to the operation queue multiple cart operation can have the same cart update promise. The helper methods all return promises and the catch handler can be used to capture any service errors. Note that for add to cart operations this could also be an error on the create order service. Below is a code snippet for how to use the operation result:

/** * The result of a single cart operation. Multiple operations can have a * reference to the same cart update promise * * @export * @interface ICartOperation * @template RES */export interface ICartOperationResult<RES> {  cartOperationPromise: Promise<RES>;  cartUpdatePromise: Promise<EVA.Core.GetShoppingCartInfoResponse>;  cartUpdateChainPromise: Promise<TGetShoppingCartInfoChainData>;}
// Assuming an async/await available// Using SDK helper method here directly as an example// See order service example below for easier typings and a better wrapper//const cartOperationPromise = getShoppingCartInfo.addToCart<EVA.Core.AddProductToOrderResponse>({  request: {    ProductID: newProductId,    QuantityOrdered: 1,    Children: [],    LineActionType: undefined, // Let the backend decide  },  reducer: addProductToOrder,});
try {  const operationResult = await cartOperationPromise;
  // This promise is actually already resolved at this point  //  const response = await operationResult.cartOperationPromise;
  // Handle success response here
  // And wait for the cart and its chain if you need to  //  await operationResult.cartUpdateChainPromise;} catch (error) {  // Log and handle the service response error  console.error('Oh noes...', error);}

Removal of services#

You should complete remove the following services from your app:

  • attachOrderToSession
  • detachOrderFromSession

Both operations can be done using a request on the getShoppingCartInfo reducer.

The equivalent of attaching an order is setting the current order id. Just supply the desired order in in the createFetchAction payload or update the setting and trigger an empty fetch request.

To detach the order you are effectively un-setting the current order. This can be done by setting the OrderID to 0 in the request payload and triggering a fetch. Again this can also be done using the setting and then trigger a fetch manually.

It is recommended to use the getShoppingCartInfo reducer to change the current order. You should have been using this reducer anyway to start all cart chaines.

Example order service#

This example comes from the next POS app update. The eva-sdk-redux typings are less flexible then the ones from the eva-sdk-repo. However with a bit of templating and wrapper method you can have fully typed requests and responses. This service also provides centralised method to set/unset the current order and to show the progress of the queue.

import { Injectable } from '@angular/core';import { ILoggable, Logger } from '../../../shared/decorators/member/logger';import {  ICartAddProductToOrderAction,  ICartAddServiceProductToOrderAction,  ICartAddBundleProductToOrderAction,  ICartAddWishListProductToOrderAction,  ICartCancelOrderAction,  ICartChangeOrderLinesToCarryOutAction,  ICartChangeOrderLinesToDeliveryAction,  ICartChangeOrderLinesToPickupAction,  ICartModifyQuantityOrderedAction,  ICartSetRequestedDateAction,  ICartSplitOrderLineAction,  ICartUpdateProductRequirementValuesForOrderLineAction,  ICartUpdateSerialNumberAction,  getShoppingCartInfo,  store,  addProductToOrder,  addServiceProductToOrder,  addBundleProductToOrder,  addWishListProductToOrder,  cancelOrder,  changeOrderLinesToCarryOut,  changeOrderLinesToDelivery,  changeOrderLinesToPickup,  modifyQuantityOrdered,  setRequestedDate,  splitOrderLine,  updateProductRequirementValuesForOrderLine,  updateSerialNumber,} from '@springtree/eva-sdk-redux';import { delay, map } from 'rxjs/operators';
/** * The order service provides access to the current cart/order and relies * on the SDK helper methods on getShoppingCartInfo to function. * The current order id is also managed through the SDK in `settings.currentOrderId`. * The current order id is persisted to local storage * * @export * @class OrderService * @implements {ILoggable} */@Logger()@Injectable()export class OrderService implements ILoggable {  /**   * Our context aware logger instance   *   * @type {Partial<Console>}   */  public logger: Partial<Console>;
  /**   * The highest seen pending operation count.   * Resets when the operation queue drains and operation count hits 0   *   * @private   * @type {number}   */  private maxOperationCount: number = 0;
  /**   * Calculates the queue progress and return it as an observable.   * The queue progress is a number between 0 and 1 which indicates the percentage   * of a progress bar being completed.   * The SDK keeps a count of the pending operations on the current cart/order.   */  public queueProgress$ = getShoppingCartInfo.pendingCartOperations$.pipe(    map((operationCount) => {      if (operationCount === 0) {        // Reset the maximum because the queue just drained        //        this.maxOperationCount = 0;      } else {        this.maxOperationCount = Math.max(this.maxOperationCount, operationCount);      }
      // If either the current count is 0 or the max count is 0 set the progress      // to 100%. This will retain the progress bar as completed when all      // operations are done      //      if (!operationCount || !this.maxOperationCount) {        return 1;      }
      // Calculate progress and ensure we do not return a negative number      // Ensure max is always seen as 1 so we don't divide by 0      //      const progress = 1 - operationCount / this.maxOperationCount;      return Math.max(0, progress);    }),  );
  /**   * The second part of the progress queue is when to show it.   * We should only show it when there are operations pending.   * Using delay we will make the activation/deactivation transitions less jumpy.   *   */  public showQueue$ = getShoppingCartInfo.pendingCartOperations$.pipe(    delay(1500),    map((operationCount) => {      return operationCount > 0;    }),  );
  /**   * Switches the currently active order id in the SDK.   * Dispatching a new getShoppingCartInfo with the requested order id will also   * be reflected in the SDK `settings.currentOrderId` and is persisted to   * storage on change (see bootstrap-store.ts:96)   *   * @param {number} orderID   * @returns   */  public setCurrentOrderByID(orderID: number) {    const [action, promise, chainPromise] = getShoppingCartInfo.createFetchAction({      OrderID: orderID,    });    store.dispatch(action);    return [promise, chainPromise];  }
  /**   * Clear the current cart/order so a new one can be started when the next   * cart operation is requested.   * This used to be DetachOrderFromSession before sessions were deprecated   *   * @returns   */  public unsetOrderID() {    const [action, promise, chainPromise] = getShoppingCartInfo.createFetchAction({      OrderID: 0,    });    store.dispatch(action);    return [promise, chainPromise];  }
  /**   * Helper method to request the current cart data to be updated.   * Often used on component initialization to force an update   *   * @returns   */  public updateCurrentOrder() {    const [action, promise] = getShoppingCartInfo.createFetchAction();    store.dispatch(action);    return promise;  }
  /**   * Wrapper method to add a product to the current order   *   * @param {Omit<ICartAddProductToOrderAction, 'reducer'>} payload   * @returns   */  public addProductToOrder(payload: Omit<ICartAddProductToOrderAction, 'reducer'>) {    return getShoppingCartInfo.addToCart<EVA.Core.AddProductToOrderResponse>({      ...payload,      reducer: addProductToOrder,    });  }
  /**   * Wrapper method to add a service product to the current order   *   * @param {Omit<ICartAddServiceProductToOrderAction, 'reducer'>} payload   * @returns   */  public addServiceProductToOrder(payload: Omit<ICartAddServiceProductToOrderAction, 'reducer'>) {    return getShoppingCartInfo.addToCart<EVA.Core.AddServiceProductToOrderResponse>({      ...payload,      reducer: addServiceProductToOrder,    });  }
  /**   * Wrapper method to add a bundle product to the current order   *   * @param {Omit<ICartAddBundleProductToOrderAction, 'reducer'>} payload   * @returns   */  public addBundleProductToOrder(payload: Omit<ICartAddBundleProductToOrderAction, 'reducer'>) {    return getShoppingCartInfo.addToCart<EVA.Core.AddBundleProductToOrderResponse>({      ...payload,      reducer: addBundleProductToOrder,    });  }
  /**   * Wrapper method to add a product from a wish list to the current order   *   * @param {Omit<ICartAddWishListProductToOrderAction, 'reducer'>} payload   * @returns   */  public addWishListProductToOrder(payload: Omit<ICartAddWishListProductToOrderAction, 'reducer'>) {    return getShoppingCartInfo.addToCart<EVA.Core.SimpleShoppingCartResponse>({      ...payload,      reducer: addWishListProductToOrder,    });  }
  /**   * Wrapper method to cancel the current order   *   * @param {Omit<ICartCancelOrderAction, 'reducer'>} payload   * @returns   */  public cancelOrder(payload: Omit<ICartCancelOrderAction, 'reducer'>) {    return getShoppingCartInfo.modifyCart<EVA.Core.CancelOrderResponse>({      ...payload,      reducer: cancelOrder,    });  }
  /**   * Wrapper method to change one or more order lines to be carry out   *   * @param {Omit<ICartChangeOrderLinesToCarryOutAction, 'reducer'>} payload   * @returns   */  public changeOrderLinesToCarryOut(payload: Omit<ICartChangeOrderLinesToCarryOutAction, 'reducer'>) {    return getShoppingCartInfo.modifyCart<EVA.Core.ShoppingCartResponse>({      ...payload,      reducer: changeOrderLinesToCarryOut,    });  }
  /**   * Wrapper method to change one or more order lines to be delivery   *   * @param {Omit<ICartChangeOrderLinesToDeliveryAction, 'reducer'>} payload   * @returns   */  public changeOrderLinesToDelivery(payload: Omit<ICartChangeOrderLinesToDeliveryAction, 'reducer'>) {    return getShoppingCartInfo.modifyCart<EVA.Core.ShoppingCartResponse>({      ...payload,      reducer: changeOrderLinesToDelivery,    });  }
  /**   * Wrapper method to change one or more order lines to be pick up   *   * @param {Omit<ICartChangeOrderLinesToPickupAction, 'reducer'>} payload   * @returns   */  public changeOrderLinesToPickup(payload: Omit<ICartChangeOrderLinesToPickupAction, 'reducer'>) {    return getShoppingCartInfo.modifyCart<EVA.Core.ShoppingCartResponse>({      ...payload,      reducer: changeOrderLinesToPickup,    });  }
  /**   * Wrapper method to modify the ordered quantity for an order line   *   * @param {Omit<ICartModifyQuantityOrderedAction, 'reducer'>} payload   * @returns   */  public modifyQuantityOrdered(payload: Omit<ICartModifyQuantityOrderedAction, 'reducer'>) {    return getShoppingCartInfo.modifyCart<EVA.Core.ModifyQuantityOrderedResponse>({      ...payload,      reducer: modifyQuantityOrdered,    });  }
  /**   * Wrapper method to set the requested order date   *   * @param {Omit<ICartSetRequestedDateAction, 'reducer'>} payload   * @returns   */  public setRequestedDate(payload: Omit<ICartSetRequestedDateAction, 'reducer'>) {    return getShoppingCartInfo.modifyCart<EVA.Core.EmptyResponseMessage>({      ...payload,      reducer: setRequestedDate,    });  }
  /**   * Wrapper method to split an order line   *   * @param {Omit<ICartSplitOrderLineAction, 'reducer'>} payload   * @returns   */  public splitOrderLine(payload: Omit<ICartSplitOrderLineAction, 'reducer'>) {    return getShoppingCartInfo.modifyCart<EVA.Core.SplitOrderLineResponse>({      ...payload,      reducer: splitOrderLine,    });  }
  /**   * Wrapper method to update product requirement values for an order line   *   * @param {Omit<ICartUpdateProductRequirementValuesForOrderLineAction, 'reducer'>} payload   * @returns   */  public updateProductRequirementValuesForOrderLine(    payload: Omit<ICartUpdateProductRequirementValuesForOrderLineAction, 'reducer'>,  ) {    return getShoppingCartInfo.modifyCart<EVA.Core.EmptyResponseMessage>({      ...payload,      reducer: updateProductRequirementValuesForOrderLine,    });  }
  /**   * Wrapper method to split an order line   *   * @param {Omit<ICartUpdateSerialNumberAction, 'reducer'>} payload   * @returns   */  public updateSerialNumber(payload: Omit<ICartUpdateSerialNumberAction, 'reducer'>) {    return getShoppingCartInfo.modifyCart<EVA.Core.EmptyResponseMessage>({      ...payload,      reducer: updateSerialNumber,    });  }}