Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 4 additions & 7 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": "5.0.0",
"commerce-sdk-isomorphic": "5.1.0-unstable-20260226081656",
"js-cookie": "^3.0.1",
"jwt-decode": "^4.0.0"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,15 @@ module.exports = {
groupBonusProductsWithQualifyingProduct: true
}
},
// Salesforce Payments configuration
// Set enabled to false to disable Salesforce Payments even if the Commerce Cloud instance supports it.
// Example URLs: sdkUrl: 'https://<instance>.unified.demandware.net/on/demandware.static/Sites-Site/-/-/internal/jscript/sfp/v1/sfp.js'
// metadataUrl: 'https://<instance>.unified.demandware.net/on/demandware.static/Sites-Site/-/-/internal/metadata/v1.json'
sfPayments: {
enabled: true,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should our default be true or false? If false then generated sites could be used for demo purposes with placeholder buttons and fake payments. If true then they could only check out if they set up a merchant account and so on for SFP in BM.

Copy link
Contributor Author

@amittapalli amittapalli Mar 2, 2026

Choose a reason for hiding this comment

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

So, its a combination of value from this switch and what is coming from shopper config API.
If both true -> feature enabled
If api true but customer decides to switch to false themselves (then its disabled)
If api false but customer kept as true (then still disabled since instance does not allow)
If api false and customer also changed to false (then its obviously disabled)

That way if there are customers that have already customized some of their components, they may not be stuck with a broken experience since they have a kill switch at all times.
We can raise this to Product as well to confirm

Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we even need this flag then? I seem to remember introducing it because we didn't have the shopper config API changes yet.

Copy link
Contributor Author

@amittapalli amittapalli Mar 2, 2026

Choose a reason for hiding this comment

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

Helps to keep it since it works as the real kill switch for customers who end up with a mixed mode of behavior. In the past we had this flag but it was never really applied or used. With this PR, I am trying to make use of it. All new customers will have SF Payments enabled regardless. But as I mentioned it allows a customer (especially existing ones) to disable it if they face any unforseen issues when the FT is true

sdkUrl: '',
metadataUrl: ''
},
// Google Cloud api config
googleCloudAPI: {
apiKey: process.env.GOOGLE_CLOUD_API_KEY
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import crypto from 'crypto'
import express from 'express'
import helmet from 'helmet'
import https from 'https'
import {createRemoteJWKSet as joseCreateRemoteJWKSet, jwtVerify, decodeJwt} from 'jose'
import path from 'path'
import {getRuntime} from '@salesforce/pwa-kit-runtime/ssr/server/express'
Expand Down Expand Up @@ -345,14 +346,21 @@ const {handler} = runtime.createHandler(options, (app) => {
'img-src': [
// Default source for product images - replace with your CDN
'*.commercecloud.salesforce.com',
'*.demandware.net'
'*.demandware.net',
'*.adyen.com' // Payment gateways
],
'script-src': [
// Used by the service worker in /worker/main.js
'storage.googleapis.com',
// Connect to Google Cloud APIs
'maps.googleapis.com',
'places.googleapis.com'
'places.googleapis.com',
// Payment gateways
'*.stripe.com',
'*.paypal.com',
'*.adyen.com',
'*.google.com',
'*.demandware.net', // Used to load a valid payment scripts in test environment
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's leave this demandware.net one out of the generated template.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can remove it though I noticed that PWA team always had it for the img src in their original code. So
'img-src': [
// Default source for product images - replace with your CDN
'.commercecloud.salesforce.com',
'
.demandware.net'
],

],
'connect-src': [
// Connect to Einstein APIs
Expand All @@ -363,11 +371,20 @@ const {handler} = runtime.createHandler(options, (app) => {
'maps.googleapis.com',
'places.googleapis.com',
// Connect to SCRT2 URLs
'*.salesforce-scrt.com'
'*.salesforce-scrt.com',
// Payment gateways
'*.demandware.net', // Used to load a valid payment scripts in test environment
'*.adyen.com',
'*.google.com'
],
'frame-src': [
// Allow frames from Salesforce site.com (Needed for MIAW)
'*.site.com'
'*.site.com',
// Payment gateways
'*.stripe.com',
'*.paypal.com',
'*.google.com',
'*.adyen.com'
]
}
}
Expand Down Expand Up @@ -429,6 +446,64 @@ const {handler} = runtime.createHandler(options, (app) => {
app.get('/favicon.ico', runtime.serveStaticFile('static/ico/favicon.ico'))

app.get('/worker.js(.map)?', runtime.serveServiceWorker)

// Helper function to transform relative icon paths to absolute URLs
function transformIconPaths(data, ecomServerHost) {
const baseUrl = `https://${ecomServerHost}/on/demandware.static/Sites-Site/-/-/internal`
const dataStr = JSON.stringify(data)
// Replace all relative icon paths with absolute URLs
const transformedStr = dataStr.replace(/"src":\s*"\/icons\//g, `"src":"${baseUrl}/icons/`)
return JSON.parse(transformedStr)
}

app.get('/api/payment-metadata', async (req, res) => {
try {
// Parse the URL to extract hostname and path
const url = new URL(config.app.sfPayments.metadataUrl)
// Use Node's https module instead of fetch
const data = await new Promise((resolve, reject) => {
const options = {
hostname: url.hostname,
path: url.pathname,
method: 'GET',
rejectUnauthorized: false, // This bypasses SSL verification
headers: {
Accept: 'application/json'
}
}

const req = https.request(options, (response) => {
let data = ''
response.on('data', (chunk) => {
data += chunk
})
response.on('end', () => {
try {
resolve(JSON.parse(data))
} catch (e) {
reject(e)
}
})
})

req.on('error', reject)
req.end()
})

// Transform relative icon paths to absolute URLs
const transformedData = transformIconPaths(data, url.hostname)

res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Content-Type', 'application/json')
res.json(transformedData)
} catch (error) {
res.status(500).json({
error: 'Failed to fetch metadata',
details: error.message
})
}
})

app.get('*', runtime.render)
})
// SSR requires that we export a single handler function called 'get', that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import crypto from 'crypto'
import express from 'express'
import helmet from 'helmet'
import https from 'https'
import {createRemoteJWKSet as joseCreateRemoteJWKSet, jwtVerify, decodeJwt} from 'jose'
import path from 'path'
import {getRuntime} from '@salesforce/pwa-kit-runtime/ssr/server/express'
Expand Down Expand Up @@ -345,14 +346,21 @@ const {handler} = runtime.createHandler(options, (app) => {
'img-src': [
// Default source for product images - replace with your CDN
'*.commercecloud.salesforce.com',
'*.demandware.net'
'*.demandware.net',
'*.adyen.com' // Payment gateways
],
'script-src': [
// Used by the service worker in /worker/main.js
'storage.googleapis.com',
// Connect to Google Cloud APIs
'maps.googleapis.com',
'places.googleapis.com'
'places.googleapis.com',
// Payment gateways
'*.stripe.com',
'*.paypal.com',
'*.adyen.com',
'*.google.com',
'*.demandware.net', // Used to load a valid payment scripts in test environment
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as above. Any idea why there are two copies of this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah. One is for customers who choose to not extend but get a copy of the template while the other is for customers who don't want the template but extend from the template. So both copies of handle bars are needed

],
'connect-src': [
// Connect to Einstein APIs
Expand All @@ -363,11 +371,20 @@ const {handler} = runtime.createHandler(options, (app) => {
'maps.googleapis.com',
'places.googleapis.com',
// Connect to SCRT2 URLs
'*.salesforce-scrt.com'
'*.salesforce-scrt.com',
// Payment gateways
'*.demandware.net', // Used to load a valid payment scripts in test environment
'*.adyen.com',
'*.google.com'
],
'frame-src': [
// Allow frames from Salesforce site.com (Needed for MIAW)
'*.site.com'
'*.site.com',
// Payment gateways
'*.stripe.com',
'*.paypal.com',
'*.google.com',
'*.adyen.com'
]
}
}
Expand Down Expand Up @@ -429,6 +446,64 @@ const {handler} = runtime.createHandler(options, (app) => {
app.get('/favicon.ico', runtime.serveStaticFile('static/ico/favicon.ico'))

app.get('/worker.js(.map)?', runtime.serveServiceWorker)

// Helper function to transform relative icon paths to absolute URLs
function transformIconPaths(data, ecomServerHost) {
const baseUrl = `https://${ecomServerHost}/on/demandware.static/Sites-Site/-/-/internal`
const dataStr = JSON.stringify(data)
// Replace all relative icon paths with absolute URLs
const transformedStr = dataStr.replace(/"src":\s*"\/icons\//g, `"src":"${baseUrl}/icons/`)
return JSON.parse(transformedStr)
}

app.get('/api/payment-metadata', async (req, res) => {
try {
// Parse the URL to extract hostname and path
const url = new URL(config.app.sfPayments.metadataUrl)
// Use Node's https module instead of fetch
const data = await new Promise((resolve, reject) => {
const options = {
hostname: url.hostname,
path: url.pathname,
method: 'GET',
rejectUnauthorized: false, // This bypasses SSL verification
headers: {
Accept: 'application/json'
}
}

const req = https.request(options, (response) => {
let data = ''
response.on('data', (chunk) => {
data += chunk
})
response.on('end', () => {
try {
resolve(JSON.parse(data))
} catch (e) {
reject(e)
}
})
})

req.on('error', reject)
req.end()
})

// Transform relative icon paths to absolute URLs
const transformedData = transformIconPaths(data, url.hostname)

res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Content-Type', 'application/json')
res.json(transformedData)
} catch (error) {
res.status(500).json({
error: 'Failed to fetch metadata',
details: error.message
})
}
})

app.get('*', runtime.render)
})
// SSR requires that we export a single handler function called 'get', that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,15 @@ module.exports = {
groupBonusProductsWithQualifyingProduct: true
}
},
// Salesforce Payments configuration
// Set enabled to false to disable Salesforce Payments even if the Commerce Cloud instance supports it.
// Example URLs: sdkUrl: 'https://<instance>.unified.demandware.net/on/demandware.static/Sites-Site/-/-/internal/jscript/sfp/v1/sfp.js'
// metadataUrl: 'https://<instance>.unified.demandware.net/on/demandware.static/Sites-Site/-/-/internal/metadata/v1.json'
sfPayments: {
enabled: true,
sdkUrl: '',
metadataUrl: ''
},
// Google Cloud api config
googleCloudAPI: {
apiKey: process.env.GOOGLE_CLOUD_API_KEY
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ const AppConfig = ({children, locals = {}}) => {
defaultDnt={DEFAULT_DNT_STATE}
// Set 'enablePWAKitPrivateClient' to true to use SLAS private client login flows.
// Make sure to also enable useSLASPrivateClient in ssr.js when enabling this setting.
enablePWAKitPrivateClient={true}
enablePWAKitPrivateClient={false}
privateClientProxyEndpoint={slasPrivateClientProxyEndpoint}
// Uncomment 'hybridAuthEnabled' if the current site has Hybrid Auth enabled. Do NOT set this flag for hybrid storefronts using Plugin SLAS.
// hybridAuthEnabled={true}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,40 +288,50 @@ export default function useAddressFields({
name: `${prefix}stateCode`,
label: formatMessage(countryCode === 'CA' ? messages.province : messages.state),
defaultValue: '',
type: 'select',
options: [
{value: '', label: ''},
...(countryCode === 'CA' ? provinceOptions : stateOptions)
],
type: countryCode === 'US' || countryCode === 'CA' ? 'select' : 'text',
options:
countryCode === 'US' || countryCode === 'CA'
? [
{value: '', label: ''},
...(countryCode === 'CA' ? provinceOptions : stateOptions)
]
: undefined,
rules: {
required:
countryCode === 'CA'
? 'Please select your province.' // FYI we won't translate this
: formatMessage({
: countryCode === 'US'
? formatMessage({
defaultMessage: 'Please select your state.',
id: 'use_address_fields.error.please_select_your_state_or_province',
description: 'Error message for a blank state (US-specific checkout)'
})
: false
},
error: errors[`${prefix}stateCode`],
control
},
postalCode: {
name: `${prefix}postalCode`,
label: formatMessage(countryCode === 'CA' ? messages.postalCode : messages.zipCode),
label: formatMessage(countryCode === 'US' ? messages.zipCode : messages.postalCode),
defaultValue: '',
type: 'text',
autoComplete: 'postal-code',
rules: {
required:
countryCode === 'CA'
? 'Please enter your postal code.' // FYI we won't translate this
: formatMessage({
? 'Please enter your postal code.'
: countryCode === 'US'
? formatMessage({
defaultMessage: 'Please enter your zip code.',
id: 'use_address_fields.error.please_enter_your_postal_or_zip',
description:
'Error message for a blank zip code (US-specific checkout)'
})
: formatMessage({
defaultMessage: 'Please enter your postal code.',
id: 'use_address_fields.error.please_enter_postal_code'
})
},
error: errors[`${prefix}postalCode`],
control
Expand Down
Loading
Loading