add auto-refund test case, fix bug detected by it
This commit is contained in:
parent
7ff93d8ef6
commit
57000c2214
99
packages/taler-integrationtests/src/test-refund-auto.ts
Normal file
99
packages/taler-integrationtests/src/test-refund-auto.ts
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Taler
|
||||||
|
(C) 2020 Taler Systems S.A.
|
||||||
|
|
||||||
|
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports.
|
||||||
|
*/
|
||||||
|
import { runTest, GlobalTestState, MerchantPrivateApi } from "./harness";
|
||||||
|
import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers";
|
||||||
|
import { CoreApiResponse } from "taler-wallet-core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run test for basic, bank-integrated withdrawal.
|
||||||
|
*/
|
||||||
|
runTest(async (t: GlobalTestState) => {
|
||||||
|
// Set up test environment
|
||||||
|
|
||||||
|
const {
|
||||||
|
wallet,
|
||||||
|
bank,
|
||||||
|
exchange,
|
||||||
|
merchant,
|
||||||
|
} = await createSimpleTestkudosEnvironment(t);
|
||||||
|
|
||||||
|
// Withdraw digital cash into the wallet.
|
||||||
|
|
||||||
|
await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" });
|
||||||
|
|
||||||
|
// Set up order.
|
||||||
|
const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", {
|
||||||
|
order: {
|
||||||
|
summary: "Buy me!",
|
||||||
|
amount: "TESTKUDOS:5",
|
||||||
|
fulfillment_url: "taler://fulfillment-success/thx",
|
||||||
|
auto_refund: {
|
||||||
|
d_ms: 3000,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let orderStatus = await MerchantPrivateApi.queryPrivateOrderStatus(merchant, {
|
||||||
|
orderId: orderResp.order_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
t.assertTrue(orderStatus.order_status === "unpaid");
|
||||||
|
|
||||||
|
// Make wallet pay for the order
|
||||||
|
|
||||||
|
const r1 = await wallet.apiRequest("preparePay", {
|
||||||
|
talerPayUri: orderStatus.taler_pay_uri,
|
||||||
|
});
|
||||||
|
t.assertTrue(r1.type === "response");
|
||||||
|
|
||||||
|
const r2 = await wallet.apiRequest("confirmPay", {
|
||||||
|
// FIXME: should be validated, don't cast!
|
||||||
|
proposalId: (r1.result as any).proposalId,
|
||||||
|
});
|
||||||
|
t.assertTrue(r2.type === "response");
|
||||||
|
|
||||||
|
// Check if payment was successful.
|
||||||
|
|
||||||
|
orderStatus = await MerchantPrivateApi.queryPrivateOrderStatus(merchant, {
|
||||||
|
orderId: orderResp.order_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
t.assertTrue(orderStatus.order_status === "paid");
|
||||||
|
|
||||||
|
const ref = await MerchantPrivateApi.giveRefund(merchant, {
|
||||||
|
amount: "TESTKUDOS:5",
|
||||||
|
instance: "default",
|
||||||
|
justification: "foo",
|
||||||
|
orderId: orderResp.order_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(ref);
|
||||||
|
|
||||||
|
// The wallet should now automatically pick up the refund.
|
||||||
|
await wallet.runUntilDone();
|
||||||
|
|
||||||
|
const transactions = await wallet.getTransactions()
|
||||||
|
console.log(JSON.stringify(transactions, undefined, 2));
|
||||||
|
|
||||||
|
const transactionTypes = transactions.transactions.map((x) => x.type);
|
||||||
|
t.assertDeepEqual(transactionTypes, ["withdrawal", "payment", "refund"]);
|
||||||
|
|
||||||
|
await t.shutdown();
|
||||||
|
});
|
@ -571,10 +571,19 @@ export async function createRefreshGroup(
|
|||||||
retryInfo: initRetryInfo(),
|
retryInfo: initRetryInfo(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (oldCoinPubs.length == 0) {
|
||||||
|
logger.warn("created refresh group with zero coins");
|
||||||
|
refreshGroup.timestampFinished = getTimestampNow();
|
||||||
|
}
|
||||||
|
|
||||||
await tx.put(Stores.refreshGroups, refreshGroup);
|
await tx.put(Stores.refreshGroups, refreshGroup);
|
||||||
|
|
||||||
logger.trace(`created refresh group ${refreshGroupId}`);
|
logger.trace(`created refresh group ${refreshGroupId}`);
|
||||||
|
|
||||||
|
processRefreshGroup(ws, refreshGroupId).catch((e) => {
|
||||||
|
logger.warn(`processing refresh group ${refreshGroupId} failed`);
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
refreshGroupId,
|
refreshGroupId,
|
||||||
};
|
};
|
||||||
|
@ -259,7 +259,9 @@ async function acceptRefunds(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const refreshCoinsPubs = Object.values(refreshCoinsMap);
|
const refreshCoinsPubs = Object.values(refreshCoinsMap);
|
||||||
|
if (refreshCoinsPubs.length > 0) {
|
||||||
await createRefreshGroup(ws, tx, refreshCoinsPubs, RefreshReason.Refund);
|
await createRefreshGroup(ws, tx, refreshCoinsPubs, RefreshReason.Refund);
|
||||||
|
}
|
||||||
|
|
||||||
// Are we done with querying yet, or do we need to do another round
|
// Are we done with querying yet, or do we need to do another round
|
||||||
// after a retry delay?
|
// after a retry delay?
|
||||||
|
@ -265,7 +265,7 @@ export async function getTransactions(
|
|||||||
|
|
||||||
refundGroupKeys.forEach((groupKey: string) => {
|
refundGroupKeys.forEach((groupKey: string) => {
|
||||||
const refundTransactionId = makeEventId(
|
const refundTransactionId = makeEventId(
|
||||||
TransactionType.Payment,
|
TransactionType.Refund,
|
||||||
pr.proposalId,
|
pr.proposalId,
|
||||||
groupKey,
|
groupKey,
|
||||||
);
|
);
|
||||||
|
@ -33,9 +33,11 @@ import {
|
|||||||
Balance,
|
Balance,
|
||||||
classifyTalerUri,
|
classifyTalerUri,
|
||||||
TalerUriType,
|
TalerUriType,
|
||||||
|
TransactionsResponse,
|
||||||
|
Transaction,
|
||||||
|
TransactionType,
|
||||||
} from "taler-wallet-core";
|
} from "taler-wallet-core";
|
||||||
|
|
||||||
|
|
||||||
import { abbrev, renderAmount, PageLink } from "../renderHtml";
|
import { abbrev, renderAmount, PageLink } from "../renderHtml";
|
||||||
import * as wxApi from "../wxApi";
|
import * as wxApi from "../wxApi";
|
||||||
|
|
||||||
@ -309,9 +311,37 @@ function formatAndCapitalize(text: string): string {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HistoryComponent = (props: any): JSX.Element => {
|
function TransactionItem(props: { tx: Transaction }): JSX.Element {
|
||||||
return <span>TBD</span>;
|
const tx = props.tx;
|
||||||
};
|
return <pre>{JSON.stringify(tx)}</pre>
|
||||||
|
}
|
||||||
|
|
||||||
|
function WalletHistory(props: any): JSX.Element {
|
||||||
|
const [transactions, setTransactions] = useState<
|
||||||
|
TransactionsResponse | undefined
|
||||||
|
>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async (): Promise<void> => {
|
||||||
|
const res = await wxApi.getTransactions();
|
||||||
|
setTransactions(res);
|
||||||
|
};
|
||||||
|
fetchData();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!transactions) {
|
||||||
|
return <div>Loading ...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{transactions.transactions.map((tx) => (
|
||||||
|
<TransactionItem tx={tx} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class WalletSettings extends React.Component<any, any> {
|
class WalletSettings extends React.Component<any, any> {
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
@ -492,6 +522,7 @@ function WalletPopup(): JSX.Element {
|
|||||||
<WalletBalanceView route="/balance" default />
|
<WalletBalanceView route="/balance" default />
|
||||||
<WalletSettings route="/settings" />
|
<WalletSettings route="/settings" />
|
||||||
<WalletDebug route="/debug" />
|
<WalletDebug route="/debug" />
|
||||||
|
<WalletHistory route="/history" />
|
||||||
</Router>
|
</Router>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -35,6 +35,7 @@ import {
|
|||||||
OperationFailedError,
|
OperationFailedError,
|
||||||
GetWithdrawalDetailsForUriRequest,
|
GetWithdrawalDetailsForUriRequest,
|
||||||
WithdrawUriInfoResponse,
|
WithdrawUriInfoResponse,
|
||||||
|
TransactionsResponse,
|
||||||
} from "taler-wallet-core";
|
} from "taler-wallet-core";
|
||||||
|
|
||||||
export interface ExtendedPermissionsResponse {
|
export interface ExtendedPermissionsResponse {
|
||||||
@ -122,6 +123,13 @@ export function getBalance(): Promise<BalancesResponse> {
|
|||||||
return callBackend("getBalances", {});
|
return callBackend("getBalances", {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get balances for all currencies/exchanges.
|
||||||
|
*/
|
||||||
|
export function getTransactions(): Promise<TransactionsResponse> {
|
||||||
|
return callBackend("getTransactions", {});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return coins to a bank account.
|
* Return coins to a bank account.
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user