Skip to content

Commit 85b2a56

Browse files
Google Pay implementation (#2891)
* fix: pwa-pr-review-changes (#2743) * Initial Google Pay commit * Some clean up * Use billing address from payment method + next step * Fix paymentDataCallbacks * Cleanup * Add utests and ssr.js * ssr.js typo * cleanup * update ssr.js * Fixing linter errors * Update dropped pieces from rebase * cleanup * Rerun tests * fixing linter errors * Linter fix(?) * Linter again --------- Co-authored-by: Avinash Kasipathy <akasipathy@salesforce.com>
1 parent 68155db commit 85b2a56

File tree

23 files changed

+3242
-348
lines changed

23 files changed

+3242
-348
lines changed

packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/ssr.js.hbs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,8 @@ const {handler} = runtime.createHandler(options, (app) => {
292292
// Default source for product images - replace with your CDN
293293
'*.commercecloud.salesforce.com',
294294
'*.demandware.net',
295-
'checkoutshopper-test.adyen.com'
295+
'checkoutshopper-test.adyen.com',
296+
'https://www.gstatic.com'
296297
],
297298
'script-src': [
298299
// Used by the service worker in /worker/main.js
@@ -308,7 +309,7 @@ const {handler} = runtime.createHandler(options, (app) => {
308309
// Connect to SCRT2 URLs
309310
'*.salesforce-scrt.com'
310311
],
311-
'frame-src': ['checkoutshopper-test.adyen.com'],
312+
'frame-src': ['checkoutshopper-test.adyen.com', 'https://pay.google.com'],
312313
'frame-ancestors': ["'self'"]
313314
}
314315
},

packages/pwa-kit-create-app/assets/templates/@salesforce/retail-react-app/app/ssr.js.hbs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,8 @@ const {handler} = runtime.createHandler(options, (app) => {
291291
'img-src': [
292292
// Default source for product images - replace with your CDN
293293
'*.commercecloud.salesforce.com',
294-
'checkoutshopper-test.adyen.com'
294+
'checkoutshopper-test.adyen.com',
295+
'https://www.gstatic.com'
295296
],
296297
'script-src': [
297298
// Used by the service worker in /worker/main.js
@@ -307,7 +308,7 @@ const {handler} = runtime.createHandler(options, (app) => {
307308
// Connect to SCRT2 URLs
308309
'*.salesforce-scrt.com'
309310
],
310-
'frame-src': ['checkoutshopper-test.adyen.com'],
311+
'frame-src': ['checkoutshopper-test.adyen.com', 'https://pay.google.com'],
311312
'frame-ancestors': ["'self'"]
312313
}
313314
},

packages/pwa-kit-create-app/package-lock.json

Lines changed: 115 additions & 118 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/template-retail-react-app/app/components/express/index.jsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import React, {useEffect, useState} from 'react'
88

99
import {useAccessToken, useCustomerId} from '@salesforce/commerce-sdk-react'
1010
import {AdyenExpressCheckoutProvider} from '@adyen/adyen-salesforce-pwa'
11-
import {ApplePayExpress} from '@salesforce/retail-react-app/app/components/apple-pay-express/index'
11+
12+
import {GooglePayExpress} from '@salesforce/retail-react-app/app/components/google-pay-express/index'
1213
import useMultiSite from '@salesforce/retail-react-app/app/hooks/use-multi-site'
1314
import useNavigation from '@salesforce/retail-react-app/app/hooks/use-navigation'
1415
import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket'
@@ -45,7 +46,8 @@ function Express() {
4546
basket={basket}
4647
navigate={navigate}
4748
>
48-
<ApplePayExpress />
49+
{/* <ApplePayExpress /> */}
50+
<GooglePayExpress />
4951
</AdyenExpressCheckoutProvider>
5052
</div>
5153
)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (c) 2025, Salesforce, Inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
export class ApiClient {
8+
url = null
9+
token = null
10+
site = null
11+
12+
constructor(url, token, site) {
13+
this.url = url
14+
this.token = token
15+
this.site = site
16+
}
17+
18+
base(method, options) {
19+
const queryParams = {
20+
siteId: this.site.id,
21+
...(options?.queryParams || {})
22+
}
23+
const fullUrl = `${this.url}?${new URLSearchParams(queryParams)}`
24+
return fetch(fullUrl, {
25+
method: method,
26+
body: options?.body || null,
27+
headers: {
28+
'Content-Type': 'application/json',
29+
authorization: `Bearer ${this.token}`,
30+
...options?.headers
31+
}
32+
})
33+
}
34+
35+
get(options) {
36+
return this.base('get', options)
37+
}
38+
39+
post(options) {
40+
return this.base('post', options)
41+
}
42+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright (c) 2025, Salesforce, Inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
import {ApiClient} from '@salesforce/retail-react-app/app/components/express/utils/api'
8+
9+
describe('ApiClient', () => {
10+
let apiClient
11+
const mockUrl = '/api/test'
12+
const mockToken = 'test-token'
13+
const mockSite = {id: 'test-site'}
14+
15+
beforeEach(() => {
16+
apiClient = new ApiClient(mockUrl, mockToken, mockSite)
17+
jest.clearAllMocks()
18+
})
19+
20+
describe('constructor', () => {
21+
it('should initialize with correct properties', () => {
22+
expect(apiClient.url).toBe(mockUrl)
23+
expect(apiClient.token).toBe(mockToken)
24+
expect(apiClient.site).toBe(mockSite)
25+
})
26+
})
27+
28+
describe('base method', () => {
29+
it('should make a GET request with correct parameters', async () => {
30+
const mockResponse = {status: 200, json: () => Promise.resolve({data: 'test'})}
31+
const mockFetch = jest.fn().mockResolvedValue(mockResponse)
32+
global.fetch = mockFetch
33+
34+
await apiClient.base('GET', {
35+
queryParams: {param1: 'value1'}
36+
})
37+
38+
expect(mockFetch).toHaveBeenCalledWith(
39+
`${mockUrl}?siteId=${mockSite.id}&param1=value1`,
40+
{
41+
method: 'GET',
42+
body: null,
43+
headers: {
44+
'Content-Type': 'application/json',
45+
authorization: `Bearer ${mockToken}`
46+
}
47+
}
48+
)
49+
})
50+
51+
it('should make a POST request with body and headers', async () => {
52+
const mockResponse = {status: 200, json: () => Promise.resolve({data: 'test'})}
53+
const mockFetch = jest.fn().mockResolvedValue(mockResponse)
54+
global.fetch = mockFetch
55+
56+
const body = JSON.stringify({test: 'data'})
57+
const headers = {custom: 'header'}
58+
59+
await apiClient.base('POST', {
60+
body,
61+
headers
62+
})
63+
64+
expect(mockFetch).toHaveBeenCalledWith(`${mockUrl}?siteId=${mockSite.id}`, {
65+
method: 'POST',
66+
body,
67+
headers: {
68+
'Content-Type': 'application/json',
69+
authorization: `Bearer ${mockToken}`,
70+
custom: 'header'
71+
}
72+
})
73+
})
74+
})
75+
76+
describe('get method', () => {
77+
it('should call base with GET method', async () => {
78+
const mockResponse = {status: 200, json: () => Promise.resolve({data: 'test'})}
79+
const mockFetch = jest.fn().mockResolvedValue(mockResponse)
80+
global.fetch = mockFetch
81+
82+
const spy = jest.spyOn(apiClient, 'base')
83+
84+
await apiClient.get({param: 'value'})
85+
86+
expect(spy).toHaveBeenCalledWith('get', {param: 'value'})
87+
})
88+
})
89+
90+
describe('post method', () => {
91+
it('should call base with POST method', async () => {
92+
const mockResponse = {status: 200, json: () => Promise.resolve({data: 'test'})}
93+
const mockFetch = jest.fn().mockResolvedValue(mockResponse)
94+
global.fetch = mockFetch
95+
96+
const spy = jest.spyOn(apiClient, 'base')
97+
98+
await apiClient.post({param: 'value'})
99+
100+
expect(spy).toHaveBeenCalledWith('post', {param: 'value'})
101+
})
102+
})
103+
})

0 commit comments

Comments
 (0)