fixing tos request

This commit is contained in:
Sebastian 2021-10-13 14:26:18 -03:00
parent 021d508337
commit fbf501e727
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
9 changed files with 220 additions and 196 deletions

View File

@ -578,7 +578,7 @@ export interface GetExchangeTosResult {
/** /**
* Markdown version of the current ToS. * Markdown version of the current ToS.
*/ */
tos: string; content: string;
/** /**
* Version tag of the current ToS. * Version tag of the current ToS.
@ -590,6 +590,11 @@ export interface GetExchangeTosResult {
* if any. * if any.
*/ */
acceptedEtag: string | undefined; acceptedEtag: string | undefined;
/**
* Accepted content type
*/
contentType: string;
} }
export interface TestPayArgs { export interface TestPayArgs {
@ -648,12 +653,14 @@ export const codecForForceExchangeUpdateRequest = (): Codec<AddExchangeRequest>
export interface GetExchangeTosRequest { export interface GetExchangeTosRequest {
exchangeBaseUrl: string; exchangeBaseUrl: string;
acceptedFormat?: string[];
} }
export const codecForGetExchangeTosRequest = (): Codec<GetExchangeTosRequest> => export const codecForGetExchangeTosRequest = (): Codec<GetExchangeTosRequest> =>
buildCodecForObject<GetExchangeTosRequest>() buildCodecForObject<GetExchangeTosRequest>()
.property("exchangeBaseUrl", codecForString()) .property("exchangeBaseUrl", codecForString())
.build("GetExchangeTosRequest"); .property("acceptedFormat", codecOptional(codecForList(codecForString())))
.build("GetExchangeTosRequest");
export interface AcceptManualWithdrawalRequest { export interface AcceptManualWithdrawalRequest {
exchangeBaseUrl: string; exchangeBaseUrl: string;

View File

@ -71,6 +71,7 @@ export interface ExchangeOperations {
updateExchangeFromUrl( updateExchangeFromUrl(
ws: InternalWalletState, ws: InternalWalletState,
baseUrl: string, baseUrl: string,
acceptedFormat?: string[],
forceNow?: boolean, forceNow?: boolean,
): Promise<{ ): Promise<{
exchange: ExchangeRecord; exchange: ExchangeRecord;

View File

@ -278,6 +278,7 @@ async function downloadExchangeWithWireInfo(
export async function updateExchangeFromUrl( export async function updateExchangeFromUrl(
ws: InternalWalletState, ws: InternalWalletState,
baseUrl: string, baseUrl: string,
acceptedFormat?: string[],
forceNow = false, forceNow = false,
): Promise<{ ): Promise<{
exchange: ExchangeRecord; exchange: ExchangeRecord;
@ -286,7 +287,7 @@ export async function updateExchangeFromUrl(
const onOpErr = (e: TalerErrorDetails): Promise<void> => const onOpErr = (e: TalerErrorDetails): Promise<void> =>
handleExchangeUpdateError(ws, baseUrl, e); handleExchangeUpdateError(ws, baseUrl, e);
return await guardOperationException( return await guardOperationException(
() => updateExchangeFromUrlImpl(ws, baseUrl, forceNow), () => updateExchangeFromUrlImpl(ws, baseUrl, acceptedFormat, forceNow),
onOpErr, onOpErr,
); );
} }
@ -411,6 +412,7 @@ async function downloadKeysInfo(
async function updateExchangeFromUrlImpl( async function updateExchangeFromUrlImpl(
ws: InternalWalletState, ws: InternalWalletState,
baseUrl: string, baseUrl: string,
acceptedFormat?: string[],
forceNow = false, forceNow = false,
): Promise<{ ): Promise<{
exchange: ExchangeRecord; exchange: ExchangeRecord;
@ -468,12 +470,28 @@ async function updateExchangeFromUrlImpl(
logger.info("finished validating exchange /wire info"); logger.info("finished validating exchange /wire info");
const tosDownload = await downloadExchangeWithTermsOfService( let tosFound: ExchangeTosDownloadResult | undefined;
baseUrl, //Remove this when exchange supports multiple content-type in accept header
ws.http, if (acceptedFormat) for (const format of acceptedFormat) {
timeout, const resp = await downloadExchangeWithTermsOfService(
"text/plain" baseUrl,
); ws.http,
timeout,
format
);
if (resp.tosContentType === format) {
tosFound = resp
break
}
}
// If none of the specified format was found try text/plain
const tosDownload = tosFound !== undefined ? tosFound :
await downloadExchangeWithTermsOfService(
baseUrl,
ws.http,
timeout,
"text/plain"
);
let recoupGroupId: string | undefined = undefined; let recoupGroupId: string | undefined = undefined;

View File

@ -920,7 +920,7 @@ export async function autoRefresh(
exchangeBaseUrl: string, exchangeBaseUrl: string,
): Promise<void> { ): Promise<void> {
logger.info(`doing auto-refresh check for '${exchangeBaseUrl}'`); logger.info(`doing auto-refresh check for '${exchangeBaseUrl}'`);
await updateExchangeFromUrl(ws, exchangeBaseUrl, true); await updateExchangeFromUrl(ws, exchangeBaseUrl, undefined, true);
let minCheckThreshold = timestampAddDuration( let minCheckThreshold = timestampAddDuration(
getTimestampNow(), getTimestampNow(),
durationFromSpec({ days: 1 }), durationFromSpec({ days: 1 }),

View File

@ -66,8 +66,6 @@ import {
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
WALLET_EXCHANGE_PROTOCOL_VERSION, WALLET_EXCHANGE_PROTOCOL_VERSION,
} from "../versions.js"; } from "../versions.js";
import { stringify } from "querystring";
import { downloadExchangeWithTermsOfService, ExchangeTosDownloadResult } from "./exchanges";
/** /**
* Logger for this file. * Logger for this file.
@ -133,11 +131,6 @@ export interface ExchangeWithdrawDetails {
*/ */
termsOfServiceAccepted: boolean; termsOfServiceAccepted: boolean;
/**
* Tos
*/
tosRequested: ExchangeTosDownloadResult | undefined
/** /**
* The exchange is trusted directly. * The exchange is trusted directly.
*/ */
@ -937,7 +930,6 @@ export async function getExchangeWithdrawalInfo(
ws: InternalWalletState, ws: InternalWalletState,
baseUrl: string, baseUrl: string,
amount: AmountJson, amount: AmountJson,
tosAcceptedFormat?: string[],
): Promise<ExchangeWithdrawDetails> { ): Promise<ExchangeWithdrawDetails> {
const { const {
exchange, exchange,
@ -1004,34 +996,6 @@ export async function getExchangeWithdrawalInfo(
} }
} }
const noTosDownloaded =
exchangeDetails.termsOfServiceContentType === undefined ||
exchangeDetails.termsOfServiceAcceptedEtag === undefined ||
exchangeDetails.termsOfServiceText === undefined;
let tosFound: ExchangeTosDownloadResult | undefined = noTosDownloaded ? undefined : {
tosContentType: exchangeDetails.termsOfServiceContentType!,
tosEtag: exchangeDetails.termsOfServiceAcceptedEtag!,
tosText: exchangeDetails.termsOfServiceText!,
};
try {
if (tosAcceptedFormat) for (const format of tosAcceptedFormat) {
const resp = await downloadExchangeWithTermsOfService(
exchangeDetails.exchangeBaseUrl,
ws.http,
{ d_ms: 1000 },
format
);
if (resp.tosContentType === format) {
tosFound = resp
break
}
}
} catch (e) {
tosFound = undefined
}
const withdrawFee = Amounts.sub( const withdrawFee = Amounts.sub(
selectedDenoms.totalWithdrawCost, selectedDenoms.totalWithdrawCost,
selectedDenoms.totalCoinValue, selectedDenoms.totalCoinValue,
@ -1054,7 +1018,6 @@ export async function getExchangeWithdrawalInfo(
walletVersion: WALLET_EXCHANGE_PROTOCOL_VERSION, walletVersion: WALLET_EXCHANGE_PROTOCOL_VERSION,
withdrawFee, withdrawFee,
termsOfServiceAccepted: tosAccepted, termsOfServiceAccepted: tosAccepted,
tosRequested: tosFound
}; };
return ret; return ret;
} }

View File

@ -217,7 +217,7 @@ async function processOnePendingOperation(
logger.trace(`running pending ${JSON.stringify(pending, undefined, 2)}`); logger.trace(`running pending ${JSON.stringify(pending, undefined, 2)}`);
switch (pending.type) { switch (pending.type) {
case PendingTaskType.ExchangeUpdate: case PendingTaskType.ExchangeUpdate:
await updateExchangeFromUrl(ws, pending.exchangeBaseUrl, forceNow); await updateExchangeFromUrl(ws, pending.exchangeBaseUrl, undefined, forceNow);
break; break;
case PendingTaskType.Refresh: case PendingTaskType.Refresh:
await processRefreshGroup(ws, pending.refreshGroupId, forceNow); await processRefreshGroup(ws, pending.refreshGroupId, forceNow);
@ -452,20 +452,24 @@ async function acceptManualWithdrawal(
async function getExchangeTos( async function getExchangeTos(
ws: InternalWalletState, ws: InternalWalletState,
exchangeBaseUrl: string, exchangeBaseUrl: string,
acceptedFormat?: string[],
): Promise<GetExchangeTosResult> { ): Promise<GetExchangeTosResult> {
const { exchange, exchangeDetails } = await updateExchangeFromUrl( const { exchangeDetails } = await updateExchangeFromUrl(
ws, ws,
exchangeBaseUrl, exchangeBaseUrl,
acceptedFormat,
); );
const tos = exchangeDetails.termsOfServiceText; const content = exchangeDetails.termsOfServiceText;
const currentEtag = exchangeDetails.termsOfServiceLastEtag; const currentEtag = exchangeDetails.termsOfServiceLastEtag;
if (!tos || !currentEtag) { const contentType = exchangeDetails.termsOfServiceContentType;
if (content === undefined || currentEtag === undefined || contentType === undefined) {
throw Error("exchange is in invalid state"); throw Error("exchange is in invalid state");
} }
return { return {
acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag, acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
currentEtag, currentEtag,
tos, content,
contentType,
}; };
} }
@ -485,7 +489,7 @@ async function getExchanges(
if (!dp) { if (!dp) {
continue; continue;
} }
const { currency, masterPublicKey } = dp; const { currency } = dp;
const exchangeDetails = await getExchangeDetails(tx, r.baseUrl); const exchangeDetails = await getExchangeDetails(tx, r.baseUrl);
if (!exchangeDetails) { if (!exchangeDetails) {
continue; continue;
@ -684,7 +688,7 @@ async function dispatchRequestInternal(
} }
case "addExchange": { case "addExchange": {
const req = codecForAddExchangeRequest().decode(payload); const req = codecForAddExchangeRequest().decode(payload);
await updateExchangeFromUrl(ws, req.exchangeBaseUrl, req.forceUpdate); await updateExchangeFromUrl(ws, req.exchangeBaseUrl, undefined, req.forceUpdate);
return {}; return {};
} }
case "listExchanges": { case "listExchanges": {
@ -696,7 +700,7 @@ async function dispatchRequestInternal(
} }
case "getExchangeWithdrawalInfo": { case "getExchangeWithdrawalInfo": {
const req = codecForGetExchangeWithdrawalInfo().decode(payload); const req = codecForGetExchangeWithdrawalInfo().decode(payload);
return await getExchangeWithdrawalInfo(ws, req.exchangeBaseUrl, req.amount, req.tosAcceptedFormat); return await getExchangeWithdrawalInfo(ws, req.exchangeBaseUrl, req.amount);
} }
case "acceptManualWithdrawal": { case "acceptManualWithdrawal": {
const req = codecForAcceptManualWithdrawalRequet().decode(payload); const req = codecForAcceptManualWithdrawalRequet().decode(payload);
@ -744,7 +748,7 @@ async function dispatchRequestInternal(
} }
case "getExchangeTos": { case "getExchangeTos": {
const req = codecForGetExchangeTosRequest().decode(payload); const req = codecForGetExchangeTosRequest().decode(payload);
return getExchangeTos(ws, req.exchangeBaseUrl); return getExchangeTos(ws, req.exchangeBaseUrl , req.acceptedFormat);
} }
case "retryPendingNow": { case "retryPendingNow": {
await runPending(ws, true); await runPending(ws, true);

View File

@ -785,21 +785,23 @@ export const NewTerms = createExample(TestedComponent, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.demo.taler.net', exchangeBaseUrl: 'exchange.demo.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
},{ }, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.test.taler.net', exchangeBaseUrl: 'exchange.test.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
}], }],
exchangeBaseUrl: 'exchange.demo.taler.net',
details: { details: {
exchangeInfo: { content: '',
baseUrl: 'exchange.demo.taler.net' contentType: '',
} as ExchangeRecord, currentEtag: '',
withdrawFee: { acceptedEtag: undefined,
currency: 'USD', },
fraction: 0, withdrawalFee: {
value: 0 currency: 'USD',
}, fraction: 0,
} as ExchangeWithdrawDetails, value: 0
},
amount: { amount: {
currency: 'USD', currency: 'USD',
value: 2, value: 2,
@ -821,21 +823,23 @@ export const TermsReviewingPLAIN = createExample(TestedComponent, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.demo.taler.net', exchangeBaseUrl: 'exchange.demo.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
},{ }, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.test.taler.net', exchangeBaseUrl: 'exchange.test.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
}], }],
exchangeBaseUrl: 'exchange.demo.taler.net',
details: { details: {
exchangeInfo: { content: '',
baseUrl: 'exchange.demo.taler.net' contentType: '',
} as ExchangeRecord, currentEtag: '',
withdrawFee: { acceptedEtag: undefined,
currency: 'USD', },
fraction: 0, withdrawalFee: {
value: 0 currency: 'USD',
}, fraction: 0,
} as ExchangeWithdrawDetails, value: 0
},
amount: { amount: {
currency: 'USD', currency: 'USD',
value: 2, value: 2,
@ -858,21 +862,23 @@ export const TermsReviewingHTML = createExample(TestedComponent, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.demo.taler.net', exchangeBaseUrl: 'exchange.demo.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
},{ }, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.test.taler.net', exchangeBaseUrl: 'exchange.test.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
}], }],
exchangeBaseUrl: 'exchange.demo.taler.net',
details: { details: {
exchangeInfo: { content: '',
baseUrl: 'exchange.demo.taler.net' contentType: '',
} as ExchangeRecord, currentEtag: '',
withdrawFee: { acceptedEtag: undefined,
currency: 'USD', },
fraction: 0, withdrawalFee: {
value: 0 currency: 'USD',
}, fraction: 0,
} as ExchangeWithdrawDetails, value: 0
},
amount: { amount: {
currency: 'USD', currency: 'USD',
value: 2, value: 2,
@ -910,21 +916,23 @@ export const TermsReviewingPDF = createExample(TestedComponent, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.demo.taler.net', exchangeBaseUrl: 'exchange.demo.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
},{ }, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.test.taler.net', exchangeBaseUrl: 'exchange.test.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
}], }],
exchangeBaseUrl: 'exchange.demo.taler.net',
details: { details: {
exchangeInfo: { content: '',
baseUrl: 'exchange.demo.taler.net' contentType: '',
} as ExchangeRecord, currentEtag: '',
withdrawFee: { acceptedEtag: undefined,
currency: 'USD', },
fraction: 0, withdrawalFee: {
value: 0 currency: 'USD',
}, fraction: 0,
} as ExchangeWithdrawDetails, value: 0
},
amount: { amount: {
currency: 'USD', currency: 'USD',
value: 2, value: 2,
@ -948,21 +956,23 @@ export const TermsReviewingXML = createExample(TestedComponent, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.demo.taler.net', exchangeBaseUrl: 'exchange.demo.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
},{ }, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.test.taler.net', exchangeBaseUrl: 'exchange.test.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
}], }],
exchangeBaseUrl: 'exchange.demo.taler.net',
details: { details: {
exchangeInfo: { content: '',
baseUrl: 'exchange.demo.taler.net' contentType: '',
} as ExchangeRecord, currentEtag: '',
withdrawFee: { acceptedEtag: undefined,
currency: 'USD', },
fraction: 0, withdrawalFee: {
value: 0 currency: 'USD',
}, fraction: 0,
} as ExchangeWithdrawDetails, value: 0
},
amount: { amount: {
currency: 'USD', currency: 'USD',
value: 2, value: 2,
@ -985,21 +995,23 @@ export const NewTermsAccepted = createExample(TestedComponent, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.demo.taler.net', exchangeBaseUrl: 'exchange.demo.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
},{ }, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.test.taler.net', exchangeBaseUrl: 'exchange.test.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
}], }],
exchangeBaseUrl: 'exchange.demo.taler.net',
details: { details: {
exchangeInfo: { content: '',
baseUrl: 'exchange.demo.taler.net' contentType: '',
} as ExchangeRecord, currentEtag: '',
withdrawFee: { acceptedEtag: undefined,
currency: 'USD', },
fraction: 0, withdrawalFee: {
value: 0 currency: 'USD',
}, fraction: 0,
} as ExchangeWithdrawDetails, value: 0
},
amount: { amount: {
currency: 'USD', currency: 'USD',
value: 2, value: 2,
@ -1021,21 +1033,23 @@ export const TermsShowAgainXML = createExample(TestedComponent, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.demo.taler.net', exchangeBaseUrl: 'exchange.demo.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
},{ }, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.test.taler.net', exchangeBaseUrl: 'exchange.test.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
}], }],
exchangeBaseUrl: 'exchange.demo.taler.net',
details: { details: {
exchangeInfo: { content: '',
baseUrl: 'exchange.demo.taler.net' contentType: '',
} as ExchangeRecord, currentEtag: '',
withdrawFee: { acceptedEtag: undefined,
currency: 'USD', },
fraction: 0, withdrawalFee: {
value: 0 currency: 'USD',
}, fraction: 0,
} as ExchangeWithdrawDetails, value: 0
},
amount: { amount: {
currency: 'USD', currency: 'USD',
value: 2, value: 2,
@ -1050,6 +1064,7 @@ export const TermsShowAgainXML = createExample(TestedComponent, {
}, },
status: 'new' status: 'new'
}, },
reviewed: true,
reviewing: true, reviewing: true,
}) })
@ -1058,21 +1073,23 @@ export const TermsChanged = createExample(TestedComponent, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.demo.taler.net', exchangeBaseUrl: 'exchange.demo.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
},{ }, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.test.taler.net', exchangeBaseUrl: 'exchange.test.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
}], }],
exchangeBaseUrl: 'exchange.demo.taler.net',
details: { details: {
exchangeInfo: { content: '',
baseUrl: 'exchange.demo.taler.net' contentType: '',
} as ExchangeRecord, currentEtag: '',
withdrawFee: { acceptedEtag: undefined,
currency: 'USD', },
fraction: 0, withdrawalFee: {
value: 0 currency: 'USD',
}, fraction: 0,
} as ExchangeWithdrawDetails, value: 0
},
amount: { amount: {
currency: 'USD', currency: 'USD',
value: 2, value: 2,
@ -1094,21 +1111,23 @@ export const TermsNotFound = createExample(TestedComponent, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.demo.taler.net', exchangeBaseUrl: 'exchange.demo.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
},{ }, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.test.taler.net', exchangeBaseUrl: 'exchange.test.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
}], }],
exchangeBaseUrl: 'exchange.demo.taler.net',
details: { details: {
exchangeInfo: { content: '',
baseUrl: 'exchange.demo.taler.net' contentType: '',
} as ExchangeRecord, currentEtag: '',
withdrawFee: { acceptedEtag: undefined,
currency: 'USD', },
fraction: 0, withdrawalFee: {
value: 0 currency: 'USD',
}, fraction: 0,
} as ExchangeWithdrawDetails, value: 0
},
amount: { amount: {
currency: 'USD', currency: 'USD',
value: 2, value: 2,
@ -1126,21 +1145,23 @@ export const TermsAlreadyAccepted = createExample(TestedComponent, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.demo.taler.net', exchangeBaseUrl: 'exchange.demo.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
},{ }, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.test.taler.net', exchangeBaseUrl: 'exchange.test.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
}], }],
exchangeBaseUrl: 'exchange.demo.taler.net',
details: { details: {
exchangeInfo: { content: '',
baseUrl: 'exchange.demo.taler.net' contentType: '',
} as ExchangeRecord, currentEtag: '',
withdrawFee: { acceptedEtag: undefined,
currency: 'USD', },
fraction: amountFractionalBase * 0.5, withdrawalFee: {
value: 0 currency: 'USD',
}, fraction: amountFractionalBase * 0.5,
} as ExchangeWithdrawDetails, value: 0
},
amount: { amount: {
currency: 'USD', currency: 'USD',
value: 2, value: 2,
@ -1159,21 +1180,23 @@ export const WithoutFee = createExample(TestedComponent, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.demo.taler.net', exchangeBaseUrl: 'exchange.demo.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
},{ }, {
currency: 'USD', currency: 'USD',
exchangeBaseUrl: 'exchange.test.taler.net', exchangeBaseUrl: 'exchange.test.taler.net',
paytoUris: ['asd'], paytoUris: ['asd'],
}], }],
exchangeBaseUrl: 'exchange.demo.taler.net',
details: { details: {
exchangeInfo: { content: '',
baseUrl: 'exchange.demo.taler.net' contentType: '',
} as ExchangeRecord, currentEtag: '',
withdrawFee: { acceptedEtag: undefined,
currency: 'USD', },
fraction: 0, withdrawalFee: {
value: 0 currency: 'USD',
}, fraction: 0,
} as ExchangeWithdrawDetails, value: 0,
},
amount: { amount: {
currency: 'USD', currency: 'USD',
value: 2, value: 2,

View File

@ -21,7 +21,7 @@
* @author Florian Dold * @author Florian Dold
*/ */
import { AmountJson, Amounts, ExchangeListItem, i18n, WithdrawUriInfoResponse } from '@gnu-taler/taler-util'; import { AmountJson, Amounts, ExchangeListItem, GetExchangeTosResult, i18n, WithdrawUriInfoResponse } from '@gnu-taler/taler-util';
import { ExchangeWithdrawDetails } from '@gnu-taler/taler-wallet-core/src/operations/withdraw'; import { ExchangeWithdrawDetails } from '@gnu-taler/taler-wallet-core/src/operations/withdraw';
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Fragment } from 'preact/jsx-runtime'; import { Fragment } from 'preact/jsx-runtime';
@ -33,7 +33,7 @@ import { SelectList } from '../components/SelectList';
import { ButtonSuccess, ButtonWarning, LinkSuccess, LinkWarning, TermsOfService, WalletAction } from '../components/styled'; import { ButtonSuccess, ButtonWarning, LinkSuccess, LinkWarning, TermsOfService, WalletAction } from '../components/styled';
import { useAsyncAsHook } from '../hooks/useAsyncAsHook'; import { useAsyncAsHook } from '../hooks/useAsyncAsHook';
import { import {
acceptWithdrawal, getExchangeWithdrawalInfo, getWithdrawalDetailsForUri, setExchangeTosAccepted, listExchanges acceptWithdrawal, getExchangeWithdrawalInfo, getWithdrawalDetailsForUri, setExchangeTosAccepted, listExchanges, getExchangeTos
} from "../wxApi"; } from "../wxApi";
import { wxMain } from '../wxBackend.js'; import { wxMain } from '../wxBackend.js';
@ -42,7 +42,9 @@ interface Props {
} }
export interface ViewProps { export interface ViewProps {
details: ExchangeWithdrawDetails; details: GetExchangeTosResult;
withdrawalFee: AmountJson;
exchangeBaseUrl: string;
amount: AmountJson; amount: AmountJson;
onSwitchExchange: (ex: string) => void; onSwitchExchange: (ex: string) => void;
onWithdraw: () => Promise<void>; onWithdraw: () => Promise<void>;
@ -54,8 +56,8 @@ export interface ViewProps {
terms: { terms: {
value?: TermsDocument; value?: TermsDocument;
status: TermsStatus; status: TermsStatus;
}, };
knownExchanges: ExchangeListItem[] knownExchanges: ExchangeListItem[];
}; };
@ -94,7 +96,7 @@ function amountToString(text: AmountJson) {
return `${amount} ${aj.currency}` return `${amount} ${aj.currency}`
} }
export function View({ details, knownExchanges, amount, onWithdraw, onSwitchExchange, terms, reviewing, onReview, onAccept, reviewed, confirmed }: ViewProps) { export function View({ details, withdrawalFee, exchangeBaseUrl, knownExchanges, amount, onWithdraw, onSwitchExchange, terms, reviewing, onReview, onAccept, reviewed, confirmed }: ViewProps) {
const needsReview = terms.status === 'changed' || terms.status === 'new' const needsReview = terms.status === 'changed' || terms.status === 'new'
const [switchingExchange, setSwitchingExchange] = useState<string | undefined>(undefined) const [switchingExchange, setSwitchingExchange] = useState<string | undefined>(undefined)
@ -107,12 +109,12 @@ export function View({ details, knownExchanges, amount, onWithdraw, onSwitchExch
{i18n.str`Digital cash withdrawal`} {i18n.str`Digital cash withdrawal`}
</h2> </h2>
<section> <section>
<Part title="Total to withdraw" text={amountToString(Amounts.sub(amount, details.withdrawFee).amount)} kind='positive' /> <Part title="Total to withdraw" text={amountToString(Amounts.sub(amount, withdrawalFee).amount)} kind='positive' />
<Part title="Chosen amount" text={amountToString(amount)} kind='neutral' /> <Part title="Chosen amount" text={amountToString(amount)} kind='neutral' />
{Amounts.isNonZero(details.withdrawFee) && {Amounts.isNonZero(withdrawalFee) &&
<Part title="Exchange fee" text={amountToString(details.withdrawFee)} kind='negative' /> <Part title="Exchange fee" text={amountToString(withdrawalFee)} kind='negative' />
} }
<Part title="Exchange" text={details.exchangeInfo.baseUrl} kind='neutral' big /> <Part title="Exchange" text={exchangeBaseUrl} kind='neutral' big />
</section> </section>
{!reviewing && {!reviewing &&
<section> <section>
@ -190,7 +192,7 @@ export function View({ details, knownExchanges, amount, onWithdraw, onSwitchExch
{terms.status === 'new' && !reviewed && !reviewing && {terms.status === 'new' && !reviewed && !reviewing &&
<ButtonSuccess <ButtonSuccess
upperCased upperCased
disabled={!details.exchangeInfo.baseUrl} disabled={!exchangeBaseUrl}
onClick={() => onReview(true)} onClick={() => onReview(true)}
> >
{i18n.str`Review exchange terms of service`} {i18n.str`Review exchange terms of service`}
@ -199,7 +201,7 @@ export function View({ details, knownExchanges, amount, onWithdraw, onSwitchExch
{terms.status === 'changed' && !reviewed && !reviewing && {terms.status === 'changed' && !reviewed && !reviewing &&
<ButtonWarning <ButtonWarning
upperCased upperCased
disabled={!details.exchangeInfo.baseUrl} disabled={!exchangeBaseUrl}
onClick={() => onReview(true)} onClick={() => onReview(true)}
> >
{i18n.str`Review new version of terms of service`} {i18n.str`Review new version of terms of service`}
@ -208,7 +210,7 @@ export function View({ details, knownExchanges, amount, onWithdraw, onSwitchExch
{(terms.status === 'accepted' || (needsReview && reviewed)) && {(terms.status === 'accepted' || (needsReview && reviewed)) &&
<ButtonSuccess <ButtonSuccess
upperCased upperCased
disabled={!details.exchangeInfo.baseUrl || confirmed} disabled={!exchangeBaseUrl || confirmed}
onClick={onWithdraw} onClick={onWithdraw}
> >
{i18n.str`Confirm withdrawal`} {i18n.str`Confirm withdrawal`}
@ -220,7 +222,7 @@ export function View({ details, knownExchanges, amount, onWithdraw, onSwitchExch
</LinkWarning> </LinkWarning>
<ButtonWarning <ButtonWarning
upperCased upperCased
disabled={!details.exchangeInfo.baseUrl} disabled={!exchangeBaseUrl}
onClick={onWithdraw} onClick={onWithdraw}
> >
{i18n.str`Withdraw anyway`} {i18n.str`Withdraw anyway`}
@ -249,11 +251,13 @@ export function WithdrawPageWithParsedURI({ uri, uriInfo }: { uri: string, uriIn
const exchange = customExchange || uriInfo.defaultExchangeBaseUrl || thisCurrencyExchanges[0]?.exchangeBaseUrl const exchange = customExchange || uriInfo.defaultExchangeBaseUrl || thisCurrencyExchanges[0]?.exchangeBaseUrl
const detailsHook = useAsyncAsHook(async () => { const detailsHook = useAsyncAsHook(async () => {
if (!exchange) throw Error('no default exchange') if (!exchange) throw Error('no default exchange')
return getExchangeWithdrawalInfo({ const tos = await getExchangeTos(exchange, ['text/xml'])
const info = await getExchangeWithdrawalInfo({
exchangeBaseUrl: exchange, exchangeBaseUrl: exchange,
amount: withdrawAmount, amount: withdrawAmount,
tosAcceptedFormat: ['text/xml'] tosAcceptedFormat: ['text/xml']
}) })
return {tos, info}
}) })
if (!detailsHook) { if (!detailsHook) {
@ -267,7 +271,7 @@ export function WithdrawPageWithParsedURI({ uri, uriInfo }: { uri: string, uriIn
const onAccept = async (): Promise<void> => { const onAccept = async (): Promise<void> => {
try { try {
await setExchangeTosAccepted(details.exchangeInfo.baseUrl, details.tosRequested?.tosEtag) await setExchangeTosAccepted(exchange, details.tos.currentEtag)
setReviewed(true) setReviewed(true)
} catch (e) { } catch (e) {
if (e instanceof Error) { if (e instanceof Error) {
@ -278,9 +282,9 @@ export function WithdrawPageWithParsedURI({ uri, uriInfo }: { uri: string, uriIn
const onWithdraw = async (): Promise<void> => { const onWithdraw = async (): Promise<void> => {
setConfirmed(true) setConfirmed(true)
console.log("accepting exchange", details.exchangeDetails.exchangeBaseUrl); console.log("accepting exchange", exchange);
try { try {
const res = await acceptWithdrawal(uri, details.exchangeInfo.baseUrl); const res = await acceptWithdrawal(uri, exchange);
console.log("accept withdrawal response", res); console.log("accept withdrawal response", res);
if (res.confirmTransferUrl) { if (res.confirmTransferUrl) {
document.location.href = res.confirmTransferUrl; document.location.href = res.confirmTransferUrl;
@ -290,17 +294,18 @@ export function WithdrawPageWithParsedURI({ uri, uriInfo }: { uri: string, uriIn
} }
}; };
const termsContent: TermsDocument | undefined = !details.tosRequested ? undefined : parseTermsOfServiceContent(details.tosRequested.tosContentType, details.tosRequested.tosText); const termsContent: TermsDocument | undefined = parseTermsOfServiceContent(details.tos.contentType, details.tos.content);
const status: TermsStatus = !termsContent ? 'notfound' : ( const status: TermsStatus = !termsContent ? 'notfound' : (
!details.exchangeDetails.termsOfServiceAcceptedEtag ? 'new' : ( !details.tos.acceptedEtag ? 'new' : (
details.tosRequested?.tosEtag !== details.exchangeDetails.termsOfServiceAcceptedEtag ? 'changed' : 'accepted' details.tos.acceptedEtag !== details.tos.currentEtag ? 'changed' : 'accepted'
)) ))
return <View onWithdraw={onWithdraw} return <View onWithdraw={onWithdraw}
// setCancelled={setCancelled} setSelecting={setSelecting} details={details.tos} amount={withdrawAmount}
details={details} amount={withdrawAmount} exchangeBaseUrl={exchange}
withdrawalFee={details.info.withdrawFee} //FIXME
terms={{ terms={{
status, value: termsContent status, value: termsContent
}} }}
@ -309,7 +314,6 @@ export function WithdrawPageWithParsedURI({ uri, uriInfo }: { uri: string, uriIn
confirmed={confirmed} confirmed={confirmed}
reviewed={reviewed} onAccept={onAccept} reviewed={reviewed} onAccept={onAccept}
reviewing={reviewing} onReview={setReviewing} reviewing={reviewing} onReview={setReviewing}
// terms={[]}
/> />
} }
export function WithdrawPage({ talerWithdrawUri }: Props): JSX.Element { export function WithdrawPage({ talerWithdrawUri }: Props): JSX.Element {

View File

@ -45,6 +45,7 @@ import {
AmountJson, AmountJson,
ExchangesListRespose, ExchangesListRespose,
AddExchangeRequest, AddExchangeRequest,
GetExchangeTosResult,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { AddBackupProviderRequest, BackupProviderState, OperationFailedError, RemoveBackupProviderRequest } from "@gnu-taler/taler-wallet-core"; import { AddBackupProviderRequest, BackupProviderState, OperationFailedError, RemoveBackupProviderRequest } from "@gnu-taler/taler-wallet-core";
import { BackupInfo } from "@gnu-taler/taler-wallet-core"; import { BackupInfo } from "@gnu-taler/taler-wallet-core";
@ -327,11 +328,14 @@ export function getExchangeWithdrawalInfo(
): Promise<ExchangeWithdrawDetails> { ): Promise<ExchangeWithdrawDetails> {
return callBackend("getExchangeWithdrawalInfo", req); return callBackend("getExchangeWithdrawalInfo", req);
} }
// export const codecForAddExchangeRequest = (): Codec<AddExchangeRequest> => export function getExchangeTos(
// buildCodecForObject<AddExchangeRequest>() exchangeBaseUrl: string,
// .property("exchangeBaseUrl", codecForString()) acceptedFormat: string[],
// .property("forceUpdate", codecOptional(codecForBoolean())) ): Promise<GetExchangeTosResult> {
// .build("AddExchangeRequest"); return callBackend("getExchangeTos", {
exchangeBaseUrl, acceptedFormat
});
}
export function addExchange( export function addExchange(
req: AddExchangeRequest, req: AddExchangeRequest,