aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension')
-rw-r--r--packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts29
-rw-r--r--packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts105
-rw-r--r--packages/taler-wallet-webextension/src/cta/InvoicePay/stories.tsx5
-rw-r--r--packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx49
-rw-r--r--packages/taler-wallet-webextension/src/cta/Payment/index.ts3
-rw-r--r--packages/taler-wallet-webextension/src/cta/Payment/state.ts4
-rw-r--r--packages/taler-wallet-webextension/src/cta/Payment/views.tsx140
-rw-r--r--packages/taler-wallet-webextension/src/hooks/useTalerActionURL.test.ts8
-rw-r--r--packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts3
9 files changed, 236 insertions, 110 deletions
diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts b/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts
index 2521ee69c..71aedc638 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts
+++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts
@@ -14,7 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { AbsoluteTime, AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util";
+import { AbsoluteTime, AmountJson, PreparePayResult, TalerErrorDetail } from "@gnu-taler/taler-util";
import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../../mui/handlers.js";
@@ -26,11 +26,14 @@ import { LoadingUriView, ReadyView } from "./views.js";
export interface Props {
talerPayPullUri: string;
onClose: () => Promise<void>;
+ goToWalletManualWithdraw: (amount?: string) => Promise<void>;
}
export type State =
| State.Loading
| State.LoadingUriError
+ | State.NoEnoughBalance
+ | State.NoBalanceForCurrency
| State.Ready;
export namespace State {
@@ -47,22 +50,38 @@ export namespace State {
export interface BaseInfo {
error: undefined;
+ uri: string;
cancel: ButtonHandler;
- }
- export interface Ready extends BaseInfo {
- status: "ready";
amount: AmountJson,
+ goToWalletManualWithdraw: (currency: string) => Promise<void>;
summary: string | undefined,
expiration: AbsoluteTime | undefined,
+ operationError?: TalerErrorDetail;
+ payStatus: PreparePayResult;
+ }
+
+ export interface NoBalanceForCurrency extends BaseInfo {
+ status: "no-balance-for-currency"
+ balance: undefined;
+ }
+ export interface NoEnoughBalance extends BaseInfo {
+ status: "no-enough-balance"
+ balance: AmountJson;
+ }
+
+ export interface Ready extends BaseInfo {
+ status: "ready";
error: undefined;
+ balance: AmountJson;
accept: ButtonHandler;
- operationError?: TalerErrorDetail;
}
}
const viewMapping: StateViewMap<State> = {
loading: Loading,
"loading-uri": LoadingUriView,
+ "no-balance-for-currency": ReadyView,
+ "no-enough-balance": ReadyView,
"ready": ReadyView,
};
diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts b/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts
index 27be1e424..f87cdf8e1 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts
+++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts
@@ -14,22 +14,31 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { AbsoluteTime, Amounts, TalerErrorDetail, TalerProtocolTimestamp } from "@gnu-taler/taler-util";
+import { AbsoluteTime, Amounts, NotificationType, PreparePayResult, PreparePayResultType, TalerErrorDetail, TalerProtocolTimestamp } from "@gnu-taler/taler-util";
import { TalerError } from "@gnu-taler/taler-wallet-core";
-import { useState } from "preact/hooks";
+import { useEffect, useState } from "preact/hooks";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import * as wxApi from "../../wxApi.js";
import { Props, State } from "./index.js";
export function useComponentState(
- { talerPayPullUri, onClose }: Props,
+ { talerPayPullUri, onClose, goToWalletManualWithdraw }: Props,
api: typeof wxApi,
): State {
const hook = useAsyncAsHook(async () => {
- return await api.checkPeerPullPayment({
+ const p2p = await api.checkPeerPullPayment({
talerUri: talerPayPullUri
})
- }, [])
+ const balance = await api.getBalance();
+ return { p2p, balance }
+ })
+
+ useEffect(() => {
+ api.onUpdateNotification([NotificationType.CoinWithdrawn], () => {
+ hook?.retry();
+ });
+ });
+
const [operationError, setOperationError] = useState<TalerErrorDetail | undefined>(undefined)
if (!hook) {
@@ -45,12 +54,84 @@ export function useComponentState(
};
}
- const { amount: purseAmount, contractTerms, peerPullPaymentIncomingId } = hook.response
+ // const { payStatus } = hook.response.p2p;
+
+ const { amount: purseAmount, contractTerms, peerPullPaymentIncomingId } = hook.response.p2p
- const amount: string = contractTerms?.amount
+
+ const amountStr: string = contractTerms?.amount
+ const amount = Amounts.parseOrThrow(amountStr)
const summary: string | undefined = contractTerms?.summary
const expiration: TalerProtocolTimestamp | undefined = contractTerms?.purse_expiration
+ const foundBalance = hook.response.balance.balances.find(
+ (b) => Amounts.parseOrThrow(b.available).currency === amount.currency,
+ );
+
+ const paymentPossible: PreparePayResult = {
+ status: PreparePayResultType.PaymentPossible,
+ proposalId: "fakeID",
+ contractTerms: {
+ } as any,
+ contractTermsHash: "asd",
+ amountRaw: hook.response.p2p.amount,
+ amountEffective: hook.response.p2p.amount,
+ noncePriv: "",
+ } as PreparePayResult
+
+ const insufficientBalance: PreparePayResult = {
+ status: PreparePayResultType.InsufficientBalance,
+ proposalId: "fakeID",
+ contractTerms: {
+ } as any,
+ amountRaw: hook.response.p2p.amount,
+ noncePriv: "",
+ }
+
+
+ const baseResult = {
+ uri: talerPayPullUri,
+ cancel: {
+ onClick: onClose
+ },
+ amount,
+ goToWalletManualWithdraw,
+ summary,
+ expiration: expiration ? AbsoluteTime.fromTimestamp(expiration) : undefined,
+ operationError,
+ }
+
+ if (!foundBalance) {
+ return {
+ status: "no-balance-for-currency",
+ error: undefined,
+ balance: undefined,
+ ...baseResult,
+ payStatus: insufficientBalance,
+ }
+ }
+
+ const foundAmount = Amounts.parseOrThrow(foundBalance.available);
+
+ //FIXME: should use pay result type since it check for coins exceptions
+ if (Amounts.cmp(foundAmount, amount) < 0) { //payStatus.status === PreparePayResultType.InsufficientBalance) {
+ return {
+ status: 'no-enough-balance',
+ error: undefined,
+ balance: foundAmount,
+ ...baseResult,
+ payStatus: insufficientBalance,
+ }
+ }
+
+ // if (payStatus.status === PreparePayResultType.AlreadyConfirmed) {
+ // return {
+ // status: "confirmed",
+ // balance: foundAmount,
+ // ...baseResult,
+ // };
+ // }
+
async function accept(): Promise<void> {
try {
const resp = await api.acceptPeerPullPayment({
@@ -69,16 +150,12 @@ export function useComponentState(
return {
status: "ready",
- amount: Amounts.parseOrThrow(amount),
error: undefined,
+ ...baseResult,
+ payStatus: paymentPossible,
+ balance: foundAmount,
accept: {
onClick: accept
},
- summary,
- expiration: expiration ? AbsoluteTime.fromTimestamp(expiration) : undefined,
- cancel: {
- onClick: onClose
- },
- operationError
}
}
diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/stories.tsx b/packages/taler-wallet-webextension/src/cta/InvoicePay/stories.tsx
index 81b79a208..5a8a51932 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoicePay/stories.tsx
+++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/stories.tsx
@@ -19,6 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
+import { PreparePayResult, PreparePayResultType } from "@gnu-taler/taler-util";
import { createExample } from "../../test-utils.js";
import { ReadyView } from "./views.js";
@@ -32,6 +33,10 @@ export const Ready = createExample(ReadyView, {
value: 1,
fraction: 0,
},
+ payStatus: {
+ status: PreparePayResultType.PaymentPossible,
+ amountEffective: "ARS:1",
+ } as PreparePayResult,
accept: {},
cancel: {},
});
diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx b/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx
index 71bdb20b8..21b666abd 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx
+++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx
@@ -14,16 +14,23 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { h, VNode } from "preact";
+import { Amounts, PreparePayResultType } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
import { Amount } from "../../components/Amount.js";
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
import { LoadingError } from "../../components/LoadingError.js";
import { LogoHeader } from "../../components/LogoHeader.js";
import { Part } from "../../components/Part.js";
-import { Link, SubTitle, WalletAction } from "../../components/styled/index.js";
+import {
+ Link,
+ SubTitle,
+ WalletAction,
+ WarningBox,
+} from "../../components/styled/index.js";
import { Time } from "../../components/Time.js";
import { useTranslationContext } from "../../context/translation.js";
import { Button } from "../../mui/Button.js";
+import { ButtonsSection, PayWithMobile } from "../Payment/views.js";
import { State } from "./index.js";
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
@@ -37,16 +44,21 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode {
);
}
-export function ReadyView({
- operationError,
- cancel,
- accept,
- expiration,
- summary,
- amount,
-}: State.Ready): VNode {
+export function ReadyView(
+ state: State.Ready | State.NoBalanceForCurrency | State.NoEnoughBalance,
+): VNode {
const { i18n } = useTranslationContext();
-
+ const {
+ operationError,
+ summary,
+ amount,
+ expiration,
+ uri,
+ status,
+ balance,
+ payStatus,
+ cancel,
+ } = state;
return (
<WalletAction>
<LogoHeader />
@@ -78,13 +90,14 @@ export function ReadyView({
kind="neutral"
/>
</section>
- <section>
- <Button variant="contained" color="success" onClick={accept.onClick}>
- <i18n.Translate>
- Pay &nbsp; {<Amount value={amount} />}
- </i18n.Translate>
- </Button>
- </section>
+ <ButtonsSection
+ amount={amount}
+ balance={balance}
+ payStatus={payStatus}
+ uri={uri}
+ payHandler={status === "ready" ? state.accept : undefined}
+ goToWalletManualWithdraw={state.goToWalletManualWithdraw}
+ />
<section>
<Link upperCased onClick={cancel.onClick}>
<i18n.Translate>Cancel</i18n.Translate>
diff --git a/packages/taler-wallet-webextension/src/cta/Payment/index.ts b/packages/taler-wallet-webextension/src/cta/Payment/index.ts
index 889e532c2..6e401f7d2 100644
--- a/packages/taler-wallet-webextension/src/cta/Payment/index.ts
+++ b/packages/taler-wallet-webextension/src/cta/Payment/index.ts
@@ -15,6 +15,7 @@
*/
import { AmountJson, ConfirmPayResult, PreparePayResult, PreparePayResultAlreadyConfirmed, PreparePayResultInsufficientBalance, PreparePayResultPaymentPossible } from "@gnu-taler/taler-util";
+import { TalerError } from "@gnu-taler/taler-wallet-core";
import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../../mui/handlers.js";
@@ -85,7 +86,7 @@ export namespace State {
status: "completed";
payStatus: PreparePayResult;
payResult: ConfirmPayResult;
- payHandler: ButtonHandler;
+ paymentError?: TalerError;
balance: AmountJson;
}
}
diff --git a/packages/taler-wallet-webextension/src/cta/Payment/state.ts b/packages/taler-wallet-webextension/src/cta/Payment/state.ts
index 842bb7ed6..ad4bb7004 100644
--- a/packages/taler-wallet-webextension/src/cta/Payment/state.ts
+++ b/packages/taler-wallet-webextension/src/cta/Payment/state.ts
@@ -101,9 +101,7 @@ export function useComponentState(
status: "completed",
balance: foundAmount,
payStatus,
- payHandler: {
- error: payErrMsg,
- },
+ paymentError: payErrMsg,
payResult,
...baseResult,
};
diff --git a/packages/taler-wallet-webextension/src/cta/Payment/views.tsx b/packages/taler-wallet-webextension/src/cta/Payment/views.tsx
index c43745909..c799607ad 100644
--- a/packages/taler-wallet-webextension/src/cta/Payment/views.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Payment/views.tsx
@@ -16,9 +16,12 @@
import {
AbsoluteTime,
+ AmountJson,
Amounts,
ConfirmPayResultType,
ContractTerms,
+ PreparePayResult,
+ PreparePayResultPaymentPossible,
PreparePayResultType,
Product,
} from "@gnu-taler/taler-util";
@@ -42,6 +45,7 @@ import {
import { Time } from "../../components/Time.js";
import { useTranslationContext } from "../../context/translation.js";
import { Button } from "../../mui/Button.js";
+import { ButtonHandler } from "../../mui/handlers.js";
import { MerchantDetails, PurchaseDetails } from "../../wallet/Transaction.js";
import { State } from "./index.js";
@@ -164,7 +168,11 @@ export function BaseView(state: SupportedStates): VNode {
)}
</section>
<ButtonsSection
- state={state}
+ amount={state.amount}
+ balance={state.balance}
+ payStatus={state.payStatus}
+ uri={state.uri}
+ payHandler={state.status === "ready" ? state.payHandler : undefined}
goToWalletManualWithdraw={state.goToWalletManualWithdraw}
/>
<section>
@@ -276,9 +284,9 @@ function ShowImportantMessage({ state }: { state: SupportedStates }): VNode {
}
if (state.status == "completed") {
- const { payResult, payHandler } = state;
- if (payHandler.error) {
- return <ErrorTalerOperation error={payHandler.error.errorDetail} />;
+ const { payResult, paymentError } = state;
+ if (paymentError) {
+ return <ErrorTalerOperation error={paymentError.errorDetail} />;
}
if (payResult.type === ConfirmPayResultType.Done) {
return (
@@ -307,15 +315,11 @@ function ShowImportantMessage({ state }: { state: SupportedStates }): VNode {
return <Fragment />;
}
-function PayWithMobile({ state }: { state: SupportedStates }): VNode {
+export function PayWithMobile({ uri }: { uri: string }): VNode {
const { i18n } = useTranslationContext();
const [showQR, setShowQR] = useState<boolean>(false);
- const privateUri =
- state.payStatus.status !== PreparePayResultType.AlreadyConfirmed
- ? `${state.uri}&n=${state.payStatus.noncePriv}`
- : state.uri;
return (
<section>
<LinkSuccess upperCased onClick={() => setShowQR((qr) => !qr)}>
@@ -327,10 +331,10 @@ function PayWithMobile({ state }: { state: SupportedStates }): VNode {
</LinkSuccess>
{showQR && (
<div>
- <QR text={privateUri} />
+ <QR text={uri} />
<i18n.Translate>
Scan the QR code or &nbsp;
- <a href={privateUri}>
+ <a href={uri}>
<i18n.Translate>click here</i18n.Translate>
</a>
</i18n.Translate>
@@ -340,50 +344,60 @@ function PayWithMobile({ state }: { state: SupportedStates }): VNode {
);
}
-function ButtonsSection({
- state,
- goToWalletManualWithdraw,
-}: {
- state: SupportedStates;
+interface ButtonSectionProps {
+ payStatus: PreparePayResult;
+ payHandler: ButtonHandler | undefined;
+ balance: AmountJson | undefined;
+ uri: string;
+ amount: AmountJson;
goToWalletManualWithdraw: (currency: string) => Promise<void>;
-}): VNode {
+}
+
+export function ButtonsSection({
+ payStatus,
+ uri,
+ payHandler,
+ balance,
+ amount,
+ goToWalletManualWithdraw,
+}: ButtonSectionProps): VNode {
const { i18n } = useTranslationContext();
- if (state.status === "ready") {
+ if (payStatus.status === PreparePayResultType.PaymentPossible) {
+ const privateUri = `${uri}&n=${payStatus.noncePriv}`;
+
return (
<Fragment>
<section>
<Button
variant="contained"
color="success"
- onClick={state.payHandler.onClick}
+ onClick={payHandler?.onClick}
>
<i18n.Translate>
Pay &nbsp;
- {<Amount value={state.payStatus.amountEffective} />}
+ {<Amount value={amount} />}
</i18n.Translate>
</Button>
</section>
- <PayWithMobile state={state} />
+ <PayWithMobile uri={privateUri} />
</Fragment>
);
}
- if (
- state.status === "no-enough-balance" ||
- state.status === "no-balance-for-currency"
- ) {
- // if (state.payStatus.status === PreparePayResultType.InsufficientBalance) {
+
+ if (payStatus.status === PreparePayResultType.InsufficientBalance) {
let BalanceMessage = "";
- if (!state.balance) {
+ if (!balance) {
BalanceMessage = i18n.str`You have no balance for this currency. Withdraw digital cash first.`;
} else {
- const balanceShouldBeEnough =
- Amounts.cmp(state.balance, state.amount) !== -1;
+ const balanceShouldBeEnough = Amounts.cmp(balance, amount) !== -1;
if (balanceShouldBeEnough) {
- BalanceMessage = i18n.str`Could not find enough coins to pay this order. Even if you have enough ${state.balance.currency} some restriction may apply.`;
+ BalanceMessage = i18n.str`Could not find enough coins to pay. Even if you have enough ${balance.currency} some restriction may apply.`;
} else {
- BalanceMessage = i18n.str`Your current balance is not enough for this order.`;
+ BalanceMessage = i18n.str`Your current balance is not enough.`;
}
}
+ const uriPrivate = `${uri}&n=${payStatus.noncePriv}`;
+
return (
<Fragment>
<section>
@@ -393,51 +407,45 @@ function ButtonsSection({
<Button
variant="contained"
color="success"
- onClick={() =>
- goToWalletManualWithdraw(Amounts.stringify(state.amount))
- }
+ onClick={() => goToWalletManualWithdraw(Amounts.stringify(amount))}
>
<i18n.Translate>Get digital cash</i18n.Translate>
</Button>
</section>
- <PayWithMobile state={state} />
+ <PayWithMobile uri={uriPrivate} />
</Fragment>
);
- // }
- }
- if (state.status === "confirmed") {
- if (state.payStatus.status === PreparePayResultType.AlreadyConfirmed) {
- return (
- <Fragment>
- <section>
- {state.payStatus.paid &&
- state.payStatus.contractTerms.fulfillment_message && (
- <Part
- title={<i18n.Translate>Merchant message</i18n.Translate>}
- text={state.payStatus.contractTerms.fulfillment_message}
- kind="neutral"
- />
- )}
- </section>
- {!state.payStatus.paid && <PayWithMobile state={state} />}
- </Fragment>
- );
- }
}
-
- if (state.status === "completed") {
- if (state.payResult.type === ConfirmPayResultType.Pending) {
- return (
+ if (payStatus.status === PreparePayResultType.AlreadyConfirmed) {
+ return (
+ <Fragment>
<section>
- <div>
- <p>
- <i18n.Translate>Processing</i18n.Translate>...
- </p>
- </div>
+ {payStatus.paid && payStatus.contractTerms.fulfillment_message && (
+ <Part
+ title={<i18n.Translate>Merchant message</i18n.Translate>}
+ text={payStatus.contractTerms.fulfillment_message}
+ kind="neutral"
+ />
+ )}
</section>
- );
- }
+ {!payStatus.paid && <PayWithMobile uri={uri} />}
+ </Fragment>
+ );
}
+ // if (state.status === "completed") {
+ // if (state.payResult.type === ConfirmPayResultType.Pending) {
+ // return (
+ // <section>
+ // <div>
+ // <p>
+ // <i18n.Translate>Processing</i18n.Translate>...
+ // </p>
+ // </div>
+ // </section>
+ // );
+ // }
+ // }
+
return <Fragment />;
}
diff --git a/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.test.ts b/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.test.ts
index c00d6d7f6..61fe86e3a 100644
--- a/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.test.ts
+++ b/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.test.ts
@@ -20,11 +20,13 @@ import { h, VNode } from "preact";
import { expect } from "chai";
describe("useTalerActionURL hook", () => {
+
it("should be set url to undefined when dismiss", async () => {
const ctx = ({ children }: { children: any }): VNode => {
return h(IoCProviderForTesting, {
value: {
findTalerUriInActiveTab: async () => "asd",
+ findTalerUriInClipboard: async () => "qwe",
},
children,
});
@@ -42,7 +44,10 @@ describe("useTalerActionURL hook", () => {
{
const [url, setDismissed] = getLastResultOrThrow();
- expect(url).equals("asd");
+ expect(url).deep.equals({
+ location: "clipboard",
+ uri: "qwe"
+ });
setDismissed(true);
}
@@ -53,7 +58,6 @@ describe("useTalerActionURL hook", () => {
if (url !== undefined) throw Error("invalid");
expect(url).undefined;
}
-
await assertNoPendingUpdate();
});
});
diff --git a/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts b/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts
index 74d7cbbd9..e1b08278b 100644
--- a/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts
+++ b/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts
@@ -52,7 +52,8 @@ export function useTalerActionURL(): [
}
}
check();
- });
+ }, [setTalerActionUrl]);
+
const url = dismissed ? undefined : talerActionUrl;
return [url, setDismissed];
}