This commit is contained in:
Florian Dold 2019-12-09 19:59:08 +01:00
parent 99bccae9fe
commit 6415564b92
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
14 changed files with 241 additions and 78 deletions

View File

@ -26,12 +26,20 @@ import {
} from "../headless/helpers"; } from "../headless/helpers";
import { openPromise, OpenedPromise } from "../util/promiseUtils"; import { openPromise, OpenedPromise } from "../util/promiseUtils";
import fs = require("fs"); import fs = require("fs");
import { HttpRequestLibrary, HttpResponse, HttpRequestOptions } from "../util/http"; import {
HttpRequestLibrary,
HttpResponse,
HttpRequestOptions,
Headers,
} from "../util/http";
// @ts-ignore: special built-in module // @ts-ignore: special built-in module
//import akono = require("akono"); //import akono = require("akono");
export { handleWorkerError, handleWorkerMessage } from "../crypto/workers/nodeThreadWorker"; export {
handleWorkerError,
handleWorkerMessage,
} from "../crypto/workers/nodeThreadWorker";
export class AndroidHttpLib implements HttpRequestLibrary { export class AndroidHttpLib implements HttpRequestLibrary {
useNfcTunnel: boolean = false; useNfcTunnel: boolean = false;
@ -66,7 +74,11 @@ export class AndroidHttpLib implements HttpRequestLibrary {
} }
} }
postJson(url: string, body: any, opt?: HttpRequestOptions): Promise<import("../util/http").HttpResponse> { postJson(
url: string,
body: any,
opt?: HttpRequestOptions,
): Promise<import("../util/http").HttpResponse> {
if (this.useNfcTunnel) { if (this.useNfcTunnel) {
const myId = this.requestId++; const myId = this.requestId++;
const p = openPromise<HttpResponse>(); const p = openPromise<HttpResponse>();
@ -89,11 +101,14 @@ export class AndroidHttpLib implements HttpRequestLibrary {
const myId = msg.id; const myId = msg.id;
const p = this.requestMap[myId]; const p = this.requestMap[myId];
if (!p) { if (!p) {
console.error(`no matching request for tunneled HTTP response, id=${myId}`); console.error(
`no matching request for tunneled HTTP response, id=${myId}`,
);
} }
const headers = new Headers();
if (msg.status != 0) { if (msg.status != 0) {
const resp: HttpResponse = { const resp: HttpResponse = {
headers: {}, headers,
status: msg.status, status: msg.status,
json: async () => JSON.parse(msg.responseText), json: async () => JSON.parse(msg.responseText),
text: async () => msg.responseText, text: async () => msg.responseText,
@ -146,7 +161,7 @@ export function installAndroidWalletListener() {
}; };
const w = await getDefaultNodeWallet(walletArgs); const w = await getDefaultNodeWallet(walletArgs);
maybeWallet = w; maybeWallet = w;
w.runRetryLoop().catch((e) => { w.runRetryLoop().catch(e => {
console.error("Error during wallet retry loop", e); console.error("Error during wallet retry loop", e);
}); });
wp.resolve(w); wp.resolve(w);
@ -191,7 +206,10 @@ export function installAndroidWalletListener() {
} }
case "confirmPay": { case "confirmPay": {
const wallet = await wp.promise; const wallet = await wp.promise;
result = await wallet.confirmPay(msg.args.proposalId, msg.args.sessionId); result = await wallet.confirmPay(
msg.args.proposalId,
msg.args.sessionId,
);
break; break;
} }
case "startTunnel": { case "startTunnel": {
@ -206,14 +224,28 @@ export function installAndroidWalletListener() {
httpLib.handleTunnelResponse(msg.args); httpLib.handleTunnelResponse(msg.args);
break; break;
} }
case "getWithdrawalInfo": { case "getWithdrawDetailsForUri": {
const wallet = await wp.promise; const wallet = await wp.promise;
result = await wallet.getWithdrawalInfo(msg.args.talerWithdrawUri); result = await wallet.getWithdrawDetailsForUri(
msg.args.talerWithdrawUri,
msg.args.selectedExchange,
);
break;
}
case "acceptExchangeTermsOfService": {
const wallet = await wp.promise;
result = await wallet.acceptExchangeTermsOfService(
msg.args.exchangeBaseUrl,
msg.args.etag,
);
break; break;
} }
case "acceptWithdrawal": { case "acceptWithdrawal": {
const wallet = await wp.promise; const wallet = await wp.promise;
result = await wallet.acceptWithdrawal(msg.args.talerWithdrawUri, msg.args.selectedExchange); result = await wallet.acceptWithdrawal(
msg.args.talerWithdrawUri,
msg.args.selectedExchange,
);
break; break;
} }
case "reset": { case "reset": {
@ -234,7 +266,7 @@ export function installAndroidWalletListener() {
maybeWallet = undefined; maybeWallet = undefined;
const w = await getDefaultNodeWallet(walletArgs); const w = await getDefaultNodeWallet(walletArgs);
maybeWallet = w; maybeWallet = w;
w.runRetryLoop().catch((e) => { w.runRetryLoop().catch(e => {
console.error("Error during wallet retry loop", e); console.error("Error during wallet retry loop", e);
}); });
wp.resolve(w); wp.resolve(w);

View File

@ -455,9 +455,10 @@ export interface ExchangeDetails {
lastUpdateTime: Timestamp; lastUpdateTime: Timestamp;
} }
export enum ExchangeUpdateStatus { export const enum ExchangeUpdateStatus {
FETCH_KEYS = "fetch_keys", FETCH_KEYS = "fetch_keys",
FETCH_WIRE = "fetch_wire", FETCH_WIRE = "fetch_wire",
FETCH_TERMS = "fetch_terms",
FINISHED = "finished", FINISHED = "finished",
} }
@ -494,6 +495,26 @@ export interface ExchangeRecord {
*/ */
timestampAdded: Timestamp; timestampAdded: Timestamp;
/**
* Terms of service text or undefined if not downloaded yet.
*/
termsOfServiceText: string | undefined;
/**
* ETag for last terms of service download.
*/
termsOfServiceLastEtag: string | undefined;
/**
* ETag for last terms of service download.
*/
termsOfServiceAcceptedEtag: string | undefined;
/**
* ETag for last terms of service download.
*/
termsOfServiceAcceptedTimestamp: Timestamp | undefined;
/** /**
* Time when the update to the exchange has been started or * Time when the update to the exchange has been started or
* undefined if no update is in progress. * undefined if no update is in progress.

View File

@ -28,6 +28,7 @@ import Axios, { AxiosPromise, AxiosResponse } from "axios";
import { import {
HttpRequestLibrary, HttpRequestLibrary,
HttpRequestOptions, HttpRequestOptions,
Headers,
} from "../util/http"; } from "../util/http";
import * as amounts from "../util/amounts"; import * as amounts from "../util/amounts";
import { Bank } from "./bank"; import { Bank } from "./bank";
@ -83,8 +84,12 @@ export class NodeHttpLib implements HttpRequestLibrary {
} }
return responseJson; return responseJson;
}; };
const headers = new Headers();
for (const hn of Object.keys(resp.headers)) {
headers.set(hn, resp.headers[hn]);
}
return { return {
headers: resp.headers, headers,
status: resp.status, status: resp.status,
text: async () => resp.data, text: async () => resp.data,
json: makeJson, json: makeJson,

View File

@ -230,8 +230,8 @@ walletCli
break; break;
case TalerUriType.TalerWithdraw: case TalerUriType.TalerWithdraw:
{ {
const withdrawInfo = await wallet.getWithdrawalInfo(uri); const withdrawInfo = await wallet.getWithdrawDetailsForUri(uri);
const selectedExchange = withdrawInfo.suggestedExchange; const selectedExchange = withdrawInfo.bankWithdrawDetails.suggestedExchange;
if (!selectedExchange) { if (!selectedExchange) {
console.error("no suggested exchange!"); console.error("no suggested exchange!");
process.exit(1); process.exit(1);

View File

@ -24,7 +24,7 @@
*/ */
export interface HttpResponse { export interface HttpResponse {
status: number; status: number;
headers: { [name: string]: string }; headers: Headers;
json(): Promise<any>; json(): Promise<any>;
text(): Promise<string>; text(): Promise<string>;
} }
@ -33,6 +33,31 @@ export interface HttpRequestOptions {
headers?: { [name: string]: string }; headers?: { [name: string]: string };
} }
/**
* Headers, roughly modeled after the fetch API's headers object.
*/
export class Headers {
private headerMap = new Map<string, string>();
get(name: string): string | null {
const r = this.headerMap.get(name.toLowerCase());
if (r) {
return r;
}
return null;
}
set(name: string, value: string): void {
const normalizedName = name.toLowerCase();
const existing = this.headerMap.get(normalizedName);
if (existing !== undefined) {
this.headerMap.set(normalizedName, existing + "," + value);
} else {
this.headerMap.set(normalizedName, value);
}
}
}
/** /**
* The request library is bundled into an interface to m responseJson: object & any;ake mocking easy. * The request library is bundled into an interface to m responseJson: object & any;ake mocking easy.
*/ */
@ -103,12 +128,12 @@ export class BrowserHttpLib implements HttpRequestLibrary {
const arr = headers.trim().split(/[\r\n]+/); const arr = headers.trim().split(/[\r\n]+/);
// Create a map of header names to values // Create a map of header names to values
const headerMap: { [name: string]: string } = {}; const headerMap = new Headers();
arr.forEach(function(line) { arr.forEach(function(line) {
const parts = line.split(": "); const parts = line.split(": ");
const header = parts.shift(); const header = parts.shift();
const value = parts.join(": "); const value = parts.join(": ");
headerMap[header!] = value; headerMap.set(header!, value);
}); });
const resp: HttpResponse = { const resp: HttpResponse = {
status: myRequest.status, status: myRequest.status,

View File

@ -16,11 +16,7 @@
import { InternalWalletState } from "./state"; import { InternalWalletState } from "./state";
import { WALLET_CACHE_BREAKER_CLIENT_VERSION } from "../wallet"; import { WALLET_CACHE_BREAKER_CLIENT_VERSION } from "../wallet";
import { import { KeysJson, Denomination, ExchangeWireJson } from "../talerTypes";
KeysJson,
Denomination,
ExchangeWireJson,
} from "../talerTypes";
import { getTimestampNow, OperationError } from "../walletTypes"; import { getTimestampNow, OperationError } from "../walletTypes";
import { import {
ExchangeRecord, ExchangeRecord,
@ -222,6 +218,62 @@ async function updateExchangeWithKeys(
); );
} }
async function updateExchangeWithTermsOfService(
ws: InternalWalletState,
exchangeBaseUrl: string,
) {
const exchange = await oneShotGet(ws.db, Stores.exchanges, exchangeBaseUrl);
if (!exchange) {
return;
}
if (exchange.updateStatus != ExchangeUpdateStatus.FETCH_TERMS) {
return;
}
const reqUrl = new URL("terms", exchangeBaseUrl);
reqUrl.searchParams.set("cacheBreaker", WALLET_CACHE_BREAKER_CLIENT_VERSION);
const headers = {
Accept: "text/plain",
};
const resp = await ws.http.get(reqUrl.href, { headers });
if (resp.status !== 200) {
throw Error(`/terms response has unexpected status code (${resp.status})`);
}
const tosText = await resp.text();
const tosEtag = resp.headers.get("etag") || undefined;
await runWithWriteTransaction(ws.db, [Stores.exchanges], async tx => {
const r = await tx.get(Stores.exchanges, exchangeBaseUrl);
if (!r) {
return;
}
if (r.updateStatus != ExchangeUpdateStatus.FETCH_TERMS) {
return;
}
r.termsOfServiceText = tosText;
r.termsOfServiceLastEtag = tosEtag;
r.updateStatus = ExchangeUpdateStatus.FINISHED;
await tx.put(Stores.exchanges, r);
});
}
export async function acceptExchangeTermsOfService(
ws: InternalWalletState,
exchangeBaseUrl: string,
etag: string | undefined,
) {
await runWithWriteTransaction(ws.db, [Stores.exchanges], async tx => {
const r = await tx.get(Stores.exchanges, exchangeBaseUrl);
if (!r) {
return;
}
r.termsOfServiceAcceptedEtag = etag;
r.termsOfServiceAcceptedTimestamp = getTimestampNow();
await tx.put(Stores.exchanges, r);
});
}
/** /**
* Fetch wire information for an exchange and store it in the database. * Fetch wire information for an exchange and store it in the database.
* *
@ -309,7 +361,7 @@ async function updateExchangeWithWireInfo(
accounts: wireInfo.accounts, accounts: wireInfo.accounts,
feesForType: feesForType, feesForType: feesForType,
}; };
r.updateStatus = ExchangeUpdateStatus.FINISHED; r.updateStatus = ExchangeUpdateStatus.FETCH_TERMS;
r.lastError = undefined; r.lastError = undefined;
await tx.put(Stores.exchanges, r); await tx.put(Stores.exchanges, r);
}); });
@ -350,6 +402,10 @@ async function updateExchangeFromUrlImpl(
updateStarted: now, updateStarted: now,
updateReason: "initial", updateReason: "initial",
timestampAdded: getTimestampNow(), timestampAdded: getTimestampNow(),
termsOfServiceAcceptedEtag: undefined,
termsOfServiceAcceptedTimestamp: undefined,
termsOfServiceLastEtag: undefined,
termsOfServiceText: undefined,
}; };
await oneShotPut(ws.db, Stores.exchanges, newExchangeRecord); await oneShotPut(ws.db, Stores.exchanges, newExchangeRecord);
} else { } else {
@ -373,6 +429,7 @@ async function updateExchangeFromUrlImpl(
await updateExchangeWithKeys(ws, baseUrl); await updateExchangeWithKeys(ws, baseUrl);
await updateExchangeWithWireInfo(ws, baseUrl); await updateExchangeWithWireInfo(ws, baseUrl);
await updateExchangeWithTermsOfService(ws, baseUrl);
const updatedExchange = await oneShotGet(ws.db, Stores.exchanges, baseUrl); const updatedExchange = await oneShotGet(ws.db, Stores.exchanges, baseUrl);

View File

@ -22,7 +22,7 @@ import { TipStatus, getTimestampNow, OperationError, NotificationType } from "..
import { TipPickupGetResponse, TipPlanchetDetail, TipResponse } from "../talerTypes"; import { TipPickupGetResponse, TipPlanchetDetail, TipResponse } from "../talerTypes";
import * as Amounts from "../util/amounts"; import * as Amounts from "../util/amounts";
import { Stores, PlanchetRecord, WithdrawalSessionRecord, initRetryInfo, updateRetryInfoTimeout } from "../dbTypes"; import { Stores, PlanchetRecord, WithdrawalSessionRecord, initRetryInfo, updateRetryInfoTimeout } from "../dbTypes";
import { getWithdrawDetailsForAmount, getVerifiedWithdrawDenomList, processWithdrawSession } from "./withdraw"; import { getExchangeWithdrawalInfo, getVerifiedWithdrawDenomList, processWithdrawSession } from "./withdraw";
import { getTalerStampSec, extractTalerStampOrThrow } from "../util/helpers"; import { getTalerStampSec, extractTalerStampOrThrow } from "../util/helpers";
import { updateExchangeFromUrl } from "./exchanges"; import { updateExchangeFromUrl } from "./exchanges";
import { getRandomBytes, encodeCrock } from "../crypto/talerCrypto"; import { getRandomBytes, encodeCrock } from "../crypto/talerCrypto";
@ -58,7 +58,7 @@ export async function getTipStatus(
]); ]);
if (!tipRecord) { if (!tipRecord) {
const withdrawDetails = await getWithdrawDetailsForAmount( const withdrawDetails = await getExchangeWithdrawalInfo(
ws, ws,
tipPickupStatus.exchange_url, tipPickupStatus.exchange_url,
amount, amount,

View File

@ -29,8 +29,8 @@ import * as Amounts from "../util/amounts";
import { import {
getTimestampNow, getTimestampNow,
AcceptWithdrawalResponse, AcceptWithdrawalResponse,
DownloadedWithdrawInfo, BankWithdrawDetails,
ReserveCreationInfo, ExchangeWithdrawDetails,
WithdrawDetails, WithdrawDetails,
OperationError, OperationError,
NotificationType, NotificationType,
@ -106,12 +106,12 @@ export function getWithdrawDenomList(
/** /**
* Get information about a withdrawal from * Get information about a withdrawal from
* a taler://withdraw URI. * a taler://withdraw URI by asking the bank.
*/ */
export async function getWithdrawalInfo( async function getBankWithdrawalInfo(
ws: InternalWalletState, ws: InternalWalletState,
talerWithdrawUri: string, talerWithdrawUri: string,
): Promise<DownloadedWithdrawInfo> { ): Promise<BankWithdrawDetails> {
const uriResult = parseWithdrawUri(talerWithdrawUri); const uriResult = parseWithdrawUri(talerWithdrawUri);
if (!uriResult) { if (!uriResult) {
throw Error("can't parse URL"); throw Error("can't parse URL");
@ -140,7 +140,7 @@ export async function acceptWithdrawal(
talerWithdrawUri: string, talerWithdrawUri: string,
selectedExchange: string, selectedExchange: string,
): Promise<AcceptWithdrawalResponse> { ): Promise<AcceptWithdrawalResponse> {
const withdrawInfo = await getWithdrawalInfo(ws, talerWithdrawUri); const withdrawInfo = await getBankWithdrawalInfo(ws, talerWithdrawUri);
const exchangeWire = await getExchangePaytoUri( const exchangeWire = await getExchangePaytoUri(
ws, ws,
selectedExchange, selectedExchange,
@ -572,11 +572,11 @@ async function processWithdrawSessionImpl(
return; return;
} }
export async function getWithdrawDetailsForAmount( export async function getExchangeWithdrawalInfo(
ws: InternalWalletState, ws: InternalWalletState,
baseUrl: string, baseUrl: string,
amount: AmountJson, amount: AmountJson,
): Promise<ReserveCreationInfo> { ): Promise<ExchangeWithdrawDetails> {
const exchangeInfo = await updateExchangeFromUrl(ws, baseUrl); const exchangeInfo = await updateExchangeFromUrl(ws, baseUrl);
const exchangeDetails = exchangeInfo.details; const exchangeDetails = exchangeInfo.details;
if (!exchangeDetails) { if (!exchangeDetails) {
@ -650,7 +650,15 @@ export async function getWithdrawDetailsForAmount(
} }
} }
const ret: ReserveCreationInfo = { let tosAccepted = false;
if (exchangeInfo.termsOfServiceAcceptedTimestamp) {
if (exchangeInfo.termsOfServiceAcceptedEtag == exchangeInfo.termsOfServiceLastEtag) {
tosAccepted = true;
}
}
const ret: ExchangeWithdrawDetails = {
earliestDepositExpiration, earliestDepositExpiration,
exchangeInfo, exchangeInfo,
exchangeWireAccounts, exchangeWireAccounts,
@ -665,6 +673,7 @@ export async function getWithdrawDetailsForAmount(
walletVersion: WALLET_PROTOCOL_VERSION, walletVersion: WALLET_PROTOCOL_VERSION,
wireFees: exchangeWireInfo, wireFees: exchangeWireInfo,
withdrawFee: acc, withdrawFee: acc,
termsOfServiceAccepted: tosAccepted,
}; };
return ret; return ret;
} }
@ -674,17 +683,17 @@ export async function getWithdrawDetailsForUri(
talerWithdrawUri: string, talerWithdrawUri: string,
maybeSelectedExchange?: string, maybeSelectedExchange?: string,
): Promise<WithdrawDetails> { ): Promise<WithdrawDetails> {
const info = await getWithdrawalInfo(ws, talerWithdrawUri); const info = await getBankWithdrawalInfo(ws, talerWithdrawUri);
let rci: ReserveCreationInfo | undefined = undefined; let rci: ExchangeWithdrawDetails | undefined = undefined;
if (maybeSelectedExchange) { if (maybeSelectedExchange) {
rci = await getWithdrawDetailsForAmount( rci = await getExchangeWithdrawalInfo(
ws, ws,
maybeSelectedExchange, maybeSelectedExchange,
info.amount, info.amount,
); );
} }
return { return {
withdrawInfo: info, bankWithdrawDetails: info,
reserveCreationInfo: rci, exchangeWithdrawDetails: rci,
}; };
} }

View File

@ -37,9 +37,8 @@ import * as Amounts from "./util/amounts";
import { import {
acceptWithdrawal, acceptWithdrawal,
getWithdrawalInfo,
getWithdrawDetailsForUri, getWithdrawDetailsForUri,
getWithdrawDetailsForAmount, getExchangeWithdrawalInfo,
} from "./wallet-impl/withdraw"; } from "./wallet-impl/withdraw";
import { import {
@ -79,7 +78,7 @@ import {
TipStatus, TipStatus,
WalletBalance, WalletBalance,
PreparePayResult, PreparePayResult,
DownloadedWithdrawInfo, BankWithdrawDetails,
WithdrawDetails, WithdrawDetails,
AcceptWithdrawalResponse, AcceptWithdrawalResponse,
PurchaseDetails, PurchaseDetails,
@ -88,6 +87,7 @@ import {
HistoryQuery, HistoryQuery,
WalletNotification, WalletNotification,
NotificationType, NotificationType,
ExchangeWithdrawDetails,
} from "./walletTypes"; } from "./walletTypes";
import { Logger } from "./util/logging"; import { Logger } from "./util/logging";
@ -97,6 +97,7 @@ import {
updateExchangeFromUrl, updateExchangeFromUrl,
getExchangeTrust, getExchangeTrust,
getExchangePaytoUri, getExchangePaytoUri,
acceptExchangeTermsOfService,
} from "./wallet-impl/exchanges"; } from "./wallet-impl/exchanges";
import { processReserve } from "./wallet-impl/reserves"; import { processReserve } from "./wallet-impl/reserves";
@ -167,8 +168,11 @@ export class Wallet {
return getExchangePaytoUri(this.ws, exchangeBaseUrl, supportedTargetTypes); return getExchangePaytoUri(this.ws, exchangeBaseUrl, supportedTargetTypes);
} }
getWithdrawDetailsForAmount(baseUrl: any, amount: AmountJson): any { getWithdrawDetailsForAmount(
return getWithdrawDetailsForAmount(this.ws, baseUrl, amount); exchangeBaseUrl: string,
amount: AmountJson,
): Promise<ExchangeWithdrawDetails> {
return getExchangeWithdrawalInfo(this.ws, exchangeBaseUrl, amount);
} }
addNotificationListener(f: (n: WalletNotification) => void): void { addNotificationListener(f: (n: WalletNotification) => void): void {
@ -194,13 +198,21 @@ export class Wallet {
await updateExchangeFromUrl(this.ws, pending.exchangeBaseUrl, forceNow); await updateExchangeFromUrl(this.ws, pending.exchangeBaseUrl, forceNow);
break; break;
case "refresh": case "refresh":
await processRefreshSession(this.ws, pending.refreshSessionId, forceNow); await processRefreshSession(
this.ws,
pending.refreshSessionId,
forceNow,
);
break; break;
case "reserve": case "reserve":
await processReserve(this.ws, pending.reservePub, forceNow); await processReserve(this.ws, pending.reservePub, forceNow);
break; break;
case "withdraw": case "withdraw":
await processWithdrawSession(this.ws, pending.withdrawSessionId, forceNow); await processWithdrawSession(
this.ws,
pending.withdrawSessionId,
forceNow,
);
break; break;
case "proposal-choice": case "proposal-choice":
// Nothing to do, user needs to accept/reject // Nothing to do, user needs to accept/reject
@ -524,6 +536,13 @@ export class Wallet {
); );
} }
async acceptExchangeTermsOfService(
exchangeBaseUrl: string,
etag: string | undefined,
) {
return acceptExchangeTermsOfService(this.ws, exchangeBaseUrl, etag);
}
async getDenoms(exchangeUrl: string): Promise<DenominationRecord[]> { async getDenoms(exchangeUrl: string): Promise<DenominationRecord[]> {
const denoms = await oneShotIterIndex( const denoms = await oneShotIterIndex(
this.db, this.db,
@ -663,6 +682,10 @@ export class Wallet {
} }
} }
/**
* Inform the wallet that the status of a reserve has changed (e.g. due to a
* confirmation from the bank.).
*/
public async handleNotifyReserve() { public async handleNotifyReserve() {
const reserves = await oneShotIter(this.db, Stores.reserves).toArray(); const reserves = await oneShotIter(this.db, Stores.reserves).toArray();
for (const r of reserves) { for (const r of reserves) {
@ -687,20 +710,6 @@ export class Wallet {
// strategy to test it. // strategy to test it.
} }
/**
* Get information about a withdrawal from
* a taler://withdraw URI.
*/
async getWithdrawalInfo(
talerWithdrawUri: string,
): Promise<DownloadedWithdrawInfo> {
try {
return getWithdrawalInfo(this.ws, talerWithdrawUri);
} finally {
this.latch.trigger();
}
}
async acceptWithdrawal( async acceptWithdrawal(
talerWithdrawUri: string, talerWithdrawUri: string,
selectedExchange: string, selectedExchange: string,

View File

@ -70,7 +70,7 @@ export class CreateReserveResponse {
* *
* Sent to the wallet frontend to be rendered and shown to the user. * Sent to the wallet frontend to be rendered and shown to the user.
*/ */
export interface ReserveCreationInfo { export interface ExchangeWithdrawDetails {
/** /**
* Exchange that the reserve will be created at. * Exchange that the reserve will be created at.
*/ */
@ -107,6 +107,11 @@ export interface ReserveCreationInfo {
*/ */
isAudited: boolean; isAudited: boolean;
/**
* Did the user already accept the current terms of service for the exchange?
*/
termsOfServiceAccepted: boolean;
/** /**
* The exchange is trusted directly. * The exchange is trusted directly.
*/ */
@ -148,8 +153,8 @@ export interface ReserveCreationInfo {
} }
export interface WithdrawDetails { export interface WithdrawDetails {
withdrawInfo: DownloadedWithdrawInfo; bankWithdrawDetails: BankWithdrawDetails;
reserveCreationInfo: ReserveCreationInfo | undefined; exchangeWithdrawDetails: ExchangeWithdrawDetails | undefined;
} }
/** /**
@ -449,7 +454,7 @@ export interface PreparePayResultPaid {
nextUrl: string; nextUrl: string;
} }
export interface DownloadedWithdrawInfo { export interface BankWithdrawDetails {
selectionDone: boolean; selectionDone: boolean;
transferDone: boolean; transferDone: boolean;
amount: AmountJson; amount: AmountJson;

View File

@ -75,7 +75,7 @@ export interface MessageMap {
}; };
"reserve-creation-info": { "reserve-creation-info": {
request: { baseUrl: string; amount: AmountJson }; request: { baseUrl: string; amount: AmountJson };
response: walletTypes.ReserveCreationInfo; response: walletTypes.ExchangeWithdrawDetails;
}; };
"get-history": { "get-history": {
request: {}; request: {};

View File

@ -57,9 +57,9 @@ function NewExchangeSelection(props: { talerWithdrawUri: string }) {
return; return;
} }
console.log("got withdrawDetails", d); console.log("got withdrawDetails", d);
if (!selectedExchange && d.withdrawInfo.suggestedExchange) { if (!selectedExchange && d.bankWithdrawDetails.suggestedExchange) {
console.log("setting selected exchange"); console.log("setting selected exchange");
setSelectedExchange(d.withdrawInfo.suggestedExchange); setSelectedExchange(d.bankWithdrawDetails.suggestedExchange);
} }
setDetails(d); setDetails(d);
}; };
@ -101,7 +101,7 @@ function NewExchangeSelection(props: { talerWithdrawUri: string }) {
} }
if (selecting) { if (selecting) {
const bankSuggestion = details && details.withdrawInfo.suggestedExchange; const bankSuggestion = details && details.bankWithdrawDetails.suggestedExchange;
return ( return (
<div> <div>
{i18n.str`Please select an exchange. You can review the details before after your selection.`} {i18n.str`Please select an exchange. You can review the details before after your selection.`}
@ -157,7 +157,7 @@ function NewExchangeSelection(props: { talerWithdrawUri: string }) {
<div> <div>
<i18n.Translate wrap="p"> <i18n.Translate wrap="p">
You are about to withdraw{" "} You are about to withdraw{" "}
<strong>{renderAmount(details.withdrawInfo.amount)}</strong> from your <strong>{renderAmount(details.bankWithdrawDetails.amount)}</strong> from your
bank account into your wallet. bank account into your wallet.
</i18n.Translate> </i18n.Translate>
<div> <div>
@ -188,8 +188,8 @@ function NewExchangeSelection(props: { talerWithdrawUri: string }) {
</span> </span>
</p> </p>
{details.reserveCreationInfo ? ( {details.exchangeWithdrawDetails ? (
<WithdrawDetailView rci={details.reserveCreationInfo} /> <WithdrawDetailView rci={details.exchangeWithdrawDetails} />
) : null} ) : null}
</div> </div>
</div> </div>

View File

@ -26,7 +26,7 @@
import { AmountJson } from "../util/amounts"; import { AmountJson } from "../util/amounts";
import * as Amounts from "../util/amounts"; import * as Amounts from "../util/amounts";
import { DenominationRecord } from "../dbTypes"; import { DenominationRecord } from "../dbTypes";
import { ReserveCreationInfo } from "../walletTypes"; import { ExchangeWithdrawDetails } from "../walletTypes";
import * as moment from "moment"; import * as moment from "moment";
import * as i18n from "../i18n"; import * as i18n from "../i18n";
import React from "react"; import React from "react";
@ -126,7 +126,7 @@ export class Collapsible extends React.Component<
} }
function AuditorDetailsView(props: { function AuditorDetailsView(props: {
rci: ReserveCreationInfo | null; rci: ExchangeWithdrawDetails | null;
}): JSX.Element { }): JSX.Element {
const rci = props.rci; const rci = props.rci;
console.log("rci", rci); console.log("rci", rci);
@ -163,7 +163,7 @@ function AuditorDetailsView(props: {
} }
function FeeDetailsView(props: { function FeeDetailsView(props: {
rci: ReserveCreationInfo | null; rci: ExchangeWithdrawDetails | null;
}): JSX.Element { }): JSX.Element {
const rci = props.rci; const rci = props.rci;
if (!rci) { if (!rci) {
@ -271,7 +271,7 @@ function FeeDetailsView(props: {
* Shows details about a withdraw request. * Shows details about a withdraw request.
*/ */
export function WithdrawDetailView(props: { export function WithdrawDetailView(props: {
rci: ReserveCreationInfo | null; rci: ExchangeWithdrawDetails | null;
}): JSX.Element { }): JSX.Element {
const rci = props.rci; const rci = props.rci;
return ( return (

View File

@ -34,7 +34,7 @@ import {
import { import {
BenchmarkResult, BenchmarkResult,
ConfirmPayResult, ConfirmPayResult,
ReserveCreationInfo, ExchangeWithdrawDetails,
SenderWireInfos, SenderWireInfos,
TipStatus, TipStatus,
WalletBalance, WalletBalance,
@ -102,7 +102,7 @@ async function callBackend<T extends MessageType>(
* from a given reserve. * from a given reserve.
*/ */
export function getReserveCreationInfo(baseUrl: string, export function getReserveCreationInfo(baseUrl: string,
amount: AmountJson): Promise<ReserveCreationInfo> { amount: AmountJson): Promise<ExchangeWithdrawDetails> {
return callBackend("reserve-creation-info", { baseUrl, amount }); return callBackend("reserve-creation-info", { baseUrl, amount });
} }