diff --git a/react-app/src/App.js b/react-app/src/App.js index df17437..60a8b65 100644 --- a/react-app/src/App.js +++ b/react-app/src/App.js @@ -5,7 +5,7 @@ import Home from './home/Home.js'; import Signup from './signup/Signup.js'; import Dashboard from './dashboard/Dashboard.js'; import Products from './dashboard/Products.js'; -import Payments from './dashboard/Payments.js'; +import Transactions from './dashboard/Transactions.js'; import Payouts from './dashboard/Payouts.js'; import Settings from './dashboard/Settings.js'; import Login from './signup/Login.js'; @@ -19,7 +19,7 @@ function App() { } /> } /> } /> - } /> + } /> } /> } /> diff --git a/react-app/src/dashboard/Dashboard.js b/react-app/src/dashboard/Dashboard.js index 5ab76d1..82d6aa2 100644 --- a/react-app/src/dashboard/Dashboard.js +++ b/react-app/src/dashboard/Dashboard.js @@ -62,9 +62,10 @@ export default function Dashboard() { - - + + +
diff --git a/react-app/src/dashboard/DashboardDrawer.js b/react-app/src/dashboard/DashboardDrawer.js index af5c765..439d4c5 100644 --- a/react-app/src/dashboard/DashboardDrawer.js +++ b/react-app/src/dashboard/DashboardDrawer.js @@ -65,12 +65,12 @@ export default function DashboardDrawer() { - + - + diff --git a/react-app/src/dashboard/Payments.js b/react-app/src/dashboard/Payments.js deleted file mode 100644 index d3c4fc5..0000000 --- a/react-app/src/dashboard/Payments.js +++ /dev/null @@ -1,40 +0,0 @@ -import * as React from 'react'; - -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 { useParams } from "react-router-dom"; - -import DashboardHeader from "./DashboardHeader.js"; -import DashboardDrawer from "./DashboardDrawer.js"; - -export default function Products() { - - return ( - - - - - - - - - - - - - -

- -
-
- ); -} \ No newline at end of file diff --git a/react-app/src/dashboard/Payouts.js b/react-app/src/dashboard/Payouts.js index 8c9387c..006885d 100644 --- a/react-app/src/dashboard/Payouts.js +++ b/react-app/src/dashboard/Payouts.js @@ -26,8 +26,8 @@ export default function Payouts() { - - + +

diff --git a/react-app/src/dashboard/Products.js b/react-app/src/dashboard/Products.js index d2fda6d..e401466 100644 --- a/react-app/src/dashboard/Products.js +++ b/react-app/src/dashboard/Products.js @@ -28,9 +28,10 @@ export default function Products() { - - + + +
diff --git a/react-app/src/dashboard/Settings.js b/react-app/src/dashboard/Settings.js index 058691d..00ef50a 100644 --- a/react-app/src/dashboard/Settings.js +++ b/react-app/src/dashboard/Settings.js @@ -27,8 +27,8 @@ export default function Settings() { - - + +

diff --git a/react-app/src/dashboard/Transactions.js b/react-app/src/dashboard/Transactions.js new file mode 100644 index 0000000..2795016 --- /dev/null +++ b/react-app/src/dashboard/Transactions.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/src/main/java/com/adyen/controller/DashboardController.java b/src/main/java/com/adyen/controller/DashboardController.java index 3f128f8..0d99ec1 100644 --- a/src/main/java/com/adyen/controller/DashboardController.java +++ b/src/main/java/com/adyen/controller/DashboardController.java @@ -1,6 +1,7 @@ package com.adyen.controller; import com.adyen.model.OnboardingLinkProperties; +import com.adyen.model.TransactionItem; import com.adyen.model.User; import com.adyen.model.balanceplatform.AccountHolder; import com.adyen.model.legalentitymanagement.LegalEntity; @@ -15,6 +16,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.Arrays; +import java.util.List; import java.util.Optional; @RestController @@ -92,6 +95,18 @@ ResponseEntity getOnboardingLink(@RequestBody OnboardingLinkProperties o ); } + @PostMapping("/getTransactions") + ResponseEntity> getTransactions() { + + if (getUserIdOnSession() == null) { + log.warn("User is not logged in"); + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } + + return new ResponseEntity<>( + getConfigurationAPIService().getTransactions(getUserIdOnSession()), HttpStatus.ACCEPTED); + } + public ConfigurationAPIService getConfigurationAPIService() { return configurationAPIService; } diff --git a/src/main/java/com/adyen/model/TransactionItem.java b/src/main/java/com/adyen/model/TransactionItem.java new file mode 100644 index 0000000..2ca76c6 --- /dev/null +++ b/src/main/java/com/adyen/model/TransactionItem.java @@ -0,0 +1,75 @@ +package com.adyen.model; + +public class TransactionItem { + private String id; + private String status; + private String type; + private String created; + private String amount; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getCreated() { + return created; + } + + public void setCreated(String created) { + this.created = created; + } + + public String getAmount() { + return amount; + } + + public void setAmount(String amount) { + this.amount = amount; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public TransactionItem id(String id) { + this.id = id; + return this; + } + + public TransactionItem status(String status) { + this.status = status; + return this; + } + + public TransactionItem created(String created) { + this.created = created; + return this; + } + + public TransactionItem amount(String amount) { + this.amount = amount; + return this; + } + + public TransactionItem type(String type) { + this.type = type; + return this; + } + +} diff --git a/src/main/java/com/adyen/service/ConfigurationAPIService.java b/src/main/java/com/adyen/service/ConfigurationAPIService.java index d42b13e..19eb50f 100644 --- a/src/main/java/com/adyen/service/ConfigurationAPIService.java +++ b/src/main/java/com/adyen/service/ConfigurationAPIService.java @@ -4,14 +4,22 @@ import com.adyen.config.ApplicationProperty; import com.adyen.enums.Environment; import com.adyen.model.AccountHolderStatus; +import com.adyen.model.TransactionItem; import com.adyen.model.balanceplatform.*; +import com.adyen.model.transfers.Transaction; +import com.adyen.model.transfers.TransactionSearchResponse; import com.adyen.service.balanceplatform.AccountHoldersApi; import com.adyen.service.balanceplatform.BalanceAccountsApi; +import com.adyen.service.transfers.TransactionsApi; +import com.adyen.util.TransactionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -32,6 +40,9 @@ public class ConfigurationAPIService { @Autowired private ApplicationProperty applicationProperty; + @Autowired + private TransactionHandler transactionHandler; + public Optional getAccountHolder(String accountHolderId) { Optional accountHolder = Optional.empty(); @@ -119,6 +130,38 @@ public BalanceAccount createBalanceAccount(String accountHolderId) { return balanceAccount; } + /** + * Get all transactions for the user (accountHolder) + * @param accountHolderId + * @return + */ + public List getTransactions(String accountHolderId) { + + List transactionItems = null; + + try { + + // in the last X days + OffsetDateTime createdSince = OffsetDateTime.now().minus(365, ChronoUnit.DAYS); + // until today + OffsetDateTime createdUntil = OffsetDateTime.now(); + // max number of transactions to fetch + Integer limit = 100; + + TransactionSearchResponse transactionSearchResponse = getTransactionsApi().getAllTransactions( + null, null, accountHolderId, null, + null, createdSince, createdUntil, limit, null); + + transactionItems = getTransactionHandler().getTransactionItems(transactionSearchResponse.getData()); + } catch (Exception e) { + log.error(e.toString(), e); + throw new RuntimeException("Cannot create BalanceAccount: " + e.getMessage()); + } + + return transactionItems; + + } + // AccountHoldersApi handler private AccountHoldersApi getAccountHoldersApi() { return new AccountHoldersApi(getApiClient()); @@ -129,6 +172,11 @@ private BalanceAccountsApi getBalanceAccountsApi() { return new BalanceAccountsApi(getApiClient()); } + private TransactionsApi getTransactionsApi() { + return new TransactionsApi(getApiClient()); + } + + // create client to access the Configuration API private Client getApiClient() { if (apiClient == null) { @@ -149,4 +197,11 @@ public void setApplicationProperty(ApplicationProperty applicationProperty) { this.applicationProperty = applicationProperty; } + public TransactionHandler getTransactionHandler() { + return transactionHandler; + } + + public void setTransactionHandler(TransactionHandler transactionHandler) { + this.transactionHandler = transactionHandler; + } } diff --git a/src/main/java/com/adyen/util/TransactionHandler.java b/src/main/java/com/adyen/util/TransactionHandler.java new file mode 100644 index 0000000..aed6eaf --- /dev/null +++ b/src/main/java/com/adyen/util/TransactionHandler.java @@ -0,0 +1,87 @@ +package com.adyen.util; + +import com.adyen.model.TransactionItem; +import com.adyen.model.transfers.Amount; +import com.adyen.model.transfers.Transaction; +import org.springframework.stereotype.Service; + +import java.text.NumberFormat; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +/** + * Helper class to work with the Transaction class + */ +@Service +public class TransactionHandler { + + /** + * Create a list of TransactionItem from the given list of Transaction + * @param transactions + * @return + */ + public List getTransactionItems(List transactions) { + List transactionItems = new ArrayList<>(); + + for(Transaction transaction : transactions) { + transactionItems.add(getTransactionItem(transaction)); + } + + return transactionItems; + } + + /** + * Create a TransactionItem from the given Transaction + * @param transaction + * @return + */ + public TransactionItem getTransactionItem(Transaction transaction) { + return new TransactionItem() + .id(transaction.getId()) + .status(transaction.getStatus().getValue()) + .type(getType(transaction.getAmount())) + .created(formatDate(transaction.getCreationDate())) + .amount(formatAmount(transaction.getAmount())); + } + + private String formatAmount(Amount amount) { + String ret = ""; + + if(amount != null) { + NumberFormat numberFormat = NumberFormat.getNumberInstance(); + // display absolute amount and format + String formattedAmount = numberFormat.format(Math.abs(amount.getValue())); + ret = amount.getCurrency() + " " + formattedAmount; + } + return ret; + } + + private String formatDate(OffsetDateTime offsetDateTime) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + return offsetDateTime.format(formatter); + } + + /** + * Determines type of the transaction + * Incoming: positive amount, funds added + * Outgoing: negative amount, funds deducted + * @param amount + * @return + */ + private String getType(Amount amount) { + String ret = ""; + + if(amount != null) { + if(amount.getValue() > 0) { + ret = "Incoming"; + } if(amount.getValue() < 0) { + ret = "Outgoing"; + } + } + + return ret; + } +}