gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: transaction details template


From: gnunet
Subject: [taler-wallet-core] branch master updated: transaction details template
Date: Thu, 26 May 2022 20:57:21 +0200

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new 24162c10 transaction details template
24162c10 is described below

commit 24162c1086c017305253c78280a82bfa9a572b1e
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Thu May 26 15:55:14 2022 -0300

    transaction details template
    
    mayor change in the template of the transaction details for every
    transaction
    more work needs to be done in wallet core for tip and refund to show
    more information about the merchant like logo and website
---
 packages/taler-util/src/talerTypes.ts              |    3 +
 packages/taler-util/src/transactionsTypes.ts       |   12 +
 .../build-fast-with-linaria.mjs                    |    1 +
 .../src/components/Amount.tsx                      |    2 +-
 .../src/components/BalanceTable.tsx                |    2 +-
 .../src/components/BankDetailsByPaytoType.tsx      |   75 +-
 .../src/components/Part.tsx                        |   99 +-
 .../src/components/styled/index.tsx                |    8 +-
 packages/taler-wallet-webextension/src/custom.d.ts |    6 +-
 packages/taler-wallet-webextension/src/stories.tsx |    8 +-
 .../taler-wallet-webextension/src/test-utils.ts    |   13 +-
 .../src/wallet/Transaction.stories.tsx             |  161 ++-
 .../src/wallet/Transaction.tsx                     | 1044 +++++++++++++-------
 .../static-dev/merchant-icon-11.jpeg               |  Bin 0 -> 60184 bytes
 14 files changed, 975 insertions(+), 459 deletions(-)

diff --git a/packages/taler-util/src/talerTypes.ts 
b/packages/taler-util/src/talerTypes.ts
index d9213ef5..7fc3fcba 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 37c1c7ef..dcaa5667 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 f6de6788..41747a74 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 c41f7faf..b415a30c 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 (
     <Fragment>
-      {amount} {aj.currency}
+      {amount}&nbsp;{aj.currency}
     </Fragment>
   );
 }
diff --git a/packages/taler-wallet-webextension/src/components/BalanceTable.tsx 
b/packages/taler-wallet-webextension/src/components/BalanceTable.tsx
index e67fb6b4..a2c91f4a 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)}
             </td>
           </tr>
         );
diff --git 
a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx 
b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
index 185021bc..3a2a12c7 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 (
-      <section style={{ textAlign: "left" }}>
+      <section
+        style={{
+          textAlign: "left",
+          border: "solid 1px black",
+          padding: 8,
+          borderRadius: 4,
+        }}
+      >
+        <p style={{ marginTop: 0 }}>Bitcoin transfer details</p>
         <p>
           <i18n.Translate>
-            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.
           </i18n.Translate>
         </p>
+        <Row
+          literal
+          name={<i18n.Translate>Reserve</i18n.Translate>}
+          value={subject}
+        />
+
         <p>
           <i18n.Translate>
             In bitcoincore wallet use &apos;Add Recipient&apos; button to add
             two additional recipient and copy addresses and amounts
           </i18n.Translate>
-          <ul>
-            <li>
-              {payto.targetPath} {Amounts.stringifyValue(amount)} BTC
-            </li>
-            {payto.segwitAddrs.map((addr, i) => (
-              <li key={i}>
-                {addr} {Amounts.stringifyValue(min)} BTC
-              </li>
-            ))}
-          </ul>
-          <i18n.Translate>
-            In Electrum wallet paste the following three lines in &apos;Pay
-            to&apos; field :
-          </i18n.Translate>
-          <ul>
-            <li>
-              {payto.targetPath},{Amounts.stringifyValue(amount)}
-            </li>
-            {payto.segwitAddrs.map((addr, i) => (
-              <li key={i}>
-                {addr} {Amounts.stringifyValue(min)} BTC
-              </li>
-            ))}
-          </ul>
+        </p>
+        <table>
+          <tr>
+            <td>{payto.targetPath}</td>
+            <td>{Amounts.stringifyValue(amount)} BTC</td>
+          </tr>
+          {payto.segwitAddrs.map((addr, i) => (
+            <tr key={i}>
+              <td>{addr}</td>
+              <td>{Amounts.stringifyValue(min)} BTC</td>
+            </tr>
+          ))}
+        </table>
+        <p>
           <i18n.Translate>
             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 ? (
     <Row
       name={<i18n.Translate>Account</i18n.Translate>}
       value={payto.targetPath}
@@ -113,10 +117,17 @@ export function BankDetailsByPaytoType({
     <Row name={<i18n.Translate>IBAN</i18n.Translate>} value={payto.iban} />
   ) : undefined;
   return (
-    <div style={{ textAlign: "left" }}>
-      <p>Bank transfer details</p>
+    <div
+      style={{
+        textAlign: "left",
+        border: "solid 1px black",
+        padding: 8,
+        borderRadius: 4,
+      }}
+    >
+      <p style={{ marginTop: 0 }}>Bank transfer details</p>
       <table>
-        {firstPart}
+        {accountPart}
         <Row
           name={<i18n.Translate>Exchange</i18n.Translate>}
           value={exchangeBaseUrl}
@@ -176,7 +187,7 @@ function Row({
           </TooltipRight>
         )}
       </td>
-      <td>
+      <td style={{ paddingRight: 8 }}>
         <b>{name}</b>
       </td>
       {literal ? (
diff --git a/packages/taler-wallet-webextension/src/components/Part.tsx 
b/packages/taler-wallet-webextension/src/components/Part.tsx
index 21c0f65d..58165a34 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 <http://www.gnu.org/licenses/>
  */
 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 (
     <div style={{ margin: "1em" }}>
-      <SmallLightText style={{ margin: ".5em" }}>{title}</SmallLightText>
+      <SmallBoldText style={{ marginBottom: "1em" }}>{title}</SmallBoldText>
       <Text
         style={{
           color:
             kind == "positive" ? "green" : kind == "negative" ? "red" : 
"black",
+          fontWeight: "lighten",
         }}
       >
+        {!showSign || kind === "neutral"
+          ? undefined
+          : kind === "positive"
+          ? "+"
+          : "-"}
         {text}
       </Text>
     </div>
   );
 }
 
+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 (
+    <CollasibleBox>
+      <div>
+        <SmallBoldText>{title}</SmallBoldText>
+        <button
+          onClick={() => {
+            setCollapsed((v) => !v);
+          }}
+        >
+          <div
+            style={{
+              transform: !collapsed ? "scaleY(-1)" : undefined,
+              height: 24,
+            }}
+            dangerouslySetInnerHTML={{ __html: arrowDown }}
+          />
+        </button>
+      </div>
+      {/* <SmallBoldText
+        style={{
+          paddingBottom: "1em",
+          paddingTop: "1em",
+          paddingLeft: "1em",
+          border: "black solid 1px",
+        }}
+      >
+        
+      </SmallBoldText> */}
+      {!collapsed && <div style={{ display: "block" }}>{text}</div>}
+    </CollasibleBox>
+  );
+}
+
 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 7517a138..a531a15d 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 521b824c..711112ad 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 <http://www.gnu.org/licenses/>
  */
-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 9c0f69ec..fd5d3c59 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 eceda616..9e219daa 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<Props>(
   Component: FunctionalComponent<Props>,
-  props: Partial<Props>,
+  props: Partial<Props> | (() => Partial<Props>),
 ): 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<Props, ContextProps>(
   Component: FunctionalComponent<Props>,
-  props: Partial<Props>,
+  props: Partial<Props> | (() => Partial<Props>),
   ContextProvider: FunctionalComponent<ContextProps>,
   contextProps: Partial<ContextProps>,
 ): 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 f162543a..493cdd1d 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 3377f98c..9ccb353a 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({
             </WarningBox>
           )}
         </section>
-        <section>
-          <div style={{ textAlign: "center" }}>{children}</div>
-        </section>
+        <section>{children}</section>
         <footer>
           <div />
           <div>
@@ -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 (
       <TransactionTemplate>
         {confirmBeforeForget ? (
@@ -219,205 +235,125 @@ export function TransactionView({
             </CenteredDialog>
           </Overlay>
         ) : undefined}
-        <SubTitle>
-          <i18n.Translate>Withdrawal</i18n.Translate>
-        </SubTitle>
-        <Time
-          timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)}
-          format="dd MMMM yyyy, HH:mm"
-        />
-        {transaction.pending ? (
-          transaction.withdrawalDetails.type ===
-          WithdrawalType.ManualTransfer ? (
-            <Fragment>
-              <BankDetailsByPaytoType
-                amount={Amounts.parseOrThrow(transaction.amountRaw)}
-                exchangeBaseUrl={transaction.exchangeBaseUrl}
-                payto={parsePaytoUri(
-                  transaction.withdrawalDetails.exchangePaytoUris[0],
-                )}
-                subject={transaction.withdrawalDetails.reservePub}
-              />
-              <p>
-                <WarningBox>
-                  <i18n.Translate>
-                    Make sure to use the correct subject, otherwise the money
-                    will not arrive in this wallet.
-                  </i18n.Translate>
-                </WarningBox>
-              </p>
-              <Part
-                big
-                title={<i18n.Translate>Total withdrawn</i18n.Translate>}
-                text={<Amount value={transaction.amountEffective} />}
-                kind="positive"
-              />
-              <Part
-                big
-                title={<i18n.Translate>Exchange fee</i18n.Translate>}
-                text={<Amount value={fee} />}
-                kind="negative"
-              />
-            </Fragment>
-          ) : (
-            <Fragment>
-              {!transaction.withdrawalDetails.confirmed &&
-              transaction.withdrawalDetails.bankConfirmationUrl ? (
-                <InfoBox>
+        <Header
+          timestamp={transaction.timestamp}
+          type={i18n.str`Withdrawal`}
+          total={total}
+          kind="positive"
+        >
+          {transaction.exchangeBaseUrl}
+        </Header>
+
+        {!transaction.pending ? undefined : transaction.withdrawalDetails
+            .type === WithdrawalType.ManualTransfer ? (
+          <Fragment>
+            <BankDetailsByPaytoType
+              amount={chosen}
+              exchangeBaseUrl={transaction.exchangeBaseUrl}
+              payto={parsePaytoUri(
+                transaction.withdrawalDetails.exchangePaytoUris[0],
+              )}
+              subject={transaction.withdrawalDetails.reservePub}
+            />
+            <WarningBox>
+              <i18n.Translate>
+                Make sure to use the correct subject, otherwise the money will
+                not arrive in this wallet.
+              </i18n.Translate>
+            </WarningBox>
+          </Fragment>
+        ) : (
+          <Fragment>
+            {!transaction.withdrawalDetails.confirmed &&
+            transaction.withdrawalDetails.bankConfirmationUrl ? (
+              <InfoBox>
+                <div style={{ display: "block" }}>
                   <i18n.Translate>
-                    The bank is waiting for confirmation. Go to the
+                    The bank did not yet confirmed the wire transfer. Go to the
+                    {` `}
                     <a
                       href={transaction.withdrawalDetails.bankConfirmationUrl}
                       target="_blank"
                       rel="noreferrer"
+                      style={{ display: "inline" }}
                     >
                       <i18n.Translate>bank site</i18n.Translate>
-                    </a>
-                  </i18n.Translate>
-                </InfoBox>
-              ) : undefined}
-              {transaction.withdrawalDetails.confirmed && (
-                <InfoBox>
-                  <i18n.Translate>
-                    Waiting for the coins to arrive
+                    </a>{" "}
+                    and check there is no pending step.
                   </i18n.Translate>
-                </InfoBox>
-              )}
-              <Part
-                big
-                title={<i18n.Translate>Total withdrawn</i18n.Translate>}
-                text={<Amount value={transaction.amountEffective} />}
-                kind="positive"
-              />
-              <Part
-                big
-                title={<i18n.Translate>Chosen amount</i18n.Translate>}
-                text={<Amount value={transaction.amountRaw} />}
-                kind="neutral"
-              />
-              <Part
-                big
-                title={<i18n.Translate>Exchange fee</i18n.Translate>}
-                text={<Amount value={fee} />}
-                kind="negative"
-              />
-            </Fragment>
-          )
-        ) : (
-          <Fragment>
-            <Part
-              big
-              title={<i18n.Translate>Total withdrawn</i18n.Translate>}
-              text={<Amount value={transaction.amountEffective} />}
-              kind="positive"
-            />
-            <Part
-              big
-              title={<i18n.Translate>Chosen amount</i18n.Translate>}
-              text={<Amount value={transaction.amountRaw} />}
-              kind="neutral"
-            />
-            <Part
-              big
-              title={<i18n.Translate>Exchange fee</i18n.Translate>}
-              text={<Amount value={fee} />}
-              kind="negative"
-            />
+                </div>
+              </InfoBox>
+            ) : undefined}
+            {transaction.withdrawalDetails.confirmed && (
+              <InfoBox>
+                <i18n.Translate>
+                  Bank has confirmed the wire transfer. Waiting for the 
exchange
+                  to send the coins
+                </i18n.Translate>
+              </InfoBox>
+            )}
           </Fragment>
         )}
         <Part
-          title={<i18n.Translate>Exchange</i18n.Translate>}
-          text={new URL(transaction.exchangeBaseUrl).hostname}
-          kind="neutral"
+          title={<i18n.Translate>Details</i18n.Translate>}
+          text={<WithdrawDetails transaction={transaction} />}
         />
       </TransactionTemplate>
     );
   }
 
-  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 (
       <TransactionTemplate>
-        <SubTitle>
-          <i18n.Translate>Payment</i18n.Translate>
-        </SubTitle>
-        <Time
-          timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)}
-          format="dd MMMM yyyy, HH:mm"
-        />
-        <br />
-        <Part
-          big
-          title={<i18n.Translate>Total paid</i18n.Translate>}
-          text={<Amount value={transaction.amountEffective} />}
+        <Header
+          timestamp={transaction.timestamp}
+          total={total}
+          type={i18n.str`Payment`}
           kind="negative"
-        />
-        {Amounts.isNonZero(fee) && (
-          <Fragment>
-            <Part
-              big
-              title={<i18n.Translate>Purchase amount</i18n.Translate>}
-              text={<Amount value={transaction.amountRaw} />}
-              kind="neutral"
-            />
-            <Part
-              title={<i18n.Translate>Purchase Fee</i18n.Translate>}
-              text={<Amount value={fee} />}
-              kind="negative"
-            />
-          </Fragment>
-        )}
-        {refunded && (
-          <Fragment>
+        >
+          {transaction.info.fulfillmentUrl ? (
+            <a
+              href={transaction.info.fulfillmentUrl}
+              target="_bank"
+              rel="noreferrer"
+            >
+              {transaction.info.summary}
+            </a>
+          ) : (
+            transaction.info.summary
+          )}
+        </Header>
+        <br />
+        {pendingRefund !== undefined && Amounts.isNonZero(pendingRefund) && (
+          <InfoBox>
+            <i18n.Translate>
+              Merchant created a refund for this order but was not 
automatically
+              picked up.
+            </i18n.Translate>
             <Part
-              big
-              title={<i18n.Translate>Total refunded</i18n.Translate>}
-              text={<Amount value={transaction.totalRefundEffective} />}
+              title={<i18n.Translate>Offer</i18n.Translate>}
+              text={<Amount value={pendingRefund} />}
               kind="positive"
             />
-            {Amounts.isNonZero(refundFee) && (
-              <Fragment>
-                <Part
-                  big
-                  title={<i18n.Translate>Refund amount</i18n.Translate>}
-                  text={<Amount value={transaction.totalRefundRaw} />}
-                  kind="neutral"
-                />
-                <Part
-                  title={<i18n.Translate>Refund fee</i18n.Translate>}
-                  text={<Amount value={refundFee} />}
-                  kind="negative"
-                />
-              </Fragment>
-            )}
-          </Fragment>
-        )}
-        {pendingRefund !== undefined && Amounts.isNonZero(pendingRefund) && (
-          <Part
-            big
-            title={<i18n.Translate>Refund pending</i18n.Translate>}
-            text={<Amount value={pendingRefund} />}
-            kind="positive"
-          />
+            <div>
+              <div />
+              <div>
+                <ButtonPrimary>
+                  <i18n.Translate>Accept</i18n.Translate>
+                </ButtonPrimary>
+              </div>
+            </div>
+          </InfoBox>
         )}
         <Part
           title={<i18n.Translate>Merchant</i18n.Translate>}
@@ -425,268 +361,630 @@ export function TransactionView({
           kind="neutral"
         />
         <Part
-          title={<i18n.Translate>Purchase</i18n.Translate>}
-          text={
-            transaction.info.fulfillmentUrl ? (
-              <a
-                href={transaction.info.fulfillmentUrl}
-                target="_bank"
-                rel="noreferrer"
-              >
-                {transaction.info.summary}
-              </a>
-            ) : (
-              transaction.info.summary
-            )
-          }
+          title={<i18n.Translate>Invoice ID</i18n.Translate>}
+          text={transaction.info.orderId}
           kind="neutral"
         />
         <Part
-          title={<i18n.Translate>Receipt</i18n.Translate>}
-          text={`#${transaction.info.orderId}`}
+          title={<i18n.Translate>Details</i18n.Translate>}
+          text={<PurchaseDetails transaction={transaction} />}
           kind="neutral"
         />
-
-        <div>
-          {transaction.info.products && transaction.info.products.length > 0 
&& (
-            <ListOfProducts>
-              {transaction.info.products.map((p, k) => (
-                <RowBorderGray key={k}>
-                  <a href="#" onClick={showLargePic}>
-                    <img src={p.image ? p.image : emptyImg} />
-                  </a>
-                  <div>
-                    {p.quantity && p.quantity > 0 && (
-                      <SmallLightText>
-                        x {p.quantity} {p.unit}
-                      </SmallLightText>
-                    )}
-                    <div>{p.description}</div>
-                  </div>
-                </RowBorderGray>
-              ))}
-            </ListOfProducts>
-          )}
-        </div>
       </TransactionTemplate>
     );
   }
 
   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 (
       <TransactionTemplate>
-        <SubTitle>
-          <i18n.Translate>Deposit</i18n.Translate>
-        </SubTitle>
-        <Time
-          timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)}
-          format="dd MMMM yyyy, HH:mm"
-        />
-        <br />
+        <Header
+          timestamp={transaction.timestamp}
+          type={i18n.str`Deposit`}
+          total={total}
+          kind="negative"
+        >
+          {transaction.targetPaytoUri}
+        </Header>
+        {payto && <PartPayto big payto={payto} kind="neutral" />}
         <Part
-          big
-          title={<i18n.Translate>Total send</i18n.Translate>}
-          text={<Amount value={transaction.amountEffective} />}
+          title={<i18n.Translate>Details</i18n.Translate>}
+          text={<DepositDetails transaction={transaction} />}
           kind="neutral"
         />
-        {Amounts.isNonZero(fee) && (
-          <Fragment>
-            <Part
-              big
-              title={<i18n.Translate>Deposit amount</i18n.Translate>}
-              text={<Amount value={transaction.amountRaw} />}
-              kind="positive"
-            />
-            <Part
-              big
-              title={<i18n.Translate>Fee</i18n.Translate>}
-              text={<Amount value={fee} />}
-              kind="negative"
-            />
-          </Fragment>
-        )}
-        {payto && <PartPayto big payto={payto} kind="neutral" />}
       </TransactionTemplate>
     );
   }
 
   if (transaction.type === TransactionType.Refresh) {
-    const fee = Amounts.sub(
+    const total = Amounts.sub(
       Amounts.parseOrThrow(transaction.amountRaw),
       Amounts.parseOrThrow(transaction.amountEffective),
     ).amount;
+
     return (
       <TransactionTemplate>
-        <SubTitle>
-          <i18n.Translate>Refresh</i18n.Translate>
-        </SubTitle>
-        <Time
-          timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)}
-          format="dd MMMM yyyy, HH:mm"
-        />
-        <br />
-        <Part
-          big
-          title={<i18n.Translate>Total refresh</i18n.Translate>}
-          text={<Amount value={transaction.amountEffective} />}
+        <Header
+          timestamp={transaction.timestamp}
+          type={i18n.str`Refresh`}
+          total={total}
           kind="negative"
+        >
+          {transaction.exchangeBaseUrl}
+        </Header>
+        <Part
+          title={<i18n.Translate>Details</i18n.Translate>}
+          text={<RefreshDetails transaction={transaction} />}
         />
-        {Amounts.isNonZero(fee) && (
-          <Fragment>
-            <Part
-              big
-              title={<i18n.Translate>Refresh amount</i18n.Translate>}
-              text={<Amount value={transaction.amountRaw} />}
-              kind="neutral"
-            />
-            <Part
-              big
-              title={<i18n.Translate>Fee</i18n.Translate>}
-              text={<Amount value={fee} />}
-              kind="negative"
-            />
-          </Fragment>
-        )}
       </TransactionTemplate>
     );
   }
 
   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 (
       <TransactionTemplate>
-        <SubTitle>
-          <i18n.Translate>Tip</i18n.Translate>
-        </SubTitle>
-        <Time
-          timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)}
-          format="dd MMMM yyyy, HH:mm"
+        <Header
+          timestamp={transaction.timestamp}
+          type={i18n.str`Tip`}
+          total={total}
+          kind="positive"
+        >
+          {transaction.merchantBaseUrl}
+        </Header>
+        {/* <Part
+          title={<i18n.Translate>Merchant</i18n.Translate>}
+          text={transaction.info.merchant.name}
+          kind="neutral"
         />
-        <br />
         <Part
-          big
-          title={<i18n.Translate>Total tip</i18n.Translate>}
-          text={<Amount value={transaction.amountRaw} />}
-          kind="positive"
+          title={<i18n.Translate>Invoice ID</i18n.Translate>}
+          text={transaction.info.orderId}
+          kind="neutral"
+        /> */}
+        <Part
+          title={<i18n.Translate>Details</i18n.Translate>}
+          text={<TipDetails transaction={transaction} />}
         />
-        {Amounts.isNonZero(fee) && (
-          <Fragment>
-            <Part
-              big
-              title={<i18n.Translate>Received amount</i18n.Translate>}
-              text={<Amount value={transaction.amountEffective} />}
-              kind="neutral"
-            />
-            <Part
-              big
-              title={<i18n.Translate>Fee</i18n.Translate>}
-              text={<Amount value={fee} />}
-              kind="negative"
-            />
-          </Fragment>
-        )}
       </TransactionTemplate>
     );
   }
 
   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 (
       <TransactionTemplate>
-        <SubTitle>
-          <i18n.Translate>Refund</i18n.Translate>
-        </SubTitle>
-        <Time
-          timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)}
-          format="dd MMMM yyyy, HH:mm"
-        />
-        <br />
-        <Part
-          big
-          title={<i18n.Translate>Total refund</i18n.Translate>}
-          text={<Amount value={transaction.amountEffective} />}
+        <Header
+          timestamp={transaction.timestamp}
+          type={i18n.str`Refund`}
+          total={total}
           kind="positive"
-        />
-        {Amounts.isNonZero(fee) && (
-          <Fragment>
-            <Part
-              big
-              title={<i18n.Translate>Refund amount</i18n.Translate>}
-              text={<Amount value={transaction.amountRaw} />}
-              kind="neutral"
-            />
-            <Part
-              big
-              title={<i18n.Translate>Fee</i18n.Translate>}
-              text={<Amount value={fee} />}
-              kind="negative"
-            />
-          </Fragment>
-        )}
+        >
+          {transaction.info.summary}
+        </Header>
+
         <Part
           title={<i18n.Translate>Merchant</i18n.Translate>}
           text={transaction.info.merchant.name}
           kind="neutral"
         />
-
         <Part
-          title={<i18n.Translate>Purchase</i18n.Translate>}
+          title={<i18n.Translate>Original order ID</i18n.Translate>}
           text={
             <a
               href={Pages.balance_transaction.replace(
                 ":tid",
                 transaction.refundedTransactionId,
               )}
-              // href={transaction.info.fulfillmentUrl}
-              // target="_bank"
-              // rel="noreferrer"
             >
-              {transaction.info.summary}
+              {transaction.info.orderId}
             </a>
           }
           kind="neutral"
         />
         <Part
-          title={<i18n.Translate>Receipt</i18n.Translate>}
-          text={`#${transaction.info.orderId}`}
+          title={<i18n.Translate>Purchase summary</i18n.Translate>}
+          text={transaction.info.summary}
           kind="neutral"
         />
-
-        <div>
-          {transaction.info.products && transaction.info.products.length > 0 
&& (
-            <ListOfProducts>
-              {transaction.info.products.map((p, k) => (
-                <RowBorderGray key={k}>
-                  <a href="#" onClick={showLargePic}>
-                    <img src={p.image ? p.image : emptyImg} />
-                  </a>
-                  <div>
-                    {p.quantity && p.quantity > 0 && (
-                      <SmallLightText>
-                        x {p.quantity} {p.unit}
-                      </SmallLightText>
-                    )}
-                    <div>{p.description}</div>
-                  </div>
-                </RowBorderGray>
-              ))}
-            </ListOfProducts>
-          )}
-        </div>
+        <Part
+          title={<i18n.Translate>Details</i18n.Translate>}
+          text={<RefundDetails transaction={transaction} />}
+        />
       </TransactionTemplate>
     );
   }
 
   return <div />;
 }
+
+function DeliveryDetails({
+  date,
+  location,
+}: {
+  date: TalerProtocolTimestamp | undefined;
+  location: Location | undefined;
+}): VNode {
+  const { i18n } = useTranslationContext();
+  return (
+    <PurchaseDetailsTable>
+      {location && (
+        <Fragment>
+          {location.country && (
+            <tr>
+              <td>
+                <i18n.Translate>Country</i18n.Translate>
+              </td>
+              <td>{location.country}</td>
+            </tr>
+          )}
+          {location.address_lines && (
+            <tr>
+              <td>
+                <i18n.Translate>Address lines</i18n.Translate>
+              </td>
+              <td>{location.address_lines}</td>
+            </tr>
+          )}
+          {location.building_number && (
+            <tr>
+              <td>
+                <i18n.Translate>Building number</i18n.Translate>
+              </td>
+              <td>{location.building_number}</td>
+            </tr>
+          )}
+          {location.building_name && (
+            <tr>
+              <td>
+                <i18n.Translate>Building name</i18n.Translate>
+              </td>
+              <td>{location.building_name}</td>
+            </tr>
+          )}
+          {location.street && (
+            <tr>
+              <td>
+                <i18n.Translate>Street</i18n.Translate>
+              </td>
+              <td>{location.street}</td>
+            </tr>
+          )}
+          {location.post_code && (
+            <tr>
+              <td>
+                <i18n.Translate>Post code</i18n.Translate>
+              </td>
+              <td>{location.post_code}</td>
+            </tr>
+          )}
+          {location.town_location && (
+            <tr>
+              <td>
+                <i18n.Translate>Town location</i18n.Translate>
+              </td>
+              <td>{location.town_location}</td>
+            </tr>
+          )}
+          {location.town && (
+            <tr>
+              <td>
+                <i18n.Translate>Town</i18n.Translate>
+              </td>
+              <td>{location.town}</td>
+            </tr>
+          )}
+          {location.district && (
+            <tr>
+              <td>
+                <i18n.Translate>District</i18n.Translate>
+              </td>
+              <td>{location.district}</td>
+            </tr>
+          )}
+          {location.country_subdivision && (
+            <tr>
+              <td>
+                <i18n.Translate>Country subdivision</i18n.Translate>
+              </td>
+              <td>{location.country_subdivision}</td>
+            </tr>
+          )}
+        </Fragment>
+      )}
+
+      {!location || !date ? undefined : (
+        <tr>
+          <td colSpan={2}>
+            <hr />
+          </td>
+        </tr>
+      )}
+      {date && (
+        <Fragment>
+          <tr>
+            <td>Date</td>
+            <td>
+              <Time
+                timestamp={AbsoluteTime.fromTimestamp(date)}
+                format="dd MMMM yyyy, HH:mm"
+              />
+            </td>
+          </tr>
+        </Fragment>
+      )}
+    </PurchaseDetailsTable>
+  );
+}
+
+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 (
+    <PurchaseDetailsTable>
+      <tr>
+        <td>Price</td>
+        <td>
+          <Amount value={transaction.amountRaw} />
+        </td>
+      </tr>
+
+      {Amounts.isNonZero(refundRaw) && (
+        <tr>
+          <td>Refunded</td>
+          <td>
+            <Amount value={transaction.totalRefundEffective} />
+          </td>
+        </tr>
+      )}
+      {Amounts.isNonZero(fee) && (
+        <tr>
+          <td>Transaction fees</td>
+          <td>
+            <Amount value={fee} />
+          </td>
+        </tr>
+      )}
+      <tr>
+        <td colSpan={2}>
+          <hr />
+        </td>
+      </tr>
+      <tr>
+        <td>Total</td>
+        <td>
+          <Amount value={total} />
+        </td>
+      </tr>
+      {hasProducts && (
+        <tr>
+          <td colSpan={2}>
+            <PartCollapsible
+              big
+              title={<i18n.Translate>Products</i18n.Translate>}
+              text={
+                <ListOfProducts>
+                  {transaction.info.products?.map((p, k) => (
+                    <Row key={k}>
+                      <a href="#" onClick={showLargePic}>
+                        <img src={p.image ? p.image : emptyImg} />
+                      </a>
+                      <div>
+                        {p.quantity && p.quantity > 0 && (
+                          <SmallLightText>
+                            x {p.quantity} {p.unit}
+                          </SmallLightText>
+                        )}
+                        <div>{p.description}</div>
+                      </div>
+                    </Row>
+                  ))}
+                </ListOfProducts>
+              }
+            />
+          </td>
+        </tr>
+      )}
+      {hasShipping && (
+        <tr>
+          <td colSpan={2}>
+            <PartCollapsible
+              big
+              title={<i18n.Translate>Delivery</i18n.Translate>}
+              text={
+                <DeliveryDetails
+                  date={transaction.info.delivery_date}
+                  location={transaction.info.delivery_location}
+                />
+              }
+            />
+          </td>
+        </tr>
+      )}
+    </PurchaseDetailsTable>
+  );
+}
+
+function RefundDetails({
+  transaction,
+}: {
+  transaction: TransactionRefund;
+}): VNode {
+  const { i18n } = useTranslationContext();
+
+  const fee = Amounts.sub(
+    Amounts.parseOrThrow(transaction.amountRaw),
+    Amounts.parseOrThrow(transaction.amountEffective),
+  ).amount;
+
+  return (
+    <PurchaseDetailsTable>
+      <tr>
+        <td>Amount</td>
+        <td>
+          <Amount value={transaction.amountRaw} />
+        </td>
+      </tr>
+
+      {Amounts.isNonZero(fee) && (
+        <tr>
+          <td>Transaction fees</td>
+          <td>
+            <Amount value={fee} />
+          </td>
+        </tr>
+      )}
+      <tr>
+        <td colSpan={2}>
+          <hr />
+        </td>
+      </tr>
+      <tr>
+        <td>Total</td>
+        <td>
+          <Amount value={transaction.amountEffective} />
+        </td>
+      </tr>
+    </PurchaseDetailsTable>
+  );
+}
+
+function DepositDetails({
+  transaction,
+}: {
+  transaction: TransactionDeposit;
+}): VNode {
+  const { i18n } = useTranslationContext();
+
+  const fee = Amounts.sub(
+    Amounts.parseOrThrow(transaction.amountRaw),
+    Amounts.parseOrThrow(transaction.amountEffective),
+  ).amount;
+
+  return (
+    <PurchaseDetailsTable>
+      <tr>
+        <td>Amount</td>
+        <td>
+          <Amount value={transaction.amountRaw} />
+        </td>
+      </tr>
+
+      {Amounts.isNonZero(fee) && (
+        <tr>
+          <td>Transaction fees</td>
+          <td>
+            <Amount value={fee} />
+          </td>
+        </tr>
+      )}
+      <tr>
+        <td colSpan={2}>
+          <hr />
+        </td>
+      </tr>
+      <tr>
+        <td>Total transfer</td>
+        <td>
+          <Amount value={transaction.amountEffective} />
+        </td>
+      </tr>
+    </PurchaseDetailsTable>
+  );
+}
+function RefreshDetails({
+  transaction,
+}: {
+  transaction: TransactionRefresh;
+}): VNode {
+  const { i18n } = useTranslationContext();
+
+  const fee = Amounts.sub(
+    Amounts.parseOrThrow(transaction.amountRaw),
+    Amounts.parseOrThrow(transaction.amountEffective),
+  ).amount;
+
+  return (
+    <PurchaseDetailsTable>
+      <tr>
+        <td>Amount</td>
+        <td>
+          <Amount value={transaction.amountRaw} />
+        </td>
+      </tr>
+      <tr>
+        <td colSpan={2}>
+          <hr />
+        </td>
+      </tr>
+      <tr>
+        <td>Transaction fees</td>
+        <td>
+          <Amount value={fee} />
+        </td>
+      </tr>
+    </PurchaseDetailsTable>
+  );
+}
+
+function TipDetails({ transaction }: { transaction: TransactionTip }): VNode {
+  const { i18n } = useTranslationContext();
+
+  const fee = Amounts.sub(
+    Amounts.parseOrThrow(transaction.amountRaw),
+    Amounts.parseOrThrow(transaction.amountEffective),
+  ).amount;
+
+  return (
+    <PurchaseDetailsTable>
+      <tr>
+        <td>Amount</td>
+        <td>
+          <Amount value={transaction.amountRaw} />
+        </td>
+      </tr>
+
+      {Amounts.isNonZero(fee) && (
+        <tr>
+          <td>Transaction fees</td>
+          <td>
+            <Amount value={fee} />
+          </td>
+        </tr>
+      )}
+      <tr>
+        <td colSpan={2}>
+          <hr />
+        </td>
+      </tr>
+      <tr>
+        <td>Total</td>
+        <td>
+          <Amount value={transaction.amountEffective} />
+        </td>
+      </tr>
+    </PurchaseDetailsTable>
+  );
+}
+
+function WithdrawDetails({
+  transaction,
+}: {
+  transaction: TransactionWithdrawal;
+}): VNode {
+  const { i18n } = useTranslationContext();
+
+  const fee = Amounts.sub(
+    Amounts.parseOrThrow(transaction.amountRaw),
+    Amounts.parseOrThrow(transaction.amountEffective),
+  ).amount;
+
+  return (
+    <PurchaseDetailsTable>
+      <tr>
+        <td>Withdraw</td>
+        <td>
+          <Amount value={transaction.amountRaw} />
+        </td>
+      </tr>
+
+      {Amounts.isNonZero(fee) && (
+        <tr>
+          <td>Transaction fees</td>
+          <td>
+            <Amount value={fee} />
+          </td>
+        </tr>
+      )}
+      <tr>
+        <td colSpan={2}>
+          <hr />
+        </td>
+      </tr>
+      <tr>
+        <td>Total</td>
+        <td>
+          <Amount value={transaction.amountEffective} />
+        </td>
+      </tr>
+    </PurchaseDetailsTable>
+  );
+}
+
+function Header({
+  timestamp,
+  total,
+  children,
+  kind,
+  type,
+}: {
+  timestamp: TalerProtocolTimestamp;
+  total: AmountJson;
+  children: ComponentChildren;
+  kind: Kind;
+  type: string;
+}): VNode {
+  return (
+    <div
+      style={{
+        display: "flex",
+        justifyContent: "space-between",
+        flexDirection: "row",
+      }}
+    >
+      <div>
+        <SubTitle>{children}</SubTitle>
+        <Time
+          timestamp={AbsoluteTime.fromTimestamp(timestamp)}
+          format="dd MMMM yyyy, HH:mm"
+        />
+      </div>
+      <div>
+        <SubTitle>
+          <Part
+            title={type}
+            text={<Amount value={total} />}
+            kind={kind}
+            showSign
+          />
+        </SubTitle>
+      </div>
+    </div>
+  );
+}
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 00000000..1777936c
Binary files /dev/null and 
b/packages/taler-wallet-webextension/static-dev/merchant-icon-11.jpeg differ

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]