From 8703ca50da6b04c3d9fb04c7ea977114653418fc Mon Sep 17 00:00:00 2001 From: Lucas Vyskubenko Date: Fri, 7 Aug 2026 16:57:38 -0300 Subject: [PATCH 1/2] fix: passing ownership cookie during calculation --- CHANGELOG.md | 21 ++++ node/__fixtures__/shipping.ts | 11 +- node/__tests__/orderForm-query.test.ts | 83 +++++++++++++ node/__tests__/ownership-middleware.test.ts | 128 ++++++++++++++++++++ node/__tests__/shipping-utils.test.ts | 17 +-- node/__tests__/shipping.test.ts | 21 ++-- node/clients/checkout.ts | 41 +++++-- node/clients/ownership.ts | 57 +++++++++ node/package.json | 2 +- node/resolvers/orderForm.ts | 24 +++- node/resolvers/shipping.ts | 48 +++++--- node/resolvers/sla.ts | 2 +- node/utils/shipping.ts | 10 +- node/yarn.lock | 10 +- 14 files changed, 415 insertions(+), 60 deletions(-) create mode 100644 node/__tests__/ownership-middleware.test.ts create mode 100644 node/clients/ownership.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d8170fce..8638bdff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- The `CheckoutOrderFormOwnership` cookie is now preserved across every request + to the order form, so Checkout stops masking `clientProfileData` and + `shippingData` after a shopper has gained ownership. Three leaks were closed: + an empty ownership cookie returned by Checkout (which it emits whenever a + cart is created) is no longer forwarded over a valid one; a newly issued + ownership is written back into `vtex.ownerId` so the remaining Checkout calls + of the same request use it instead of the value `@withOwnerId` snapshotted + from the incoming request; and `updateOrderFormShipping` now forwards the + ownership cookie that the `shippingData` attachment rotates, which was + previously discarded. The ownership cookie is also forwarded by the + `orderForm` query regardless of `enableOrderFormOptimization`, since no other + app sets it. +- Every Checkout route now reads the ownership cookie back from its response, + through a client middleware rather than per-method plumbing. Previously only + the three methods built on the `*Raw` verbs could see their response headers, + which left `patch` routes such as `addItem` and `updateItems` unable to + capture a rotation at all, since `HttpClient` exposes no `patchRaw`. The + middleware also warns when Checkout hands back an empty ownership while the + request already holds one. + ## [0.68.0] - 2026-06-12 ### Fixed diff --git a/node/__fixtures__/shipping.ts b/node/__fixtures__/shipping.ts index f1e8171b..4af2cfd7 100644 --- a/node/__fixtures__/shipping.ts +++ b/node/__fixtures__/shipping.ts @@ -1,6 +1,5 @@ import { AddressType, DELIVERY, PICKUP_IN_POINT } from '../constants' import { EMPTY_ORDER_FORM } from './orderForm' -import { Clients } from '../clients' const SLA = { deliveryIds: [], @@ -313,8 +312,10 @@ export const ORDER_FORM_WITH_EMPTY_SHIPPING_DATA = { ...EMPTY_ORDER_FORM, } -export const clients = ({ - checkout: { - updateOrderFormShipping: jest.fn(), +export const shippingContext = ({ + clients: { + checkout: { + updateOrderFormShipping: jest.fn(), + }, }, -} as unknown) as Clients +} as unknown) as Context diff --git a/node/__tests__/orderForm-query.test.ts b/node/__tests__/orderForm-query.test.ts index e3b76b8d..2e79dbf7 100644 --- a/node/__tests__/orderForm-query.test.ts +++ b/node/__tests__/orderForm-query.test.ts @@ -108,6 +108,28 @@ describe('queries.orderForm — happy path', () => { expect(ctx.cookies.set).not.toHaveBeenCalled() }) + + it('forwards the ownership cookie even when enableOrderFormOptimization is false', async () => { + const ctx = setupQueryContext() + ctx.clients.checkout.orderFormRaw.mockResolvedValue({ + data: baseOrderForm(), + headers: { + 'set-cookie': [ + 'checkout.vtex.com=__ofid=order-1; domain=oldhost.com', + 'CheckoutOrderFormOwnership=owner-1; domain=oldhost.com', + ], + }, + }) + + await queries.orderForm(null, {}, toContext(ctx)) + + expect(ctx.cookies.set).toHaveBeenCalledTimes(1) + expect(ctx.cookies.set).toHaveBeenCalledWith( + 'CheckoutOrderFormOwnership', + 'owner-1', + expect.any(Object) + ) + }) }) describe('queries.orderForm — broken cookie recovery', () => { @@ -432,6 +454,67 @@ describe('forwardCheckoutCookies', () => { expect(ctx.cookies.set).not.toHaveBeenCalled() }) + /** + * Checkout issues an empty `CheckoutOrderFormOwnership` whenever a cart is + * created. Writing it through would revoke the ownership the shopper already + * holds and make Checkout mask their profile and shipping data from then on. + */ + it('never overwrites the ownership cookie with an empty value', async () => { + const ctx = buildCtx() + const headers = { + 'set-cookie': [ + 'CheckoutOrderFormOwnership=; domain=oldhost.com', + 'checkout.vtex.com=__ofid=order-1; domain=oldhost.com', + ], + } + + await forwardCheckoutCookies(headers, toContext(ctx)) + + expect(ctx.cookies.set).toHaveBeenCalledTimes(1) + expect(ctx.cookies.set).toHaveBeenCalledWith( + 'checkout.vtex.com', + '__ofid=order-1', + expect.any(Object) + ) + }) + + it('keeps the ownership already in context when checkout returns an empty one', async () => { + const ctx = makeContext({ + headers: { 'x-forwarded-host': 'newhost.com' }, + vtex: { ownerId: 'owner-1' }, + }) + + await forwardCheckoutCookies( + { 'set-cookie': ['CheckoutOrderFormOwnership=; domain=oldhost.com'] }, + toContext(ctx) + ) + + expect(ctx.vtex.ownerId).toBe('owner-1') + }) + + /** + * `@withOwnerId` snapshots the cookie off the incoming request, so without + * this sync every later Checkout call of the same request would keep sending + * the stale (usually empty) ownership and get masked data back. + */ + it('syncs a newly issued ownership into vtex.ownerId for the rest of the request', async () => { + const ctx = makeContext({ + headers: { 'x-forwarded-host': 'newhost.com' }, + vtex: { ownerId: undefined }, + }) + + await forwardCheckoutCookies( + { + 'set-cookie': [ + 'CheckoutOrderFormOwnership=owner-2; domain=oldhost.com', + ], + }, + toContext(ctx) + ) + + expect(ctx.vtex.ownerId).toBe('owner-2') + }) + it('respects a custom allow list', async () => { const ctx = buildCtx() const headers = { diff --git a/node/__tests__/ownership-middleware.test.ts b/node/__tests__/ownership-middleware.test.ts new file mode 100644 index 00000000..edb539d5 --- /dev/null +++ b/node/__tests__/ownership-middleware.test.ts @@ -0,0 +1,128 @@ +import { keepOwnership } from '../clients/ownership' + +/** + * `keepOwnership` is the only place that captures `CheckoutOrderFormOwnership` + * for routes whose verb has no raw variant (`patch`, used by `addItem` and + * `updateItems`). It runs as a client middleware, so these tests drive it the + * way koa-compose does: call it with a middleware context and a `next` that + * populates the response. + */ + +const makeIOContext = (ownerId?: string) => + (({ + ownerId, + logger: { warn: jest.fn(), error: jest.fn(), info: jest.fn() }, + } as unknown) as CustomIOContext) + +const makeMiddlewareContext = (setCookies?: string[]) => + ({ + config: { url: '/api/checkout/pub/orderForm/of-1/items' }, + response: setCookies + ? { headers: { 'set-cookie': setCookies } } + : undefined, + } as any) + +const runMiddleware = async ( + ioContext: CustomIOContext, + setCookies?: string[] +) => { + const middlewareContext = makeMiddlewareContext() + const next = jest.fn(async () => { + // The response only exists once the downstream middlewares have run. + middlewareContext.response = makeMiddlewareContext(setCookies).response + }) + + await keepOwnership(ioContext)(middlewareContext, next) + + return { middlewareContext, next } +} + +describe('keepOwnership', () => { + it('adopts an ownership issued by any checkout route', async () => { + const ioContext = makeIOContext() + + await runMiddleware(ioContext, [ + 'CheckoutOrderFormOwnership=owner-1; domain=host.com; path=/', + ]) + + expect(ioContext.ownerId).toBe('owner-1') + }) + + it('replaces a stale ownership with the one just rotated', async () => { + const ioContext = makeIOContext('owner-1') + + await runMiddleware(ioContext, [ + 'CheckoutOrderFormOwnership=owner-2; domain=host.com', + ]) + + expect(ioContext.ownerId).toBe('owner-2') + }) + + it('waits for the downstream middlewares before reading the response', async () => { + const ioContext = makeIOContext() + + const { next } = await runMiddleware(ioContext, [ + 'CheckoutOrderFormOwnership=owner-1; domain=host.com', + ]) + + expect(next).toHaveBeenCalledTimes(1) + expect(ioContext.ownerId).toBe('owner-1') + }) + + it('keeps the current ownership when checkout returns an empty one', async () => { + const ioContext = makeIOContext('owner-1') + + await runMiddleware(ioContext, [ + 'CheckoutOrderFormOwnership=; domain=host.com', + ]) + + expect(ioContext.ownerId).toBe('owner-1') + expect(ioContext.logger.warn).toHaveBeenCalledWith( + expect.objectContaining({ + message: + 'Checkout returned an empty ownership cookie; keeping the current one', + }) + ) + }) + + it('stays quiet when checkout returns an empty ownership and there is none to lose', async () => { + const ioContext = makeIOContext() + + await runMiddleware(ioContext, [ + 'CheckoutOrderFormOwnership=; domain=host.com', + ]) + + expect(ioContext.ownerId).toBeUndefined() + expect(ioContext.logger.warn).not.toHaveBeenCalled() + }) + + it('ignores cookies other than the ownership one', async () => { + const ioContext = makeIOContext('owner-1') + + await runMiddleware(ioContext, [ + 'checkout.vtex.com=__ofid=order-1; domain=host.com', + '.ASPXAUTH=hash; domain=host.com', + ]) + + expect(ioContext.ownerId).toBe('owner-1') + expect(ioContext.logger.warn).not.toHaveBeenCalled() + }) + + it('is a no-op when the response carries no cookies', async () => { + const ioContext = makeIOContext('owner-1') + + await runMiddleware(ioContext, []) + + expect(ioContext.ownerId).toBe('owner-1') + expect(ioContext.logger.warn).not.toHaveBeenCalled() + }) + + it('is a no-op when the request produced no response', async () => { + const ioContext = makeIOContext('owner-1') + + await runMiddleware(ioContext) + + expect(ioContext.ownerId).toBe('owner-1') + expect(ioContext.logger.warn).not.toHaveBeenCalled() + }) +}) diff --git a/node/__tests__/shipping-utils.test.ts b/node/__tests__/shipping-utils.test.ts index 631f888d..fa473060 100644 --- a/node/__tests__/shipping-utils.test.ts +++ b/node/__tests__/shipping-utils.test.ts @@ -6,7 +6,8 @@ import { selectAddress, selectShippingOption, } from '../utils/shipping' -import { makeClientsMock, toClients } from '../__fixtures__/clients' +import { makeClientsMock } from '../__fixtures__/clients' +import { makeContext, toContext } from '../__fixtures__/context' import { makeAddress, makeLogisticsInfo, @@ -270,10 +271,9 @@ describe('utils/shipping — getShippingInfo totalizer auto-correction', () => { const totalizersRef = orderForm.totalizers const shippingTotalizerRef = orderForm.totalizers[0] - await getShippingInfo({ - clients: toClients(clientsMock), - orderForm, - }) + const ctx = toContext(makeContext({ clients: clientsMock })) + + await getShippingInfo({ ctx, orderForm }) expect(clientsMock.checkout.updateOrderFormShipping).toHaveBeenCalledTimes( 1 @@ -283,7 +283,8 @@ describe('utils/shipping — getShippingInfo totalizer auto-correction', () => { expect.objectContaining({ logisticsInfo: expect.any(Array), selectedAddresses: [baseDeliveryAddress], - }) + }), + ctx ) // Intentional mutation contract — see suite-level comment above. @@ -303,7 +304,7 @@ describe('utils/shipping — getShippingInfo totalizer auto-correction', () => { const orderForm = buildOrderForm(100, 100) await getShippingInfo({ - clients: toClients(clientsMock), + ctx: toContext(makeContext({ clients: clientsMock })), orderForm, }) @@ -318,7 +319,7 @@ describe('utils/shipping — getShippingInfo totalizer auto-correction', () => { orderForm.totalizers = [] await getShippingInfo({ - clients: toClients(clientsMock), + ctx: toContext(makeContext({ clients: clientsMock })), orderForm, }) diff --git a/node/__tests__/shipping.test.ts b/node/__tests__/shipping.test.ts index c785f790..9aa12f92 100644 --- a/node/__tests__/shipping.test.ts +++ b/node/__tests__/shipping.test.ts @@ -8,7 +8,7 @@ import { ORDER_FORM_WITH_SCHEDULED_DELIVERY, ORDER_FORM_WITH_SCHEDULED_DELIVERY_AND_PICKUPS, ORDER_FORM_WITH_UNAVAILABLE_ITEM_LOGISTICS_INFO, - clients, + shippingContext, } from '../__fixtures__/shipping' import { getShippingInfo } from '../utils/shipping' @@ -25,7 +25,7 @@ describe('Shipping Resolvers', () => { expect( await getShippingInfo({ - clients, + ctx: shippingContext, orderForm: ORDER_FORM_WITH_EMPTY_SHIPPING_DATA, }) ).toEqual(expectedResult) @@ -42,7 +42,7 @@ describe('Shipping Resolvers', () => { expect( await getShippingInfo({ - clients, + ctx: shippingContext, orderForm: ORDER_FORM_WITH_EMPTY_LOGISTICS_INFO, }) ).toEqual(expectedResult) @@ -113,7 +113,10 @@ describe('Shipping Resolvers', () => { } expect( - await getShippingInfo({ clients, orderForm: ORDER_FORM_WITH_PICKUPS }) + await getShippingInfo({ + ctx: shippingContext, + orderForm: ORDER_FORM_WITH_PICKUPS, + }) ).toEqual(expectedResult) }) @@ -160,7 +163,7 @@ describe('Shipping Resolvers', () => { expect( await getShippingInfo({ - clients, + ctx: shippingContext, orderForm: ORDER_FORM_WITH_SCHEDULED_DELIVERY, }) ).toEqual(expectedResult) @@ -232,7 +235,7 @@ describe('Shipping Resolvers', () => { expect( await getShippingInfo({ - clients, + ctx: shippingContext, orderForm: ORDER_FORM_WITH_SCHEDULED_DELIVERY_AND_PICKUPS, }) ).toEqual(expectedResult) @@ -281,7 +284,7 @@ describe('Shipping Resolvers', () => { expect( await getShippingInfo({ - clients, + ctx: shippingContext, orderForm: ORDER_FORM_WITH_DIFFERENT_SLAS_BETWEEN_LOGISTICS_INFO, }) ).toEqual(expectedResult) @@ -338,7 +341,7 @@ describe('Shipping Resolvers', () => { expect( await getShippingInfo({ - clients, + ctx: shippingContext, orderForm: ORDER_FORM_WITH_DUPLICATED_SLAS_WITH_DIFFERENT_DELIVERY_IDS, }) ).toEqual(expectedResult) @@ -410,7 +413,7 @@ describe('Shipping Resolvers', () => { expect( await getShippingInfo({ - clients, + ctx: shippingContext, orderForm: ORDER_FORM_WITH_UNAVAILABLE_ITEM_LOGISTICS_INFO, }) ).toEqual(expectedResult) diff --git a/node/clients/checkout.ts b/node/clients/checkout.ts index bf4bf725..d744789c 100644 --- a/node/clients/checkout.ts +++ b/node/clients/checkout.ts @@ -8,8 +8,13 @@ import { import { UserProfileInput } from 'vtex.checkout-graphql' import { OWNERSHIP_COOKIE } from '../constants' import { forwardCheckoutCookies } from '../resolvers/orderForm' +import { keepOwnership } from './ownership' -import { checkoutCookieFormat, ownershipCookieFormat, statusToError } from '../utils' +import { + checkoutCookieFormat, + ownershipCookieFormat, + statusToError, +} from '../utils' export interface SimulationData { country: string @@ -30,6 +35,10 @@ export class Checkout extends JanusClient { ? { VtexIdclientAutCookie: ctx.storeUserAuthToken } : null), }, + middlewares: [ + ...(options?.middlewares ?? []), + keepOwnership((ctx as unknown) as CustomIOContext), + ], }) } @@ -125,23 +134,30 @@ export class Checkout extends JanusClient { public updateOrderFormProfile = async ( orderFormId: string, fields: UserProfileInput, - ctx: Context, + ctx: Context ) => { const { data, headers } = await this.postRaw( this.routes.attachmentsData(orderFormId, 'clientProfileData'), fields, { metric: 'checkout-updateOrderFormProfile' } ) - forwardCheckoutCookies(headers, ctx, [OWNERSHIP_COOKIE]) + await forwardCheckoutCookies(headers, ctx, [OWNERSHIP_COOKIE]) return data } - public updateOrderFormShipping = (orderFormId: string, shipping: any) => - this.post( + public updateOrderFormShipping = async ( + orderFormId: string, + shipping: any, + ctx: Context + ) => { + const { data, headers } = await this.postRaw( this.routes.attachmentsData(orderFormId, 'shippingData'), shipping, { metric: 'checkout-updateOrderFormShipping' } ) + await forwardCheckoutCookies(headers, ctx, [OWNERSHIP_COOKIE]) + return data + } public updateOrderFormMarketingData = ( orderFormId: string, @@ -405,10 +421,13 @@ export class Checkout extends JanusClient { } private getCommonHeaders = () => { - const { orderFormId, ownerId, vtexRCSessionIdv7, vtexRCMacIdv7 } = (this.context as unknown) as CustomIOContext + const { orderFormId, ownerId, vtexRCSessionIdv7, vtexRCMacIdv7 } = (this + .context as unknown) as CustomIOContext const checkoutCookie = orderFormId ? checkoutCookieFormat(orderFormId) : '' const ownershipCookie = ownerId ? ownershipCookieFormat(ownerId) : '' - const rcSessionCookie = vtexRCSessionIdv7 ? `VtexRCSessionIdv7=${vtexRCSessionIdv7};` : '' + const rcSessionCookie = vtexRCSessionIdv7 + ? `VtexRCSessionIdv7=${vtexRCSessionIdv7};` + : '' const rcMacCookie = vtexRCMacIdv7 ? `VtexRCMacIdv7=${vtexRCMacIdv7};` : '' return { Cookie: `${checkoutCookie}${ownershipCookie}${rcSessionCookie}${rcMacCookie}vtex_segment=${this.context.segmentToken};vtex_session=${this.context.sessionToken};`, @@ -494,7 +513,13 @@ export class Checkout extends JanusClient { export class CheckoutNoCookies extends Checkout { constructor(ctx: IOContext, options?: InstanceOptions) { super( - { ...ctx, orderFormId: null, ownerId: null, vtexRCSessionIdv7: null, vtexRCMacIdv7: null } as any, + { + ...ctx, + orderFormId: null, + ownerId: null, + vtexRCSessionIdv7: null, + vtexRCMacIdv7: null, + } as any, { ...options, headers: {} } ) } diff --git a/node/clients/ownership.ts b/node/clients/ownership.ts new file mode 100644 index 00000000..01882f02 --- /dev/null +++ b/node/clients/ownership.ts @@ -0,0 +1,57 @@ +import { MiddlewareContext } from '@vtex/api' +import { parse } from 'set-cookie-parser' + +import { OWNERSHIP_COOKIE } from '../constants' + +const readIssuedOwnership = (setCookies: string[]) => { + const issued = setCookies + .map(setCookie => parse(setCookie)[0]) + .find(cookie => cookie?.name === OWNERSHIP_COOKIE) + + return issued?.value +} + +/** + * Keeps `CheckoutOrderFormOwnership` alive across every Checkout route. + * + * All routes already send the cookie out through `getCommonHeaders`, but until + * this middleware only the three methods built on the `*Raw` verbs could read + * it back. Checkout rotates the ownership whenever profile or shipping data + * changes, so a rotation that goes uncaptured leaves the remaining calls of the + * request authenticating with a stale value and getting masked data back. + * + * Running as a client middleware covers every verb — notably `patch`, which + * `HttpClient` exposes with no raw variant, so `addItem` and `updateItems` + * cannot see their own response headers from the calling method. + */ +export const keepOwnership = (ioContext: CustomIOContext) => async ( + middlewareContext: MiddlewareContext, + next: () => Promise +) => { + await next() + + const setCookies: string[] = + middlewareContext.response?.headers?.['set-cookie'] ?? [] + + if (setCookies.length === 0) { + return + } + + const issuedOwnership = readIssuedOwnership(setCookies) + + if (issuedOwnership) { + ioContext.ownerId = issuedOwnership + return + } + + // Checkout blanks the cookie when it hands out a new cart. Adopting that + // empty value would revoke the ownership the shopper already holds, so we + // keep the current one and record that it happened. + if (issuedOwnership === '' && ioContext.ownerId) { + ioContext.logger.warn({ + message: + 'Checkout returned an empty ownership cookie; keeping the current one', + url: middlewareContext.config?.url, + }) + } +} diff --git a/node/package.json b/node/package.json index 2fd01aa9..58a95f0c 100644 --- a/node/package.json +++ b/node/package.json @@ -25,7 +25,7 @@ "@types/node": "^12.0.0", "@types/ramda": "types/npm-ramda#dist", "@types/set-cookie-parser": "^2.4.2", - "@vtex/api": "6.50.1", + "@vtex/api": "6.51.0", "@vtex/test-tools": "^3.1.0", "@vtex/tsconfig": "^0.2.0", "typescript": "3.9.7", diff --git a/node/resolvers/orderForm.ts b/node/resolvers/orderForm.ts index 1cec5c4c..249a86c5 100644 --- a/node/resolvers/orderForm.ts +++ b/node/resolvers/orderForm.ts @@ -140,7 +140,7 @@ export const root = { ) => { const shippingInfo = await getShippingInfo({ orderForm, - clients: ctx.clients, + ctx, }) const isValid = await isShippingValid(orderForm, shippingInfo, ctx) @@ -217,11 +217,24 @@ export async function forwardCheckoutCookies( const parseAndClean = compose(parseCookie, replaceDomain(host)) const cleanCookies = forwardedSetCookies.map(parseAndClean) cleanCookies.forEach(({ name, value, options }) => { + // Checkout sends the ownership cookie empty whenever a cart is created. + // Forwarding it would erase the ownership the shopper already holds, and + // from then on Checkout would mask their profile and shipping data. + if (name === OWNERSHIP_COOKIE && !value) { + return + } + if (options.secure && !ctx.cookies.secure) { ctx.cookies.secure = true } ctx.cookies.set(name, value, options) + + if (name === OWNERSHIP_COOKIE) { + // The remaining Checkout calls of this request must use the ownership + // just issued, not the one @withOwnerId read from the incoming request. + ctx.vtex.ownerId = value + } }) } @@ -267,7 +280,14 @@ export const queries = { ) if (storeSettings.enableOrderFormOptimization) { - forwardCheckoutCookies(headers, ctx) + await forwardCheckoutCookies(headers, ctx) + } else { + /** + * The reasoning above does not apply to the ownership cookie: no other + * app sets it, so dropping it here would leave the shopper without + * ownership and Checkout would mask their personal data. + */ + await forwardCheckoutCookies(headers, ctx, [OWNERSHIP_COOKIE]) } return newOrderForm diff --git a/node/resolvers/shipping.ts b/node/resolvers/shipping.ts index 0f87381b..14e305d3 100644 --- a/node/resolvers/shipping.ts +++ b/node/resolvers/shipping.ts @@ -42,10 +42,14 @@ export const mutations = { orderForm.shippingData && orderForm.shippingData.logisticsInfo const shippingData = getShippingData(address, logisticsInfo) - const newOrderForm = await checkout.updateOrderFormShipping(orderFormId!, { - ...shippingData, - clearAddressIfPostalCodeNotFound: false, - }) + const newOrderForm = await checkout.updateOrderFormShipping( + orderFormId!, + { + ...shippingData, + clearAddressIfPostalCodeNotFound: false, + }, + ctx + ) return newOrderForm }, @@ -66,10 +70,14 @@ export const mutations = { deliveryChannel: DELIVERY, }) - const newOrderForm = await checkout.updateOrderFormShipping(orderFormId!, { - ...newShippingData, - clearAddressIfPostalCodeNotFound: false, - }) + const newOrderForm = await checkout.updateOrderFormShipping( + orderFormId!, + { + ...newShippingData, + clearAddressIfPostalCodeNotFound: false, + }, + ctx + ) return newOrderForm }, @@ -91,10 +99,14 @@ export const mutations = { deliveryChannel: PICKUP_IN_POINT, }) - const newOrderForm = await checkout.updateOrderFormShipping(orderFormId!, { - ...newShippingData, - clearAddressIfPostalCodeNotFound: false, - }) + const newOrderForm = await checkout.updateOrderFormShipping( + orderFormId!, + { + ...newShippingData, + clearAddressIfPostalCodeNotFound: false, + }, + ctx + ) return newOrderForm }, @@ -117,10 +129,14 @@ export const mutations = { shippingData: orderForm.shippingData, }) - const newOrderForm = await checkout.updateOrderFormShipping(orderFormId!, { - ...newShippingData, - clearAddressIfPostalCodeNotFound: false, - }) + const newOrderForm = await checkout.updateOrderFormShipping( + orderFormId!, + { + ...newShippingData, + clearAddressIfPostalCodeNotFound: false, + }, + ctx + ) return newOrderForm }, diff --git a/node/resolvers/sla.ts b/node/resolvers/sla.ts index 973d476c..ebf85ca1 100644 --- a/node/resolvers/sla.ts +++ b/node/resolvers/sla.ts @@ -21,6 +21,6 @@ export const queries = { 'shippingData' | 'totalizers' | 'orderFormId' | 'value' > - return getShippingInfo({ clients: ctx.clients, orderForm }) + return getShippingInfo({ ctx, orderForm }) }, } diff --git a/node/utils/shipping.ts b/node/utils/shipping.ts index dc5c9450..af32a1ef 100644 --- a/node/utils/shipping.ts +++ b/node/utils/shipping.ts @@ -10,7 +10,6 @@ import { getFormattedDeliveryOptions, hasDeliveryOption, } from './delivery-options' -import { Clients } from '../clients' import { DELIVERY, PICKUP_IN_POINT } from '../constants' import { formatBusinessHoursList } from './pickup' @@ -91,10 +90,10 @@ export const selectAddress = ({ } export const getShippingInfo = async ({ - clients, + ctx, orderForm, }: { - clients: Clients + ctx: Context orderForm: Pick< CheckoutOrderForm, 'shippingData' | 'totalizers' | 'orderFormId' | 'value' @@ -154,9 +153,10 @@ export const getShippingInfo = async ({ shippingData: orderForm.shippingData, }) - await clients.checkout.updateOrderFormShipping( + await ctx.clients.checkout.updateOrderFormShipping( orderForm.orderFormId, - newShippingData + newShippingData, + ctx ) const difference = selectedDeliveryOption.price - shippingTotalizer.value diff --git a/node/yarn.lock b/node/yarn.lock index e3c427c7..6242cff5 100644 --- a/node/yarn.lock +++ b/node/yarn.lock @@ -2016,10 +2016,10 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@vtex/api@6.50.1": - version "6.50.1" - resolved "https://registry.yarnpkg.com/@vtex/api/-/api-6.50.1.tgz#a86578982a7aac7c7a8df2b9ec3df18bc43f01f2" - integrity sha512-4IlmYwCXKpkdpN2KN6NkPuRwnjet3ilSoET3PBOTTdZqE/mnuvIxRZRaQy+Yp7Gxu0XKAVdp/8SQJhsJaa6Unw== +"@vtex/api@6.51.0": + version "6.51.0" + resolved "https://registry.yarnpkg.com/@vtex/api/-/api-6.51.0.tgz#97aeb306619ff49fd890595b90568883eaf77d83" + integrity sha512-vRWKB4G1FPPt67rwWx+xGLanuP5y+M9SVmbKu3PzlTrqgq/aW/mINSUGa/Nhm64UBqIQCkVic7/smZ7kKFq52w== dependencies: "@types/koa" "^2.11.0" "@types/koa-compose" "^3.2.3" @@ -6462,7 +6462,7 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -stats-lite@vtex/node-stats-lite#dist: +"stats-lite@github:vtex/node-stats-lite#dist": version "2.2.1" resolved "https://codeload.github.com/vtex/node-stats-lite/tar.gz/a0b5ee91861f31b6ec845146b4906faf5172c430" dependencies: From f8988942b6d16b876cb76d9a37507cd6f6b12702 Mon Sep 17 00:00:00 2001 From: Lucas Vyskubenko Date: Tue, 11 Aug 2026 10:16:08 -0300 Subject: [PATCH 2/2] Release v0.69.0 --- CHANGELOG.md | 2 ++ manifest.json | 6 ++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8638bdff..3a32c9ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.69.0] - 2026-08-11 + ### Fixed - The `CheckoutOrderFormOwnership` cookie is now preserved across every request to the order form, so Checkout stops masking `clientProfileData` and diff --git a/manifest.json b/manifest.json index 3ce7cd2c..4622632e 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "name": "checkout-graphql", "vendor": "vtex", - "version": "0.68.0", + "version": "0.69.0", "title": "Checkout GraphQL", "description": "Checkout GraphQL API", "builders": { @@ -15,9 +15,7 @@ }, "credentialType": "absolute", "mustUpdateAt": "2019-11-05", - "registries": [ - "smartcheckout" - ], + "registries": ["smartcheckout"], "policies": [ { "name": "vtex.messages:translate-messages"