report manual withdrawals properly in transaction list

This commit is contained in:
Florian Dold 2020-07-16 14:44:59 +05:30
parent c6d80b0128
commit 75c5c59316
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
10 changed files with 167 additions and 68 deletions

View File

@ -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));
}

View File

@ -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,
),
});
});

View File

@ -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) {

View File

@ -221,10 +221,6 @@ export interface ReserveHistoryRecord {
export interface ReserveBankInfo {
statusUrl: string;
confirmUrl?: string;
amount: AmountJson;
bankWithdrawalGroupId: string;
withdrawalStarted: boolean;
denomSel: DenomSelectionState;
}
/**
@ -285,12 +281,28 @@ 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;

View File

@ -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.
*/
exchangeBaseUrl?: string;
reservePublicKey: string;
/**
* true if the bank has confirmed the withdrawal, false if not.
* Payto URIs that the exchange supports.
*/
exchangePaytoUris: string[];
}
interface WithdrawalDetailsForTalerBankIntegrationApi {
type: WithdrawalType.TalerBankIntegrationApi;
/**
* 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 {

View File

@ -146,7 +146,7 @@ export interface ExchangeWithdrawDetails {
walletVersion: string;
}
export interface WithdrawDetails {
export interface WithdrawalDetailsResponse {
bankWithdrawDetails: BankWithdrawDetails;
exchangeWithdrawDetails: ExchangeWithdrawDetails | undefined;
}

View File

@ -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,

View File

@ -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 };

View File

@ -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) {

View File

@ -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,