added integration with the wallet-core to get info about the last tos approved
This commit is contained in:
parent
57b6cd4269
commit
9f00987380
@ -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 {
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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,
|
||||
|
@ -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(),
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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, {
|
||||
|
@ -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={[]}
|
||||
/>
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user