-
Notifications
You must be signed in to change notification settings - Fork 212
W-21354759: Implement SF Payments support in create app package #3698
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
681117c
78cc3c9
bac1d4d
66b0251
e22fe12
acd79e0
1e3ec54
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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' | ||
|
|
@@ -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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's leave this demandware.net one out of the generated template.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| ], | ||
| 'connect-src': [ | ||
| // Connect to Einstein APIs | ||
|
|
@@ -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' | ||
| ] | ||
| } | ||
| } | ||
|
|
@@ -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 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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' | ||
|
|
@@ -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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above. Any idea why there are two copies of this?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
@@ -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' | ||
| ] | ||
| } | ||
| } | ||
|
|
@@ -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 | ||
|
|
||
There was a problem hiding this comment.
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.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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