[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-merchant-backoffice] branch master updated: Add wire transfer for
From: |
gnunet |
Subject: |
[taler-merchant-backoffice] branch master updated: Add wire transfer form |
Date: |
Mon, 31 Jan 2022 12:49:35 +0100 |
This is an automated email from the git hooks/post-receive script.
ms pushed a commit to branch master
in repository merchant-backoffice.
The following commit(s) were added to refs/heads/master by this push:
new e84b098 Add wire transfer form
e84b098 is described below
commit e84b098ea9b2c60c7ba1db6c82f65ca2079ddb9c
Author: ms <ms@taler.net>
AuthorDate: Mon Jan 31 12:49:27 2022 +0100
Add wire transfer form
---
packages/bank/src/pages/home/index.tsx | 185 +++++++++++++++++++++++++++---
packages/bank/tests/__tests__/homepage.js | 56 ++++++---
2 files changed, 210 insertions(+), 31 deletions(-)
diff --git a/packages/bank/src/pages/home/index.tsx
b/packages/bank/src/pages/home/index.tsx
index 7c99869..528ed02 100644
--- a/packages/bank/src/pages/home/index.tsx
+++ b/packages/bank/src/pages/home/index.tsx
@@ -10,7 +10,7 @@ import "../../scss/main.scss";
/**
* ToDo:
*
- * - the page needs a "home" button that either redirects
+ * - the page needs a "home" button that either redirects to
* the profile page (when the user is logged in), or to
* the very initial home page.
*
@@ -36,6 +36,13 @@ interface BackendStateType {
password: string;
}
+/**
+ * Request body of POST /transactions.
+ */
+interface TransactionRequestType {
+ paytoUri: string;
+}
+
/**
* Request body of /register.
*/
@@ -54,8 +61,9 @@ interface PageStateType {
error?: string;
talerWithdrawUri?: string;
withdrawalOutcome?: string;
+ transferOutcome?: string;
/**
- * Not strictly a presentational element, could
+ * Not strictly a presentational value, could
* be moved in a future "withdrawal state" object.
*/
withdrawalId?: string;
@@ -73,6 +81,53 @@ interface AccountStateType {
* Helpers. *
***********/
+/**
+ * Get username from the backend state, and throw
+ * exception if not found.
+ */
+function getUsername(backendState: BackendStateTypeOpt): string {
+ if (typeof backendState === "undefined") {
+ throw Error("Username can't be found in a undefined backend state.")
+ }
+ return backendState.username;
+}
+
+/**
+ * Helps extracting the credentials from the state
+ * and wraps the actual call to 'fetch'. Should be
+ * enclosed in a try-catch block by the caller.
+ */
+async function postToBackend(
+ uri: string,
+ backendState: BackendStateTypeOpt,
+ body: string
+): Promise<any> {
+ if (typeof backendState === "undefined") {
+ throw Error("Credentials can't be found in a undefined backend state.")
+ }
+ const { username, password } = backendState;
+ let headers = prepareHeaders(username, password);
+ /**
+ * NOTE: tests show that when a same object is being
+ * POSTed, caching might prevent same requests from being
+ * made. Hence, trying to POST twice the same amount might
+ * get silently ignored.
+ *
+ * headers.append("cache-control", "no-store");
+ * headers.append("cache-control", "no-cache");
+ * headers.append("pragma", "no-cache");
+ * */
+
+ // Backend URL must have been stored _with_ a final slash.
+ const url = new URL(uri, backendState.url)
+ return await fetch(url.href, {
+ method: 'POST',
+ headers: headers,
+ body: body,
+ }
+ );
+}
+
function useTransactionPageNumber(): [number, StateUpdater<number>] {
const ret = useNotNullLocalStorage("transaction-page", "0");
@@ -163,7 +218,7 @@ function usePageState(
const retObj: PageStateType = JSON.parse(ret[0]);
const retSetter: StateUpdater<PageStateType> = function(val) {
const newVal = val instanceof Function ? JSON.stringify(val(retObj)) :
JSON.stringify(val)
- console.log("setting new page state", newVal)
+ console.log("Setting new page state", newVal)
ret[1](newVal)
}
return [retObj, retSetter];
@@ -334,6 +389,49 @@ async function confirmWithdrawalCall(
}
}
+/**
+ * This function creates a new transaction. It reads a Payto
+ * address entered by the user and POSTs it to the bank. No
+ * sanity-check of the input happens before the POST as this is
+ * already conducted by the backend.
+ */
+async function createTransactionCall(
+ req: TransactionRequestType,
+ backendState: BackendStateTypeOpt,
+ pageStateSetter: StateUpdater<PageStateType>
+) {
+ try {
+ var res = await postToBackend(
+ `access-api/accounts/${getUsername(backendState)}/withdrawals`,
+ backendState,
+ JSON.stringify(req)
+ )
+ }
+ catch (error) {
+ console.log("Could not POST transaction request to the bank", error);
+ pageStateSetter((prevState) => ({
+ ...prevState,
+ hasError: true,
+ error: `Could not create the wire transfer: ${error}`}))
+ return;
+ }
+ // POST happened, status not sure yet.
+ if (!res.ok) {
+ console.log(`Transfer creation gave response error (${res.status})`,
res.statusText);
+ pageStateSetter((prevState) => ({
+ ...prevState,
+ hasError: true,
+ error: `Transfer creation gave response error (${res.status})`}))
+ return;
+ }
+ // status is 200 OK here, tell the user.
+ console.log("Wire transfer created!");
+ pageStateSetter((prevState) => ({
+ ...prevState,
+ transferOutcome: "Wire transfer created!"
+ }))
+}
+
/**
* This function creates a withdrawal operation via the Access API.
*
@@ -341,8 +439,8 @@ async function confirmWithdrawalCall(
* user should receive a QR code of the "taler://withdraw/" type and
* supposed to scan it with their phone.
*
- * After the scan, the page should refresh itself and inform the user
- * about the operation's outcome.
+ * TODO: (1) after the scan, the page should refresh itself and inform the
+ * user about the operation's outcome. (2) use POST helper.
*/
async function createWithdrawalCall(
amount: string,
@@ -394,15 +492,15 @@ async function createWithdrawalCall(
hasError: true,
error: `Withdrawal creation gave response error (${res.status})`}))
return;
- } else {
- console.log("Withdrawal operation created!");
- let resp = await res.json();
- pageStateSetter((prevState) => ({
- ...prevState,
- withdrawalInProgress: true,
- talerWithdrawUri: resp.taler_withdraw_uri,
- withdrawalId: resp.withdrawal_id}))
- }
+ }
+
+ console.log("Withdrawal operation created!");
+ let resp = await res.json();
+ pageStateSetter((prevState) => ({
+ ...prevState,
+ withdrawalInProgress: true,
+ talerWithdrawUri: resp.taler_withdraw_uri,
+ withdrawalId: resp.withdrawal_id}))
}
async function loginCall(
@@ -515,7 +613,7 @@ function Transactions(Props: any): VNode {
`access-api/accounts/${accountLabel}/transactions?page=${pageNumber}`
);
if (typeof error !== "undefined") {
- console.log("balance error", error);
+ console.log("transactions not found error", error);
switch(error.status) {
case 404: {
return <p>Transactions page {pageNumber} was not found.</p>
@@ -550,8 +648,10 @@ function Transactions(Props: any): VNode {
* Show only the account's balance.
*/
function Account(Props: any): VNode {
+ console.log("DUMPING PROPS", Props)
const {
withdrawalOutcome,
+ transferOutcome,
talerWithdrawUri,
accountLabel } = Props;
/**
@@ -584,6 +684,18 @@ function Account(Props: any): VNode {
}
if (!data) return <p>Retrieving the profile page...</p>;
+ /**
+ * Wire transfer reached a final state: show it. Note:
+ * such state is usually successful, as errors should
+ * have been reported earlier.
+ */
+ if (transferOutcome) {
+ return <Fragment>
+ <p>{transferOutcome}</p>
+ {Props.children}
+ </Fragment>
+ }
+
/**
* Withdrawal reached a final state: show it.
*/
@@ -737,6 +849,8 @@ export function BankHome(): VNode {
}));
return <p>Error: waiting for details...</p>;
}
+
+ var transactionData: TransactionRequestType;
return (
<SWRWithCredentials
username={backendState.username}
@@ -746,6 +860,7 @@ export function BankHome(): VNode {
<Account
withdrawalOutcome={pageState.withdrawalOutcome}
talerWithdrawUri={pageState.talerWithdrawUri}
+ transferOutcome={pageState.transferOutcome}
accountLabel={backendState.username}>
{ /**
@@ -758,6 +873,16 @@ export function BankHome(): VNode {
pageStateSetter
)}}>{i18n`Charge Taler wallet`}</button>
}
+
+ { /**
+ * Wire transfer reached a persisten state: offer to
+ * return back to the pristine profile page.
+ */
+ pageState.transferOutcome && <button onClick={() => {
+ pageStateSetter((prevState) => {
+ const { transferOutcome, ...rest } = prevState;
+ return {...rest};})}}>{i18n`Close wire transfer`}</button>
+ }
{ /**
* Withdrawal reached a persisten state: offer to
@@ -766,7 +891,7 @@ export function BankHome(): VNode {
pageState.withdrawalOutcome && <button onClick={() => {
pageStateSetter((prevState) => {
const { withdrawalOutcome, withdrawalId, ...rest } =
prevState;
- return {...rest, withdrawalInProgress:
false};})}}>{i18n`Close`}</button>
+ return {...rest, withdrawalInProgress:
false};})}}>{i18n`Close Taler withdrawal`}</button>
}
{ /**
@@ -783,7 +908,33 @@ export function BankHome(): VNode {
backendState,
pageState.withdrawalId,
pageStateSetter);}}>{i18n`Abort withdrawal`}</button>
- </div>}
+ </div>
+ }
+
+ { /**
+ * Offer wire transfer, if no withdrawal is in progress.
+ */
+ !pageState.withdrawalInProgress && <Fragment>
+ <p>Please, include the 'amount' query parameter.</p>
+ <input
+ type="text"
+ placeholder="payto address" // changing this breaks tests.
+ required
+ onInput={(e): void => {
+ transactionData = {
+ ...transactionData,
+ paytoUri: e.currentTarget.value,
+ };
+ }} />
+ <button onClick={() => {
+ createTransactionCall(
+ transactionData,
+ backendState,
+ pageStateSetter
+ );
+ }}>{i18n`Create wire transfer`}</button>
+ </Fragment>
+ }
</Account>
{ /* The user is logged in: offer to log out. */ }
<button onClick={() => {
diff --git a/packages/bank/tests/__tests__/homepage.js
b/packages/bank/tests/__tests__/homepage.js
index d489d8e..039bab1 100644
--- a/packages/bank/tests/__tests__/homepage.js
+++ b/packages/bank/tests/__tests__/homepage.js
@@ -52,6 +52,46 @@ function fillCredentialsForm() {
}
fetchMock.enableMocks();
+function signUp(context) {
+ render(<BankHome />);
+ const { username, signupButton } = fillCredentialsForm();
+ fetch.once("{}", {
+ status: 200
+ }).once(JSON.stringify({
+ balance: {
+ amount: "EUR:10",
+ credit_debit_indicator: "credit"
+ },
+ paytoUri: "payto://iban/123/ABC"
+ }))
+ fireEvent.click(signupButton);
+ context.username = username;
+}
+
+describe("wire transfer", () => {
+ beforeEach(() => {
+ signUp({}); // context unused
+ })
+ test("Wire transfer success", async () => {
+ const transferButton = screen.getByText("Create wire transfer");
+ const payto = screen.getByPlaceholderText("payto address");
+ fireEvent.input(payto, {target: {value:
"payto://only-checked-by-the-backend!"}})
+ fetch.once("{}"); // 200 OK
+ fireEvent.click(transferButton);
+ await screen.findByText("wire transfer created", {exact: false})
+ })
+ test("Wire transfer fail", async () => {
+ const transferButton = screen.getByText("Create wire transfer");
+ const payto = screen.getByPlaceholderText("payto address");
+ fireEvent.input(payto, {target: {value:
"payto://only-checked-by-the-backend!"}})
+ fetch.once("{}", {status: 400});
+ fireEvent.click(transferButton);
+ // assert this below does NOT appear.
+ await waitFor(() => expect(
+ screen.queryByText("wire transfer created", {exact:
false})).not.toBeInTheDocument());
+ })
+})
+
describe("withdraw", () => {
afterEach(() => {
fetch.resetMocks();
@@ -60,19 +100,7 @@ describe("withdraw", () => {
// Register and land on the profile page.
beforeEach(() => {
- render(<BankHome />);
- const { username, signupButton } = fillCredentialsForm();
- fetch.once("{}", {
- status: 200
- }).once(JSON.stringify({
- balance: {
- amount: "EUR:10",
- credit_debit_indicator: "credit"
- },
- paytoUri: "payto://iban/123/ABC"
- }))
- fireEvent.click(signupButton);
- context.username = username;
+ signUp(context);
})
let context = {username: null};
@@ -308,7 +336,7 @@ describe("home page", () => {
const { username, signinButton } = fillCredentialsForm();
fetch.once("{}", {status: 404});
fireEvent.click(signinButton);
- await screen.findByText("username was not found", {exact: false})
+ await screen.findByText("username or account label not found", {exact:
false})
})
test("login wrong credentials", async () => {
render(<BankHome />);
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-merchant-backoffice] branch master updated: Add wire transfer form,
gnunet <=