aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2020-07-16 14:44:59 +0530
committerFlorian Dold <florian.dold@gmail.com>2020-07-16 14:44:59 +0530
commit75c5c59316a428fbebe2448d9d79a70689565657 (patch)
tree9dbc53b428011a8e24273f6c6e1fe70121e8d3d2
parentc6d80b01281a6af578fb3468698419ddd5c172f8 (diff)
report manual withdrawals properly in transaction list
-rw-r--r--src/operations/reserves.ts31
-rw-r--r--src/operations/transactions.ts110
-rw-r--r--src/operations/withdraw.ts4
-rw-r--r--src/types/dbTypes.ts27
-rw-r--r--src/types/transactions.ts44
-rw-r--r--src/types/walletTypes.ts2
-rw-r--r--src/wallet.ts5
-rw-r--r--src/webex/messages.ts2
-rw-r--r--src/webex/pages/withdraw.tsx6
-rw-r--r--src/webex/wxApi.ts4
10 files changed, 167 insertions, 68 deletions
diff --git a/src/operations/reserves.ts b/src/operations/reserves.ts
index 2761dfaf9..ff20ce9ba 100644
--- a/src/operations/reserves.ts
+++ b/src/operations/reserves.ts
@@ -53,7 +53,6 @@ import {
processWithdrawGroup,
getBankWithdrawalInfo,
denomSelectionInfoToState,
- getWithdrawDenomList,
} from "./withdraw";
import {
guardOperationException,
@@ -106,22 +105,25 @@ export async function createReserve(
let bankInfo: ReserveBankInfo | undefined;
if (req.bankWithdrawStatusUrl) {
- const denomSelInfo = await selectWithdrawalDenoms(
- ws,
- canonExchange,
- req.amount,
- );
- const denomSel = denomSelectionInfoToState(denomSelInfo);
bankInfo = {
statusUrl: req.bankWithdrawStatusUrl,
- amount: req.amount,
- bankWithdrawalGroupId: encodeCrock(getRandomBytes(32)),
- withdrawalStarted: false,
- denomSel,
};
}
+ const initialWithdrawalGroupId = encodeCrock(getRandomBytes(32));
+
+ const denomSelInfo = await selectWithdrawalDenoms(
+ ws,
+ canonExchange,
+ req.amount,
+ );
+ const initialDenomSel = denomSelectionInfoToState(denomSelInfo);
+
const reserveRecord: ReserveRecord = {
+ instructedAmount: req.amount,
+ initialWithdrawalGroupId,
+ initialDenomSel,
+ initialWithdrawalStarted: false,
timestampCreated: now,
exchangeBaseUrl: canonExchange,
reservePriv: keypair.priv,
@@ -750,10 +752,9 @@ async function depleteReserve(
let withdrawalGroupId: string;
- const bankInfo = newReserve.bankInfo;
- if (bankInfo && !bankInfo.withdrawalStarted) {
- withdrawalGroupId = bankInfo.bankWithdrawalGroupId;
- bankInfo.withdrawalStarted = true;
+ if (!newReserve.initialWithdrawalStarted) {
+ withdrawalGroupId = newReserve.initialWithdrawalGroupId;
+ newReserve.initialWithdrawalStarted = true;
} else {
withdrawalGroupId = encodeCrock(randomBytes(32));
}
diff --git a/src/operations/transactions.ts b/src/operations/transactions.ts
index 9a3d48bb3..f2845cb18 100644
--- a/src/operations/transactions.ts
+++ b/src/operations/transactions.ts
@@ -32,7 +32,10 @@ import {
Transaction,
TransactionType,
PaymentStatus,
+ WithdrawalType,
+ WithdrawalDetails,
} from "../types/transactions";
+import { WithdrawalDetailsResponse } from "../types/walletTypes";
/**
* Create an event ID from the type and the primary key for the event.
@@ -156,6 +159,7 @@ export async function getTransactions(
Stores.reserveUpdatedEvents,
Stores.recoupGroups,
],
+ // Report withdrawals that are currently in progress.
async (tx) => {
tx.iter(Stores.withdrawalGroups).forEachAsync(async (wsr) => {
if (
@@ -171,34 +175,62 @@ export async function getTransactions(
return;
}
- let amountRaw: AmountJson | undefined = undefined;
-
- if (wsr.source.type === WithdrawalSourceType.Reserve) {
- const r = await tx.get(Stores.reserves, wsr.source.reservePub);
- if (r?.bankInfo?.amount) {
- amountRaw = r.bankInfo.amount;
+ switch (wsr.source.type) {
+ case WithdrawalSourceType.Reserve: {
+ const r = await tx.get(Stores.reserves, wsr.source.reservePub);
+ if (!r) {
+ break;
+ }
+ let amountRaw: AmountJson | undefined = undefined;
+ if (wsr.withdrawalGroupId === r.initialWithdrawalGroupId) {
+ amountRaw = r.instructedAmount;
+ } else {
+ amountRaw = wsr.denomsSel.totalWithdrawCost;
+ }
+ let withdrawalDetails: WithdrawalDetails;
+ if (r.bankInfo) {
+ withdrawalDetails = {
+ type: WithdrawalType.TalerBankIntegrationApi,
+ confirmed: true,
+ bankConfirmationUrl: r.bankInfo.confirmUrl,
+ };
+ } else {
+ const exchange = await tx.get(Stores.exchanges, r.exchangeBaseUrl);
+ if (!exchange) {
+ // FIXME: report somehow
+ break;
+ }
+ withdrawalDetails = {
+ type: WithdrawalType.ManualTransfer,
+ reservePublicKey: r.reservePub,
+ exchangePaytoUris: exchange.wireInfo?.accounts.map((x) => x.payto_uri) ?? [],
+ };
+ }
+ transactions.push({
+ type: TransactionType.Withdrawal,
+ amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
+ amountRaw: Amounts.stringify(amountRaw),
+ withdrawalDetails,
+ exchangeBaseUrl: wsr.exchangeBaseUrl,
+ pending: !wsr.timestampFinish,
+ timestamp: wsr.timestampStart,
+ transactionId: makeEventId(
+ TransactionType.Withdrawal,
+ wsr.withdrawalGroupId,
+ ),
+ });
}
+ break;
+ default:
+ // Tips are reported via their own event
+ break;
}
- if (!amountRaw) {
- amountRaw = wsr.denomsSel.totalWithdrawCost;
- }
-
- transactions.push({
- type: TransactionType.Withdrawal,
- amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
- amountRaw: Amounts.stringify(amountRaw),
- confirmed: true,
- exchangeBaseUrl: wsr.exchangeBaseUrl,
- pending: !wsr.timestampFinish,
- timestamp: wsr.timestampStart,
- transactionId: makeEventId(
- TransactionType.Withdrawal,
- wsr.withdrawalGroupId,
- ),
- });
});
- tx.iter(Stores.reserves).forEach((r) => {
+ // Report pending withdrawals based on reserves that
+ // were created, but where the actual withdrawal group has
+ // not started yet.
+ tx.iter(Stores.reserves).forEachAsync(async (r) => {
if (shouldSkipCurrency(transactionsRequest, r.currency)) {
return;
}
@@ -213,23 +245,41 @@ export async function getTransactions(
default:
return;
}
- if (!r.bankInfo) {
+ if (r.initialWithdrawalStarted) {
return;
}
+ let withdrawalDetails: WithdrawalDetails;
+ if (r.bankInfo) {
+ withdrawalDetails = {
+ type: WithdrawalType.TalerBankIntegrationApi,
+ confirmed: false,
+ bankConfirmationUrl: r.bankInfo.confirmUrl,
+ }
+ } else {
+ const exchange = await tx.get(Stores.exchanges, r.exchangeBaseUrl);
+ if (!exchange) {
+ // FIXME: report somehow
+ return;
+ }
+ withdrawalDetails = {
+ type: WithdrawalType.ManualTransfer,
+ reservePublicKey: r.reservePub,
+ exchangePaytoUris: exchange.wireInfo?.accounts.map((x) => x.payto_uri) ?? [],
+ };
+ }
transactions.push({
type: TransactionType.Withdrawal,
- confirmed: false,
- amountRaw: Amounts.stringify(r.bankInfo.amount),
+ amountRaw: Amounts.stringify(r.instructedAmount),
amountEffective: Amounts.stringify(
- r.bankInfo.denomSel.totalCoinValue,
+ r.initialDenomSel.totalCoinValue,
),
exchangeBaseUrl: r.exchangeBaseUrl,
pending: true,
timestamp: r.timestampCreated,
- bankConfirmationUrl: r.bankInfo.confirmUrl,
+ withdrawalDetails: withdrawalDetails,
transactionId: makeEventId(
TransactionType.Withdrawal,
- r.bankInfo.bankWithdrawalGroupId,
+ r.initialWithdrawalGroupId,
),
});
});
diff --git a/src/operations/withdraw.ts b/src/operations/withdraw.ts
index 284743415..fd850f140 100644
--- a/src/operations/withdraw.ts
+++ b/src/operations/withdraw.ts
@@ -32,7 +32,7 @@ import {
import {
BankWithdrawDetails,
ExchangeWithdrawDetails,
- WithdrawDetails,
+ WithdrawalDetailsResponse,
OperationError,
} from "../types/walletTypes";
import {
@@ -708,7 +708,7 @@ export async function getWithdrawDetailsForUri(
ws: InternalWalletState,
talerWithdrawUri: string,
maybeSelectedExchange?: string,
-): Promise<WithdrawDetails> {
+): Promise<WithdrawalDetailsResponse> {
const info = await getBankWithdrawalInfo(ws, talerWithdrawUri);
let rci: ExchangeWithdrawDetails | undefined = undefined;
if (maybeSelectedExchange) {
diff --git a/src/types/dbTypes.ts b/src/types/dbTypes.ts
index 6693e22a2..55f16f40b 100644
--- a/src/types/dbTypes.ts
+++ b/src/types/dbTypes.ts
@@ -221,10 +221,6 @@ export interface ReserveHistoryRecord {
export interface ReserveBankInfo {
statusUrl: string;
confirmUrl?: string;
- amount: AmountJson;
- bankWithdrawalGroupId: string;
- withdrawalStarted: boolean;
- denomSel: DenomSelectionState;
}
/**
@@ -286,11 +282,27 @@ export interface ReserveRecord {
exchangeWire: string;
/**
+ * Amount that was sent by the user to fund the reserve.
+ */
+ instructedAmount: AmountJson;
+
+ /**
* Extra state for when this is a withdrawal involving
* a Taler-integrated bank.
*/
bankInfo?: ReserveBankInfo;
+ initialWithdrawalGroupId: string;
+
+ /**
+ * Did we start the first withdrawal for this reserve?
+ *
+ * We only report a pending withdrawal for the reserve before
+ * the first withdrawal has started.
+ */
+ initialWithdrawalStarted: boolean;
+ initialDenomSel: DenomSelectionState;
+
reserveStatus: ReserveRecordStatus;
/**
@@ -1436,6 +1448,13 @@ export interface DenomSelectionState {
}[];
}
+/**
+ * Group of withdrawal operations that need to be executed.
+ * (Either for a normal withdrawal or from a tip.)
+ *
+ * The withdrawal group record is only created after we know
+ * the coin selection we want to withdraw.
+ */
export interface WithdrawalGroupRecord {
withdrawalGroupId: string;
diff --git a/src/types/transactions.ts b/src/types/transactions.ts
index 6ed9a52d4..aa618cd4e 100644
--- a/src/types/transactions.ts
+++ b/src/types/transactions.ts
@@ -105,18 +105,35 @@ export const enum TransactionType {
Tip = "tip",
}
-// This should only be used for actual withdrawals
-// and not for tips that have their own transactions type.
-interface TransactionWithdrawal extends TransactionCommon {
- type: TransactionType.Withdrawal;
+export const enum WithdrawalType {
+ TalerBankIntegrationApi = "taler-bank-integration-api",
+ ManualTransfer = "manual-transfer",
+}
+
+export type WithdrawalDetails =
+ | WithdrawalDetailsForManualTransfer
+ | WithdrawalDetailsForTalerBankIntegrationApi;
+
+interface WithdrawalDetailsForManualTransfer {
+ type: WithdrawalType.ManualTransfer;
/**
- * Exchange of the withdrawal.
+ * Public key of the reserve that needs to be funded
+ * manually.
+ */
+ reservePublicKey: string;
+
+ /**
+ * Payto URIs that the exchange supports.
*/
- exchangeBaseUrl?: string;
+ exchangePaytoUris: string[];
+}
+
+interface WithdrawalDetailsForTalerBankIntegrationApi {
+ type: WithdrawalType.TalerBankIntegrationApi;
/**
- * true if the bank has confirmed the withdrawal, false if not.
+ * Set to true if the bank has confirmed the withdrawal, false if not.
* An unconfirmed withdrawal usually requires user-input and should be highlighted in the UI.
* See also bankConfirmationUrl below.
*/
@@ -127,6 +144,17 @@ interface TransactionWithdrawal extends TransactionCommon {
* initiated confirmation.
*/
bankConfirmationUrl?: string;
+}
+
+// This should only be used for actual withdrawals
+// and not for tips that have their own transactions type.
+interface TransactionWithdrawal extends TransactionCommon {
+ type: TransactionType.Withdrawal;
+
+ /**
+ * Exchange of the withdrawal.
+ */
+ exchangeBaseUrl: string;
/**
* Amount that got subtracted from the reserve balance.
@@ -137,6 +165,8 @@ interface TransactionWithdrawal extends TransactionCommon {
* Amount that actually was (or will be) added to the wallet's balance.
*/
amountEffective: AmountString;
+
+ withdrawalDetails: WithdrawalDetails;
}
export const enum PaymentStatus {
diff --git a/src/types/walletTypes.ts b/src/types/walletTypes.ts
index 4b6d867a2..74f2428dd 100644
--- a/src/types/walletTypes.ts
+++ b/src/types/walletTypes.ts
@@ -146,7 +146,7 @@ export interface ExchangeWithdrawDetails {
walletVersion: string;
}
-export interface WithdrawDetails {
+export interface WithdrawalDetailsResponse {
bankWithdrawDetails: BankWithdrawDetails;
exchangeWithdrawDetails: ExchangeWithdrawDetails | undefined;
}
diff --git a/src/wallet.ts b/src/wallet.ts
index e04c849d5..737704fd6 100644
--- a/src/wallet.ts
+++ b/src/wallet.ts
@@ -64,10 +64,9 @@ import {
TipStatus,
WalletBalance,
PreparePayResult,
- WithdrawDetails,
+ WithdrawalDetailsResponse,
AcceptWithdrawalResponse,
PurchaseDetails,
- ExchangeWithdrawDetails as ExchangeWithdrawalDetails,
RefreshReason,
ExchangeListItem,
ExchangesListRespose,
@@ -477,7 +476,7 @@ export class Wallet {
async getWithdrawDetailsForUri(
talerWithdrawUri: string,
maybeSelectedExchange?: string,
- ): Promise<WithdrawDetails> {
+ ): Promise<WithdrawalDetailsResponse> {
return getWithdrawDetailsForUri(
this.ws,
talerWithdrawUri,
diff --git a/src/webex/messages.ts b/src/webex/messages.ts
index 8120d4f94..5cf2fefdb 100644
--- a/src/webex/messages.ts
+++ b/src/webex/messages.ts
@@ -146,7 +146,7 @@ export interface MessageMap {
talerWithdrawUri: string;
maybeSelectedExchange: string | undefined;
};
- response: walletTypes.WithdrawDetails;
+ response: walletTypes.WithdrawalDetailsResponse;
};
"accept-withdrawal": {
request: { talerWithdrawUri: string; selectedExchange: string };
diff --git a/src/webex/pages/withdraw.tsx b/src/webex/pages/withdraw.tsx
index d8ac3c455..c4e4ebbb9 100644
--- a/src/webex/pages/withdraw.tsx
+++ b/src/webex/pages/withdraw.tsx
@@ -23,7 +23,7 @@
import * as i18n from "../i18n";
-import { WithdrawDetails } from "../../types/walletTypes";
+import { WithdrawalDetailsResponse } from "../../types/walletTypes";
import { WithdrawDetailView, renderAmount } from "../renderHtml";
@@ -35,7 +35,7 @@ import {
} from "../wxApi";
function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element {
- const [details, setDetails] = useState<WithdrawDetails | undefined>();
+ const [details, setDetails] = useState<WithdrawalDetailsResponse | undefined>();
const [selectedExchange, setSelectedExchange] = useState<
string | undefined
>();
@@ -56,7 +56,7 @@ function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element {
useEffect(() => {
const fetchData = async (): Promise<void> => {
console.log("getting from", talerWithdrawUri);
- let d: WithdrawDetails | undefined = undefined;
+ let d: WithdrawalDetailsResponse | undefined = undefined;
try {
d = await getWithdrawDetails(talerWithdrawUri, selectedExchange);
} catch (e) {
diff --git a/src/webex/wxApi.ts b/src/webex/wxApi.ts
index 0901005b5..47e73ca4c 100644
--- a/src/webex/wxApi.ts
+++ b/src/webex/wxApi.ts
@@ -38,7 +38,7 @@ import {
WalletBalance,
PurchaseDetails,
WalletDiagnostics,
- WithdrawDetails,
+ WithdrawalDetailsResponse,
PreparePayResult,
AcceptWithdrawalResponse,
ExtendedPermissionsResponse,
@@ -283,7 +283,7 @@ export function benchmarkCrypto(repetitions: number): Promise<BenchmarkResult> {
export function getWithdrawDetails(
talerWithdrawUri: string,
maybeSelectedExchange: string | undefined,
-): Promise<WithdrawDetails> {
+): Promise<WithdrawalDetailsResponse> {
return callBackend("get-withdraw-details", {
talerWithdrawUri,
maybeSelectedExchange,