[Developer Blog](/developer-blog/)

# Checking out a marketplace shopping cart

From a customer’s point of view, checking out on a Sharetribe marketplace is fairly simple. When checking out a cart, we need to add a few more actions in between the steps of the default flow under the hood. In this blog series, we are diving into the ins and outs of building a multi-vendor shopping cart with single-vendor checkout using the Sharetribe Developer Platform.

Mar 18, 2024

![Store checkout machine with a person holding the screen with one hand and a receipt with the other.](https://images.prismic.io/sharetribe/a17bd3bf-e450-4f61-9472-8decf36dad78_simon-kadula-OluDgzgCHp4-unsplash.jpg?auto=compress%2Cformat&fit=max&w=3840)

[![Sari has long light-brown hair and bangs and she's wearing a striped shirt with a slightly puffy collar. She's smiling at the camera.](https://images.prismic.io/sharetribe/b5473113-1cc5-4d08-a39d-2139e4ba1861_sari.png?auto=compress%2Cformat&fit=max&w=3840)](/author/sari-saariaho/)

Sari Saariaho

Developer Advocacy Guild Lead

**Note: The implementation in this article is built on top of [Sharetribe Web Template v4.1.0](https://github.com/sharetribe/web-template/releases/tag/v4.1.0), so if you are working on a later version, you may need to make some adjustments.**

From a customer’s point of view, checking out on a Sharetribe marketplace is fairly simple. The customer enters their payment information on the checkout page and clicks a button, and soon enough they are shown the order page.

Under the hood, things are a bit more complicated. The default checkout flow makes three to five API calls as a part of the journey from clicking “Buy now” to showing the order page:

* Initiate a transaction with Sharetribe API
* Confirm the payment intent with Stripe API
* Transition the transaction with Sharetribe API
* Optionally send a message with Sharetribe API, if the customer included one in the transaction
* Optionally save the customer’s payment method with Sharetribe API, if they selected to save it

When checking out a cart, we need to add a few more actions in between the steps of the default flow:

* After the main transaction has been initiated, initiate the child stock transactions and update their transaction ids in the main transaction’s protected data
* After the payment has been confirmed, confirm stock for the child stock transactions
* After the whole process is complete, clear all items from the cart that was just purchased

To make all of this happen, we need to make the following changes:

* Update _CheckoutPage.duck.js_ to handle cart checkout
* Update the server endpoint for transaction initiation to handle cart transactions
* Add the necessary mapped props and initial data handling to _CheckoutPage.js_
* Add and update helper functions on _CheckoutPageTransactionHelpers.js_
* Initiate the correct checkout flow on _CheckoutPageWithPayment.js_ when the customer submits the payment form

All the code examples used in this guide can be found in [this Gist](https://gist.github.com/SariSaar/722e1e0b5e6cc298d4ba04891e60478b).

In this blog series, we are diving into the ins and outs of building a multi-vendor shopping cart with single-vendor checkout using the Sharetribe Developer Platform. The previous articles in this guide dived into

* [adding listings to cart,](/developer-blog/adding-items-to-cart/)
* [viewing cart items,](/developer-blog/viewing-cart-items/)
* [calculating shopping cart price](/developer-blog/calculating-shopping-cart-price/), and
* [designing a shopping cart transaction flow](/developer-blog/designing-a-shopping-cart-transaction-flow/)

If you didn't already, read those first!

## Update CheckoutPage.duck.js to handle cart checkout

---

Checking out a shopping cart actually begins on the cart page, as the customer clicks the cart page “Buy now” button. When that happens, the cart page calls the _CheckoutPage.duck_ _setInitialValues_ Redux action, which sets the payload to _CheckoutPage_ state.

The initial values sent from _CartPage_ look like this:

    const initialValues = {
      listing,
      cartListings: cartListingDetails,
      cartAuthorId: currentAuthor.id.uuid,
      orderData: {
        cart: authorCart,
        deliveryMethod: delivery,
      },
      confirmPaymentError: null,
    };

By default, _CheckoutPage_ state already contains attributes for _listing_, _orderData_, and _confirmPaymentError_. In other words, we only need to add two attributes to _CheckoutPage_ state: _cartListings_, and _cartAuthorId_. 

   src
    ├── containers
            ├── CheckoutPage
                    ├── CheckoutPage.duck.js

Add these attributes into _CheckoutPage.duck.js_ _initialState_:

const initialState = {
  ...,
  cartListings: null,
  cartAuthorId: null,
};

After calling _setInitialValues_, CartPage then navigates the user to CheckoutPage. At that point, the page immediately calls the _speculateTransaction_ thunk to get the upcoming transaction’s details without actually initiating the transaction yet. 

We will include cart in the order parameters later in this guide, but let’s make the necessary changes to _speculateTransaction_ at this point:

* remove references to _quantity_, as the main transaction process for purchases no longer handles stock, and
* add _cart_ to _orderData_

export const speculateTransaction = (
...
  const { deliveryMethod, bookingDates, cart, ...otherOrderParams } = orderParams;
  const bookingParamsMaybe = bookingDates || {};
  // Parameters only for client app's server
  const orderData = deliveryMethod ? { cart, deliveryMethod } : { cart };
  // Parameters for Marketplace API
  const transitionParams = {
    ...bookingParamsMaybe,
    ...otherOrderParams,
    cardToken: 'CheckoutPage_speculative_card_token',
  };

Once the customer clicks “Buy now” on the checkout page, the first step in processing the checkout is the _initiateOrder_ thunk. Let’s make corresponding changes there, too

* remove references to _quantity_, and
* add _cart_ to _orderData_

export const initiateOrder = (
...
  const { deliveryMethod, bookingDates, cart, ...otherOrderParams } = orderParams;
  const bookingParamsMaybe = bookingDates || {};
  // Parameters only for client app's server
  const orderData = deliveryMethod ? { cart, deliveryMethod } : { cart };
...

We still need to add a few thunks for managing the child transactions. From [this file](https://gist.github.com/SariSaar/722e1e0b5e6cc298d4ba04891e60478b#file-checkoutpage-duck-js), add the thunks _createStockReservationTransactions_ and _updateStockReservationTransactions_ to the end of _CheckoutPage.duck.js_.

The _createStockReservationTransactions_ thunk does the following things:

* It gets the cart listing ids from the main transaction’s protected data
* It maps the cart listing ids to an array of SDK calls, one for each listing id, with the correct stock reservation counts
* Then, it promises all the SDK calls, and reduces the response into an object with listing ids as keys and their associated stock transaction ids as values:

childTransactions: {
	[childListingId1]: childTransactionId1,
	[childListingId2]: childTransactionId2,
	...
}

* Then, it calls the SDK to update the parent transaction’s _protectedData_ with the _childTransactions_ attribute
* Finally, it returns the updated main transaction

The _updateStockReservationTransactions_ thunk does the following things:

* It gets the _childTransactions_ object from the main transaction’s protected data
* It maps the child transactions to an array of SDK calls to transition each one with the specified transition
* It promises all the SDK calls and then returns the main transaction

## Update the server endpoint for transaction initiation to handle cart transactions

---

Next, we need to update the template server endpoint that gets used for initiating transactions with a privileged transition. The server also has an endpoint for transitioning transactions with a privileged transition, which is used in cases where the customer has first started an inquiry with the provider, and then proceeds to start an order on that same listing. However, in this implementation we have not allowed users to first start an inquiry and then continue the same transaction with a cart purchase, so we do not need to update that endpoint to handle a cart.

In the _initiate-privileged_ endpoint, we want to allow both regular and cart transactions, because in this marketplace, bookings are not handled with a cart logic. This means that we need to determine whether the incoming request contains a _cart_ parameter, and then distinguish the behavior accordingly.

   server
      ├── api
            ├── initiate-privileged.js

Let’s start with importing the same cart line item calculation we already created for the _cart-transaction-line-items_ endpoint. We will alias it with _cartLineItems_ so that we know which calculation to use in which context. We will also import the listing id helper we created earlier.

const { transactionLineItems: cartLineItems } = require('../api-util/cartLineItems');
const { getListingIdsFromCart } = require('../api-util/lineItemHelpers');

We will need the value of _cart_ in this endpoint. In addition, we’ll define a boolean for whether or not the cart exists, so that we can conditionally pick the correct options later on.

module.exports = (req, res) => {
  const { isSpeculative, orderData, bodyParams, queryParams } = req.body;
  const { cart } = orderData;
  const isCart = !!cart;
...

If the transaction has a cart, we need to fetch all its listings, so we can redefine _listingPromise_. Note that since _listingPromise_ is now an array, we also need to wrap the non-cart listing promise in an array as well. That way, we can spread it as the attributes to _Promise.all_. 

We’ll also change the ordering of the _Promise.all_ attributes array – because we do not know how many listing promises we have, we move _fetchCommission(sdk)_ as the first Promise. That way, when handling the responses, we can assign the first item in the response array to _fetchAssetsResponse_ and the rest to _showListingResponse_. We also set the listing response to _listingData_ instead of _listing_ for better naming accuracy.

 const listingPromise = isCart
    ? () => getListingIdsFromCart(cart).map(id => sdk.listings.show({ id }))
    : () => [sdk.listings.show({ id: bodyParams?.params?.listingId })];
  Promise.all([fetchCommission(sdk), ...listingPromise()])
    .then(([fetchAssetsResponse, ...showListingResponse]) => {
      const listingData = isCart
        ? showListingResponse.map(resp => resp.data.data)
        : showListingResponse[0].data.data;
...

Finally, we determine which line item function to use to calculate line items, and pass _listingData_ instead of _listing_ to the function.

      const lineItemFunction = isCart ? cartLineItems : transactionLineItems;
      lineItems = lineItemFunction(
        listingData,
        { ...orderData, ...bodyParams.params },
        providerCommission,
        customerCommission
);

Once line items have been calculated, the rest of the endpoint proceeds the same whether there is a cart or not.

You can see all changes to the _initiate-privileged_ endpoint in [this Gist file](https://gist.github.com/SariSaar/722e1e0b5e6cc298d4ba04891e60478b#file-initiate-privileged-js).

## Add the necessary mapped props and initial data handling to CheckoutPage.js

---

The template uses _CheckoutPage.js_ as a wrapper container to show one of two alternatives:

* _CheckoutPageWithPayment.js_ is used when the transaction involves payments
* _CheckoutPageWithInquiryProcess.js_ is used for free messaging transactions.

This means that for mapping state and dispatch to props, we need to use _CheckoutPage.js_, and then pass the necessary props onwards to _CheckoutPageWithPayment.js_.

   src
    ├── containers
            ├── CheckoutPage
                    ├── CheckoutPage.js

First, add the necessary imports to _CheckoutPage.js_:

import {
  ...
  createStockReservationTransactions,
  updateStockReservationTransactions,
} from './CheckoutPage.duck';
import { clearCart } from '../CartPage/CartPage.duck';
...

In the very beginning of the component (titled _EnhancedCheckoutPage_), we have a _useEffect_ hook that sends _initialData_ to be set to _pageData_ in certain navigation situations, such as navigating through a link. Let’s add _cartListings_ to _initialData_ here. We will update _handlePageData_ a bit later in this guide.

const EnhancedCheckoutPage = props => {
...
  useEffect(() => {
    const {
      ...
      cartListings,
    } = props;
    const initialData = { orderData, listing, cartListings, transaction };
    const data = handlePageData(initialData, STORAGE_KEY, history);

Then, we can add the necessary attributes to _mapStateToProps_ and _mapDispatchToProps_. By default, _CheckoutPage.js_ passes all its props to the child components, so we don’t need to make changes to props handling itself.

const mapStateToProps = state => {
  const {
    ...
    cartListings,
    cartAuthorId,
  } = state.CheckoutPage;
  const { currentUser } = state.user;
  const { confirmCardPaymentError, paymentIntent, retrievePaymentIntentError } = state.stripe;
  return {
    ...
    cartListings,
    cartAuthorId,
  };
};

const mapDispatchToProps = dispatch => ({
...
  onReserveCartItemStock: (transaction, processAlias, transitionName, parentTransitionName) =>
    dispatch(
      createStockReservationTransactions(
        transaction,
        processAlias,
        transitionName,
        parentTransitionName
      )
    ),
  onUpdateCartItemStock: (transaction, transitionName) =>
    dispatch(updateStockReservationTransactions(transaction, transitionName)),
  onClearCart: authorId => dispatch(clearCart(authorId)),
});
...

Then, update the _setInitialValues_ function to also save _cartListings_ into session storage if the user is signed in:

CheckoutPage.setInitialValues = (initialValues, saveToSessionStorage = false) => {
  if (saveToSessionStorage) {
    const { listing, cartListings, orderData } = initialValues;
    storeData(orderData, listing, cartListings, null, STORAGE_KEY);
  }

  return setInitialValues(initialValues);
};

### Update data storing helpers in CheckoutPageSessionHelpers.js

Let's now update the necessary session helpers in _CheckoutPageSessionHelpers.js_ as well, so that the data we just set up gets saved correctly.

   src
    ├── containers
            ├── CheckoutPage
                    ├── CheckoutPageSessionHelpers.js

The _storeData_ helper adds data to session storage behind a specified key. Add _cartListings_ to the function parameters and the _data_ object.

// Stores given bookinData, listing, cartListings, and transaction to sessionStorage
export const storeData = (orderData, listing, cartListings, transaction, storageKey) => {
  if (window && window.sessionStorage && listing && orderData) {
    const data = {
      orderData,
      listing,
      cartListings,
      transaction,
      storedAt: new Date(),
    };
    ...
  }
};

The _storedData_ function retrieves data from session storage with the specified storage key. Here, add _cartListings_ to the _checkoutPageData_ destructuring, as well as the return statement.

// Get stored data
export const storedData = storageKey => {
  if (window && window.sessionStorage) {
    const checkoutPageData = window.sessionStorage.getItem(storageKey);
    const reviver = (k, v) => {...};
    // Note: orderData may contain bookingDates if booking process is used.
    const { orderData, listing, cartListings, transaction, storedAt } = checkoutPageData
      ? JSON.parse(checkoutPageData, reviver)
      : {};

    ...

    if (isStoredDataValid) {
      return { orderData, listing, cartListings, transaction };
    }
  }
  return {};
};

The final session helper we will update is _handlePageData_, which was called in the _useEffect_ hook when _CheckoutPage_ first loads.

/**
 * Save page data to sessionstorage if the data is passed through navigation
 *
 * @param {Object} pageData an object containing orderData, listing and transaction entities, and optionally cartListings.
 * @param {String} storageKey key for the sessionStorage
 * @param {Object} history navigation related object with pushState action
 * @returns pageData
 */
export const handlePageData = ({ orderData, listing, cartListings, transaction }, storageKey, history) => {
  // Browser's back navigation should not rewrite data in session store.
  // Action is 'POP' on both history.back() and page refresh cases.
  // Action is 'PUSH' when user has directed through a link
  // Action is 'REPLACE' when user has directed through login/signup process
  const hasNavigatedThroughLink = history.action === 'PUSH' || history.action === 'REPLACE';
  const hasDataInProps = !!(orderData && (listing || cartListings) && hasNavigatedThroughLink);
  if (hasDataInProps) {
    // Store data only if data is passed through props and user has navigated through a link.
    storeData(orderData, listing, cartListings, transaction, storageKey);
  }
  // NOTE: stored data can be empty if user has already successfully completed transaction.
  const pageData = hasDataInProps ? { orderData, listing, cartListings, transaction } : storedData(storageKey);
  return pageData;
};

## Add and update helper functions on CheckoutPageTransactionHelpers.js

---

Remember the multi-step default checkout flow we talked about in the beginning of this article? That flow is constructed in the _CheckoutPageTransactionHelpers.js_ file. 

   src
    ├── containers
            ├── CheckoutPage
                    ├── CheckoutPageTransactionHelpers.js

The file exports a function called _processCheckoutWithPayment_, which first constructs five individual sub-functions corresponding to the functionalities mentioned earlier:

* Initiate a transaction with Sharetribe API
* Confirm the payment intent with Stripe API
* Transition the transaction with Sharetribe API
* Optionally send a message with Sharetribe API, if the customer included one in the transaction
* Optionally save the customer’s payment method with Sharetribe API, if they selected to save it

The functions are built so that each function expects to receive a set of parameters that corresponds to the return value of the previous function. This way, they can eventually be composed into an async chain of promise calls.

When we add cart functionality here, we need to do the following things:

* create the necessary sub-functions for managing the cart stock transactions, paying close attention to the return values of each function, because they become the parameters to the next function in the chain
* pass the sub-functions to the _processCheckoutWithPayment_ function
* compose the async chain of promise calls in the correct order when handling a cart transaction, and using the default chain when not

A part of this work has already been done – when we created the _createStockReservationTransactions_ and _updateStockReservationTransactions_ thunks earlier in this guide, we set them to return the main transaction. This is because steps 2 and 4 in the default chain expect to get the transaction as their _fnParams_, so now we can inject functions calling those thunks into the chain.

We will handle the distinction between cart transactions and regular ones by creating a new helper function, _processCartCheckoutWithPayment_. In this function, we will construct the necessary sub-functions, and then call the main _processCheckoutWithPayment_ with the original parameters as well as the new functions.

/**
 * Create additional functions for processing cart checkout
 * @param {*} orderParams
 * @param {*} extraPaymentParams
 */
export const processCartCheckoutWithPayment = (
  orderParams,
  extraPaymentParams,
  cartItemProcessAlias,
  cartTransitions,
  onReserveCartItemStock,
  onUpdateCartItemStock,
  clearAuthorCart
) => {
/**
   * - create fn for triggering createStockReservationTransactions => this comes after initiateOrder
   * - create fn for triggering updateStockReservationTransactions with confirm => this comes after confirming Stripe but before confirming the backend
   * - create fn for clearing the cart author's cart
   */

  const { process } = extraPaymentParams;
  const fnReserveCartStock = fnParams => {
    return onReserveCartItemStock(
      fnParams,
      cartItemProcessAlias,
      cartTransitions.CART_TRANSITION_RESERVE_STOCK,
      process.transitions.UPDATE_CHILD_TRANSACTIONS
    );
  };

  const fnConfirmCartStock = fnParams => {
    return onUpdateCartItemStock(fnParams, cartTransitions.CART_TRANSITION_CONFIRM_STOCK);
  };

  const fnClearCart = fnParams => {
    clearAuthorCart();
    return fnParams;
  };

  const cartFns = {
    fnReserveCartStock,
    fnConfirmCartStock,
    fnClearCart,
  };

  return processCheckoutWithPayment(orderParams, extraPaymentParams, cartFns);
};

Now, we need to update _processCheckoutWithPayment_ to accept a new _cartFns_ parameter and then, towards the very end of the function, conditionally compose the _handlePaymentIntentCreation_ function either with or without the _cartFns_ functions.

export const processCheckoutWithPayment = (orderParams, extraPaymentParams, cartFns = null) => {
  ...
  const isCart = !!cartFns;
  let handlePaymentIntentCreation;
  if (isCart) {
    const { fnReserveCartStock, fnConfirmCartStock, fnClearCart } = cartFns;
    handlePaymentIntentCreation = composeAsync(
      fnRequestPayment,
      fnReserveCartStock,
      fnConfirmCardPayment,
      fnConfirmPayment,
      fnConfirmCartStock,
      fnSendMessage,
      fnSavePaymentMethod,
      fnClearCart
    );
  } else {
    handlePaymentIntentCreation = composeAsync(
      fnRequestPayment,
      fnConfirmCardPayment,
      fnConfirmPayment,
      fnSendMessage,
      fnSavePaymentMethod
    );
  }
  return handlePaymentIntentCreation(orderParams);
};

## Initiate the correct checkout flow on CheckoutPageWithPayment.js

---

We’re approaching the final step of the process! Well, at least the final file of this guide – _CheckoutPageWithPayment.js_. After this change, your customer will be able to check out their cart from a single vendor.

   src
    ├── containers
            ├── CheckoutPage
                    ├── CheckoutPageWithPayment.js

Again, let’s start with some imports. We’ll import the _processCartCheckoutWithPayment_ function we just created. In addition, we’ll import some details from the cart stock transaction process we created in an earlier blog post.

import {
  ...  processCartCheckoutWithPayment,
} from './CheckoutPageTransactionHelpers.js';
import {
  transitions as cartTransitions,
  graph as cartProcess,
} from '../../transactions/transactionProcessCartStock.js';
const { id: cartItemProcessAlias } = cartProcess;

Next, we’ll update the _getOrderParams_ function to pass the cart information to the backend endpoint correctly. Since we want to both use the cart in the line item calculation and save it in the transaction’s protected data, we need to include it in both places when creating order params.

Note, too, that we can remove _quantity_ handling from _orderParams_ – in the _CheckoutPage.duck.js_ step, we removed any quantity handling from the relevant endpoints, so we do not need to pass it here, either.

const getOrderParams = (pageData, shippingDetails, optionalPaymentParams, config) => {
  const cart = pageData.orderData?.cart;
  const cartMaybe = cart ? { cart } : {};
  const deliveryMethod = pageData.orderData?.deliveryMethod;
  const deliveryMethodMaybe = deliveryMethod ? { deliveryMethod } : {};
  const { listingType, unitType } = pageData?.listing?.attributes?.publicData || {};
  const protectedDataMaybe = {
    protectedData: {
      ...getTransactionTypeData(listingType, unitType, config),
      ...deliveryMethodMaybe,
      ...cartMaybe,
      ...shippingDetails,
    },
  };
  // These are the order parameters for the first payment-related transition
  // which is either initiate-transition or initiate-transition-after-enquiry
  const orderParams = {
    listingId: pageData?.listing?.id,
    ...deliveryMethodMaybe,
    ...cartMaybe,
    ...bookingDatesMaybe(pageData.orderData?.bookingDates),
    ...protectedDataMaybe,
    ...optionalPaymentParams,
  };
  return orderParams;
};

Next, we will need to update the _handleSubmit_ function that gets called when the customer clicks “Buy now”. First, let’s add the props we included on _CheckoutPage.js_ to the function props restructuring:

const handleSubmit = (values, process, props, stripe, submitting, setSubmitting) => {
  if (submitting) {
    return;
  }
  setSubmitting(true);
  const {
    ...
    onReserveCartItemStock,
    onUpdateCartItemStock,
    onClearCart,
    cartAuthorId,
  } = props;

By default, the function calls _processCheckoutWithPayment_. We will update this logic so that the function first checks whether this is a cart transaction, and then uses either the cart checkout function or the default one. Then, we will prepare some additional parameters for the cart checkout function, and then call _checkoutFn_ with all the necessary parameters.

  ...
  // These are the order parameters for the first payment-related transition
  // which is either initiate-transition or initiate-transition-after-enquiry
  const orderParams = getOrderParams(pageData, shippingDetails, optionalPaymentParams, config);
  const isCart = !!orderParams.cart;
  const checkoutFn = isCart ? processCartCheckoutWithPayment : processCheckoutWithPayment;
  const clearAuthorCart = () => onClearCart(cartAuthorId);
  const cartParamsMaybe = isCart
    ? [
        cartItemProcessAlias,
        cartTransitions,
        onReserveCartItemStock,
        onUpdateCartItemStock,
        clearAuthorCart,
      ]
    : [];
  // There are multiple XHR calls that needs to be made against Stripe API and Sharetribe Marketplace API on checkout with payments
  checkoutFn(orderParams, requestPaymentParams, ...cartParamsMaybe)
    .then(response => {
    ...

After these changes, you should be able to click “Buy now” on _CartPage_ and initiate the transaction!

## Summary

---

In this guide, we updated the marketplace to allow checking out a vendor’s shopping cart.

* We updated _CheckoutPage.duck.js_ to handle cart checkout
* We updated the server endpoint for transaction initiation to handle cart transactions
* We added the necessary mapped props and initial data handling to _CheckoutPage.js_
* We added and updated helper functions on _CheckoutPageTransactionHelpers.js_
* We updated _CheckoutPageWithPayment.js_ to initiate the correct checkout flow when the customer submits the payment form

The next article is the final instalment in this shopping cart series. In it, we will [display all cart listings](/developer-blog/showing-purchased-cart-listings/), instead of just the main listing, during and after checking out!

## You might also like...

[![A drawing of a flowchart on white paper on top of a wooden work area, with a manicured hand holding a marker and another hand holding down the paper.](https://images.prismic.io/sharetribe/013df4f4-c444-4a1b-ad83-289811834245_kelly-sikkema-lFtttcsx5Vk-unsplash.jpg?auto=compress%2Cformat&fit=max&w=3840)Designing a shopping cart transaction flow](/developer-blog/designing-a-shopping-cart-transaction-flow/)[![A farmer's market table full of vegetables and their price signs, with cherry tomato baskets and their price sign in the forefront.](https://images.prismic.io/sharetribe/c11f0fe7-abdb-48f7-9f56-2cd6591f8113_anne-preble-SAPvKo12dQE-unsplash.jpg?auto=compress%2Cformat&fit=max&w=3840)Calculating shopping cart price](/developer-blog/calculating-shopping-cart-price/)[![Two paper bags of groceries on a wooden floor. Visible on top are apples, bagels, cabbage, bananas, and a baguette.](https://images.prismic.io/sharetribe/d9240361-8fe7-405a-9e39-ecede4ad8a05_maria-lin-kim-8RaUEd8zD-U-unsplash.jpg?auto=compress%2Cformat&fit=max&w=3840)Showing purchased cart listings](/developer-blog/showing-purchased-cart-listings/)

## Liked this? Get email updates on new Sharetribe Developer Blog posts.

[Subscribe](#subscribe-dev-blog)