diff --git a/config/application.properties b/config/application.properties index 728551d..2316732 100644 --- a/config/application.properties +++ b/config/application.properties @@ -18,7 +18,18 @@ ADYEN_LEM_API_KEY= ## HMAC key to validate incoming webhook requests ADYEN_HMAC_KEY= +## URL of the Session Authentication API to create the session token required by Adyen AfP web components +## https://docs.adyen.com/platforms/build-user-dashboards/ +## +## Default: https://test.adyen.com/authe/api/v1/sessions +ADYEN_SESSION_AUTHENTICATION_API_URL=https://test.adyen.com/authe/api/v1/sessions + ## (Optional) Id of the Hosted Onboarding Theme created in the Customer Area -# When undefined the default theme will be used +## When undefined the default theme will be used ADYEN_HOSTED_ONBOARDING_THEME_ID= +## URL where the AfP components will appear +## Examples: https://www.example.com | https://*.example.com | http://localhost +## +ADYEN_COMPONENTS_ALLOW_ORIGIN= + diff --git a/react-app/package-lock.json b/react-app/package-lock.json index 230831e..aaec604 100644 --- a/react-app/package-lock.json +++ b/react-app/package-lock.json @@ -8,6 +8,7 @@ "name": "react-app", "version": "0.1.0", "dependencies": { + "@adyen/adyen-platform-experience-web": "^1.1.1", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", "@fontsource/roboto": "^5.1.0", @@ -37,6 +38,18 @@ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz", "integrity": "sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==" }, + "node_modules/@adyen/adyen-platform-experience-web": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@adyen/adyen-platform-experience-web/-/adyen-platform-experience-web-1.1.1.tgz", + "integrity": "sha512-CV3HKyoq/vxsmcJZ75EV7EY+Vioocpq/1qP9ygelTzmUH4x7VaYQSXjZd8SV87WcA4AMAVN0krz5jUhNEvym3A==", + "dependencies": { + "classnames": "^2.5.1", + "core-js": "^3.36.0" + }, + "peerDependencies": { + "preact": "^10.13.2" + } + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -5676,6 +5689,11 @@ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==" }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "node_modules/clean-css": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz", @@ -5892,9 +5910,9 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "node_modules/core-js": { - "version": "3.33.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.0.tgz", - "integrity": "sha512-HoZr92+ZjFEKar5HS6MC776gYslNOKHt75mEBKWKnPeFDpZ6nH5OeF3S6HFT1mUAUZKrzkez05VboaX8myjSuw==", + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.1.tgz", + "integrity": "sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -13021,6 +13039,16 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "node_modules/preact": { + "version": "10.24.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.2.tgz", + "integrity": "sha512-1cSoF0aCC8uaARATfrlz4VCBqE8LwZwRfLgkxJOQwAlQt6ayTmi0D9OF7nXid1POI5SZidFuG9CnlXbDfLqY/Q==", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", diff --git a/react-app/package.json b/react-app/package.json index 3635a4f..9453af7 100644 --- a/react-app/package.json +++ b/react-app/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@adyen/adyen-platform-experience-web": "^1.1.1", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", "@fontsource/roboto": "^5.1.0", diff --git a/react-app/src/dashboard/Dashboard.js b/react-app/src/dashboard/Dashboard.js index 82d6aa2..8a4d8dd 100644 --- a/react-app/src/dashboard/Dashboard.js +++ b/react-app/src/dashboard/Dashboard.js @@ -8,7 +8,6 @@ import Grid from "@mui/material/Grid"; import Toolbar from '@mui/material/Toolbar'; import Typography from '@mui/material/Typography'; import Divider from '@mui/material/Divider'; -import BarChartIcon from '@mui/icons-material/BarChart'; import { useNavigate } from 'react-router-dom'; diff --git a/react-app/src/dashboard/Transactions.js b/react-app/src/dashboard/Transactions.js index 2795016..7008d06 100644 --- a/react-app/src/dashboard/Transactions.js +++ b/react-app/src/dashboard/Transactions.js @@ -8,131 +8,77 @@ import Divider from '@mui/material/Divider'; import { useNavigate } from 'react-router-dom'; import Paper from '@mui/material/Paper'; -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableCell from '@mui/material/TableCell'; -import TableContainer from '@mui/material/TableContainer'; -import TableHead from '@mui/material/TableHead'; -import TablePagination from '@mui/material/TablePagination'; -import TableRow from '@mui/material/TableRow'; import DashboardHeader from "./DashboardHeader.js"; import DashboardDrawer from "./DashboardDrawer.js"; +import { AdyenPlatformExperience, TransactionsOverview } from '@adyen/adyen-platform-experience-web'; +import "@adyen/adyen-platform-experience-web/adyen-platform-experience-web.css"; + export default function Products() { const navigate = useNavigate() - const [page, setPage] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(10); - const [rows, setRows] = useState([]); - - const handleChangePage = (event, newPage) => { - setPage(newPage); - }; - - const handleChangeRowsPerPage = (event) => { - setRowsPerPage(+event.target.value); - setPage(0); - }; - - useEffect(() => { - const fetchData = async () => { - try { - const response = await axios.post('/api/dashboard/getTransactions'); - setRows(response.data); - } catch (error) { - console.error('API request error:', error); - navigate('/'); - } - }; - - fetchData(); - }, []); - - return ( - - - - - - - - - - - - - - - - - - - {columns.map((column) => ( - - {column.label} - - ))} - - - - {rows - .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) - .map((row) => { - return ( - - {columns.map((column) => { - const value = row[column.id]; - return ( - - {column.format && typeof value === 'number'? column.format(value) : value} - - ); - })} - - ); - })} - -
-
- -
- - -
-
+ async function sessionRequest() { + try { + const response = await fetch('/api/dashboard/getTransactions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) { + throw new Error(`Error: ${response.status} ${response.statusText}`); + } + + return await response.json(); + } catch (error) { + console.error("sessionRequest error: ", error); + throw error; + } + } + + async function initializeCore() { + const core = await AdyenPlatformExperience({ + environment: 'test', // test or live + locale: 'en-US', + async onSessionCreate() { + const session = await sessionRequest(); + return session; + } + }); + const transactionsOverview = new TransactionsOverview({ core: core, preferredLimit: 10, allowLimitSelection: true }); + + transactionsOverview.mount('#transactions-overview-container'); + } + + initializeCore(); + + return ( + + + + + + + + + + + + + + +
+
+ +
+
); } - -const columns = [ - { id: 'id', label: 'ID', minWidth: 100 }, - { id: 'status', label: 'Status', minWidth: 120 }, - { id: 'type', label: 'Type', minWidth: 120 }, - { id: 'created', label: 'Created', minWidth: 220 }, - { id: 'amount', label: 'Amount', minWidth: 150 }, -]; \ No newline at end of file diff --git a/react-app/src/dashboard/TransactionsCustomView.js b/react-app/src/dashboard/TransactionsCustomView.js new file mode 100644 index 0000000..2795016 --- /dev/null +++ b/react-app/src/dashboard/TransactionsCustomView.js @@ -0,0 +1,138 @@ +import React, { useState, useEffect } from 'react'; +import axios from "axios"; + +import Box from '@mui/material/Box'; +import Chip from '@mui/material/Chip'; +import Toolbar from '@mui/material/Toolbar'; +import Divider from '@mui/material/Divider'; +import { useNavigate } from 'react-router-dom'; + +import Paper from '@mui/material/Paper'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TablePagination from '@mui/material/TablePagination'; +import TableRow from '@mui/material/TableRow'; + +import DashboardHeader from "./DashboardHeader.js"; +import DashboardDrawer from "./DashboardDrawer.js"; + +export default function Products() { + + const navigate = useNavigate() + + const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(10); + const [rows, setRows] = useState([]); + + const handleChangePage = (event, newPage) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = (event) => { + setRowsPerPage(+event.target.value); + setPage(0); + }; + + useEffect(() => { + const fetchData = async () => { + try { + const response = await axios.post('/api/dashboard/getTransactions'); + setRows(response.data); + } catch (error) { + console.error('API request error:', error); + navigate('/'); + } + }; + + fetchData(); + }, []); + + return ( + + + + + + + + + + + + + + + + + + + {columns.map((column) => ( + + {column.label} + + ))} + + + + {rows + .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) + .map((row) => { + return ( + + {columns.map((column) => { + const value = row[column.id]; + return ( + + {column.format && typeof value === 'number'? column.format(value) : value} + + ); + })} + + ); + })} + +
+
+ +
+ + +
+
+ ); +} + +const columns = [ + { id: 'id', label: 'ID', minWidth: 100 }, + { id: 'status', label: 'Status', minWidth: 120 }, + { id: 'type', label: 'Type', minWidth: 120 }, + { id: 'created', label: 'Created', minWidth: 220 }, + { id: 'amount', label: 'Amount', minWidth: 150 }, +]; \ No newline at end of file diff --git a/react-app/src/home/Home.js b/react-app/src/home/Home.js index 97b85de..af12f8b 100644 --- a/react-app/src/home/Home.js +++ b/react-app/src/home/Home.js @@ -18,7 +18,7 @@ const card1 = (
- Image Description + Users onboarding

@@ -35,7 +35,7 @@ const card2 = (
- Image Description + Support for multiple payment methods

@@ -52,7 +52,7 @@ const card3 = (
- Image Description + Online shopping

diff --git a/react-app/src/index.css b/react-app/src/index.css index ec2585e..e47ff11 100644 --- a/react-app/src/index.css +++ b/react-app/src/index.css @@ -1,13 +1,27 @@ body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } + +:root { + --adyen-sdk-color-outline-primary-active: lightgreen; + --adyen-sdk-color-background-inverse-primary: #0abf53; + --adyen-sdk-color-background-inverse-primary-hover: #57d389; + --adyen-sdk-color-background-disabled: #9ecdb1; + --adyen-sdk-border-radius-m: 20px; +} + +.adyen-pe-modal { + border-radius: 0; + border-left: 3px solid lightgreen; + height: 100%; + right: 0; +} \ No newline at end of file diff --git a/react-app/src/index.js b/react-app/src/index.js index 94d8ae4..9513af1 100644 --- a/react-app/src/index.js +++ b/react-app/src/index.js @@ -5,6 +5,8 @@ import { ThemeProvider } from '@mui/material/styles'; import App from './App'; import theme from './theme'; +import "./index.css"; + const rootElement = document.getElementById('root'); const root = ReactDOM.createRoot(rootElement); diff --git a/src/main/java/com/adyen/config/ApplicationProperty.java b/src/main/java/com/adyen/config/ApplicationProperty.java index 0030c0d..347c740 100644 --- a/src/main/java/com/adyen/config/ApplicationProperty.java +++ b/src/main/java/com/adyen/config/ApplicationProperty.java @@ -24,6 +24,12 @@ public class ApplicationProperty { @Value("${ADYEN_HOSTED_ONBOARDING_THEME_ID}") private String hostedOnboardingThemeId; + @Value("${ADYEN_SESSION_AUTHENTICATION_API_URL:https://test.adyen.com/authe/api/v1/sessions}") + private String sessionAuthenticationApiUrl; + + @Value("${ADYEN_COMPONENTS_ALLOW_ORIGIN}") + private String componentsAllowOrigin; + public String getApiKey() { return apiKey; } @@ -71,4 +77,20 @@ public String getHmacKey() { public void setHmacKey(String hmacKey) { this.hmacKey = hmacKey; } + + public String getSessionAuthenticationApiUrl() { + return sessionAuthenticationApiUrl; + } + + public void setSessionAuthenticationApiUrl(String sessionAuthenticationApiUrl) { + this.sessionAuthenticationApiUrl = sessionAuthenticationApiUrl; + } + + public String getComponentsAllowOrigin() { + return componentsAllowOrigin; + } + + public void setComponentsAllowOrigin(String componentsAllowOrigin) { + this.componentsAllowOrigin = componentsAllowOrigin; + } } diff --git a/src/main/java/com/adyen/controller/DashboardController.java b/src/main/java/com/adyen/controller/DashboardController.java index 0d99ec1..98fc43a 100644 --- a/src/main/java/com/adyen/controller/DashboardController.java +++ b/src/main/java/com/adyen/controller/DashboardController.java @@ -1,14 +1,14 @@ package com.adyen.controller; -import com.adyen.model.OnboardingLinkProperties; -import com.adyen.model.TransactionItem; -import com.adyen.model.User; +import com.adyen.config.ApplicationProperty; +import com.adyen.model.*; import com.adyen.model.balanceplatform.AccountHolder; import com.adyen.model.legalentitymanagement.LegalEntity; import com.adyen.model.legalentitymanagement.OnboardingLink; import com.adyen.service.ConfigurationAPIService; import com.adyen.service.LegalEntityManagementAPIService; import com.adyen.util.LegalEntityHandler; +import com.adyen.util.RestClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -32,6 +32,11 @@ public class DashboardController extends BaseController { private LegalEntityManagementAPIService legalEntityManagementAPIService; @Autowired private LegalEntityHandler legalEntityHandler; + @Autowired + private RestClient restClient; + @Autowired + private ApplicationProperty applicationProperty; + /** * Get User who has logged in (user id found in Session) @@ -95,14 +100,55 @@ ResponseEntity getOnboardingLink(@RequestBody OnboardingLinkProperties o ); } + /** + * Displays the AccountHolder transactions using the Adyen Transactions component + * + * This demonstrates how to integrate the Adyen web component that fetches and + * displays the transactions + * + * @return + */ @PostMapping("/getTransactions") - ResponseEntity> getTransactions() { + ResponseEntity getTransactions() { if (getUserIdOnSession() == null) { log.warn("User is not logged in"); return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); } + // Perform session call to obtain a valid Session token + SessionRequest sessionRequest = new SessionRequest() + .allowOrigin(getApplicationProperty().getComponentsAllowOrigin()) + .product("platform") + .policy(new SessionRequestPolicy() + .resources(List.of(new PolicyResource() + .accountHolderId(getUserIdOnSession()) + .type("accountHolder"))) + .roles(List.of("Transactions Overview Component: View"))); + + SessionResponse response = restClient.call(getApplicationProperty().getSessionAuthenticationApiUrl(), sessionRequest); + + return new ResponseEntity<>(response, HttpStatus.ACCEPTED); + } + + /** + * Displays the AccountHolder transactions in a custom view + * + * This demonstrates how to fetch the AccountHolder transactions via API and + * display them in a custom-built view + * + * @return + */ + @PostMapping("/getTransactionsCustomView") + ResponseEntity> getTransactionsCustomView() { + + if (getUserIdOnSession() == null) { + log.warn("User is not logged in"); + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } + + + return new ResponseEntity<>( getConfigurationAPIService().getTransactions(getUserIdOnSession()), HttpStatus.ACCEPTED); } @@ -130,4 +176,20 @@ public LegalEntityHandler getLegalEntityHandler() { public void setLegalEntityHandler(LegalEntityHandler legalEntityHandler) { this.legalEntityHandler = legalEntityHandler; } + + public RestClient getRestClient() { + return restClient; + } + + public void setRestClient(RestClient restClient) { + this.restClient = restClient; + } + + public ApplicationProperty getApplicationProperty() { + return applicationProperty; + } + + public void setApplicationProperty(ApplicationProperty applicationProperty) { + this.applicationProperty = applicationProperty; + } } diff --git a/src/main/java/com/adyen/model/PolicyResource.java b/src/main/java/com/adyen/model/PolicyResource.java new file mode 100644 index 0000000..ad9cca3 --- /dev/null +++ b/src/main/java/com/adyen/model/PolicyResource.java @@ -0,0 +1,36 @@ +package com.adyen.model; + +import java.util.List; + +public class PolicyResource { + + private String accountHolderId; + private String type; + + public String getAccountHolderId() { + return accountHolderId; + } + + public void setAccountHolderId(String accountHolderId) { + this.accountHolderId = accountHolderId; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public PolicyResource accountHolderId(String accountHolderId) { + this.accountHolderId = accountHolderId; + return this; + } + + public PolicyResource type(String type) { + this.type = type; + return this; + } + +} diff --git a/src/main/java/com/adyen/model/SessionRequest.java b/src/main/java/com/adyen/model/SessionRequest.java new file mode 100644 index 0000000..bf6b4e8 --- /dev/null +++ b/src/main/java/com/adyen/model/SessionRequest.java @@ -0,0 +1,50 @@ +package com.adyen.model; + +import java.security.Policy; + +public class SessionRequest { + + private String allowOrigin; + private String product; + private SessionRequestPolicy policy; + + public String getAllowOrigin() { + return allowOrigin; + } + + public void setAllowOrigin(String allowOrigin) { + this.allowOrigin = allowOrigin; + } + + public String getProduct() { + return product; + } + + public void setProduct(String product) { + this.product = product; + } + + public SessionRequestPolicy getPolicy() { + return policy; + } + + public void setPolicy(SessionRequestPolicy policy) { + this.policy = policy; + } + + public SessionRequest allowOrigin(String allowOrigin) { + this.allowOrigin = allowOrigin; + return this; + } + + public SessionRequest product(String product) { + this.product = product; + return this; + } + + public SessionRequest policy(SessionRequestPolicy policy) { + this.policy = policy; + return this; + } + +} diff --git a/src/main/java/com/adyen/model/SessionRequestPolicy.java b/src/main/java/com/adyen/model/SessionRequestPolicy.java new file mode 100644 index 0000000..f8a1d9d --- /dev/null +++ b/src/main/java/com/adyen/model/SessionRequestPolicy.java @@ -0,0 +1,36 @@ +package com.adyen.model; + +import java.util.List; + +public class SessionRequestPolicy { + + private List resources; + private List roles; + + public List getResources() { + return resources; + } + + public void setResources(List resources) { + this.resources = resources; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public SessionRequestPolicy resources(List resources) { + this.resources = resources; + return this; + } + + public SessionRequestPolicy roles(List roles) { + this.roles = roles; + return this; + } + +} diff --git a/src/main/java/com/adyen/model/SessionResponse.java b/src/main/java/com/adyen/model/SessionResponse.java new file mode 100644 index 0000000..baa178c --- /dev/null +++ b/src/main/java/com/adyen/model/SessionResponse.java @@ -0,0 +1,23 @@ +package com.adyen.model; + +public class SessionResponse { + + private String id; + private String token; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } +} diff --git a/src/main/java/com/adyen/util/RestClient.java b/src/main/java/com/adyen/util/RestClient.java new file mode 100644 index 0000000..4a9ffa0 --- /dev/null +++ b/src/main/java/com/adyen/util/RestClient.java @@ -0,0 +1,52 @@ +package com.adyen.util; + +import com.adyen.config.ApplicationProperty; +import com.adyen.model.PolicyResource; +import com.adyen.model.SessionRequest; +import com.adyen.model.SessionRequestPolicy; +import com.adyen.model.SessionResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.List; + +@Service +public class RestClient { + + private final Logger log = LoggerFactory.getLogger(RestClient.class); + + @Autowired + private ApplicationProperty applicationProperty; + + public SessionResponse call(String url, SessionRequest payload) { + + log.info("call {}", url); + + RestTemplate restTemplate = new RestTemplate(); + + // Set headers + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("X-API-Key", this.getApplicationProperty().getBclApiKey()); + + HttpEntity entity = new HttpEntity<>(payload, headers); + + // Perform the API request + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, entity, SessionResponse.class); + log.info(response.toString()); + + return response.getBody(); + } + + public void setApplicationProperty(ApplicationProperty applicationProperty) { + this.applicationProperty = applicationProperty; + } + + public ApplicationProperty getApplicationProperty() { + return applicationProperty; + } +}