The VTEX shopping cart default behavior is to keep the cart alive until an order is placed. When this happens, a new cart is created. If a business rule requires a new cart every time a user logs out, we can use the Session Watcher to clear cart information.
It is not possible to get a brand new cart, but we can repurpose the current cart by removing any existing information on it.
This document will guide you on how to use the Session Watcher app to clean up an existing cart during logout.
Clients
VTEX IO Service Context gives you access to external clients. Clients are additional services with methods that give you access to different core applications. Here is a list of out-of-the-box client services available.
apps
assets
billing
billingMetrics
builder
events
id
licenseManager
masterdata
messagesGraphQL
metadata
registry
router
segment
settings
session
tenant
vbase
workspaces
catalogGraphQL
paymentProvider
You can always extend your App Clients if you need to give it access to external or internal providers.
Checkout Client
The VTEX IO Service Context does not have an out-of-the-box checkout client. The following steps will guide you on this setup of a client from our internal APIs.
- Create a new
./node/clients/checkout.ts
file where you can keep all the methods for theCheckout
class. This class will extend JanusClient (VTEX’s internal router), you can import it from@vtex/api
. It should look like the example below. Types are also imported from@vtex/api
.
_10import type { InstanceOptions, IOContext } from ‘@’vtex/api’_10import { JanusClient } from ‘@vtex/api’_10_10export class Checkout extends JanusClient {_10 constructor(context: IOContext, options?: InstanceOptions) {_10 super(context, { …options })_10 }_10}
- These internal APIs are going through
portal.vtexcommercestable.com.br/api/*
, which means we need to declare it as anoutbound-access
in ourmanifest.json
file under "policies".
_10"policies": [_10 {_10 "name": "outbound-access",_10 "attrs": {_10 "host": "portal.vtexcommercestable.com.br",_10 "path": "/api/*"_10 }_10},
The same thing is required if you're accessing an external endpoint. Always add the host
and path
as an outbound-access
.
Do not use protocol in the
host
(http, https). Thehost
does not support wildcard within the address. It can be either fixedhost
or only*
, never both. Thepath
supports wildcard.
- Now that you have an initial class setup, you can let your application know that it exists. Do this by importing it to the
Clients
constructor under./node/clients/index.ts
_10import { IOClients } from ‘@vtex/api’_10_10import { Checkout } from ‘@vtex/api’_10_10// Extend the default IOClients implementation with our own custom clients._10export class Clients extends IOClients {_10 public get checkout() {_10 return this.getOrSet(‘checkout’, Checkout)_10 }_10}
The checkout client will now be available to our handler, just like the others.
_10{_10 "name": "outbound-access",_10 "attrs": {_10 "host": "portal.vtexcommercestable.com.br",_10 "path": "/checkout/changeToAnonymousUser/*"_10}
- Now you need to create your methods. The first thing you need is to change our
orderForm
(shopping cart) to anonymous. This will erase theclientProfileData
, but will not remove the items from the cart, which will be done in another moment.
This method uses a path starting with /checkout
, which means we need to include it to our policy by creating a new outbound-access
entry.
Back tocheckout.ts
, add a new public method inside the Checkout
class.
_10public changeToAnonymous = (orderFormId: string) => {_10 return this.http.getRaw(`/checkout/changeToAnonymousUser/$\{orderFormId\}`, {_10 metric ‘change-to-anonymous’,_10 })_10}
JanusClient
gives you access tohttp
and its methods. This example usesgetRaw
, which will return the entire response, includinghttpCode
, headers, etc. If you do not need it, you can use onlyget
instead. Thismetric
property is used for logging, it is not not strictly necessary.
- Create the other methods,
orderForm
, andupdateItems
, as shown below.
_19public orderForm = (orderFormId: string) => {_19 return this.http.post(_19 `/api/checkout/pub/orderForm/$\{orderFormId\}`,_19 { expectedOrderFormSections: [‘items’] },_19 {_19 metric: ‘get-orderForm’,_19 }_19 )_19}_19_19public updateItems = (orderFormId: string, orderItems: any) => {_19 return this.http.post(_19 `/api/checkout/pub/orderForm/$\{orderFormId\}/items/update`,_19 { orderItems } ,_19 {_19 Metric: ‘update-orderForm-items’,_19 }_19 )_19}
Clear Cart handler
Back to ./resolvers/index.ts
load the checkout client from the context of our method clearCart
and expose both orderFormId
and email
from the request body.
The logical flow is:
You should have code similar to the example below.
_70/* eslint-disable no-console */_70import { json } from ‘co-body’_70_70export const resolvers = {_70 Routes: {_70 clearCart: async (ctx: Context) => {_70 Const {_70 req,_70 clients: { checkout },_70 } = ctx_70_70 const body: any = await json(req)_70 const email = body?.authentication?.storeUserEmail?.value ?? null_70 const orderFormId = body?.checkout?.orderFormId?.value ?? null_70_70 console.log(‘clearCart =>’, body)_70_70 ctx.set(‘Content-Type, ‘application/json’)_70 ctx.set(‘Cache-Control’, ‘no-cache, no-store’) _70_70 const res = {_70 public: {_70 demo: {_70 value: email ? ‘User Authenticated’ : ‘User not authenticated’,_70 },_70 },_70 }_70_70 // If user is not authenticated, and we have an orderFormId_70 if (!email && orderFormId) {_70 // Get the current orderForm data_70 const orderform: any await checkout.orderForm(orderFormId)_70_70 // Only if we have a user assigned to this orderForm_70 if (orderForm?.clientProfileData?.email) {_70 // Change to anonymous_70 await checkout.changeToAnonymous(orderFormId).catch((err) => {_70 if (err.response.status >= 400) {_70 console.log(‘Cart response error =>’, err.response.statusText)_70 } else {_70 console.log(‘Cart response all good =>’, err.response.status)_70 }_70 })_70_70 /*_70 * To remove items from the cart_70 * If we have items in the cart_70 */_70 if (orderForm?.items?.length) {_70 // Create a orderItems array with the item position and new quantity set to 0_70 const orderItems = orderForm.items.map((_: any, index: number) => {_70 return {_70 index,_70 quantity: 0,_70 }_70 })_70_70 // Update the cart items with the new quantity_70 await checkout.updateItems(orderFormId, orderItems).catch((err) => {_70 console.log(‘Error removing all items =>’, err)_70 })_70 }_70 }_70 }_70 _70 ctx.response.body = res_70 ctx.response.status = 200_70 },_70 },_70}
Don't forget to change the vendor in your
./manifest.json
file.
To link the app, run this command:
_10vtex link
If you wish, you can download this complete example