report manual withdrawals properly in transaction list
This commit is contained in:
parent
c6d80b0128
commit
75c5c59316
@ -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));
|
||||
}
|
||||
|
@ -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,
|
||||
),
|
||||
});
|
||||
});
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -146,7 +146,7 @@ export interface ExchangeWithdrawDetails {
|
||||
walletVersion: string;
|
||||
}
|
||||
|
||||
export interface WithdrawDetails {
|
||||
export interface WithdrawalDetailsResponse {
|
||||
bankWithdrawDetails: BankWithdrawDetails;
|
||||
exchangeWithdrawDetails: ExchangeWithdrawDetails | undefined;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 };
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user