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 {
|
export interface GetExchangeWithdrawalInfo {
|
||||||
exchangeBaseUrl: string;
|
exchangeBaseUrl: string;
|
||||||
amount: AmountJson;
|
amount: AmountJson;
|
||||||
|
tosAcceptedFormat?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForGetExchangeWithdrawalInfo = (): Codec<GetExchangeWithdrawalInfo> =>
|
export const codecForGetExchangeWithdrawalInfo = (): Codec<GetExchangeWithdrawalInfo> =>
|
||||||
buildCodecForObject<GetExchangeWithdrawalInfo>()
|
buildCodecForObject<GetExchangeWithdrawalInfo>()
|
||||||
.property("exchangeBaseUrl", codecForString())
|
.property("exchangeBaseUrl", codecForString())
|
||||||
.property("amount", codecForAmountJson())
|
.property("amount", codecForAmountJson())
|
||||||
|
.property("tosAcceptedFormat", codecOptional(codecForList(codecForString())))
|
||||||
.build("GetExchangeWithdrawalInfo");
|
.build("GetExchangeWithdrawalInfo");
|
||||||
|
|
||||||
export interface AbortProposalRequest {
|
export interface AbortProposalRequest {
|
||||||
|
@ -449,6 +449,11 @@ export interface ExchangeDetailsRecord {
|
|||||||
*/
|
*/
|
||||||
termsOfServiceText: string | undefined;
|
termsOfServiceText: string | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* content-type of the last downloaded termsOfServiceText.
|
||||||
|
*/
|
||||||
|
termsOfServiceContentType: string | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ETag for last terms of service download.
|
* ETag for last terms of service download.
|
||||||
*/
|
*/
|
||||||
|
@ -306,6 +306,7 @@ export async function importBackup(
|
|||||||
termsOfServiceAcceptedEtag: backupExchangeDetails.tos_accepted_etag,
|
termsOfServiceAcceptedEtag: backupExchangeDetails.tos_accepted_etag,
|
||||||
termsOfServiceText: undefined,
|
termsOfServiceText: undefined,
|
||||||
termsOfServiceLastEtag: undefined,
|
termsOfServiceLastEtag: undefined,
|
||||||
|
termsOfServiceContentType: undefined,
|
||||||
termsOfServiceAcceptedTimestamp:
|
termsOfServiceAcceptedTimestamp:
|
||||||
backupExchangeDetails.tos_accepted_timestamp,
|
backupExchangeDetails.tos_accepted_timestamp,
|
||||||
wireInfo,
|
wireInfo,
|
||||||
|
@ -127,20 +127,22 @@ function getExchangeRequestTimeout(e: ExchangeRecord): Duration {
|
|||||||
return { d_ms: 5000 };
|
return { d_ms: 5000 };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExchangeTosDownloadResult {
|
export interface ExchangeTosDownloadResult {
|
||||||
tosText: string;
|
tosText: string;
|
||||||
tosEtag: string;
|
tosEtag: string;
|
||||||
|
tosContentType: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadExchangeWithTermsOfService(
|
export async function downloadExchangeWithTermsOfService(
|
||||||
exchangeBaseUrl: string,
|
exchangeBaseUrl: string,
|
||||||
http: HttpRequestLibrary,
|
http: HttpRequestLibrary,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
|
contentType: string,
|
||||||
): Promise<ExchangeTosDownloadResult> {
|
): Promise<ExchangeTosDownloadResult> {
|
||||||
const reqUrl = new URL("terms", exchangeBaseUrl);
|
const reqUrl = new URL("terms", exchangeBaseUrl);
|
||||||
reqUrl.searchParams.set("cacheBreaker", WALLET_CACHE_BREAKER_CLIENT_VERSION);
|
reqUrl.searchParams.set("cacheBreaker", WALLET_CACHE_BREAKER_CLIENT_VERSION);
|
||||||
const headers = {
|
const headers = {
|
||||||
Accept: "text/plain",
|
Accept: contentType,
|
||||||
};
|
};
|
||||||
|
|
||||||
const resp = await http.get(reqUrl.href, {
|
const resp = await http.get(reqUrl.href, {
|
||||||
@ -149,8 +151,9 @@ async function downloadExchangeWithTermsOfService(
|
|||||||
});
|
});
|
||||||
const tosText = await readSuccessResponseTextOrThrow(resp);
|
const tosText = await readSuccessResponseTextOrThrow(resp);
|
||||||
const tosEtag = resp.headers.get("etag") || "unknown";
|
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,
|
baseUrl,
|
||||||
ws.http,
|
ws.http,
|
||||||
timeout,
|
timeout,
|
||||||
|
"text/plain"
|
||||||
);
|
);
|
||||||
|
|
||||||
let recoupGroupId: string | undefined = undefined;
|
let recoupGroupId: string | undefined = undefined;
|
||||||
@ -506,6 +510,7 @@ async function updateExchangeFromUrlImpl(
|
|||||||
wireInfo,
|
wireInfo,
|
||||||
termsOfServiceText: tosDownload.tosText,
|
termsOfServiceText: tosDownload.tosText,
|
||||||
termsOfServiceAcceptedEtag: undefined,
|
termsOfServiceAcceptedEtag: undefined,
|
||||||
|
termsOfServiceContentType: tosDownload.tosContentType,
|
||||||
termsOfServiceLastEtag: tosDownload.tosEtag,
|
termsOfServiceLastEtag: tosDownload.tosEtag,
|
||||||
termsOfServiceAcceptedTimestamp: getTimestampNow(),
|
termsOfServiceAcceptedTimestamp: getTimestampNow(),
|
||||||
};
|
};
|
||||||
|
@ -66,6 +66,8 @@ 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.
|
||||||
@ -131,6 +133,11 @@ export interface ExchangeWithdrawDetails {
|
|||||||
*/
|
*/
|
||||||
termsOfServiceAccepted: boolean;
|
termsOfServiceAccepted: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tos
|
||||||
|
*/
|
||||||
|
tosRequested: ExchangeTosDownloadResult | undefined
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The exchange is trusted directly.
|
* The exchange is trusted directly.
|
||||||
*/
|
*/
|
||||||
@ -930,6 +937,7 @@ 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,
|
||||||
@ -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(
|
const withdrawFee = Amounts.sub(
|
||||||
selectedDenoms.totalWithdrawCost,
|
selectedDenoms.totalWithdrawCost,
|
||||||
selectedDenoms.totalCoinValue,
|
selectedDenoms.totalCoinValue,
|
||||||
@ -1018,6 +1054,7 @@ 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;
|
||||||
}
|
}
|
||||||
|
@ -696,7 +696,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);
|
return await getExchangeWithdrawalInfo(ws, req.exchangeBaseUrl, req.amount, req.tosAcceptedFormat);
|
||||||
}
|
}
|
||||||
case "acceptManualWithdrawal": {
|
case "acceptManualWithdrawal": {
|
||||||
const req = codecForAcceptManualWithdrawalRequet().decode(payload);
|
const req = codecForAcceptManualWithdrawalRequet().decode(payload);
|
||||||
|
@ -35,6 +35,8 @@ export default {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const termsXml = `<?xml version="1.0" encoding="utf-8"?>
|
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">
|
<document source="/home/grothoff/research/taler/exchange/contrib/tos/tos.rst">
|
||||||
<section ids="terms-of-service" names="terms\ of\ service">
|
<section ids="terms-of-service" names="terms\ of\ service">
|
||||||
<title>Terms Of Service</title>
|
<title>Terms Of Service</title>
|
||||||
@ -234,7 +236,6 @@ const termsXml = `<?xml version="1.0" encoding="utf-8"?>
|
|||||||
<enumerated_list enumtype="loweralpha" prefix="(" suffix=")">
|
<enumerated_list enumtype="loweralpha" prefix="(" suffix=")">
|
||||||
<list_item>
|
<list_item>
|
||||||
<paragraph>any lost profits, data loss, cost of procurement of substitute goods or
|
<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,
|
services, or direct, indirect, incidental, special, punitive, compensatory,
|
||||||
or consequential damages of any kind whatsoever resulting from:</paragraph>
|
or consequential damages of any kind whatsoever resulting from:</paragraph>
|
||||||
</list_item>
|
</list_item>
|
||||||
@ -377,7 +378,6 @@ const termsXml = `<?xml version="1.0" encoding="utf-8"?>
|
|||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</document>
|
</document>
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const WithdrawNewTermsXML = createExample(TestedComponent, {
|
export const WithdrawNewTermsXML = createExample(TestedComponent, {
|
||||||
|
@ -30,7 +30,7 @@ import { LogoHeader } from '../components/LogoHeader';
|
|||||||
import { Part } from '../components/Part';
|
import { Part } from '../components/Part';
|
||||||
import { ButtonDestructive, ButtonSuccess, ButtonWarning, LinkSuccess, TermsOfService, WalletAction } from '../components/styled';
|
import { ButtonDestructive, ButtonSuccess, ButtonWarning, LinkSuccess, TermsOfService, WalletAction } from '../components/styled';
|
||||||
import {
|
import {
|
||||||
acceptWithdrawal, getExchangeWithdrawalInfo, getWithdrawalDetailsForUri, onUpdateNotification
|
acceptWithdrawal, getExchangeWithdrawalInfo, getWithdrawalDetailsForUri, onUpdateNotification, setExchangeTosAccepted
|
||||||
} from "../wxApi";
|
} from "../wxApi";
|
||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
|
|
||||||
@ -48,6 +48,7 @@ export interface ViewProps {
|
|||||||
onAccept: (b: boolean) => void;
|
onAccept: (b: boolean) => void;
|
||||||
reviewing: boolean;
|
reviewing: boolean;
|
||||||
accepted: boolean;
|
accepted: boolean;
|
||||||
|
confirmed: boolean;
|
||||||
terms: {
|
terms: {
|
||||||
value?: TermsDocument;
|
value?: TermsDocument;
|
||||||
status: TermsStatus;
|
status: TermsStatus;
|
||||||
@ -75,7 +76,7 @@ function amountToString(text: AmountLike) {
|
|||||||
return `${amount} ${aj.currency}`
|
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'
|
const needsReview = terms.status === 'changed' || terms.status === 'new'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -172,7 +173,7 @@ export function View({ details, amount, onWithdraw, terms, reviewing, onReview,
|
|||||||
<div>
|
<div>
|
||||||
<ButtonSuccess
|
<ButtonSuccess
|
||||||
upperCased
|
upperCased
|
||||||
disabled={!details.exchangeInfo.baseUrl}
|
disabled={!details.exchangeInfo.baseUrl || confirmed}
|
||||||
onClick={onWithdraw}
|
onClick={onWithdraw}
|
||||||
>
|
>
|
||||||
{i18n.str`Confirm withdrawal`}
|
{i18n.str`Confirm withdrawal`}
|
||||||
@ -203,6 +204,7 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element
|
|||||||
const [updateCounter, setUpdateCounter] = useState(1);
|
const [updateCounter, setUpdateCounter] = useState(1);
|
||||||
const [reviewing, setReviewing] = useState<boolean>(false)
|
const [reviewing, setReviewing] = useState<boolean>(false)
|
||||||
const [accepted, setAccepted] = useState<boolean>(false)
|
const [accepted, setAccepted] = useState<boolean>(false)
|
||||||
|
const [confirmed, setConfirmed] = useState<boolean>(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return onUpdateNotification(() => {
|
return onUpdateNotification(() => {
|
||||||
@ -231,7 +233,8 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element
|
|||||||
if (!uriInfo || !uriInfo.defaultExchangeBaseUrl) return
|
if (!uriInfo || !uriInfo.defaultExchangeBaseUrl) return
|
||||||
const res = await getExchangeWithdrawalInfo({
|
const res = await getExchangeWithdrawalInfo({
|
||||||
exchangeBaseUrl: uriInfo.defaultExchangeBaseUrl,
|
exchangeBaseUrl: uriInfo.defaultExchangeBaseUrl,
|
||||||
amount: Amounts.parseOrThrow(uriInfo.amount)
|
amount: Amounts.parseOrThrow(uriInfo.amount),
|
||||||
|
tosAcceptedFormat: ['text/json', 'text/xml', 'text/pdf']
|
||||||
})
|
})
|
||||||
setDetails(res)
|
setDetails(res)
|
||||||
}
|
}
|
||||||
@ -242,10 +245,19 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element
|
|||||||
return <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>;
|
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> => {
|
const onWithdraw = async (): Promise<void> => {
|
||||||
if (!details) {
|
if (!details) {
|
||||||
throw Error("can't accept, no exchange selected");
|
throw Error("can't accept, no exchange selected");
|
||||||
}
|
}
|
||||||
|
setConfirmed(true)
|
||||||
console.log("accepting exchange", details.exchangeInfo.baseUrl);
|
console.log("accepting exchange", details.exchangeInfo.baseUrl);
|
||||||
const res = await acceptWithdrawal(talerWithdrawUri, details.exchangeInfo.baseUrl);
|
const res = await acceptWithdrawal(talerWithdrawUri, details.exchangeInfo.baseUrl);
|
||||||
console.log("accept withdrawal response", res);
|
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>;
|
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}
|
return <View onWithdraw={onWithdraw}
|
||||||
// setCancelled={setCancelled} setSelecting={setSelecting}
|
// setCancelled={setCancelled} setSelecting={setSelecting}
|
||||||
details={details} amount={uriInfo.amount}
|
details={details} amount={uriInfo.amount}
|
||||||
terms={{} as any}
|
terms={{
|
||||||
accepted={accepted} onAccept={setAccepted}
|
status, value: termsContent
|
||||||
|
}}
|
||||||
|
confirmed={confirmed}
|
||||||
|
accepted={accepted} onAccept={onAccept}
|
||||||
reviewing={reviewing} onReview={setReviewing}
|
reviewing={reviewing} onReview={setReviewing}
|
||||||
// terms={[]}
|
// terms={[]}
|
||||||
/>
|
/>
|
||||||
|
@ -39,6 +39,7 @@ import {
|
|||||||
RetryTransactionRequest,
|
RetryTransactionRequest,
|
||||||
SetWalletDeviceIdRequest,
|
SetWalletDeviceIdRequest,
|
||||||
GetExchangeWithdrawalInfo,
|
GetExchangeWithdrawalInfo,
|
||||||
|
AcceptExchangeTosRequest,
|
||||||
} 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";
|
||||||
@ -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
|
* Get diagnostics information
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user