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
2 changes: 1 addition & 1 deletion packages/commerce-sdk-react/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
## v5.1.0-dev
- Bump commerce-sdk-isomorphic to 5.1.0-unstable-20260226081656
- Bump commerce-sdk-isomorphic to 5.1.0
- Add Node 24 support. Drop Node 16 support. [#3652](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3652)
- Add Shopper Consents API support [#3674](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3674)

Expand Down
8 changes: 4 additions & 4 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.1.0-unstable-20260226081656",
"commerce-sdk-isomorphic": "5.1.0",
"js-cookie": "^3.0.1",
"jwt-decode": "^4.0.0"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,14 @@ module.exports = {
}
},
// 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'
// Set enabled to true to enable Salesforce Payments (requires the Salesforce Payments feature toggle to be enabled on the Commerce Cloud instance).
// Set enabled to false to disable Salesforce Payments on the storefront (the Commerce Cloud feature toggle is unaffected).
// sdkUrl and metadataUrl are hosted on your Commerce Cloud instance. Replace <hostname> with your instance hostname.
// This may be a demandware.net hostname (e.g., myinstance.unified.demandware.net) or a vanity/custom hostname.
// sdkUrl: 'https://<hostname>/on/demandware.static/Sites-Site/-/-/internal/jscript/sfp/v1/sfp.js'
// metadataUrl: 'https://<hostname>/on/demandware.static/Sites-Site/-/-/internal/metadata/v1.json'
sfPayments: {
enabled: true,
enabled: false,
sdkUrl: '',
metadataUrl: ''
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
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 @@ -359,7 +358,8 @@ const {handler} = runtime.createHandler(options, (app) => {
'*.stripe.com',
'*.paypal.com',
'*.adyen.com',
'*.google.com',
'pay.google.com',
'www.gstatic.com',
'*.demandware.net' // Used to load a valid payment scripts in test environment
],
'connect-src': [
Expand All @@ -375,16 +375,21 @@ const {handler} = runtime.createHandler(options, (app) => {
// Payment gateways
'*.demandware.net', // Used to load a valid payment scripts in test environment
'*.adyen.com',
'*.google.com'
'*.paypal.com',
'pay.google.com',
'payments.google.com',
'google.com',
'www.google.com'
],
'frame-src': [
// Allow frames from Salesforce site.com (Needed for MIAW)
'*.site.com',
// Payment gateways
'*.stripe.com',
'*.paypal.com',
'*.google.com',
'*.adyen.com'
'*.adyen.com',
'payments.google.com',
'pay.google.com'
]
}
}
Expand Down Expand Up @@ -450,49 +455,30 @@ const {handler} = runtime.createHandler(options, (app) => {
// 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)
const methodTypes = data?.paymentMethodTypes
if (methodTypes) {
for (const method of Object.values(methodTypes)) {
for (const image of method.images ?? []) {
if (image.src?.startsWith('/icons/')) {
image.src = `${baseUrl}${image.src}`
}
}
}
}
return data
}


// Helper function to fetch payment metadata from the Commerce Cloud instance
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()
const response = await fetch(config.app.sfPayments.metadataUrl, {
headers: { Accept: 'application/json' }
})

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

if (!response.ok) {
throw new Error(`Metadata request failed with status: ${response.status}`)
}
const data = await response.json()
const transformedData = transformIconPaths(data, new URL(config.app.sfPayments.metadataUrl).hostname)
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Content-Type', 'application/json')
res.json(transformedData)
Expand All @@ -503,6 +489,7 @@ const {handler} = runtime.createHandler(options, (app) => {
})
}
})

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,7 +10,6 @@
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 @@ -359,7 +358,8 @@ const {handler} = runtime.createHandler(options, (app) => {
'*.stripe.com',
'*.paypal.com',
'*.adyen.com',
'*.google.com',
'pay.google.com',
'www.gstatic.com',
'*.demandware.net' // Used to load a valid payment scripts in test environment
],
'connect-src': [
Expand All @@ -375,16 +375,21 @@ const {handler} = runtime.createHandler(options, (app) => {
// Payment gateways
'*.demandware.net', // Used to load a valid payment scripts in test environment
'*.adyen.com',
'*.google.com'
'*.paypal.com',
'pay.google.com',
'payments.google.com',
'google.com',
'www.google.com'
],
'frame-src': [
// Allow frames from Salesforce site.com (Needed for MIAW)
'*.site.com',
// Payment gateways
'*.stripe.com',
'*.paypal.com',
'*.google.com',
'*.adyen.com'
'*.adyen.com',
'payments.google.com',
'pay.google.com'
]
}
}
Expand Down Expand Up @@ -450,49 +455,30 @@ const {handler} = runtime.createHandler(options, (app) => {
// 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)
const methodTypes = data?.paymentMethodTypes
if (methodTypes) {
for (const method of Object.values(methodTypes)) {
for (const image of method.images ?? []) {
if (image.src?.startsWith('/icons/')) {
image.src = `${baseUrl}${image.src}`
}
}
}
}
return data
}


// Helper function to fetch payment metadata from the Commerce Cloud instance
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()
const response = await fetch(config.app.sfPayments.metadataUrl, {
headers: { Accept: 'application/json' }
})

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

if (!response.ok) {
throw new Error(`Metadata request failed with status: ${response.status}`)
}
const data = await response.json()
const transformedData = transformIconPaths(data, new URL(config.app.sfPayments.metadataUrl).hostname)
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Content-Type', 'application/json')
res.json(transformedData)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,14 @@ module.exports = {
}
},
// 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'
// Set enabled to true to enable Salesforce Payments (requires the Salesforce Payments feature toggle to be enabled on the Commerce Cloud instance).
// Set enabled to false to disable Salesforce Payments on the storefront (the Commerce Cloud feature toggle is unaffected).
// sdkUrl and metadataUrl are hosted on your Commerce Cloud instance. Replace <hostname> with your instance hostname.
// This may be a demandware.net hostname (e.g., myinstance.unified.demandware.net) or a vanity/custom hostname.
// sdkUrl: 'https://<hostname>/on/demandware.static/Sites-Site/-/-/internal/jscript/sfp/v1/sfp.js'
// metadataUrl: 'https://<hostname>/on/demandware.static/Sites-Site/-/-/internal/metadata/v1.json'
sfPayments: {
enabled: true,
enabled: false,
sdkUrl: '',
metadataUrl: ''
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ export const useCurrentBasket = ({id = ''} = {}) => {
])

return {
data: currentBasket,
...restOfQuery,
data: currentBasket,
derivedData: {
// Only true if a non-temporary basket exists (temporary baskets are filtered out above)
hasBasket: !!currentBasket,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export const useSFPayments = () => {
}
}, [status.loaded])

const metadataUrl = config?.app?.sfPayments?.metadataUrl
const localEnabled = config?.app?.sfPayments?.enabled ?? true

const {data: serverMetadata, isLoading: serverMetadataLoading} = useQuery({
queryKey: ['payment-metadata'],
queryFn: async () => {
Expand All @@ -52,6 +55,9 @@ export const useSFPayments = () => {
}
return await response.json()
},
// Only fetch metadata if metadataUrl is set and sfPayments is enabled,
// prevents any 500 on server side and unnecessary network requests
enabled: localEnabled && !!metadataUrl,
staleTime: 10 * 60 * 1000 // 10 minutes
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ describe('useSFPayments hook', () => {
mockGetConfig.mockReturnValue({
app: {
sfPayments: {
sdkUrl: 'https://test.sfpayments.com/sdk.js'
sdkUrl: 'https://test.sfpayments.com/sdk.js',
metadataUrl: 'https://test.sfpayments.com/metadata'
}
}
})
Expand Down Expand Up @@ -298,6 +299,43 @@ describe('useSFPayments hook', () => {
})
})

describe('metadata query guard', () => {
test('does not fetch metadata when metadataUrl is empty', async () => {
mockGetConfig.mockReturnValue({
app: {
sfPayments: {
sdkUrl: 'https://test.sfpayments.com/sdk.js',
metadataUrl: ''
}
}
})

renderWithQueryClient(<TestComponent />)

await waitFor(() => {
expect(mockFetch).not.toHaveBeenCalled()
})
})

test('does not fetch metadata when sfPayments is disabled', async () => {
mockGetConfig.mockReturnValue({
app: {
sfPayments: {
enabled: false,
sdkUrl: 'https://test.sfpayments.com/sdk.js',
metadataUrl: 'https://test.sfpayments.com/metadata'
}
}
})

renderWithQueryClient(<TestComponent />)

await waitFor(() => {
expect(mockFetch).not.toHaveBeenCalled()
})
})
})

describe('confirming basket state', () => {
test('startConfirming updates confirmingBasket', async () => {
let hookData
Expand Down
Loading
Loading