From 290c2c14c0d1a20169b320249499cccec044f951 Mon Sep 17 00:00:00 2001 From: Vasil Dimitrov Date: Wed, 15 Oct 2025 16:37:09 +0300 Subject: [PATCH 1/5] Add abandon and fulfill to shop api --- components/js-api-client/README.md | 4 +- .../src/core/shop/create-cart-manager.ts | 31 ++++++++ .../js-api-client/tests/shop/cart.test.ts | 79 +++++++++++++------ 3 files changed, 89 insertions(+), 25 deletions(-) diff --git a/components/js-api-client/README.md b/components/js-api-client/README.md index b6c31413..8f0fcc68 100644 --- a/components/js-api-client/README.md +++ b/components/js-api-client/README.md @@ -304,11 +304,13 @@ const hydrated = await cart.hydrate({ items: [{ sku: 'SKU-1', quantity: 1 }], }); -// Add/remove items and place the order +// Add/remove items, abandon or place and fulfill the cart and assign the orderId await cart.addSkuItem(hydrated.id, { sku: 'SKU-2', quantity: 2 }); await cart.setCustomer(hydrated.id, { identifier: 'customer-123', email: 'john@doe.com' }); await cart.setMeta(hydrated.id, { merge: true, meta: [{ key: 'source', value: 'web' }] }); +await cart.abandon(hydrated.id); await cart.place(hydrated.id); +await cart.fulfill(hydrated.id, orderId); ``` ## Signature verification (async) diff --git a/components/js-api-client/src/core/shop/create-cart-manager.ts b/components/js-api-client/src/core/shop/create-cart-manager.ts index 523ed8c2..1b0ceabe 100644 --- a/components/js-api-client/src/core/shop/create-cart-manager.ts +++ b/components/js-api-client/src/core/shop/create-cart-manager.ts @@ -28,6 +28,35 @@ export const createCartManager = (apiClient: ClientInterface) => { return response.place; }; + const abandon = async (id: string, onCart?: OC) => { + const mutation = { + abandon: { + __args: { + id, + }, + id: true, + ...onCart, + }, + }; + const response = await apiClient.shopCartApi<{ abandon: WithId }>(jsonToGraphQLQuery({ mutation })); + return response.abandon; + }; + + const fulfill = async (id: string, orderId: string, onCart?: OC) => { + const mutation = { + fulfill: { + __args: { + id, + orderId, + }, + id: true, + ...onCart, + }, + }; + const response = await apiClient.shopCartApi<{ fulfill: WithId }>(jsonToGraphQLQuery({ mutation })); + return response.fulfill; + }; + const addSkuItem = async (id: string, intent: CartSkuItemInput, onCart?: OC) => { const input = CartSkuItemInputSchema.parse(intent); const mutation = { @@ -124,6 +153,8 @@ export const createCartManager = (apiClient: ClientInterface) => { return { hydrate, place, + fulfill, + abandon, addSkuItem, removeItem, setMeta, diff --git a/components/js-api-client/tests/shop/cart.test.ts b/components/js-api-client/tests/shop/cart.test.ts index 50f70d0d..a748e645 100644 --- a/components/js-api-client/tests/shop/cart.test.ts +++ b/components/js-api-client/tests/shop/cart.test.ts @@ -1,18 +1,41 @@ import { test, expect, describe, beforeAll } from 'vitest'; -import { ClientInterface, createCartManager, createOrderFetcher, createOrderManager } from '../../src'; +import { ClientInterface, createCartManager, createOrderManager } from '../../src'; import { createApiClient } from '../util'; -import { AddressType, defaultCartContext } from '@crystallize/schema/shop'; +import { defaultCartContext, CustomerInput } from '@crystallize/schema/shop'; describe('Cart Tests', () => { let CrystallizeClient: ClientInterface; - let cartId: string; + let customerIdentifier = 'test-customer'; + const items = [ + { + sku: 'bishop-marble-normal', + name: 'Bamboo Chair', + quantity: 2, + }, + ]; + const customer: CustomerInput = { + isGuest: true, + addresses: [ + { + type: 'billing', + firstName: 'Test', + lastName: 'Customer', + }, + ], + identifier: 'test-customer', + firstName: 'Test', + lastName: 'Customer', + type: 'individual', + }; beforeAll(() => { CrystallizeClient = createApiClient(); }); - test('Hydrate a Cart', async () => { + test('Hydrate, place and fulfill a cart', async () => { + const orderManager = createOrderManager(CrystallizeClient); const cartManager = createCartManager(CrystallizeClient); + const cart = await cartManager.hydrate<{ customer: { lastName: string; @@ -20,26 +43,8 @@ describe('Cart Tests', () => { }>( { context: defaultCartContext, - customer: { - isGuest: true, - addresses: [ - { - type: 'billing', - firstName: 'Test', - lastName: 'Customer', - }, - ], - identifier: 'test-customer', - firstName: 'Test', - lastName: 'Customer', - type: 'individual', - }, - items: [ - { - sku: 'bishop-marble-normal', - quantity: 2, - }, - ], + customer, + items, }, { customer: { @@ -52,5 +57,31 @@ describe('Cart Tests', () => { expect(cart).toHaveProperty('customer'); expect(cart.customer).toHaveProperty('lastName'); expect(cart.customer.lastName).toBe('Customer'); + + await cartManager.place(cart.id); + + const order = await orderManager.register({ + cart: items, + customer: { + identifier: customerIdentifier, + type: 'individual', + }, + }); + + await cartManager.fulfill(cart.id, order.id); + }); + + test('Hydrate and abandon cart', async () => { + const cartManager = createCartManager(CrystallizeClient); + + const cart = await cartManager.hydrate({ + context: defaultCartContext, + customer, + items, + }); + + expect(cart).toHaveProperty('id'); + + await cartManager.abandon(cart.id); }); }); From 7114f9e364b5374d2f0152eb8403929a9a94b781 Mon Sep 17 00:00:00 2001 From: Vasil Dimitrov Date: Wed, 15 Oct 2025 16:40:58 +0300 Subject: [PATCH 2/5] Fix typo --- components/js-api-client/tests/shop/cart.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/js-api-client/tests/shop/cart.test.ts b/components/js-api-client/tests/shop/cart.test.ts index a748e645..3752fb54 100644 --- a/components/js-api-client/tests/shop/cart.test.ts +++ b/components/js-api-client/tests/shop/cart.test.ts @@ -71,7 +71,7 @@ describe('Cart Tests', () => { await cartManager.fulfill(cart.id, order.id); }); - test('Hydrate and abandon cart', async () => { + test('Hydrate and abandon a cart', async () => { const cartManager = createCartManager(CrystallizeClient); const cart = await cartManager.hydrate({ From 035a0e80d6394e1acf304812929666e34c4c043f Mon Sep 17 00:00:00 2001 From: Vasil Dimitrov Date: Wed, 15 Oct 2025 16:45:04 +0300 Subject: [PATCH 3/5] Bump timeout on a test --- components/js-api-client/tests/shop/cart.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/js-api-client/tests/shop/cart.test.ts b/components/js-api-client/tests/shop/cart.test.ts index 3752fb54..b2e29959 100644 --- a/components/js-api-client/tests/shop/cart.test.ts +++ b/components/js-api-client/tests/shop/cart.test.ts @@ -69,7 +69,7 @@ describe('Cart Tests', () => { }); await cartManager.fulfill(cart.id, order.id); - }); + }, 10000); test('Hydrate and abandon a cart', async () => { const cartManager = createCartManager(CrystallizeClient); From 1dca5533d647842b0a894c20d0973cf1be4d2870 Mon Sep 17 00:00:00 2001 From: Vasil Dimitrov Date: Wed, 15 Oct 2025 16:58:43 +0300 Subject: [PATCH 4/5] Bump timeout for the other test as well --- components/js-api-client/tests/shop/cart.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/js-api-client/tests/shop/cart.test.ts b/components/js-api-client/tests/shop/cart.test.ts index b2e29959..e91e9266 100644 --- a/components/js-api-client/tests/shop/cart.test.ts +++ b/components/js-api-client/tests/shop/cart.test.ts @@ -83,5 +83,5 @@ describe('Cart Tests', () => { expect(cart).toHaveProperty('id'); await cartManager.abandon(cart.id); - }); + }, 10000); }); From fb78b0401bcd077d629b41ad34534587d5017410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morel=20Se=CC=81bastien?= Date: Wed, 15 Oct 2025 10:58:42 -0700 Subject: [PATCH 5/5] feat(js-api-client): add fetch --- components/js-api-client/package.json | 2 +- .../src/core/shop/create-cart-manager.ts | 15 +++++++++++++++ components/js-api-client/tests/shop/cart.test.ts | 8 +++++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/components/js-api-client/package.json b/components/js-api-client/package.json index ca5fd9c0..2659a3fa 100644 --- a/components/js-api-client/package.json +++ b/components/js-api-client/package.json @@ -1,7 +1,7 @@ { "name": "@crystallize/js-api-client", "license": "MIT", - "version": "5.1.0", + "version": "5.2.0", "type": "module", "author": "Crystallize (https://crystallize.com)", "contributors": [ diff --git a/components/js-api-client/src/core/shop/create-cart-manager.ts b/components/js-api-client/src/core/shop/create-cart-manager.ts index 1b0ceabe..bfabc40e 100644 --- a/components/js-api-client/src/core/shop/create-cart-manager.ts +++ b/components/js-api-client/src/core/shop/create-cart-manager.ts @@ -14,6 +14,20 @@ import { transformCartCustomerInput, transformCartInput } from './helpers.js'; type WithId = R & { id: string }; export const createCartManager = (apiClient: ClientInterface) => { + const fetch = async (id: string, onCart?: OC) => { + const query = { + cart: { + __args: { + id, + }, + id: true, + ...onCart, + }, + }; + const response = await apiClient.shopCartApi<{ cart: WithId }>(jsonToGraphQLQuery({ query })); + return response.cart; + }; + const place = async (id: string, onCart?: OC) => { const mutation = { place: { @@ -153,6 +167,7 @@ export const createCartManager = (apiClient: ClientInterface) => { return { hydrate, place, + fetch, fulfill, abandon, addSkuItem, diff --git a/components/js-api-client/tests/shop/cart.test.ts b/components/js-api-client/tests/shop/cart.test.ts index e91e9266..1dc7587e 100644 --- a/components/js-api-client/tests/shop/cart.test.ts +++ b/components/js-api-client/tests/shop/cart.test.ts @@ -1,7 +1,7 @@ import { test, expect, describe, beforeAll } from 'vitest'; import { ClientInterface, createCartManager, createOrderManager } from '../../src'; import { createApiClient } from '../util'; -import { defaultCartContext, CustomerInput } from '@crystallize/schema/shop'; +import { defaultCartContext, CustomerInput, Cart } from '@crystallize/schema/shop'; describe('Cart Tests', () => { let CrystallizeClient: ClientInterface; @@ -69,6 +69,12 @@ describe('Cart Tests', () => { }); await cartManager.fulfill(cart.id, order.id); + + const fetchedCart = await cartManager.fetch(cart.id, { + state: true, + }); + + expect(fetchedCart.state).toBe('ordered'); }, 10000); test('Hydrate and abandon a cart', async () => {