added integration with the wallet-core to get info about the last tos approved

This commit is contained in:
Sebastian 2021-09-13 15:32:06 -03:00
parent 57b6cd4269
commit 9f00987380
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
9 changed files with 109 additions and 14 deletions

View File

@ -716,12 +716,14 @@ export const codecForGetWithdrawalDetailsForUri = (): Codec<GetWithdrawalDetails
export interface GetExchangeWithdrawalInfo {
exchangeBaseUrl: string;
amount: AmountJson;
tosAcceptedFormat?: string[];
}
export const codecForGetExchangeWithdrawalInfo = (): Codec<GetExchangeWithdrawalInfo> =>
buildCodecForObject<GetExchangeWithdrawalInfo>()
.property("exchangeBaseUrl", codecForString())
.property("amount", codecForAmountJson())
.property("tosAcceptedFormat", codecOptional(codecForList(codecForString())))
.build("GetExchangeWithdrawalInfo");
export interface AbortProposalRequest {

View File

@ -449,6 +449,11 @@ export interface ExchangeDetailsRecord {
*/
termsOfServiceText: string | undefined;
/**
* content-type of the last downloaded termsOfServiceText.
*/
termsOfServiceContentType: string | undefined;
/**
* ETag for last terms of service download.
*/

View File

@ -306,6 +306,7 @@ export async function importBackup(
termsOfServiceAcceptedEtag: backupExchangeDetails.tos_accepted_etag,
termsOfServiceText: undefined,
termsOfServiceLastEtag: undefined,
termsOfServiceContentType: undefined,
termsOfServiceAcceptedTimestamp:
backupExchangeDetails.tos_accepted_timestamp,
wireInfo,

View File

@ -127,20 +127,22 @@ function getExchangeRequestTimeout(e: ExchangeRecord): Duration {
return { d_ms: 5000 };
}
interface ExchangeTosDownloadResult {
export interface ExchangeTosDownloadResult {
tosText: string;
tosEtag: string;
tosContentType: string;
}
async function downloadExchangeWithTermsOfService(
export async function downloadExchangeWithTermsOfService(
exchangeBaseUrl: string,
http: HttpRequestLibrary,
timeout: Duration,
contentType: string,
): Promise<ExchangeTosDownloadResult> {
const reqUrl = new URL("terms", exchangeBaseUrl);
reqUrl.searchParams.set("cacheBreaker", WALLET_CACHE_BREAKER_CLIENT_VERSION);
const headers = {
Accept: "text/plain",
Accept: contentType,
};
const resp = await http.get(reqUrl.href, {
@ -149,8 +151,9 @@ async function downloadExchangeWithTermsOfService(
});
const tosText = await readSuccessResponseTextOrThrow(resp);
const tosEtag = resp.headers.get("etag") || "unknown";
const tosContentType = resp.headers.get("content-type") || "text/plain";
return { tosText, tosEtag };
return { tosText, tosEtag, tosContentType };
}
/**
@ -469,6 +472,7 @@ async function updateExchangeFromUrlImpl(
baseUrl,
ws.http,
timeout,
"text/plain"
);
let recoupGroupId: string | undefined = undefined;
@ -506,6 +510,7 @@ async function updateExchangeFromUrlImpl(
wireInfo,
termsOfServiceText: tosDownload.tosText,
termsOfServiceAcceptedEtag: undefined,
termsOfServiceContentType: tosDownload.tosContentType,
termsOfServiceLastEtag: tosDownload.tosEtag,
termsOfServiceAcceptedTimestamp: getTimestampNow(),
};

View File

@ -66,6 +66,8 @@ import {
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
WALLET_EXCHANGE_PROTOCOL_VERSION,
} from "../versions.js";
import { stringify } from "querystring";
import { downloadExchangeWithTermsOfService, ExchangeTosDownloadResult } from "./exchanges";
/**
* Logger for this file.
@ -131,6 +133,11 @@ export interface ExchangeWithdrawDetails {
*/
termsOfServiceAccepted: boolean;
/**
* Tos
*/
tosRequested: ExchangeTosDownloadResult | undefined
/**
* The exchange is trusted directly.
*/
@ -930,6 +937,7 @@ export async function getExchangeWithdrawalInfo(
ws: InternalWalletState,
baseUrl: string,
amount: AmountJson,
tosAcceptedFormat?: string[],
): Promise<ExchangeWithdrawDetails> {
const {
exchange,
@ -996,6 +1004,34 @@ 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(
selectedDenoms.totalWithdrawCost,
selectedDenoms.totalCoinValue,
@ -1018,6 +1054,7 @@ export async function getExchangeWithdrawalInfo(
walletVersion: WALLET_EXCHANGE_PROTOCOL_VERSION,
withdrawFee,
termsOfServiceAccepted: tosAccepted,
tosRequested: tosFound
};
return ret;
}

View File

@ -696,7 +696,7 @@ async function dispatchRequestInternal(
}
case "getExchangeWithdrawalInfo": {
const req = codecForGetExchangeWithdrawalInfo().decode(payload);
return await getExchangeWithdrawalInfo(ws, req.exchangeBaseUrl, req.amount);
return await getExchangeWithdrawalInfo(ws, req.exchangeBaseUrl, req.amount, req.tosAcceptedFormat);
}
case "acceptManualWithdrawal": {
const req = codecForAcceptManualWithdrawalRequet().decode(payload);

View File

@ -35,6 +35,8 @@ export default {
};
const termsXml = `<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE document PUBLIC "+//IDN docutils.sourceforge.net//DTD Docutils Generic//EN//XML" "http://docutils.sourceforge.net/docs/ref/docutils.dtd">
<!-- Generated by Docutils 0.14 -->
<document source="/home/grothoff/research/taler/exchange/contrib/tos/tos.rst">
<section ids="terms-of-service" names="terms\ of\ service">
<title>Terms Of Service</title>
@ -234,7 +236,6 @@ const termsXml = `<?xml version="1.0" encoding="utf-8"?>
<enumerated_list enumtype="loweralpha" prefix="(" suffix=")">
<list_item>
<paragraph>any lost profits, data loss, cost of procurement of substitute goods or
* TLSv1.3 (IN), TLS Unknown, Unknown (23):
services, or direct, indirect, incidental, special, punitive, compensatory,
or consequential damages of any kind whatsoever resulting from:</paragraph>
</list_item>
@ -377,7 +378,6 @@ const termsXml = `<?xml version="1.0" encoding="utf-8"?>
</section>
</section>
</document>
`;
export const WithdrawNewTermsXML = createExample(TestedComponent, {

View File

@ -30,7 +30,7 @@ import { LogoHeader } from '../components/LogoHeader';
import { Part } from '../components/Part';
import { ButtonDestructive, ButtonSuccess, ButtonWarning, LinkSuccess, TermsOfService, WalletAction } from '../components/styled';
import {
acceptWithdrawal, getExchangeWithdrawalInfo, getWithdrawalDetailsForUri, onUpdateNotification
acceptWithdrawal, getExchangeWithdrawalInfo, getWithdrawalDetailsForUri, onUpdateNotification, setExchangeTosAccepted
} from "../wxApi";
import { h } from 'preact';
@ -48,6 +48,7 @@ export interface ViewProps {
onAccept: (b: boolean) => void;
reviewing: boolean;
accepted: boolean;
confirmed: boolean;
terms: {
value?: TermsDocument;
status: TermsStatus;
@ -75,7 +76,7 @@ function amountToString(text: AmountLike) {
return `${amount} ${aj.currency}`
}
export function View({ details, amount, onWithdraw, terms, reviewing, onReview, onAccept, accepted }: ViewProps) {
export function View({ details, amount, onWithdraw, terms, reviewing, onReview, onAccept, accepted, confirmed }: ViewProps) {
const needsReview = terms.status === 'changed' || terms.status === 'new'
return (
@ -172,7 +173,7 @@ export function View({ details, amount, onWithdraw, terms, reviewing, onReview,
<div>
<ButtonSuccess
upperCased
disabled={!details.exchangeInfo.baseUrl}
disabled={!details.exchangeInfo.baseUrl || confirmed}
onClick={onWithdraw}
>
{i18n.str`Confirm withdrawal`}
@ -203,6 +204,7 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element
const [updateCounter, setUpdateCounter] = useState(1);
const [reviewing, setReviewing] = useState<boolean>(false)
const [accepted, setAccepted] = useState<boolean>(false)
const [confirmed, setConfirmed] = useState<boolean>(false)
useEffect(() => {
return onUpdateNotification(() => {
@ -231,7 +233,8 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element
if (!uriInfo || !uriInfo.defaultExchangeBaseUrl) return
const res = await getExchangeWithdrawalInfo({
exchangeBaseUrl: uriInfo.defaultExchangeBaseUrl,
amount: Amounts.parseOrThrow(uriInfo.amount)
amount: Amounts.parseOrThrow(uriInfo.amount),
tosAcceptedFormat: ['text/json', 'text/xml', 'text/pdf']
})
setDetails(res)
}
@ -242,10 +245,19 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element
return <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>;
}
const onAccept = async (): Promise<void> => {
if (!details) {
throw Error("can't accept, no exchange selected");
}
await setExchangeTosAccepted(details.exchangeDetails.exchangeBaseUrl, details.tosRequested?.tosEtag)
setAccepted(true)
}
const onWithdraw = async (): Promise<void> => {
if (!details) {
throw Error("can't accept, no exchange selected");
}
setConfirmed(true)
console.log("accepting exchange", details.exchangeInfo.baseUrl);
const res = await acceptWithdrawal(talerWithdrawUri, details.exchangeInfo.baseUrl);
console.log("accept withdrawal response", res);
@ -267,11 +279,33 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element
return <span><i18n.Translate>Getting withdrawal details.</i18n.Translate></span>;
}
let termsContent: TermsDocument | undefined = undefined;
if (details.tosRequested) {
if (details.tosRequested.tosContentType === 'text/xml') {
try {
const document = new DOMParser().parseFromString(details.tosRequested.tosText, "text/xml")
termsContent = { type: 'xml', document }
} catch (e) {
console.log(e)
debugger;
}
}
}
const status: TermsStatus = !termsContent ? 'notfound' : (
!details.exchangeDetails.termsOfServiceAcceptedEtag ? 'new' : (
details.tosRequested?.tosEtag !== details.exchangeDetails.termsOfServiceAcceptedEtag ? 'changed' : 'accepted'
))
return <View onWithdraw={onWithdraw}
// setCancelled={setCancelled} setSelecting={setSelecting}
details={details} amount={uriInfo.amount}
terms={{} as any}
accepted={accepted} onAccept={setAccepted}
terms={{
status, value: termsContent
}}
confirmed={confirmed}
accepted={accepted} onAccept={onAccept}
reviewing={reviewing} onReview={setReviewing}
// terms={[]}
/>

View File

@ -39,6 +39,7 @@ import {
RetryTransactionRequest,
SetWalletDeviceIdRequest,
GetExchangeWithdrawalInfo,
AcceptExchangeTosRequest,
} from "@gnu-taler/taler-util";
import { AddBackupProviderRequest, BackupProviderState, OperationFailedError, RemoveBackupProviderRequest } from "@gnu-taler/taler-wallet-core";
import { BackupInfo } from "@gnu-taler/taler-wallet-core";
@ -251,6 +252,16 @@ export function acceptWithdrawal(
});
}
export function setExchangeTosAccepted(
exchangeBaseUrl: string,
etag: string | undefined
): Promise<void> {
return callBackend("setExchangeTosAccepted", {
exchangeBaseUrl, etag
} as AcceptExchangeTosRequest)
}
/**
* Get diagnostics information
*/
@ -287,7 +298,7 @@ export function getWithdrawalDetailsForUri(
/**
* Get diagnostics information
*/
export function getExchangeWithdrawalInfo(
export function getExchangeWithdrawalInfo(
req: GetExchangeWithdrawalInfo,
): Promise<ExchangeWithdrawDetails> {
return callBackend("getExchangeWithdrawalInfo", req);