some ui fixing from belen comments
This commit is contained in:
parent
8cde98947b
commit
b1bf3538e6
@ -407,7 +407,7 @@ export class Amounts {
|
||||
return `${a.currency}:${s}`;
|
||||
}
|
||||
|
||||
static stringifyValue(a: AmountJson): string {
|
||||
static stringifyValue(a: AmountJson, minFractional: number = 0): string {
|
||||
const av = a.value + Math.floor(a.fraction / amountFractionalBase);
|
||||
const af = a.fraction % amountFractionalBase;
|
||||
let s = av.toString();
|
||||
@ -416,7 +416,7 @@ export class Amounts {
|
||||
s = s + ".";
|
||||
let n = af;
|
||||
for (let i = 0; i < amountFractionalLength; i++) {
|
||||
if (!n) {
|
||||
if (!n && i >= minFractional) {
|
||||
break;
|
||||
}
|
||||
s = s + Math.floor((n / amountFractionalBase) * 10).toString();
|
||||
|
@ -22,7 +22,7 @@ export function ErrorMessage({ title, description }: { title?: string|VNode; des
|
||||
const [showErrorDetail, setShowErrorDetail] = useState(false);
|
||||
if (!title)
|
||||
return null;
|
||||
return <ErrorBox>
|
||||
return <ErrorBox style={{paddingTop: 0, paddingBottom: 0}}>
|
||||
<div>
|
||||
<p>{title}</p>
|
||||
{ description && <button onClick={() => { setShowErrorDetail(v => !v); }}>
|
||||
|
@ -520,8 +520,7 @@ export const ErrorBox = styled.div`
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
/* margin: 0.5em; */
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
padding: 1em;
|
||||
/* width: 100%; */
|
||||
color: #721c24;
|
||||
background: #f8d7da;
|
||||
@ -539,6 +538,19 @@ export const ErrorBox = styled.div`
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const SuccessBox = styled(ErrorBox)`
|
||||
color: #0f5132;
|
||||
background-color: #d1e7dd;
|
||||
border-color: #badbcc;
|
||||
`
|
||||
|
||||
export const WarningBox = styled(ErrorBox)`
|
||||
color: #664d03;
|
||||
background-color: #fff3cd;
|
||||
border-color: #ffecb5;
|
||||
`
|
||||
|
||||
export const PopupNavigation = styled.div<{ devMode?: boolean }>`
|
||||
background-color:#0042b2;
|
||||
height: 35px;
|
||||
|
@ -30,7 +30,7 @@ export default {
|
||||
},
|
||||
};
|
||||
|
||||
export const InsufficientBalance = createExample(TestedComponent, {
|
||||
export const NoBalance = createExample(TestedComponent, {
|
||||
payStatus: {
|
||||
status: PreparePayResultType.InsufficientBalance,
|
||||
noncePriv: '',
|
||||
@ -46,6 +46,27 @@ export const InsufficientBalance = createExample(TestedComponent, {
|
||||
}
|
||||
});
|
||||
|
||||
export const NoEnoughBalance = createExample(TestedComponent, {
|
||||
payStatus: {
|
||||
status: PreparePayResultType.InsufficientBalance,
|
||||
noncePriv: '',
|
||||
proposalId: "proposal1234",
|
||||
contractTerms: {
|
||||
merchant: {
|
||||
name: 'someone'
|
||||
},
|
||||
summary: 'some beers',
|
||||
amount: 'USD:10',
|
||||
} as Partial<ContractTerms> as any,
|
||||
amountRaw: 'USD:10',
|
||||
},
|
||||
balance: {
|
||||
currency: 'USD',
|
||||
fraction: 40000000,
|
||||
value: 9
|
||||
}
|
||||
});
|
||||
|
||||
export const PaymentPossible = createExample(TestedComponent, {
|
||||
uri: 'taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0',
|
||||
payStatus: {
|
||||
@ -66,6 +87,26 @@ export const PaymentPossible = createExample(TestedComponent, {
|
||||
}
|
||||
});
|
||||
|
||||
export const PaymentPossibleWithFee = createExample(TestedComponent, {
|
||||
uri: 'taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0',
|
||||
payStatus: {
|
||||
status: PreparePayResultType.PaymentPossible,
|
||||
amountEffective: 'USD:10.20',
|
||||
amountRaw: 'USD:10',
|
||||
noncePriv: '',
|
||||
contractTerms: {
|
||||
nonce: '123213123',
|
||||
merchant: {
|
||||
name: 'someone'
|
||||
},
|
||||
amount: 'USD:10',
|
||||
summary: 'some beers',
|
||||
} as Partial<ContractTerms> as any,
|
||||
contractTermsHash: '123456',
|
||||
proposalId: 'proposal1234'
|
||||
}
|
||||
});
|
||||
|
||||
export const AlreadyConfirmedWithFullfilment = createExample(TestedComponent, {
|
||||
payStatus: {
|
||||
status: PreparePayResultType.AlreadyConfirmed,
|
||||
@ -102,3 +143,22 @@ export const AlreadyConfirmedWithoutFullfilment = createExample(TestedComponent,
|
||||
paid: false,
|
||||
}
|
||||
});
|
||||
|
||||
export const AlreadyPaid = createExample(TestedComponent, {
|
||||
payStatus: {
|
||||
status: PreparePayResultType.AlreadyConfirmed,
|
||||
amountEffective: 'USD:10',
|
||||
amountRaw: 'USD:10',
|
||||
contractTerms: {
|
||||
merchant: {
|
||||
name: 'someone'
|
||||
},
|
||||
fulfillment_message: 'congratulations! you are looking at the fulfillment message! ',
|
||||
summary: 'some beers',
|
||||
amount: 'USD:10',
|
||||
} as Partial<ContractTerms> as any,
|
||||
contractTermsHash: '123456',
|
||||
proposalId: 'proposal1234',
|
||||
paid: true,
|
||||
}
|
||||
});
|
||||
|
@ -24,56 +24,45 @@
|
||||
*/
|
||||
// import * as i18n from "../i18n";
|
||||
|
||||
import { renderAmount, ProgressButton } from "../renderHtml";
|
||||
import * as wxApi from "../wxApi";
|
||||
|
||||
import { useState, useEffect } from "preact/hooks";
|
||||
|
||||
import { AmountLike, ConfirmPayResultDone, getJsonI18n, i18n } from "@gnu-taler/taler-util";
|
||||
import {
|
||||
PreparePayResult,
|
||||
ConfirmPayResult,
|
||||
AmountJson,
|
||||
PreparePayResultType,
|
||||
Amounts,
|
||||
ContractTerms,
|
||||
ConfirmPayResultType,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { JSX, VNode, h, Fragment } from "preact";
|
||||
import { ButtonDestructive, ButtonSuccess, ButtonWarning, LinkSuccess, LinkWarning, WalletAction } from "../components/styled";
|
||||
import { AmountJson, AmountLike, Amounts, ConfirmPayResult, ConfirmPayResultDone, ConfirmPayResultType, ContractTerms, getJsonI18n, i18n, PreparePayResult, PreparePayResultType } from "@gnu-taler/taler-util";
|
||||
import { Fragment, JSX, VNode } from "preact";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { LogoHeader } from "../components/LogoHeader";
|
||||
import { Part } from "../components/Part";
|
||||
import { QR } from "../components/QR";
|
||||
import { ButtonSuccess, LinkSuccess, SuccessBox, WalletAction, WarningBox } from "../components/styled";
|
||||
import { useBalances } from "../hooks/useBalances";
|
||||
import * as wxApi from "../wxApi";
|
||||
|
||||
interface Props {
|
||||
talerPayUri?: string
|
||||
}
|
||||
|
||||
export function AlreadyPaid({ payStatus }: { payStatus: PreparePayResult }) {
|
||||
const fulfillmentUrl = payStatus.contractTerms.fulfillment_url;
|
||||
let message;
|
||||
if (fulfillmentUrl) {
|
||||
message = (
|
||||
<span>
|
||||
You have already paid for this article. Click{" "}
|
||||
<a href={fulfillmentUrl} target="_bank" rel="external">here</a> to view it again.
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
message = <span>
|
||||
You have already paid for this article:{" "}
|
||||
<em>
|
||||
{payStatus.contractTerms.fulfillment_message ?? "no message given"}
|
||||
</em>
|
||||
</span>;
|
||||
}
|
||||
return <section class="main">
|
||||
<h1>GNU Taler Wallet</h1>
|
||||
<article class="fade">
|
||||
{message}
|
||||
</article>
|
||||
</section>
|
||||
}
|
||||
// export function AlreadyPaid({ payStatus }: { payStatus: PreparePayResult }) {
|
||||
// const fulfillmentUrl = payStatus.contractTerms.fulfillment_url;
|
||||
// let message;
|
||||
// if (fulfillmentUrl) {
|
||||
// message = (
|
||||
// <span>
|
||||
// You have already paid for this article. Click{" "}
|
||||
// <a href={fulfillmentUrl} target="_bank" rel="external">here</a> to view it again.
|
||||
// </span>
|
||||
// );
|
||||
// } else {
|
||||
// message = <span>
|
||||
// You have already paid for this article:{" "}
|
||||
// <em>
|
||||
// {payStatus.contractTerms.fulfillment_message ?? "no message given"}
|
||||
// </em>
|
||||
// </span>;
|
||||
// }
|
||||
// return <section class="main">
|
||||
// <h1>GNU Taler Wallet</h1>
|
||||
// <article class="fade">
|
||||
// {message}
|
||||
// </article>
|
||||
// </section>
|
||||
// }
|
||||
|
||||
const doPayment = async (payStatus: PreparePayResult): Promise<ConfirmPayResultDone> => {
|
||||
if (payStatus.status !== "payment-possible") {
|
||||
@ -98,6 +87,12 @@ export function PayPage({ talerPayUri }: Props): JSX.Element {
|
||||
const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>(undefined);
|
||||
const [payErrMsg, setPayErrMsg] = useState<string | undefined>("");
|
||||
|
||||
const balance = useBalances()
|
||||
const balanceWithoutError = balance?.error ? [] : (balance?.response.balances || [])
|
||||
|
||||
const foundBalance = balanceWithoutError.find(b => payStatus && Amounts.parseOrThrow(b.available).currency === Amounts.parseOrThrow(payStatus?.amountRaw).currency)
|
||||
const foundAmount = foundBalance ? Amounts.parseOrThrow(foundBalance.available) : undefined
|
||||
|
||||
useEffect(() => {
|
||||
if (!talerPayUri) return;
|
||||
const doFetch = async (): Promise<void> => {
|
||||
@ -115,24 +110,24 @@ export function PayPage({ talerPayUri }: Props): JSX.Element {
|
||||
return <span>Loading payment information ...</span>;
|
||||
}
|
||||
|
||||
if (payResult && payResult.type === ConfirmPayResultType.Done) {
|
||||
if (payResult.contractTerms.fulfillment_message) {
|
||||
const obj = {
|
||||
fulfillment_message: payResult.contractTerms.fulfillment_message,
|
||||
fulfillment_message_i18n:
|
||||
payResult.contractTerms.fulfillment_message_i18n,
|
||||
};
|
||||
const msg = getJsonI18n(obj, "fulfillment_message");
|
||||
return (
|
||||
<div>
|
||||
<p>Payment succeeded.</p>
|
||||
<p>{msg}</p>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return <span>Redirecting ...</span>;
|
||||
}
|
||||
}
|
||||
// if (payResult && payResult.type === ConfirmPayResultType.Done) {
|
||||
// if (payResult.contractTerms.fulfillment_message) {
|
||||
// const obj = {
|
||||
// fulfillment_message: payResult.contractTerms.fulfillment_message,
|
||||
// fulfillment_message_i18n:
|
||||
// payResult.contractTerms.fulfillment_message_i18n,
|
||||
// };
|
||||
// const msg = getJsonI18n(obj, "fulfillment_message");
|
||||
// return (
|
||||
// <div>
|
||||
// <p>Payment succeeded.</p>
|
||||
// <p>{msg}</p>
|
||||
// </div>
|
||||
// );
|
||||
// } else {
|
||||
// return <span>Redirecting ...</span>;
|
||||
// }
|
||||
// }
|
||||
|
||||
const onClick = async () => {
|
||||
try {
|
||||
@ -147,7 +142,7 @@ export function PayPage({ talerPayUri }: Props): JSX.Element {
|
||||
|
||||
}
|
||||
|
||||
return <PaymentRequestView uri={talerPayUri} payStatus={payStatus} onClick={onClick} payErrMsg={payErrMsg} />;
|
||||
return <PaymentRequestView uri={talerPayUri} payStatus={payStatus} onClick={onClick} payErrMsg={payErrMsg} balance={foundAmount} />;
|
||||
}
|
||||
|
||||
export interface PaymentRequestViewProps {
|
||||
@ -155,8 +150,9 @@ export interface PaymentRequestViewProps {
|
||||
onClick: () => void;
|
||||
payErrMsg?: string;
|
||||
uri: string;
|
||||
balance: AmountJson | undefined;
|
||||
}
|
||||
export function PaymentRequestView({ uri, payStatus, onClick, payErrMsg }: PaymentRequestViewProps) {
|
||||
export function PaymentRequestView({ uri, payStatus, onClick, payErrMsg, balance }: PaymentRequestViewProps) {
|
||||
let totalFees: AmountJson = Amounts.getZero(payStatus.amountRaw);
|
||||
const contractTerms: ContractTerms = payStatus.contractTerms;
|
||||
|
||||
@ -183,71 +179,98 @@ export function PaymentRequestView({ uri, payStatus, onClick, payErrMsg }: Payme
|
||||
merchantName = <strong>(pub: {contractTerms.merchant_pub})</strong>;
|
||||
}
|
||||
|
||||
const [showQR, setShowQR] = useState<boolean>(false)
|
||||
const privateUri = payStatus.status !== PreparePayResultType.AlreadyConfirmed ? `${uri}&n=${payStatus.noncePriv}` : uri
|
||||
return <WalletAction>
|
||||
<LogoHeader />
|
||||
<h2>
|
||||
{i18n.str`Digital cash payment`}
|
||||
</h2>
|
||||
<section>
|
||||
{payStatus.status === PreparePayResultType.InsufficientBalance ?
|
||||
<Part title="Insufficient balance" text="No enough coins to pay" kind='negative' /> :
|
||||
<Part big title="Total amount with fee" 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' />}
|
||||
function Alternative() {
|
||||
const [showQR, setShowQR] = useState<boolean>(false)
|
||||
const privateUri = payStatus.status !== PreparePayResultType.AlreadyConfirmed ? `${uri}&n=${payStatus.noncePriv}` : uri
|
||||
return <section>
|
||||
<LinkSuccess upperCased onClick={() => setShowQR(qr => !qr)}>
|
||||
{!showQR ? i18n.str`Pay with a mobile phone` : i18n.str`Hide QR`}
|
||||
</LinkSuccess>
|
||||
{showQR && <div>
|
||||
<QR text={privateUri} />
|
||||
Scan the QR code or <a href={privateUri}>click here</a>
|
||||
</div>}
|
||||
</section>
|
||||
{showQR && <section>
|
||||
<QR text={privateUri} />
|
||||
Scan the QR code or <a href={privateUri}>click here</a>
|
||||
</section>}
|
||||
<section>
|
||||
{payErrMsg ? (
|
||||
}
|
||||
|
||||
function ButtonsSection() {
|
||||
if (payErrMsg) {
|
||||
return <section>
|
||||
<div>
|
||||
<p>Payment failed: {payErrMsg}</p>
|
||||
<button class="pure-button button-success" onClick={onClick} >
|
||||
{i18n.str`Retry`}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
payStatus.status === PreparePayResultType.PaymentPossible ? <Fragment>
|
||||
<LinkSuccess upperCased onClick={() => setShowQR(qr => !qr)}>
|
||||
{!showQR ? i18n.str`Complete with mobile wallet` : i18n.str`Hide QR`}
|
||||
</LinkSuccess>
|
||||
</section>
|
||||
}
|
||||
if (payStatus.status === PreparePayResultType.PaymentPossible) {
|
||||
return <Fragment>
|
||||
<section>
|
||||
<ButtonSuccess upperCased>
|
||||
{i18n.str`Confirm payment`}
|
||||
{i18n.str`Pay`} {amountToString(payStatus.amountEffective)}
|
||||
</ButtonSuccess>
|
||||
</Fragment> : (
|
||||
payStatus.status === PreparePayResultType.InsufficientBalance ? <Fragment>
|
||||
<LinkSuccess upperCased onClick={() => setShowQR(qr => !qr)}>
|
||||
{!showQR ? i18n.str`Pay with other device` : i18n.str`Hide QR`}
|
||||
</LinkSuccess>
|
||||
<ButtonDestructive upperCased disabled>
|
||||
{i18n.str`No enough coins`}
|
||||
</ButtonDestructive>
|
||||
</Fragment> :
|
||||
<Fragment>
|
||||
{payStatus.contractTerms.fulfillment_message && <div>
|
||||
{payStatus.contractTerms.fulfillment_message}
|
||||
</div>}
|
||||
<LinkWarning upperCased href={payStatus.contractTerms.fulfillment_url}>
|
||||
{i18n.str`Already paid`}
|
||||
</LinkWarning>
|
||||
</Fragment>
|
||||
</section>
|
||||
<Alternative />
|
||||
</Fragment>
|
||||
}
|
||||
if (payStatus.status === PreparePayResultType.InsufficientBalance) {
|
||||
return <Fragment>
|
||||
<section>
|
||||
{balance ? <WarningBox>
|
||||
Your balance of {amountToString(balance)} is not enough to pay for this purchase
|
||||
</WarningBox> : <WarningBox>
|
||||
Your balance is not enough to pay for this purchase.
|
||||
</WarningBox>}
|
||||
</section>
|
||||
<section>
|
||||
<ButtonSuccess upperCased>
|
||||
{i18n.str`Withdraw digital cash`}
|
||||
</ButtonSuccess>
|
||||
</section>
|
||||
<Alternative />
|
||||
</Fragment>
|
||||
}
|
||||
if (payStatus.status === PreparePayResultType.AlreadyConfirmed) {
|
||||
return <Fragment>
|
||||
<section>
|
||||
{payStatus.paid && contractTerms.fulfillment_message && <Part title="Merchant message" text={contractTerms.fulfillment_message} kind='neutral' />}
|
||||
</section>
|
||||
{!payStatus.paid && <Alternative />}
|
||||
</Fragment>
|
||||
}
|
||||
return <span />
|
||||
}
|
||||
|
||||
)
|
||||
)}
|
||||
return <WalletAction>
|
||||
<LogoHeader />
|
||||
|
||||
<h2>
|
||||
{i18n.str`Digital cash payment`}
|
||||
</h2>
|
||||
{payStatus.status === PreparePayResultType.AlreadyConfirmed &&
|
||||
(payStatus.paid ? <SuccessBox> Already paid </SuccessBox> : <WarningBox> Already confirmed </WarningBox>)
|
||||
}
|
||||
<section>
|
||||
{payStatus.status !== PreparePayResultType.InsufficientBalance && Amounts.isNonZero(totalFees) &&
|
||||
<Part big title="Total to pay" text={amountToString(payStatus.amountEffective)} kind='negative' />
|
||||
}
|
||||
<Part big title="Purchase amount" text={amountToString(payStatus.amountRaw)} kind='neutral' />
|
||||
{Amounts.isNonZero(totalFees) && <Fragment>
|
||||
<Part big title="Fee" text={amountToString(totalFees)} kind='negative' />
|
||||
</Fragment>
|
||||
}
|
||||
<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>
|
||||
<ButtonsSection />
|
||||
|
||||
</WalletAction>
|
||||
}
|
||||
|
||||
function amountToString(text: AmountLike) {
|
||||
const aj = Amounts.jsonifyAmount(text)
|
||||
const amount = Amounts.stringifyValue(aj)
|
||||
const amount = Amounts.stringifyValue(aj, 2)
|
||||
return `${amount} ${aj.currency}`
|
||||
}
|
||||
|
@ -32,7 +32,6 @@ export type BalancesHook = BalancesHookOk | BalancesHookError | undefined;
|
||||
|
||||
export function useBalances(): BalancesHook {
|
||||
const [balance, setBalance] = useState<BalancesHook>(undefined);
|
||||
console.log('render balance')
|
||||
useEffect(() => {
|
||||
async function checkBalance() {
|
||||
try {
|
||||
|
Loading…
Reference in New Issue
Block a user