Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a903665
One click checkout
syadupathi-sf Dec 30, 2025
1e7e5fd
changes to fix install, tests and lint - needs to be reviewed
syadupathi-sf Dec 30, 2025
b707b09
Change log
syadupathi-sf Dec 30, 2025
a72864b
revert the test change in pwa-kit-runtime
syadupathi-sf Dec 30, 2025
0fd5d2c
Change commerce-sdk-react package-lock in an attempt to get the CI wo…
syadupathi-sf Dec 30, 2025
fdb57a2
Increase bundle size of template-retail-react-app by 1KB
syadupathi-sf Dec 30, 2025
355bc0d
@W-20542850 Remove promotion from Shipping Method summary view (#3563)
syadupathi-sf Jan 7, 2026
96f1b89
W-19728108 Add amount to payment instrument in the basket (#3568)
syadupathi-sf Jan 8, 2026
5c8c6ce
W-19728108 Amount for the registered shopper (#3572)
syadupathi-sf Jan 12, 2026
8fd1ab2
W-20892453 Remove store pickup shipping option (#3575)
syadupathi-sf Jan 14, 2026
81973db
@W-20892497 Show Phone number in Contact Info summary (#3576)
syadupathi-sf Jan 14, 2026
128ccd5
@W-20892554 Show spinner when user registration checkbox is clicked (…
syadupathi-sf Jan 15, 2026
a5deb6c
@W-20892592 Remove gift messaging for multi shipment (#3579)
syadupathi-sf Jan 15, 2026
80c1c80
@W-20892530 @W-20892577 Billing Address Validation and Using contact …
syadupathi-sf Jan 16, 2026
0e79176
Merge branch 'develop' into feature/1cc_merge
syadupathi-sf Jan 16, 2026
a0d8325
@W-20931438 Add spinner for place order button (#3587)
syadupathi-sf Jan 20, 2026
35a9a4d
Update isomorphic version to the newly released version (#3592)
syadupathi-sf Jan 21, 2026
d6fe32c
Merge branch 'develop' into feature/1cc_merge
syadupathi-sf Jan 21, 2026
b9cb29d
Fix SDK tests (#3593)
syadupathi-sf Jan 21, 2026
b7515c4
@W-20931758 Allow guest shopper to change payment (#3591)
syadupathi-sf Jan 22, 2026
c292a78
@W-21000333: [BOPIS] Should skip pick up address (#3601)
dannyphan2000 Jan 23, 2026
9fa4455
W-21005962 Disable user registration when billing address is not vali…
syadupathi-sf Jan 23, 2026
411fc67
@W-21000338: [BOPIS] Auto-populate billing address with default addre…
dannyphan2000 Jan 25, 2026
dbb914e
@W-21006290: [BOPIS] Continue to Shipping Address label fix for BOPIS…
dannyphan2000 Jan 25, 2026
9c618de
@W-21000344: [BOPIS] Pickup address component enhancement (#3607)
dannyphan2000 Jan 25, 2026
753f306
W-21005976 Newly registered user can change payment (#3608)
syadupathi-sf Jan 26, 2026
4f04c56
@W-21038080: [1CC][PWA] Add new feature toggle in config to template …
dannyphan2000 Jan 28, 2026
8a25361
@W-21038080: Rework to use billing address phone number for guest sho…
dannyphan2000 Jan 28, 2026
aff72eb
@ W-20540715 Address 1CC feature branch review comments (#3619)
syadupathi-sf Jan 29, 2026
b73115a
Merge branch 'develop' into feature/1cc_merge
syadupathi-sf Jan 30, 2026
44b2347
test
syadupathi-sf Jan 30, 2026
7ed0da9
passwordless mode updates
syadupathi-sf Jan 30, 2026
e6b9f3d
fix unit test
syadupathi-sf Jan 30, 2026
77551f2
@W-20892603: Remove empty shipment fix (#3622)
dannyphan2000 Jan 30, 2026
6649505
Update packages/template-retail-react-app/app/pages/checkout-one-clic…
syadupathi-sf Jan 30, 2026
5144503
revert to generic error message as per code review comment
syadupathi-sf Jan 30, 2026
3db7729
activity event tracking for 1cc (#3627)
kumaravinashcommercecloud Jan 30, 2026
82e8e36
Merge branch 'develop' into feature/1cc_merge
syadupathi-sf Jan 30, 2026
1eb4f7b
test failure (#3629)
kumaravinashcommercecloud Jan 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/commerce-sdk-react/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## v4.5.0-dev (Jan 19, 2026)
- Upgrade to commerce-sdk-isomorphic v4.2.0 and introduce Payment Instrument SCAPI integration [#3552](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3552)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Upgrade to commerce-sdk-isomorphic v4.2.0 and introduce Payment Instrument SCAPI integration [#3552](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3552)
- Upgrade to commerce-sdk-isomorphic v5.0.0 and introduce Payment Instrument SCAPI integration [#3552](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3552)


## v4.4.0-dev (Dec 17, 2025)
- [Bugfix]Ensure code_verifier can be optional in resetPassword call [#3567](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3567)
- [Improvement] Strengthening typescript types on custom endpoint options and fetchOptions types [#3589](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3589)
Expand Down
20 changes: 10 additions & 10 deletions packages/commerce-sdk-react/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/commerce-sdk-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"version": "node ./scripts/version.js"
},
"dependencies": {
"commerce-sdk-isomorphic": "4.2.0",
"commerce-sdk-isomorphic": "5.0.0-preview.1",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commerce-sdk-react package will need its major version bumped up too. Historically, whenever we jumped to the next major version of this underlying dependency, we also bumped the major version of our package.

Call this npm script: npm run bump-version:commerce-sdk-react -- 100.0.0

Copy link
Collaborator Author

@syadupathi-sf syadupathi-sf Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be 5.0.0? 100.0.0 is an example, I assume @vmarta

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@syadupathi-sf commerce-sdk-react is currently at 4.4.0-dev. So pls bump it up to 5.0.0-dev.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linking to the PR that will release isomorphic v5: SalesforceCommerceCloud/commerce-sdk-isomorphic#268

"js-cookie": "^3.0.1",
"jwt-decode": "^4.0.0"
},
Expand Down
37 changes: 20 additions & 17 deletions packages/commerce-sdk-react/src/auth/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,15 @@
import Auth, {AuthData} from './'
import {waitFor} from '@testing-library/react'
import jwt from 'jsonwebtoken'
import {
helpers,
ShopperCustomersTypes,
ShopperCustomers,
ShopperLogin
} from 'commerce-sdk-isomorphic'
import {helpers, ShopperCustomersTypes, ShopperCustomers} from 'commerce-sdk-isomorphic'
import * as utils from '../utils'
import {SLAS_SECRET_PLACEHOLDER} from '../constant'
import {ShopperLoginTypes} from 'commerce-sdk-isomorphic'
import {
DEFAULT_SLAS_REFRESH_TOKEN_REGISTERED_TTL,
DEFAULT_SLAS_REFRESH_TOKEN_GUEST_TTL
} from './index'
import {ApiClientConfigParams, RequireKeys} from '../hooks/types'
import {RequireKeys} from '../hooks/types'

const baseCustomer: RequireKeys<ShopperCustomersTypes.Customer, 'login'> = {
customerId: 'customerId',
Expand Down Expand Up @@ -70,7 +65,8 @@ jest.mock('commerce-sdk-isomorphic', () => {
fetchOptions: {
credentials: config?.fetchOptions?.credentials || 'same-origin'
}
}
},
authorizePasswordlessCustomer: jest.fn().mockResolvedValue({})
}))
}
})
Expand Down Expand Up @@ -923,29 +919,36 @@ describe('Auth', () => {

test('authorizePasswordless calls isomorphic authorizePasswordless', async () => {
const auth = new Auth(config)
const slasClient = (auth as any).client
await auth.authorizePasswordless({
callbackURI: 'callbackURI',
userid: 'userid',
mode: 'callback'
})
expect(helpers.authorizePasswordless).toHaveBeenCalled()
const functionArg = (helpers.authorizePasswordless as jest.Mock).mock.calls[0][0]
expect(slasClient.authorizePasswordlessCustomer).toHaveBeenCalled()
const functionArg = (slasClient.authorizePasswordlessCustomer as jest.Mock).mock.calls[0][0]
expect(functionArg).toMatchObject({
parameters: {
callbackURI: 'callbackURI',
userid: 'userid',
mode: 'callback'
body: {
user_id: 'userid',
mode: 'callback',
channel_id: 'siteId',
callback_uri: 'callbackURI'
}
})
})

test('authorizePasswordless sets mode to sms as configured', async () => {
const auth = new Auth(configPasswordlessSms)
const slasClient = (auth as any).client
await auth.authorizePasswordless({userid: 'userid'})
expect(helpers.authorizePasswordless).toHaveBeenCalled()
const functionArg = (helpers.authorizePasswordless as jest.Mock).mock.calls[0][0]
expect(slasClient.authorizePasswordlessCustomer).toHaveBeenCalled()
const functionArg = (slasClient.authorizePasswordlessCustomer as jest.Mock).mock.calls[0][0]
expect(functionArg).toMatchObject({
parameters: {userid: 'userid', mode: 'sms'}
body: {
user_id: 'userid',
mode: 'sms',
channel_id: 'siteId'
}
})
})

Expand Down
76 changes: 62 additions & 14 deletions packages/commerce-sdk-react/src/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
isOriginTrusted,
onClient,
getDefaultCookieAttributes,
isAbsoluteUrl,
stringToBase64,
extractCustomParameters
} from '../utils'
Expand Down Expand Up @@ -96,10 +95,20 @@ type AuthorizePasswordlessParams = {
callbackURI?: string
userid: string
mode?: string
locale?: string
/** When true, SLAS will register the customer as part of the passwordless flow */
register_customer?: boolean | string
/** Optional registration details forwarded to SLAS when register_customer=true */
first_name?: string
last_name?: string
email?: string
phone_number?: string
}

type GetPasswordLessAccessTokenParams = {
pwdlessLoginToken: string
/** When true, SLAS will register the customer if not already registered */
register_customer?: boolean | string
}

/**
Expand All @@ -125,6 +134,8 @@ type AuthDataKeys =
| typeof DWSID_COOKIE_NAME
| 'code_verifier'
| 'uido'
| 'idp_refresh_token'
| 'dnt'

type AuthDataMap = Record<
AuthDataKeys,
Expand Down Expand Up @@ -1268,26 +1279,55 @@ class Auth {
* A wrapper method for commerce-sdk-isomorphic helper: authorizePasswordless.
*/
async authorizePasswordless(parameters: AuthorizePasswordlessParams) {
const slasClient = this.client
const usid = this.get('usid')
const callbackURI = parameters.callbackURI || this.passwordlessLoginCallbackURI
const finalMode = callbackURI ? 'callback' : parameters.mode || 'sms'
const finalMode = parameters.mode || (callbackURI ? 'callback' : 'sms')

const res = await helpers.authorizePasswordless({
slasClient: this.client,
credentials: {
clientSecret: this.clientSecret
const options = {
headers: {
Authorization: ''
},
parameters: {
...(callbackURI && {callbackURI: callbackURI}),
...(parameters.register_customer !== undefined && {
register_customer:
typeof parameters.register_customer === 'boolean'
? String(parameters.register_customer)
: parameters.register_customer
})
},
body: {
user_id: parameters.userid,
mode: finalMode,
locale: parameters.locale,
// Include usid and site as required by SLAS
...(usid && {usid}),
userid: parameters.userid,
mode: finalMode
channel_id: slasClient.clientConfig.parameters.siteId,
...(finalMode === 'callback' && callbackURI && {callback_uri: callbackURI}),
...(parameters.last_name && {last_name: parameters.last_name}),
...(parameters.email && {email: parameters.email}),
...(parameters.first_name && {first_name: parameters.first_name}),
...(parameters.phone_number && {phone_number: parameters.phone_number})
}
})
if (res && res.status !== 200) {
const errorData = await res.json()
throw new Error(`${res.status} ${String(errorData.message)}`)
} as {
headers?: {[key: string]: string}
parameters?: Record<string, string>
body: ShopperLoginTypes.authorizePasswordlessCustomerBodyType &
helpers.CustomRequestBody
}

// Use Basic auth header when using private client
if (this.clientSecret) {
options.headers = options.headers || {}
options.headers.Authorization = `Basic ${stringToBase64(
`${slasClient.clientConfig.parameters.clientId}:${this.clientSecret}`
)}`
} else {
// If not using private client, avoid sending Authorization header
delete options.headers
}

const res = await slasClient.authorizePasswordlessCustomer(options)
return res
}

Expand All @@ -1297,14 +1337,22 @@ class Auth {
async getPasswordLessAccessToken(parameters: GetPasswordLessAccessTokenParams) {
const pwdlessLoginToken = parameters.pwdlessLoginToken || ''
const dntPref = this.getDnt({includeDefaults: true})
const usid = this.get('usid')
const token = await helpers.getPasswordLessAccessToken({
slasClient: this.client,
credentials: {
clientSecret: this.clientSecret
},
parameters: {
pwdlessLoginToken,
dnt: dntPref !== undefined ? String(dntPref) : undefined
dnt: dntPref !== undefined ? String(dntPref) : undefined,
...(usid && {usid}),
...(parameters.register_customer !== undefined && {
register_customer:
typeof parameters.register_customer === 'boolean'
? String(parameters.register_customer)
: parameters.register_customer
})
}
})
const isGuest = false
Expand Down
30 changes: 30 additions & 0 deletions packages/commerce-sdk-react/src/hooks/ShopperCustomers/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,36 @@ export const cacheUpdateMatrix: CacheUpdateMatrix<Client> = {
]
}
},
updateCustomerPaymentInstrument(customerId, {parameters}, response) {
const newParams = {...parameters}
return {
update: [
{
queryKey: getCustomerPaymentInstrument.queryKey(newParams)
},
{
queryKey: getCustomer.queryKey(newParams),
updater: createUpdateFunction((customer: Customer) => {
if (!customer.paymentInstruments) return customer
const idx = customer.paymentInstruments.findIndex(
({paymentInstrumentId}) =>
paymentInstrumentId === parameters.paymentInstrumentId
)
if (idx >= 0) {
customer.paymentInstruments[idx] = response as any
// If this instrument is now default, unset others
if ((response as any)?.default) {
customer.paymentInstruments = customer.paymentInstruments.map(
(pi, i) => (i === idx ? pi : {...pi, default: false})
) as any
}
}
return customer
})
}
]
}
},
createCustomerPaymentInstrument(customerId, {parameters}, response) {
const newParams = {...parameters, paymentInstrumentId: response.paymentInstrumentId}
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe('Shopper Customers hooks', () => {
// or add it to the `expected` array with a comment explaining "TODO" or "never" (and why).
expect(unimplemented).toEqual([
'getExternalProfile', // TODO: Implement when the endpoint exits closed beta
'getPublicProductListItems', // TODO: Implement when the endpoint exits closed beta
'registerExternalProfile' // TODO: Implement when the endpoint exits closed beta
])
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ const basePaymentInstrument: ShopperCustomersTypes.CustomerPaymentInstrument = {
paymentBankAccount: {},
paymentCard: {cardType: 'fake'},
paymentInstrumentId: 'paymentInstrumentId',
paymentMethodId: 'paymentMethodId'
paymentMethodId: 'paymentMethodId',
default: false
}
const baseCustomer: RequireKeys<
ShopperCustomersTypes.Customer,
Expand Down Expand Up @@ -176,6 +177,50 @@ describe('ShopperCustomers mutations', () => {
expect(result.current.mutation.data).toBeUndefined()
assertRemoveQuery(result.current.query)
})
test('`updateCustomerPaymentInstrument` updates cache on success', async () => {
// 0. Setup
const customer = baseCustomer
const oldData = basePaymentInstrument
const newData: ShopperCustomersTypes.CustomerPaymentInstrument = {
...basePaymentInstrument,
default: true
}
const options = createOptions<'updateCustomerPaymentInstrument'>({
// Only updating default flag for this test
default: true as any
})

mockQueryEndpoint(customersEndpoint, customer) // getCustomer
mockQueryEndpoint(customersEndpoint, oldData) // getCustomerPaymentInstrument
mockMutationEndpoints(customersEndpoint, newData) // this mutation
mockQueryEndpoint(customersEndpoint, {test: 'this should not get used'}) // getCustomer refetch
mockQueryEndpoint(customersEndpoint, {test: 'this should not get used'}) // getCustomerPaymentInstrument refetch

const {result} = renderHookWithProviders(() => ({
customer: queries.useCustomer(queryOptions),
mutation: useShopperCustomersMutation('updateCustomerPaymentInstrument'),
query: queries.useCustomerPaymentInstrument(queryOptions)
}))

// 1. Populate cache with initial data
await waitAndExpectSuccess(() => result.current.customer)
await waitAndExpectSuccess(() => result.current.query)
expect(result.current.customer.data).toEqual(customer)
expect(result.current.query.data).toEqual(oldData)

// 2. Do update mutation
act(() => result.current.mutation.mutate(options))
await waitAndExpectSuccess(() => result.current.mutation)
expect(result.current.mutation.data).toEqual(newData)
// query updated
assertUpdateQuery(result.current.query, newData)
// customer cache updated (instrument replaced)
const expectedCustomer = {
...customer,
paymentInstruments: [newData]
}
assertUpdateQuery(result.current.customer, expectedCustomer as any)
})
test('`removeCustomerAddress` updates cache on success', async () => {
// 0. Setup
const customer = baseCustomer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ export const ShopperCustomersMutations = {
* @returns A TanStack Query mutation hook for interacting with the Shopper Customers `createCustomerPaymentInstrument` endpoint.
*/
CreateCustomerPaymentInstrument: 'createCustomerPaymentInstrument',
/**
* Updates a customer's payment instrument.
* @returns A TanStack Query mutation hook for interacting with the Shopper Customers `updateCustomerPaymentInstrument` endpoint.
*/
UpdateCustomerPaymentInstrument: 'updateCustomerPaymentInstrument',
/**
* Deletes a customer's payment instrument.
* @returns A TanStack Query mutation hook for interacting with the Shopper Customers `deleteCustomerPaymentInstrument` endpoint.
Expand Down
Loading
Loading