issue #5860
This commit is contained in:
parent
490620ad04
commit
315b167bee
@ -22,6 +22,7 @@ export interface PayUriResult {
|
|||||||
orderId: string;
|
orderId: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
claimToken: string | undefined;
|
claimToken: string | undefined;
|
||||||
|
noncePriv: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WithdrawUriResult {
|
export interface WithdrawUriResult {
|
||||||
@ -147,6 +148,7 @@ export function parsePayUri(s: string): PayUriResult | undefined {
|
|||||||
const c = pi?.rest.split("?");
|
const c = pi?.rest.split("?");
|
||||||
const q = new URLSearchParams(c[1] ?? "");
|
const q = new URLSearchParams(c[1] ?? "");
|
||||||
const claimToken = q.get("c") ?? undefined;
|
const claimToken = q.get("c") ?? undefined;
|
||||||
|
const noncePriv = q.get("n") ?? undefined;
|
||||||
const parts = c[0].split("/");
|
const parts = c[0].split("/");
|
||||||
if (parts.length < 3) {
|
if (parts.length < 3) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -163,6 +165,7 @@ export function parsePayUri(s: string): PayUriResult | undefined {
|
|||||||
orderId,
|
orderId,
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
claimToken,
|
claimToken,
|
||||||
|
noncePriv,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,6 +325,7 @@ export const codecForPreparePayResultPaymentPossible = (): Codec<PreparePayResul
|
|||||||
.property("contractTerms", codecForContractTerms())
|
.property("contractTerms", codecForContractTerms())
|
||||||
.property("proposalId", codecForString())
|
.property("proposalId", codecForString())
|
||||||
.property("contractTermsHash", codecForString())
|
.property("contractTermsHash", codecForString())
|
||||||
|
.property("noncePriv", codecForString())
|
||||||
.property(
|
.property(
|
||||||
"status",
|
"status",
|
||||||
codecForConstString(PreparePayResultType.PaymentPossible),
|
codecForConstString(PreparePayResultType.PaymentPossible),
|
||||||
@ -336,6 +337,7 @@ export const codecForPreparePayResultInsufficientBalance = (): Codec<PreparePayR
|
|||||||
.property("amountRaw", codecForAmountString())
|
.property("amountRaw", codecForAmountString())
|
||||||
.property("contractTerms", codecForAny())
|
.property("contractTerms", codecForAny())
|
||||||
.property("proposalId", codecForString())
|
.property("proposalId", codecForString())
|
||||||
|
.property("noncePriv", codecForString())
|
||||||
.property(
|
.property(
|
||||||
"status",
|
"status",
|
||||||
codecForConstString(PreparePayResultType.InsufficientBalance),
|
codecForConstString(PreparePayResultType.InsufficientBalance),
|
||||||
@ -354,6 +356,7 @@ export const codecForPreparePayResultAlreadyConfirmed = (): Codec<PreparePayResu
|
|||||||
.property("contractTerms", codecForAny())
|
.property("contractTerms", codecForAny())
|
||||||
.property("contractTermsHash", codecForString())
|
.property("contractTermsHash", codecForString())
|
||||||
.property("proposalId", codecForString())
|
.property("proposalId", codecForString())
|
||||||
|
.property("noncePriv", codecForString())
|
||||||
.build("PreparePayResultAlreadyConfirmed");
|
.build("PreparePayResultAlreadyConfirmed");
|
||||||
|
|
||||||
export const codecForPreparePayResult = (): Codec<PreparePayResult> =>
|
export const codecForPreparePayResult = (): Codec<PreparePayResult> =>
|
||||||
@ -385,6 +388,7 @@ export interface PreparePayResultPaymentPossible {
|
|||||||
contractTermsHash: string;
|
contractTermsHash: string;
|
||||||
amountRaw: string;
|
amountRaw: string;
|
||||||
amountEffective: string;
|
amountEffective: string;
|
||||||
|
noncePriv: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PreparePayResultInsufficientBalance {
|
export interface PreparePayResultInsufficientBalance {
|
||||||
@ -392,6 +396,7 @@ export interface PreparePayResultInsufficientBalance {
|
|||||||
proposalId: string;
|
proposalId: string;
|
||||||
contractTerms: ContractTerms;
|
contractTerms: ContractTerms;
|
||||||
amountRaw: string;
|
amountRaw: string;
|
||||||
|
noncePriv: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PreparePayResultAlreadyConfirmed {
|
export interface PreparePayResultAlreadyConfirmed {
|
||||||
@ -402,6 +407,7 @@ export interface PreparePayResultAlreadyConfirmed {
|
|||||||
amountEffective: string;
|
amountEffective: string;
|
||||||
contractTermsHash: string;
|
contractTermsHash: string;
|
||||||
proposalId: string;
|
proposalId: string;
|
||||||
|
noncePriv: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BankWithdrawDetails {
|
export interface BankWithdrawDetails {
|
||||||
|
@ -213,7 +213,7 @@ export class CryptoApi {
|
|||||||
ws.w = null;
|
ws.w = null;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(e);
|
logger.error(e as string);
|
||||||
}
|
}
|
||||||
if (ws.currentWorkItem !== null) {
|
if (ws.currentWorkItem !== null) {
|
||||||
ws.currentWorkItem.reject(e);
|
ws.currentWorkItem.reject(e);
|
||||||
@ -379,6 +379,10 @@ export class CryptoApi {
|
|||||||
return this.doRpc<{ priv: string; pub: string }>("createEddsaKeypair", 1);
|
return this.doRpc<{ priv: string; pub: string }>("createEddsaKeypair", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eddsaGetPublic(key: string): Promise<{ priv: string; pub: string }> {
|
||||||
|
return this.doRpc<{ priv: string; pub: string }>("eddsaGetPublic", 1, key);
|
||||||
|
}
|
||||||
|
|
||||||
rsaUnblind(sig: string, bk: string, pk: string): Promise<string> {
|
rsaUnblind(sig: string, bk: string, pk: string): Promise<string> {
|
||||||
return this.doRpc<string>("rsaUnblind", 4, sig, bk, pk);
|
return this.doRpc<string>("rsaUnblind", 4, sig, bk, pk);
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,7 @@ import {
|
|||||||
setupRefreshTransferPub,
|
setupRefreshTransferPub,
|
||||||
setupTipPlanchet,
|
setupTipPlanchet,
|
||||||
setupWithdrawPlanchet,
|
setupWithdrawPlanchet,
|
||||||
|
eddsaGetPublic,
|
||||||
} from "../talerCrypto.js";
|
} from "../talerCrypto.js";
|
||||||
import { randomBytes } from "../primitives/nacl-fast.js";
|
import { randomBytes } from "../primitives/nacl-fast.js";
|
||||||
import { kdf } from "../primitives/kdf.js";
|
import { kdf } from "../primitives/kdf.js";
|
||||||
@ -141,7 +142,7 @@ function timestampRoundedToBuffer(ts: Timestamp): Uint8Array {
|
|||||||
class SignaturePurposeBuilder {
|
class SignaturePurposeBuilder {
|
||||||
private chunks: Uint8Array[] = [];
|
private chunks: Uint8Array[] = [];
|
||||||
|
|
||||||
constructor(private purposeNum: number) {}
|
constructor(private purposeNum: number) { }
|
||||||
|
|
||||||
put(bytes: Uint8Array): SignaturePurposeBuilder {
|
put(bytes: Uint8Array): SignaturePurposeBuilder {
|
||||||
this.chunks.push(Uint8Array.from(bytes));
|
this.chunks.push(Uint8Array.from(bytes));
|
||||||
@ -170,7 +171,6 @@ class SignaturePurposeBuilder {
|
|||||||
function buildSigPS(purposeNum: number): SignaturePurposeBuilder {
|
function buildSigPS(purposeNum: number): SignaturePurposeBuilder {
|
||||||
return new SignaturePurposeBuilder(purposeNum);
|
return new SignaturePurposeBuilder(purposeNum);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CryptoImplementation {
|
export class CryptoImplementation {
|
||||||
static enableTracing = false;
|
static enableTracing = false;
|
||||||
|
|
||||||
@ -361,6 +361,13 @@ export class CryptoImplementation {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eddsaGetPublic(key: string): { priv: string; pub: string } {
|
||||||
|
return {
|
||||||
|
priv: key,
|
||||||
|
pub: encodeCrock(eddsaGetPublic(decodeCrock(key)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unblind a blindly signed value.
|
* Unblind a blindly signed value.
|
||||||
*/
|
*/
|
||||||
|
@ -875,7 +875,9 @@ async function startDownloadProposal(
|
|||||||
orderId: string,
|
orderId: string,
|
||||||
sessionId: string | undefined,
|
sessionId: string | undefined,
|
||||||
claimToken: string | undefined,
|
claimToken: string | undefined,
|
||||||
|
noncePriv: string | undefined,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
|
|
||||||
const oldProposal = await ws.db
|
const oldProposal = await ws.db
|
||||||
.mktx((x) => ({ proposals: x.proposals }))
|
.mktx((x) => ({ proposals: x.proposals }))
|
||||||
.runReadOnly(async (tx) => {
|
.runReadOnly(async (tx) => {
|
||||||
@ -884,12 +886,20 @@ async function startDownloadProposal(
|
|||||||
orderId,
|
orderId,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
if (oldProposal) {
|
|
||||||
|
/**
|
||||||
|
* If we have already claimed this proposal with the same sessionId
|
||||||
|
* nonce and claim token, reuse it.
|
||||||
|
*/
|
||||||
|
if (oldProposal &&
|
||||||
|
oldProposal.downloadSessionId === sessionId &&
|
||||||
|
oldProposal.noncePriv === noncePriv &&
|
||||||
|
oldProposal.claimToken === claimToken) {
|
||||||
await processDownloadProposal(ws, oldProposal.proposalId);
|
await processDownloadProposal(ws, oldProposal.proposalId);
|
||||||
return oldProposal.proposalId;
|
return oldProposal.proposalId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { priv, pub } = await ws.cryptoApi.createEddsaKeypair();
|
const { priv, pub } = await (noncePriv ? ws.cryptoApi.eddsaGetPublic(noncePriv) : ws.cryptoApi.createEddsaKeypair());
|
||||||
const proposalId = encodeCrock(getRandomBytes(32));
|
const proposalId = encodeCrock(getRandomBytes(32));
|
||||||
|
|
||||||
const proposalRecord: ProposalRecord = {
|
const proposalRecord: ProposalRecord = {
|
||||||
@ -1405,6 +1415,7 @@ export async function checkPaymentByProposalId(
|
|||||||
status: PreparePayResultType.InsufficientBalance,
|
status: PreparePayResultType.InsufficientBalance,
|
||||||
contractTerms: d.contractTermsRaw,
|
contractTerms: d.contractTermsRaw,
|
||||||
proposalId: proposal.proposalId,
|
proposalId: proposal.proposalId,
|
||||||
|
noncePriv: proposal.noncePriv,
|
||||||
amountRaw: Amounts.stringify(d.contractData.amount),
|
amountRaw: Amounts.stringify(d.contractData.amount),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1417,6 +1428,7 @@ export async function checkPaymentByProposalId(
|
|||||||
status: PreparePayResultType.PaymentPossible,
|
status: PreparePayResultType.PaymentPossible,
|
||||||
contractTerms: d.contractTermsRaw,
|
contractTerms: d.contractTermsRaw,
|
||||||
proposalId: proposal.proposalId,
|
proposalId: proposal.proposalId,
|
||||||
|
noncePriv: proposal.noncePriv,
|
||||||
amountEffective: Amounts.stringify(totalCost),
|
amountEffective: Amounts.stringify(totalCost),
|
||||||
amountRaw: Amounts.stringify(res.paymentAmount),
|
amountRaw: Amounts.stringify(res.paymentAmount),
|
||||||
contractTermsHash: d.contractData.contractTermsHash,
|
contractTermsHash: d.contractData.contractTermsHash,
|
||||||
@ -1453,6 +1465,7 @@ export async function checkPaymentByProposalId(
|
|||||||
amountRaw: Amounts.stringify(purchase.download.contractData.amount),
|
amountRaw: Amounts.stringify(purchase.download.contractData.amount),
|
||||||
amountEffective: Amounts.stringify(purchase.totalPayCost),
|
amountEffective: Amounts.stringify(purchase.totalPayCost),
|
||||||
proposalId,
|
proposalId,
|
||||||
|
noncePriv: proposal.noncePriv,
|
||||||
};
|
};
|
||||||
} else if (!purchase.timestampFirstSuccessfulPay) {
|
} else if (!purchase.timestampFirstSuccessfulPay) {
|
||||||
return {
|
return {
|
||||||
@ -1463,6 +1476,7 @@ export async function checkPaymentByProposalId(
|
|||||||
amountRaw: Amounts.stringify(purchase.download.contractData.amount),
|
amountRaw: Amounts.stringify(purchase.download.contractData.amount),
|
||||||
amountEffective: Amounts.stringify(purchase.totalPayCost),
|
amountEffective: Amounts.stringify(purchase.totalPayCost),
|
||||||
proposalId,
|
proposalId,
|
||||||
|
noncePriv: proposal.noncePriv,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const paid = !purchase.paymentSubmitPending;
|
const paid = !purchase.paymentSubmitPending;
|
||||||
@ -1475,6 +1489,7 @@ export async function checkPaymentByProposalId(
|
|||||||
amountEffective: Amounts.stringify(purchase.totalPayCost),
|
amountEffective: Amounts.stringify(purchase.totalPayCost),
|
||||||
...(paid ? { nextUrl: purchase.download.contractData.orderId } : {}),
|
...(paid ? { nextUrl: purchase.download.contractData.orderId } : {}),
|
||||||
proposalId,
|
proposalId,
|
||||||
|
noncePriv: proposal.noncePriv,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1507,6 +1522,7 @@ export async function preparePayForUri(
|
|||||||
uriResult.orderId,
|
uriResult.orderId,
|
||||||
uriResult.sessionId,
|
uriResult.sessionId,
|
||||||
uriResult.claimToken,
|
uriResult.claimToken,
|
||||||
|
uriResult.noncePriv,
|
||||||
);
|
);
|
||||||
|
|
||||||
return checkPaymentByProposalId(ws, proposalId, uriResult.sessionId);
|
return checkPaymentByProposalId(ws, proposalId, uriResult.sessionId);
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
"history": "4.10.1",
|
"history": "4.10.1",
|
||||||
"preact": "^10.5.13",
|
"preact": "^10.5.13",
|
||||||
"preact-router": "^3.2.1",
|
"preact-router": "^3.2.1",
|
||||||
|
"qrcode-generator": "^1.4.4",
|
||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
37
packages/taler-wallet-webextension/src/components/QR.tsx
Normal file
37
packages/taler-wallet-webextension/src/components/QR.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Taler
|
||||||
|
(C) 2021 Taler Systems S.A.
|
||||||
|
|
||||||
|
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { useEffect, useRef } from "preact/hooks";
|
||||||
|
import qrcode from "qrcode-generator";
|
||||||
|
|
||||||
|
export function QR({ text }: { text: string; }):VNode {
|
||||||
|
const divRef = useRef<HTMLDivElement>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!divRef.current) return
|
||||||
|
const qr = qrcode(0, 'L');
|
||||||
|
qr.addData(text);
|
||||||
|
qr.make();
|
||||||
|
divRef.current.innerHTML = qr.createSvgTag({
|
||||||
|
scalable: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div style={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||||
|
<div style={{ width: '50%', minWidth: 200, maxWidth: 300 }} ref={divRef} />
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
@ -29,10 +29,11 @@ export const PaymentStatus = styled.div<{ color: string }>`
|
|||||||
|
|
||||||
export const WalletAction = styled.section`
|
export const WalletAction = styled.section`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
text-align: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
max-width: 50%;
|
/* max-width: 50%; */
|
||||||
|
|
||||||
margin: auto;
|
margin: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -42,6 +43,10 @@ export const WalletAction = styled.section`
|
|||||||
}
|
}
|
||||||
section {
|
section {
|
||||||
margin-bottom: 2em;
|
margin-bottom: 2em;
|
||||||
|
& button {
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
export const WalletActionOld = styled.section`
|
export const WalletActionOld = styled.section`
|
||||||
@ -628,6 +633,7 @@ export const TermsOfService = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
max-width: 500px;
|
||||||
|
|
||||||
& > header {
|
& > header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -33,6 +33,7 @@ export default {
|
|||||||
export const InsufficientBalance = createExample(TestedComponent, {
|
export const InsufficientBalance = createExample(TestedComponent, {
|
||||||
payStatus: {
|
payStatus: {
|
||||||
status: PreparePayResultType.InsufficientBalance,
|
status: PreparePayResultType.InsufficientBalance,
|
||||||
|
noncePriv: '',
|
||||||
proposalId: "proposal1234",
|
proposalId: "proposal1234",
|
||||||
contractTerms: {
|
contractTerms: {
|
||||||
merchant: {
|
merchant: {
|
||||||
@ -45,15 +46,19 @@ export const InsufficientBalance = createExample(TestedComponent, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const PaymentPossible = createExample(TestedComponent, {
|
export const PaymentPossible = createExample(TestedComponent, {
|
||||||
|
uri: 'taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0',
|
||||||
payStatus: {
|
payStatus: {
|
||||||
status: PreparePayResultType.PaymentPossible,
|
status: PreparePayResultType.PaymentPossible,
|
||||||
amountEffective: 'USD:10',
|
amountEffective: 'USD:10',
|
||||||
amountRaw: 'USD:10',
|
amountRaw: 'USD:10',
|
||||||
|
noncePriv: '',
|
||||||
contractTerms: {
|
contractTerms: {
|
||||||
|
nonce: '123213123',
|
||||||
merchant: {
|
merchant: {
|
||||||
name: 'someone'
|
name: 'someone'
|
||||||
},
|
},
|
||||||
amount: 'USD:10',
|
amount: 'USD:10',
|
||||||
|
summary: 'some beers',
|
||||||
} as Partial<ContractTerms> as any,
|
} as Partial<ContractTerms> as any,
|
||||||
contractTermsHash: '123456',
|
contractTermsHash: '123456',
|
||||||
proposalId: 'proposal1234'
|
proposalId: 'proposal1234'
|
||||||
@ -65,6 +70,7 @@ export const AlreadyConfirmedWithFullfilment = createExample(TestedComponent, {
|
|||||||
status: PreparePayResultType.AlreadyConfirmed,
|
status: PreparePayResultType.AlreadyConfirmed,
|
||||||
amountEffective: 'USD:10',
|
amountEffective: 'USD:10',
|
||||||
amountRaw: 'USD:10',
|
amountRaw: 'USD:10',
|
||||||
|
noncePriv: '',
|
||||||
contractTerms: {
|
contractTerms: {
|
||||||
merchant: {
|
merchant: {
|
||||||
name: 'someone'
|
name: 'someone'
|
||||||
@ -82,6 +88,7 @@ export const AlreadyConfirmedWithoutFullfilment = createExample(TestedComponent,
|
|||||||
payStatus: {
|
payStatus: {
|
||||||
status: PreparePayResultType.AlreadyConfirmed,
|
status: PreparePayResultType.AlreadyConfirmed,
|
||||||
amountEffective: 'USD:10',
|
amountEffective: 'USD:10',
|
||||||
|
noncePriv: '',
|
||||||
amountRaw: 'USD:10',
|
amountRaw: 'USD:10',
|
||||||
contractTerms: {
|
contractTerms: {
|
||||||
merchant: {
|
merchant: {
|
||||||
|
@ -29,7 +29,7 @@ import * as wxApi from "../wxApi";
|
|||||||
|
|
||||||
import { useState, useEffect } from "preact/hooks";
|
import { useState, useEffect } from "preact/hooks";
|
||||||
|
|
||||||
import { ConfirmPayResultDone, getJsonI18n, i18n } from "@gnu-taler/taler-util";
|
import { AmountLike, ConfirmPayResultDone, getJsonI18n, i18n } from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
PreparePayResult,
|
PreparePayResult,
|
||||||
ConfirmPayResult,
|
ConfirmPayResult,
|
||||||
@ -39,7 +39,11 @@ import {
|
|||||||
ContractTerms,
|
ContractTerms,
|
||||||
ConfirmPayResultType,
|
ConfirmPayResultType,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { JSX, VNode, h } from "preact";
|
import { JSX, VNode, h, Fragment } from "preact";
|
||||||
|
import { ButtonSuccess, LinkSuccess, WalletAction } from "../components/styled";
|
||||||
|
import { LogoHeader } from "../components/LogoHeader";
|
||||||
|
import { Part } from "../components/Part";
|
||||||
|
import { QR } from "../components/QR";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
talerPayUri?: string
|
talerPayUri?: string
|
||||||
@ -143,17 +147,17 @@ export function PayPage({ talerPayUri }: Props): JSX.Element {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return <PaymentRequestView payStatus={payStatus} onClick={onClick} payErrMsg={payErrMsg} />;
|
return <PaymentRequestView uri={talerPayUri} payStatus={payStatus} onClick={onClick} payErrMsg={payErrMsg} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaymentRequestViewProps {
|
export interface PaymentRequestViewProps {
|
||||||
payStatus: PreparePayResult;
|
payStatus: PreparePayResult;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
payErrMsg?: string;
|
payErrMsg?: string;
|
||||||
|
uri: string;
|
||||||
}
|
}
|
||||||
export function PaymentRequestView({ payStatus, onClick, payErrMsg }: PaymentRequestViewProps) {
|
export function PaymentRequestView({ uri, payStatus, onClick, payErrMsg }: PaymentRequestViewProps) {
|
||||||
let totalFees: AmountJson | undefined = undefined;
|
let totalFees: AmountJson = Amounts.getZero(payStatus.amountRaw);
|
||||||
let insufficientBalance = false;
|
let insufficientBalance = false;
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const contractTerms: ContractTerms = payStatus.contractTerms;
|
const contractTerms: ContractTerms = payStatus.contractTerms;
|
||||||
@ -174,6 +178,7 @@ export function PaymentRequestView({ payStatus, onClick, payErrMsg }: PaymentReq
|
|||||||
|
|
||||||
if (payStatus.status == PreparePayResultType.InsufficientBalance) {
|
if (payStatus.status == PreparePayResultType.InsufficientBalance) {
|
||||||
insufficientBalance = true;
|
insufficientBalance = true;
|
||||||
|
return <div>no te alcanza</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payStatus.status === PreparePayResultType.PaymentPossible) {
|
if (payStatus.status === PreparePayResultType.PaymentPossible) {
|
||||||
@ -191,65 +196,62 @@ export function PaymentRequestView({ payStatus, onClick, payErrMsg }: PaymentReq
|
|||||||
merchantName = <strong>(pub: {contractTerms.merchant_pub})</strong>;
|
merchantName = <strong>(pub: {contractTerms.merchant_pub})</strong>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const amount = (
|
const [showQR, setShowQR] = useState<boolean>(false)
|
||||||
<strong>{renderAmount(Amounts.parseOrThrow(contractTerms.amount))}</strong>
|
const privateUri = `${uri}&n=${payStatus.noncePriv}`
|
||||||
);
|
return <WalletAction>
|
||||||
|
<LogoHeader />
|
||||||
|
<h2>
|
||||||
|
{i18n.str`Digital cash payment`}
|
||||||
|
</h2>
|
||||||
|
<section>
|
||||||
|
<Part big title="Total paid" text={amountToString(payStatus.amountEffective)} kind='negative' />
|
||||||
|
<Part big title="Purchase amount" text={amountToString(payStatus.amountRaw)} kind='neutral' />
|
||||||
|
{Amounts.isNonZero(totalFees) && <Part big title="Fee" text={amountToString(totalFees)} kind='negative' />}
|
||||||
|
<Part title="Merchant" text={contractTerms.merchant.name} kind='neutral' />
|
||||||
|
<Part title="Purchase" text={contractTerms.summary} kind='neutral' />
|
||||||
|
{contractTerms.order_id && <Part title="Receipt" text={`#${contractTerms.order_id}`} kind='neutral' />}
|
||||||
|
</section>
|
||||||
|
{showQR && <section>
|
||||||
|
<QR text={privateUri} />
|
||||||
|
<a href={privateUri}>or click here to pay with a installed wallet</a>
|
||||||
|
</section>}
|
||||||
|
<section>
|
||||||
|
{payErrMsg ? (
|
||||||
|
<div>
|
||||||
|
<p>Payment failed: {payErrMsg}</p>
|
||||||
|
<button
|
||||||
|
class="pure-button button-success"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{i18n.str`Retry`}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Fragment>
|
||||||
|
|
||||||
return <section class="main">
|
<LinkSuccess
|
||||||
<h1>GNU Taler Wallet</h1>
|
upperCased
|
||||||
<article class="fade">
|
// disabled={!details.exchangeInfo.baseUrl}
|
||||||
<div>
|
onClick={() => setShowQR(qr => !qr)}
|
||||||
<p>
|
>
|
||||||
<i18n.Translate>
|
{!showQR ? i18n.str`Complete with mobile wallet` : i18n.str`Hide QR`}
|
||||||
The merchant <span>{merchantName}</span> offers you to purchase:
|
</LinkSuccess>
|
||||||
</i18n.Translate>
|
<ButtonSuccess
|
||||||
<div style={{ textAlign: "center" }}>
|
upperCased
|
||||||
<strong>{contractTerms.summary}</strong>
|
// disabled={!details.exchangeInfo.baseUrl}
|
||||||
</div>
|
// onClick={() => onReview(true)}
|
||||||
{totalFees ? (
|
>
|
||||||
<i18n.Translate>
|
{i18n.str`Confirm payment`}
|
||||||
The total price is <span>{amount} </span>
|
</ButtonSuccess>
|
||||||
(plus <span>{renderAmount(totalFees)}</span> fees).
|
</Fragment>
|
||||||
</i18n.Translate>
|
)}
|
||||||
) : (
|
|
||||||
<i18n.Translate>
|
|
||||||
The total price is <span>{amount}</span>.
|
|
||||||
</i18n.Translate>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{insufficientBalance ? (
|
</section>
|
||||||
<div>
|
</WalletAction>
|
||||||
<p style={{ color: "red", fontWeight: "bold" }}>
|
}
|
||||||
Unable to pay: Your balance is insufficient.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{payErrMsg ? (
|
function amountToString(text: AmountLike) {
|
||||||
<div>
|
const aj = Amounts.jsonifyAmount(text)
|
||||||
<p>Payment failed: {payErrMsg}</p>
|
const amount = Amounts.stringifyValue(aj)
|
||||||
<button
|
return `${amount} ${aj.currency}`
|
||||||
class="pure-button button-success"
|
}
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
{i18n.str`Retry`}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
<ProgressButton
|
|
||||||
isLoading={loading}
|
|
||||||
disabled={insufficientBalance}
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
{i18n.str`Confirm payment`}
|
|
||||||
</ProgressButton>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -80,20 +80,18 @@ export function View({ details, amount, onWithdraw, terms, reviewing, onReview,
|
|||||||
const needsReview = terms.status === 'changed' || terms.status === 'new'
|
const needsReview = terms.status === 'changed' || terms.status === 'new'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WalletAction style={{ textAlign: 'center' }}>
|
<WalletAction>
|
||||||
<LogoHeader />
|
<LogoHeader />
|
||||||
<h2>
|
<h2>
|
||||||
{i18n.str`Digital cash withdrawal`}
|
{i18n.str`Digital cash withdrawal`}
|
||||||
</h2>
|
</h2>
|
||||||
<section>
|
<section>
|
||||||
<div>
|
<Part title="Total to withdraw" text={amountToString(Amounts.sub(Amounts.parseOrThrow(amount), details.withdrawFee).amount)} kind='positive' />
|
||||||
<Part title="Total to withdraw" text={amountToString(Amounts.sub(Amounts.parseOrThrow(amount), details.withdrawFee).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(details.withdrawFee) &&
|
<Part title="Exchange fee" text={amountToString(details.withdrawFee)} kind='negative' />
|
||||||
<Part title="Exchange fee" text={amountToString(details.withdrawFee)} kind='negative' />
|
}
|
||||||
}
|
<Part title="Exchange" text={details.exchangeInfo.baseUrl} kind='neutral' big />
|
||||||
<Part title="Exchange" text={details.exchangeInfo.baseUrl} kind='neutral' big />
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
{!reviewing &&
|
{!reviewing &&
|
||||||
<section>
|
<section>
|
||||||
@ -132,63 +130,50 @@ export function View({ details, amount, onWithdraw, terms, reviewing, onReview,
|
|||||||
}
|
}
|
||||||
{(reviewing || accepted) &&
|
{(reviewing || accepted) &&
|
||||||
<section>
|
<section>
|
||||||
<div>
|
<CheckboxOutlined
|
||||||
<CheckboxOutlined
|
name="terms"
|
||||||
name="terms"
|
enabled={accepted}
|
||||||
enabled={accepted}
|
label={i18n.str`I accept the exchange terms of service`}
|
||||||
label={i18n.str`I accept the exchange terms of service`}
|
onToggle={() => {
|
||||||
onToggle={() => {
|
onAccept(!accepted)
|
||||||
onAccept(!accepted)
|
onReview(false)
|
||||||
onReview(false)
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
{terms.status === 'new' && !accepted &&
|
{terms.status === 'new' && !accepted &&
|
||||||
<div>
|
<ButtonSuccess
|
||||||
<ButtonSuccess
|
upperCased
|
||||||
upperCased
|
disabled={!details.exchangeInfo.baseUrl}
|
||||||
disabled={!details.exchangeInfo.baseUrl}
|
onClick={() => onReview(true)}
|
||||||
onClick={() => onReview(true)}
|
>
|
||||||
>
|
{i18n.str`Review exchange terms of service`}
|
||||||
{i18n.str`Review exchange terms of service`}
|
</ButtonSuccess>
|
||||||
</ButtonSuccess>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
{terms.status === 'changed' && !accepted &&
|
{terms.status === 'changed' && !accepted &&
|
||||||
<div>
|
<ButtonWarning
|
||||||
<ButtonWarning
|
upperCased
|
||||||
upperCased
|
disabled={!details.exchangeInfo.baseUrl}
|
||||||
disabled={!details.exchangeInfo.baseUrl}
|
onClick={() => onReview(true)}
|
||||||
onClick={() => onReview(true)}
|
>
|
||||||
>
|
{i18n.str`Review new version of terms of service`}
|
||||||
{i18n.str`Review new version of terms of service`}
|
</ButtonWarning>
|
||||||
</ButtonWarning>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
{(terms.status === 'accepted' || (needsReview && accepted)) &&
|
{(terms.status === 'accepted' || (needsReview && accepted)) &&
|
||||||
<div>
|
<ButtonSuccess
|
||||||
<ButtonSuccess
|
upperCased
|
||||||
upperCased
|
disabled={!details.exchangeInfo.baseUrl || confirmed}
|
||||||
disabled={!details.exchangeInfo.baseUrl || confirmed}
|
onClick={onWithdraw}
|
||||||
onClick={onWithdraw}
|
>
|
||||||
>
|
{i18n.str`Confirm withdrawal`}
|
||||||
{i18n.str`Confirm withdrawal`}
|
</ButtonSuccess>
|
||||||
</ButtonSuccess>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
{terms.status === 'notfound' &&
|
{terms.status === 'notfound' &&
|
||||||
<div>
|
<ButtonDestructive upperCased disabled>
|
||||||
<ButtonDestructive
|
{i18n.str`Exchange doesn't have terms of service`}
|
||||||
upperCased
|
</ButtonDestructive>
|
||||||
disabled={true}
|
|
||||||
>
|
|
||||||
{i18n.str`Exchange doesn't have terms of service`}
|
|
||||||
</ButtonDestructive>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
</WalletAction>
|
</WalletAction>
|
||||||
@ -231,12 +216,16 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
if (!uriInfo || !uriInfo.defaultExchangeBaseUrl) return
|
if (!uriInfo || !uriInfo.defaultExchangeBaseUrl) return
|
||||||
const res = await getExchangeWithdrawalInfo({
|
try {
|
||||||
exchangeBaseUrl: uriInfo.defaultExchangeBaseUrl,
|
const res = await getExchangeWithdrawalInfo({
|
||||||
amount: Amounts.parseOrThrow(uriInfo.amount),
|
exchangeBaseUrl: uriInfo.defaultExchangeBaseUrl,
|
||||||
tosAcceptedFormat: ['text/json', 'text/xml', 'text/pdf']
|
amount: Amounts.parseOrThrow(uriInfo.amount),
|
||||||
})
|
tosAcceptedFormat: ['text/json', 'text/xml', 'text/pdf']
|
||||||
setDetails(res)
|
})
|
||||||
|
setDetails(res)
|
||||||
|
} catch (e) {
|
||||||
|
setError(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fetchData()
|
fetchData()
|
||||||
}, [uriInfo])
|
}, [uriInfo])
|
||||||
@ -249,8 +238,12 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element
|
|||||||
if (!details) {
|
if (!details) {
|
||||||
throw Error("can't accept, no exchange selected");
|
throw Error("can't accept, no exchange selected");
|
||||||
}
|
}
|
||||||
await setExchangeTosAccepted(details.exchangeDetails.exchangeBaseUrl, details.tosRequested?.tosEtag)
|
try {
|
||||||
setAccepted(true)
|
await setExchangeTosAccepted(details.exchangeDetails.exchangeBaseUrl, details.tosRequested?.tosEtag)
|
||||||
|
setAccepted(true)
|
||||||
|
} catch (e) {
|
||||||
|
setError(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onWithdraw = async (): Promise<void> => {
|
const onWithdraw = async (): Promise<void> => {
|
||||||
@ -259,10 +252,14 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element
|
|||||||
}
|
}
|
||||||
setConfirmed(true)
|
setConfirmed(true)
|
||||||
console.log("accepting exchange", details.exchangeInfo.baseUrl);
|
console.log("accepting exchange", details.exchangeInfo.baseUrl);
|
||||||
const res = await acceptWithdrawal(talerWithdrawUri, details.exchangeInfo.baseUrl);
|
try {
|
||||||
console.log("accept withdrawal response", res);
|
const res = await acceptWithdrawal(talerWithdrawUri, details.exchangeInfo.baseUrl);
|
||||||
if (res.confirmTransferUrl) {
|
console.log("accept withdrawal response", res);
|
||||||
document.location.href = res.confirmTransferUrl;
|
if (res.confirmTransferUrl) {
|
||||||
|
document.location.href = res.confirmTransferUrl;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setConfirmed(false)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -288,7 +285,7 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
debugger;
|
debugger;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,6 +248,7 @@ importers:
|
|||||||
preact-cli: ^3.0.5
|
preact-cli: ^3.0.5
|
||||||
preact-render-to-string: ^5.1.19
|
preact-render-to-string: ^5.1.19
|
||||||
preact-router: ^3.2.1
|
preact-router: ^3.2.1
|
||||||
|
qrcode-generator: ^1.4.4
|
||||||
rimraf: ^3.0.2
|
rimraf: ^3.0.2
|
||||||
rollup: ^2.37.1
|
rollup: ^2.37.1
|
||||||
rollup-plugin-css-only: ^3.1.0
|
rollup-plugin-css-only: ^3.1.0
|
||||||
@ -264,6 +265,7 @@ importers:
|
|||||||
history: 4.10.1
|
history: 4.10.1
|
||||||
preact: 10.5.14
|
preact: 10.5.14
|
||||||
preact-router: 3.2.1_preact@10.5.14
|
preact-router: 3.2.1_preact@10.5.14
|
||||||
|
qrcode-generator: 1.4.4
|
||||||
tslib: 2.3.1
|
tslib: 2.3.1
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@babel/core': 7.13.16
|
'@babel/core': 7.13.16
|
||||||
@ -16698,6 +16700,10 @@ packages:
|
|||||||
engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
|
engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/qrcode-generator/1.4.4:
|
||||||
|
resolution: {integrity: sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/qs/6.10.1:
|
/qs/6.10.1:
|
||||||
resolution: {integrity: sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==}
|
resolution: {integrity: sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
|
Loading…
Reference in New Issue
Block a user