diff --git a/packages/taler-util/src/talerTypes.ts b/packages/taler-util/src/talerTypes.ts
index d9213ef5d..7fc3fcba0 100644
--- a/packages/taler-util/src/talerTypes.ts
+++ b/packages/taler-util/src/talerTypes.ts
@@ -362,6 +362,9 @@ export interface MerchantInfo {
name: string;
jurisdiction?: Location;
address?: Location;
+ logo?: string;
+ website?: string;
+ email?: string;
}
export interface Tax {
diff --git a/packages/taler-util/src/transactionsTypes.ts b/packages/taler-util/src/transactionsTypes.ts
index 37c1c7ef1..dcaa56675 100644
--- a/packages/taler-util/src/transactionsTypes.ts
+++ b/packages/taler-util/src/transactionsTypes.ts
@@ -33,6 +33,7 @@ import {
codecForInternationalizedString,
codecForMerchantInfo,
codecForProduct,
+ Location,
} from "./talerTypes.js";
import {
Codec,
@@ -276,6 +277,17 @@ export interface OrderShortInfo {
*/
products: Product[] | undefined;
+ /**
+ * Time indicating when the order should be delivered.
+ * May be overwritten by individual products.
+ */
+ delivery_date?: TalerProtocolTimestamp;
+
+ /**
+ * Delivery location for (all!) products.
+ */
+ delivery_location?: Location;
+
/**
* URL of the fulfillment, given by the merchant
*/
diff --git a/packages/taler-wallet-webextension/build-fast-with-linaria.mjs b/packages/taler-wallet-webextension/build-fast-with-linaria.mjs
index f6de67885..41747a745 100755
--- a/packages/taler-wallet-webextension/build-fast-with-linaria.mjs
+++ b/packages/taler-wallet-webextension/build-fast-with-linaria.mjs
@@ -54,6 +54,7 @@ export const buildConfig = {
loader: {
'.svg': 'text',
'.png': 'dataurl',
+ '.jpeg': 'dataurl',
},
target: [
'es6'
diff --git a/packages/taler-wallet-webextension/src/components/Amount.tsx b/packages/taler-wallet-webextension/src/components/Amount.tsx
index c41f7faf6..b415a30cd 100644
--- a/packages/taler-wallet-webextension/src/components/Amount.tsx
+++ b/packages/taler-wallet-webextension/src/components/Amount.tsx
@@ -6,7 +6,7 @@ export function Amount({ value }: { value: AmountJson | AmountString }): VNode {
const amount = Amounts.stringifyValue(aj, 2);
return (
- {amount} {aj.currency}
+ {amount} {aj.currency}
);
}
diff --git a/packages/taler-wallet-webextension/src/components/BalanceTable.tsx b/packages/taler-wallet-webextension/src/components/BalanceTable.tsx
index e67fb6b4d..a2c91f4a1 100644
--- a/packages/taler-wallet-webextension/src/components/BalanceTable.tsx
+++ b/packages/taler-wallet-webextension/src/components/BalanceTable.tsx
@@ -44,7 +44,7 @@ export function BalanceTable({
width: "100%",
}}
>
- {Amounts.stringifyValue(av)}
+ {Amounts.stringifyValue(av, 2)}
);
diff --git a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
index 185021bc0..3a2a12c72 100644
--- a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
+++ b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
@@ -46,43 +46,47 @@ export function BankDetailsByPaytoType({
if (payto.isKnown && payto.targetType === "bitcoin") {
const min = segwitMinAmount(amount.currency);
return (
-
+
+ Bitcoin transfer details
- Bitcoin exchange need a transaction with 3 output, one output is the
+ The exchange need a transaction with 3 output, one output is the
exchange account and the other two are segwit fake address for
- metadata with an minimum amount. Reserve pub : {subject}
+ metadata with an minimum amount.
+ Reserve}
+ value={subject}
+ />
+
In bitcoincore wallet use 'Add Recipient' button to add
two additional recipient and copy addresses and amounts
-
-
- {payto.targetPath} {Amounts.stringifyValue(amount)} BTC
-
- {payto.segwitAddrs.map((addr, i) => (
-
- {addr} {Amounts.stringifyValue(min)} BTC
-
- ))}
-
-
- In Electrum wallet paste the following three lines in 'Pay
- to' field :
-
-
-
- {payto.targetPath},{Amounts.stringifyValue(amount)}
-
- {payto.segwitAddrs.map((addr, i) => (
-
- {addr} {Amounts.stringifyValue(min)} BTC
-
- ))}
-
+
+
+
+ {payto.targetPath}
+ {Amounts.stringifyValue(amount)} BTC
+
+ {payto.segwitAddrs.map((addr, i) => (
+
+ {addr}
+ {Amounts.stringifyValue(min)} BTC
+
+ ))}
+
+
Make sure the amount show{" "}
{Amounts.stringifyValue(Amounts.sum([amount, min, min]).amount)}{" "}
@@ -93,7 +97,7 @@ export function BankDetailsByPaytoType({
);
}
- const firstPart = !payto.isKnown ? (
+ const accountPart = !payto.isKnown ? (
Account
}
value={payto.targetPath}
@@ -113,10 +117,17 @@ export function BankDetailsByPaytoType({
IBAN} value={payto.iban} />
) : undefined;
return (
-
-
Bank transfer details
+
+
Bank transfer details
- {firstPart}
+ {accountPart}
Exchange}
value={exchangeBaseUrl}
@@ -176,7 +187,7 @@ function Row({
)}
-
+
{name}
{literal ? (
diff --git a/packages/taler-wallet-webextension/src/components/Part.tsx b/packages/taler-wallet-webextension/src/components/Part.tsx
index 21c0f65dc..58165a349 100644
--- a/packages/taler-wallet-webextension/src/components/Part.tsx
+++ b/packages/taler-wallet-webextension/src/components/Part.tsx
@@ -14,33 +14,122 @@
GNU Taler; see the file COPYING. If not, see
*/
import { PaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
+import { styled } from "@linaria/react";
import { Fragment, h, VNode } from "preact";
-import { ExtraLargeText, LargeText, SmallLightText } from "./styled/index.js";
+import { useState } from "preact/hooks";
+import {
+ ExtraLargeText,
+ LargeText,
+ SmallBoldText,
+ SmallLightText,
+} from "./styled/index.js";
export type Kind = "positive" | "negative" | "neutral";
interface Props {
- title: VNode;
+ title: VNode | string;
text: VNode | string;
- kind: Kind;
+ kind?: Kind;
big?: boolean;
+ showSign?: boolean;
}
-export function Part({ text, title, kind, big }: Props): VNode {
+export function Part({
+ text,
+ title,
+ kind = "neutral",
+ big,
+ showSign,
+}: Props): VNode {
const Text = big ? ExtraLargeText : LargeText;
return (
- {title}
+ {title}
+ {!showSign || kind === "neutral"
+ ? undefined
+ : kind === "positive"
+ ? "+"
+ : "-"}
{text}
);
}
+const CollasibleBox = styled.div`
+ border: 1px solid black;
+ border-radius: 0.25em;
+ display: flex;
+ vertical-align: middle;
+ justify-content: space-between;
+ flex-direction: column;
+ /* margin: 0.5em; */
+ padding: 0.5em;
+ /* margin: 1em; */
+ /* width: 100%; */
+ /* color: #721c24; */
+ /* background: #f8d7da; */
+
+ & > div {
+ display: flex;
+ justify-content: space-between;
+ div {
+ margin-top: auto;
+ margin-bottom: auto;
+ }
+ & > button {
+ align-self: center;
+ font-size: 100%;
+ padding: 0;
+ height: 28px;
+ width: 28px;
+ }
+ }
+`;
+import arrowDown from "../svg/chevron-down.svg";
+
+export function PartCollapsible({ text, title, big, showSign }: Props): VNode {
+ const Text = big ? ExtraLargeText : LargeText;
+ const [collapsed, setCollapsed] = useState(true);
+
+ return (
+
+
+
{title}
+
{
+ setCollapsed((v) => !v);
+ }}
+ >
+
+
+
+ {/*
+
+ */}
+ {!collapsed && {text}
}
+
+ );
+}
+
interface PropsPayto {
payto: PaytoUri;
kind: Kind;
diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx
index 7517a1388..a531a15dc 100644
--- a/packages/taler-wallet-webextension/src/components/styled/index.tsx
+++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx
@@ -87,7 +87,7 @@ export const WalletBox = styled.div<{ noPadding?: boolean }>`
justify-content: space-between;
align-items: center;
& > * {
- width: 500px;
+ width: 600px;
}
& > section {
padding: ${({ noPadding }) => (noPadding ? "0px" : "8px")};
@@ -660,6 +660,12 @@ export const WarningText = styled.div`
export const SmallText = styled.div`
font-size: small;
`;
+
+export const SmallBoldText = styled.div`
+ font-size: small;
+ font-weight: bold;
+`;
+
export const LargeText = styled.div`
font-size: large;
`;
diff --git a/packages/taler-wallet-webextension/src/custom.d.ts b/packages/taler-wallet-webextension/src/custom.d.ts
index 521b824c7..711112ad8 100644
--- a/packages/taler-wallet-webextension/src/custom.d.ts
+++ b/packages/taler-wallet-webextension/src/custom.d.ts
@@ -13,7 +13,11 @@
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see
*/
-declare module "*.jpeg" {
+ declare module "*.jpeg" {
+ const content: any;
+ export default content;
+}
+declare module "*.jpg" {
const content: any;
export default content;
}
diff --git a/packages/taler-wallet-webextension/src/stories.tsx b/packages/taler-wallet-webextension/src/stories.tsx
index 9c0f69ec4..fd5d3c590 100644
--- a/packages/taler-wallet-webextension/src/stories.tsx
+++ b/packages/taler-wallet-webextension/src/stories.tsx
@@ -330,9 +330,11 @@ function Application(): VNode {
const hash = location.hash.substring(1);
const found = document.getElementById(hash);
if (found) {
- found.scrollIntoView({
- block: "center",
- });
+ setTimeout(() => {
+ found.scrollIntoView({
+ block: "center",
+ });
+ }, 10);
}
}
}, []);
diff --git a/packages/taler-wallet-webextension/src/test-utils.ts b/packages/taler-wallet-webextension/src/test-utils.ts
index eceda616f..9e219daa6 100644
--- a/packages/taler-wallet-webextension/src/test-utils.ts
+++ b/packages/taler-wallet-webextension/src/test-utils.ts
@@ -26,22 +26,27 @@ options.requestAnimationFrame = (fn: () => void) => {
export function createExample(
Component: FunctionalComponent,
- props: Partial,
+ props: Partial | (() => Partial),
): ComponentChildren {
+ //FIXME: props are evaluated on build time
+ // in some cases we want to evaluated the props on render time so we can get some relative timestamp
+ // check how we can build evaluatedProps in render time
+ const evaluatedProps = typeof props === "function" ? props() : props
const Render = (args: any): VNode => create(Component, args);
- Render.args = props;
+ Render.args = evaluatedProps;
return Render;
}
export function createExampleWithCustomContext(
Component: FunctionalComponent,
- props: Partial,
+ props: Partial | (() => Partial),
ContextProvider: FunctionalComponent,
contextProps: Partial,
): ComponentChildren {
+ const evaluatedProps = typeof props === "function" ? props() : props
const Render = (args: any): VNode => create(Component, args);
const WithContext = (args: any): VNode => create(ContextProvider, { ...contextProps, children: [Render(args)] } as any);
- WithContext.args = props
+ WithContext.args = evaluatedProps
return WithContext
}
diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
index f162543ae..493cdd1d7 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
@@ -30,6 +30,7 @@ import {
TransactionTip,
TransactionType,
TransactionWithdrawal,
+ WithdrawalDetails,
WithdrawalType,
} from "@gnu-taler/taler-util";
import { DevContextProviderForTesting } from "../context/devContext.js";
@@ -57,6 +58,8 @@ const commonTransaction = {
transactionId: "12",
} as TransactionCommon;
+import merchantIcon from "../../static-dev/merchant-icon-11.jpeg";
+
const exampleData = {
withdraw: {
...commonTransaction,
@@ -65,27 +68,34 @@ const exampleData = {
withdrawalDetails: {
confirmed: false,
reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
- exchangePaytoUris: ["payto://x-taler-bank/bank/account"],
+ exchangePaytoUris: ["payto://x-taler-bank/bank.demo.taler.net/Exchange"],
type: WithdrawalType.ManualTransfer,
},
} as TransactionWithdrawal,
payment: {
...commonTransaction,
- amountEffective: "KUDOS:11",
+ amountEffective: "KUDOS:12",
type: TransactionType.Payment,
info: {
contractTermsHash: "ASDZXCASD",
merchant: {
name: "the merchant",
+ logo: merchantIcon,
+ website: "https://www.themerchant.taler",
+ email: "contact@merchant.taler",
},
orderId: "2021.167-03NPY6MCYMVGT",
products: [],
summary: "Essay: Why the Devil's Advocate Doesn't Help Reach the Truth",
fulfillmentMessage: "",
+ // delivery_date: { t_s: 1 },
+ // delivery_location: {
+ // address_lines: [""],
+ // },
},
refundPending: undefined,
- totalRefundEffective: "USD:0",
- totalRefundRaw: "USD:0",
+ totalRefundEffective: "KUDOS:0",
+ totalRefundRaw: "KUDOS:0",
proposalId: "1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0",
status: PaymentStatus.Accepted,
} as TransactionPayment,
@@ -93,7 +103,7 @@ const exampleData = {
...commonTransaction,
type: TransactionType.Deposit,
depositGroupId: "#groupId",
- targetPaytoUri: "payto://x-taler-bank/bank/account",
+ targetPaytoUri: "payto://x-taler-bank/bank.demo.taler.net/Exchange",
} as TransactionDeposit,
refresh: {
...commonTransaction,
@@ -117,7 +127,7 @@ const exampleData = {
},
orderId: "2021.167-03NPY6MCYMVGT",
products: [],
- summary: "the summary",
+ summary: "Essay: Why the Devil's Advocate Doesn't Help Reach the Truth",
fulfillmentMessage: "",
},
refundPending: undefined,
@@ -143,20 +153,27 @@ export const Withdraw = createExample(TestedComponent, {
transaction: exampleData.withdraw,
});
-export const WithdrawOneMinuteAgo = createExample(TestedComponent, {
+export const WithdrawFiveMinutesAgo = createExample(TestedComponent, () => ({
transaction: {
...exampleData.withdraw,
- timestamp: TalerProtocolTimestamp.fromSeconds(new Date().getTime() - 60),
+ timestamp: TalerProtocolTimestamp.fromSeconds(
+ new Date().getTime() / 1000 - 60 * 5,
+ ),
},
-});
+}));
-export const WithdrawOneMinuteAgoAndPending = createExample(TestedComponent, {
- transaction: {
- ...exampleData.withdraw,
- timestamp: TalerProtocolTimestamp.fromSeconds(new Date().getTime() - 60),
- pending: true,
- },
-});
+export const WithdrawFiveMinutesAgoAndPending = createExample(
+ TestedComponent,
+ () => ({
+ transaction: {
+ ...exampleData.withdraw,
+ timestamp: TalerProtocolTimestamp.fromSeconds(
+ new Date().getTime() / 1000 - 60 * 5,
+ ),
+ pending: true,
+ },
+ }),
+);
export const WithdrawError = createExample(TestedComponent, {
transaction: {
@@ -177,17 +194,17 @@ export const WithdrawErrorInDevMode = createExampleInCustomContext(
{ value: true },
);
-export const WithdrawPendingManual = createExample(TestedComponent, {
+export const WithdrawPendingManual = createExample(TestedComponent, () => ({
transaction: {
...exampleData.withdraw,
withdrawalDetails: {
type: WithdrawalType.ManualTransfer,
exchangePaytoUris: ["payto://iban/asdasdasd"],
reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
- },
+ } as WithdrawalDetails,
pending: true,
},
-});
+}));
export const WithdrawPendingTalerBankUnconfirmed = createExample(
TestedComponent,
@@ -231,10 +248,95 @@ export const PaymentError = createExample(TestedComponent, {
},
});
-export const PaymentWithoutFee = createExample(TestedComponent, {
+export const PaymentWithRefund = createExample(TestedComponent, {
+ transaction: {
+ ...exampleData.payment,
+ amountRaw: "KUDOS:12",
+ totalRefundEffective: "KUDOS:1",
+ totalRefundRaw: "KUDOS:1",
+ },
+});
+
+export const PaymentWithDeliveryDate = createExample(TestedComponent, {
+ transaction: {
+ ...exampleData.payment,
+ amountRaw: "KUDOS:12",
+ info: {
+ ...exampleData.payment.info,
+ delivery_date: {
+ t_s: new Date().getTime() / 1000,
+ },
+ },
+ },
+});
+
+export const PaymentWithDeliveryAddr = createExample(TestedComponent, {
+ transaction: {
+ ...exampleData.payment,
+ amountRaw: "KUDOS:12",
+ info: {
+ ...exampleData.payment.info,
+ delivery_location: {
+ country: "Argentina",
+ street: "Elm Street",
+ district: "CABA",
+ post_code: "1101",
+ },
+ },
+ },
+});
+
+export const PaymentWithDeliveryFull = createExample(TestedComponent, {
+ transaction: {
+ ...exampleData.payment,
+ amountRaw: "KUDOS:12",
+ info: {
+ ...exampleData.payment.info,
+ delivery_date: {
+ t_s: new Date().getTime() / 1000,
+ },
+ delivery_location: {
+ country: "Argentina",
+ street: "Elm Street",
+ district: "CABA",
+ post_code: "1101",
+ },
+ },
+ },
+});
+
+export const PaymentWithRefundPending = createExample(TestedComponent, {
+ transaction: {
+ ...exampleData.payment,
+ amountRaw: "KUDOS:12",
+ refundPending: "KUDOS:3",
+ totalRefundEffective: "KUDOS:1",
+ totalRefundRaw: "KUDOS:1",
+ },
+});
+
+export const PaymentWithFeeAndRefund = createExample(TestedComponent, {
transaction: {
...exampleData.payment,
amountRaw: "KUDOS:11",
+ totalRefundEffective: "KUDOS:1",
+ totalRefundRaw: "KUDOS:1",
+ },
+});
+
+export const PaymentWithFeeAndRefundFee = createExample(TestedComponent, {
+ transaction: {
+ ...exampleData.payment,
+ amountRaw: "KUDOS:11",
+ totalRefundEffective: "KUDOS:1",
+ totalRefundRaw: "KUDOS:2",
+ },
+});
+
+export const PaymentWithoutFee = createExample(TestedComponent, {
+ transaction: {
+ ...exampleData.payment,
+ amountRaw: "KUDOS:12",
},
});
@@ -249,7 +351,7 @@ export const PaymentWithProducts = createExample(TestedComponent, {
...exampleData.payment,
info: {
...exampleData.payment.info,
- summary: "this order has 5 products",
+ summary: "summary of 5 products",
products: [
{
description: "t-shirt",
@@ -360,20 +462,3 @@ export const RefundError = createExample(TestedComponent, {
export const RefundPending = createExample(TestedComponent, {
transaction: { ...exampleData.refund, pending: true },
});
-
-export const RefundWithProducts = createExample(TestedComponent, {
- transaction: {
- ...exampleData.refund,
- info: {
- ...exampleData.refund.info,
- products: [
- {
- description: "t-shirt",
- },
- {
- description: "beer",
- },
- ],
- },
- } as TransactionRefund,
-});
diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
index 3377f98c7..9ccb353a9 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
@@ -16,14 +16,24 @@
import {
AbsoluteTime,
+ AmountJson,
Amounts,
+ Location,
NotificationType,
parsePaytoUri,
parsePayUri,
+ TalerProtocolTimestamp,
Transaction,
+ TransactionDeposit,
+ TransactionPayment,
+ TransactionRefresh,
+ TransactionRefund,
+ TransactionTip,
TransactionType,
+ TransactionWithdrawal,
WithdrawalType,
} from "@gnu-taler/taler-util";
+import { styled } from "@linaria/react";
import { differenceInSeconds } from "date-fns";
import { ComponentChildren, Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
@@ -33,15 +43,17 @@ import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType.js"
import { ErrorTalerOperation } from "../components/ErrorTalerOperation.js";
import { Loading } from "../components/Loading.js";
import { LoadingError } from "../components/LoadingError.js";
-import { Part, PartPayto } from "../components/Part.js";
+import { Kind, Part, PartCollapsible, PartPayto } from "../components/Part.js";
import {
Button,
+ ButtonBox,
ButtonDestructive,
ButtonPrimary,
CenteredDialog,
InfoBox,
ListOfProducts,
Overlay,
+ Row,
RowBorderGray,
SmallLightText,
SubTitle,
@@ -119,6 +131,14 @@ export interface WalletTransactionProps {
onBack: () => void;
}
+const PurchaseDetailsTable = styled.table`
+ width: 100%;
+
+ & > tr > td:nth-child(2n) {
+ text-align: right;
+ }
+`;
+
export function TransactionView({
transaction,
onDelete,
@@ -168,9 +188,7 @@ export function TransactionView({
)}
-
+
@@ -189,10 +207,8 @@ export function TransactionView({
}
if (transaction.type === TransactionType.Withdrawal) {
- const fee = Amounts.sub(
- Amounts.parseOrThrow(transaction.amountRaw),
- Amounts.parseOrThrow(transaction.amountEffective),
- ).amount;
+ const total = Amounts.parseOrThrow(transaction.amountEffective);
+ const chosen = Amounts.parseOrThrow(transaction.amountRaw);
return (
{confirmBeforeForget ? (
@@ -219,205 +235,125 @@ export function TransactionView({
) : undefined}
-
- Withdrawal
-
-
- {transaction.pending ? (
- transaction.withdrawalDetails.type ===
- WithdrawalType.ManualTransfer ? (
-
-
-
-
+
+ {transaction.exchangeBaseUrl}
+
+
+ {!transaction.pending ? undefined : transaction.withdrawalDetails
+ .type === WithdrawalType.ManualTransfer ? (
+
+
+
+
+ Make sure to use the correct subject, otherwise the money will
+ not arrive in this wallet.
+
+
+
+ ) : (
+
+ {!transaction.withdrawalDetails.confirmed &&
+ transaction.withdrawalDetails.bankConfirmationUrl ? (
+
+
- Make sure to use the correct subject, otherwise the money
- will not arrive in this wallet.
-
-
-
-
Total withdrawn}
- text={ }
- kind="positive"
- />
- Exchange fee}
- text={ }
- kind="negative"
- />
-
- ) : (
-
- {!transaction.withdrawalDetails.confirmed &&
- transaction.withdrawalDetails.bankConfirmationUrl ? (
-
-
- The bank is waiting for confirmation. Go to the
+ The bank did not yet confirmed the wire transfer. Go to the
+ {` `}
bank site
-
+ {" "}
+ and check there is no pending step.
-
- ) : undefined}
- {transaction.withdrawalDetails.confirmed && (
-
-
- Waiting for the coins to arrive
-
-
- )}
- Total withdrawn}
- text={ }
- kind="positive"
- />
- Chosen amount}
- text={ }
- kind="neutral"
- />
- Exchange fee}
- text={ }
- kind="negative"
- />
-
- )
- ) : (
-
- Total withdrawn}
- text={ }
- kind="positive"
- />
- Chosen amount}
- text={ }
- kind="neutral"
- />
- Exchange fee}
- text={ }
- kind="negative"
- />
+
+
+ ) : undefined}
+ {transaction.withdrawalDetails.confirmed && (
+
+
+ Bank has confirmed the wire transfer. Waiting for the exchange
+ to send the coins
+
+
+ )}
)}
Exchange}
- text={new URL(transaction.exchangeBaseUrl).hostname}
- kind="neutral"
+ title={Details }
+ text={ }
/>
);
}
- const showLargePic = (): void => {
- return;
- };
-
if (transaction.type === TransactionType.Payment) {
- const fee = Amounts.sub(
- Amounts.parseOrThrow(transaction.amountEffective),
- Amounts.parseOrThrow(transaction.amountRaw),
- ).amount;
-
- const refundFee = Amounts.sub(
- Amounts.parseOrThrow(transaction.totalRefundRaw),
- Amounts.parseOrThrow(transaction.totalRefundEffective),
- ).amount;
- const refunded = Amounts.isNonZero(
- Amounts.parseOrThrow(transaction.totalRefundRaw),
- );
const pendingRefund =
transaction.refundPending === undefined
? undefined
: Amounts.parseOrThrow(transaction.refundPending);
+
+ const total = Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountEffective),
+ Amounts.parseOrThrow(transaction.totalRefundEffective),
+ ).amount;
+
return (
-
- Payment
-
-
-
- Total paid}
- text={ }
+
- {Amounts.isNonZero(fee) && (
-
+ >
+ {transaction.info.fulfillmentUrl ? (
+
+ {transaction.info.summary}
+
+ ) : (
+ transaction.info.summary
+ )}
+
+
+ {pendingRefund !== undefined && Amounts.isNonZero(pendingRefund) && (
+
+
+ Merchant created a refund for this order but was not automatically
+ picked up.
+
Purchase amount}
- text={ }
- kind="neutral"
- />
- Purchase Fee}
- text={ }
- kind="negative"
- />
-
- )}
- {refunded && (
-
- Total refunded}
- text={ }
+ title={Offer }
+ text={ }
kind="positive"
/>
- {Amounts.isNonZero(refundFee) && (
-
- Refund amount}
- text={ }
- kind="neutral"
- />
- Refund fee}
- text={ }
- kind="negative"
- />
-
- )}
-
- )}
- {pendingRefund !== undefined && Amounts.isNonZero(pendingRefund) && (
- Refund pending}
- text={ }
- kind="positive"
- />
+
+
)}
Merchant}
@@ -425,268 +361,630 @@ export function TransactionView({
kind="neutral"
/>
Purchase}
- text={
- transaction.info.fulfillmentUrl ? (
-
- {transaction.info.summary}
-
- ) : (
- transaction.info.summary
- )
- }
+ title={Invoice ID }
+ text={transaction.info.orderId}
kind="neutral"
/>
Receipt}
- text={`#${transaction.info.orderId}`}
+ title={Details }
+ text={ }
kind="neutral"
/>
-
-
- {transaction.info.products && transaction.info.products.length > 0 && (
-
- {transaction.info.products.map((p, k) => (
-
-
-
-
-
- {p.quantity && p.quantity > 0 && (
-
- x {p.quantity} {p.unit}
-
- )}
-
{p.description}
-
-
- ))}
-
- )}
-
);
}
if (transaction.type === TransactionType.Deposit) {
- const fee = Amounts.sub(
- Amounts.parseOrThrow(transaction.amountEffective),
- Amounts.parseOrThrow(transaction.amountRaw),
- ).amount;
+ const total = Amounts.parseOrThrow(transaction.amountRaw);
const payto = parsePaytoUri(transaction.targetPaytoUri);
return (
-
- Deposit
-
-
-
+
+ {transaction.targetPaytoUri}
+
+ {payto && }
Total send}
- text={ }
+ title={Details }
+ text={ }
kind="neutral"
/>
- {Amounts.isNonZero(fee) && (
-
- Deposit amount}
- text={ }
- kind="positive"
- />
- Fee}
- text={ }
- kind="negative"
- />
-
- )}
- {payto && }
);
}
if (transaction.type === TransactionType.Refresh) {
- const fee = Amounts.sub(
+ const total = Amounts.sub(
Amounts.parseOrThrow(transaction.amountRaw),
Amounts.parseOrThrow(transaction.amountEffective),
).amount;
+
return (
-
- Refresh
-
-
-
- Total refresh}
- text={ }
+
+ {transaction.exchangeBaseUrl}
+
+ Details}
+ text={ }
/>
- {Amounts.isNonZero(fee) && (
-
- Refresh amount}
- text={ }
- kind="neutral"
- />
- Fee}
- text={ }
- kind="negative"
- />
-
- )}
);
}
if (transaction.type === TransactionType.Tip) {
- const fee = Amounts.sub(
- Amounts.parseOrThrow(transaction.amountRaw),
- Amounts.parseOrThrow(transaction.amountEffective),
- ).amount;
+ const total = Amounts.parseOrThrow(transaction.amountEffective);
+
return (
-
- Tip
-
-
-
- Total tip}
- text={ }
+
+ {transaction.merchantBaseUrl}
+
+ {/* Merchant}
+ text={transaction.info.merchant.name}
+ kind="neutral"
+ />
+ Invoice ID}
+ text={transaction.info.orderId}
+ kind="neutral"
+ /> */}
+ Details}
+ text={ }
/>
- {Amounts.isNonZero(fee) && (
-
- Received amount}
- text={ }
- kind="neutral"
- />
- Fee}
- text={ }
- kind="negative"
- />
-
- )}
);
}
if (transaction.type === TransactionType.Refund) {
- const fee = Amounts.sub(
- Amounts.parseOrThrow(transaction.amountRaw),
- Amounts.parseOrThrow(transaction.amountEffective),
- ).amount;
+ const total = Amounts.parseOrThrow(transaction.amountEffective);
return (
-
- Refund
-
-
-
- Total refund}
- text={ }
+
- {Amounts.isNonZero(fee) && (
-
- Refund amount}
- text={ }
- kind="neutral"
- />
- Fee}
- text={ }
- kind="negative"
- />
-
- )}
+ >
+ {transaction.info.summary}
+
+
Merchant}
text={transaction.info.merchant.name}
kind="neutral"
/>
-
Purchase}
+ title={Original order ID }
text={
- {transaction.info.summary}
+ {transaction.info.orderId}
}
kind="neutral"
/>
Receipt}
- text={`#${transaction.info.orderId}`}
+ title={Purchase summary }
+ text={transaction.info.summary}
kind="neutral"
/>
-
-
- {transaction.info.products && transaction.info.products.length > 0 && (
-
- {transaction.info.products.map((p, k) => (
-
-
-
-
-
- {p.quantity && p.quantity > 0 && (
-
- x {p.quantity} {p.unit}
-
- )}
-
{p.description}
-
-
- ))}
-
- )}
-
+ Details}
+ text={ }
+ />
);
}
return
;
}
+
+function DeliveryDetails({
+ date,
+ location,
+}: {
+ date: TalerProtocolTimestamp | undefined;
+ location: Location | undefined;
+}): VNode {
+ const { i18n } = useTranslationContext();
+ return (
+
+ {location && (
+
+ {location.country && (
+
+
+ Country
+
+ {location.country}
+
+ )}
+ {location.address_lines && (
+
+
+ Address lines
+
+ {location.address_lines}
+
+ )}
+ {location.building_number && (
+
+
+ Building number
+
+ {location.building_number}
+
+ )}
+ {location.building_name && (
+
+
+ Building name
+
+ {location.building_name}
+
+ )}
+ {location.street && (
+
+
+ Street
+
+ {location.street}
+
+ )}
+ {location.post_code && (
+
+
+ Post code
+
+ {location.post_code}
+
+ )}
+ {location.town_location && (
+
+
+ Town location
+
+ {location.town_location}
+
+ )}
+ {location.town && (
+
+
+ Town
+
+ {location.town}
+
+ )}
+ {location.district && (
+
+
+ District
+
+ {location.district}
+
+ )}
+ {location.country_subdivision && (
+
+
+ Country subdivision
+
+ {location.country_subdivision}
+
+ )}
+
+ )}
+
+ {!location || !date ? undefined : (
+
+
+
+
+
+ )}
+ {date && (
+
+
+ Date
+
+
+
+
+
+ )}
+
+ );
+}
+
+function PurchaseDetails({
+ transaction,
+}: {
+ transaction: TransactionPayment;
+}): VNode {
+ const { i18n } = useTranslationContext();
+
+ const partialFee = Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountEffective),
+ Amounts.parseOrThrow(transaction.amountRaw),
+ ).amount;
+
+ const refundRaw = Amounts.parseOrThrow(transaction.totalRefundRaw);
+
+ const refundFee = Amounts.sub(
+ refundRaw,
+ Amounts.parseOrThrow(transaction.totalRefundEffective),
+ ).amount;
+
+ const fee = Amounts.sum([partialFee, refundFee]).amount;
+
+ const hasProducts =
+ transaction.info.products && transaction.info.products.length > 0;
+
+ const hasShipping =
+ transaction.info.delivery_date !== undefined ||
+ transaction.info.delivery_location !== undefined;
+
+ const showLargePic = (): void => {
+ return;
+ };
+
+ const total = Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountEffective),
+ Amounts.parseOrThrow(transaction.totalRefundEffective),
+ ).amount;
+
+ return (
+
+
+ Price
+
+
+
+
+
+ {Amounts.isNonZero(refundRaw) && (
+
+ Refunded
+
+
+
+
+ )}
+ {Amounts.isNonZero(fee) && (
+
+ Transaction fees
+
+
+
+
+ )}
+
+
+
+
+
+
+ Total
+
+
+
+
+ {hasProducts && (
+
+
+ Products}
+ text={
+
+ {transaction.info.products?.map((p, k) => (
+
+
+
+
+
+ {p.quantity && p.quantity > 0 && (
+
+ x {p.quantity} {p.unit}
+
+ )}
+
{p.description}
+
+
+ ))}
+
+ }
+ />
+
+
+ )}
+ {hasShipping && (
+
+
+ Delivery}
+ text={
+
+ }
+ />
+
+
+ )}
+
+ );
+}
+
+function RefundDetails({
+ transaction,
+}: {
+ transaction: TransactionRefund;
+}): VNode {
+ const { i18n } = useTranslationContext();
+
+ const fee = Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountRaw),
+ Amounts.parseOrThrow(transaction.amountEffective),
+ ).amount;
+
+ return (
+
+
+ Amount
+
+
+
+
+
+ {Amounts.isNonZero(fee) && (
+
+ Transaction fees
+
+
+
+
+ )}
+
+
+
+
+
+
+ Total
+
+
+
+
+
+ );
+}
+
+function DepositDetails({
+ transaction,
+}: {
+ transaction: TransactionDeposit;
+}): VNode {
+ const { i18n } = useTranslationContext();
+
+ const fee = Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountRaw),
+ Amounts.parseOrThrow(transaction.amountEffective),
+ ).amount;
+
+ return (
+
+
+ Amount
+
+
+
+
+
+ {Amounts.isNonZero(fee) && (
+
+ Transaction fees
+
+
+
+
+ )}
+
+
+
+
+
+
+ Total transfer
+
+
+
+
+
+ );
+}
+function RefreshDetails({
+ transaction,
+}: {
+ transaction: TransactionRefresh;
+}): VNode {
+ const { i18n } = useTranslationContext();
+
+ const fee = Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountRaw),
+ Amounts.parseOrThrow(transaction.amountEffective),
+ ).amount;
+
+ return (
+
+
+ Amount
+
+
+
+
+
+
+
+
+
+
+ Transaction fees
+
+
+
+
+
+ );
+}
+
+function TipDetails({ transaction }: { transaction: TransactionTip }): VNode {
+ const { i18n } = useTranslationContext();
+
+ const fee = Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountRaw),
+ Amounts.parseOrThrow(transaction.amountEffective),
+ ).amount;
+
+ return (
+
+
+ Amount
+
+
+
+
+
+ {Amounts.isNonZero(fee) && (
+
+ Transaction fees
+
+
+
+
+ )}
+
+
+
+
+
+
+ Total
+
+
+
+
+
+ );
+}
+
+function WithdrawDetails({
+ transaction,
+}: {
+ transaction: TransactionWithdrawal;
+}): VNode {
+ const { i18n } = useTranslationContext();
+
+ const fee = Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountRaw),
+ Amounts.parseOrThrow(transaction.amountEffective),
+ ).amount;
+
+ return (
+
+
+ Withdraw
+
+
+
+
+
+ {Amounts.isNonZero(fee) && (
+
+ Transaction fees
+
+
+
+
+ )}
+
+
+
+
+
+
+ Total
+
+
+
+
+
+ );
+}
+
+function Header({
+ timestamp,
+ total,
+ children,
+ kind,
+ type,
+}: {
+ timestamp: TalerProtocolTimestamp;
+ total: AmountJson;
+ children: ComponentChildren;
+ kind: Kind;
+ type: string;
+}): VNode {
+ return (
+
+
+ {children}
+
+
+
+
+ }
+ kind={kind}
+ showSign
+ />
+
+
+
+ );
+}
diff --git a/packages/taler-wallet-webextension/static-dev/merchant-icon-11.jpeg b/packages/taler-wallet-webextension/static-dev/merchant-icon-11.jpeg
new file mode 100644
index 000000000..1777936c8
Binary files /dev/null and b/packages/taler-wallet-webextension/static-dev/merchant-icon-11.jpeg differ