diff options
Diffstat (limited to 'packages/merchant-backend-ui/src/pages')
11 files changed, 1545 insertions, 0 deletions
diff --git a/packages/merchant-backend-ui/src/pages/DepletedTip.stories.tsx b/packages/merchant-backend-ui/src/pages/DepletedTip.stories.tsx new file mode 100644 index 000000000..c20f6dc18 --- /dev/null +++ b/packages/merchant-backend-ui/src/pages/DepletedTip.stories.tsx @@ -0,0 +1,40 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { h, VNode, FunctionalComponent } from 'preact'; +import { DepletedTip as TestedComponent } from './DepletedTip'; + + +export default { +  title: 'DepletedTip', +  component: TestedComponent, +  argTypes: { +  }, +}; + +function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) { +  const r = (args: any) => <Component {...args} /> +  r.args = props +  return r +} + +export const Example = createExample(TestedComponent, { +}); diff --git a/packages/merchant-backend-ui/src/pages/DepletedTip.tsx b/packages/merchant-backend-ui/src/pages/DepletedTip.tsx new file mode 100644 index 000000000..756b08d6a --- /dev/null +++ b/packages/merchant-backend-ui/src/pages/DepletedTip.tsx @@ -0,0 +1,60 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ +import { Fragment, h, render, VNode } from 'preact'; +import { render as renderToString } from 'preact-render-to-string'; +import { Footer } from '../components/Footer'; +import "../css/pure-min.css"; +import "../css/style.css"; +import { Page } from '../styled'; + +function Head(): VNode { +  return <title>Status of your tip</title> +} + +export function DepletedTip(): VNode { +  return <Page> +    <section> +      <h1>Tip already collected</h1> +      <div> +        You have already collected this tip. +      </div> +    </section> +    <Footer /> +  </Page> +} + +export function mount(): void { +  try { +    render(<DepletedTip />, document.body); +  } catch (e) { +    console.error("got error", e); +    if (e instanceof Error) { +      document.body.innerText = `Fatal error: "${e.message}".  Please report this bug at https://bugs.gnunet.org/.`; +    } +  } +} + +export function buildTimeRendering(): { head: string, body: string } { +  return { +    head: renderToString(<Head />), +    body: renderToString(<DepletedTip />) +  } +} diff --git a/packages/merchant-backend-ui/src/pages/OfferRefund.stories.tsx b/packages/merchant-backend-ui/src/pages/OfferRefund.stories.tsx new file mode 100644 index 000000000..92694f867 --- /dev/null +++ b/packages/merchant-backend-ui/src/pages/OfferRefund.stories.tsx @@ -0,0 +1,45 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { h, VNode, FunctionalComponent } from 'preact'; +import { createSVG } from '../components/QR'; +import { OfferRefund as TestedComponent } from './OfferRefund'; + + +export default { +  title: 'OfferRefund', +  component: TestedComponent, +  argTypes: { +  }, +}; + +function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) { +  const r = (args: any) => <Component {...args} /> +  r.args = props +  return r +} + +const REFUND_URI_EXAMPLE = 'taler://pay/backend.demo.taler.net/instances/blog/2021.249-022NW2KG88QGA/def537eb-00c2-4a8b-8a17-0be034d118d3?c=2Y4N4PMST7KYAPS83428GTPCD4' + +export const Example = createExample(TestedComponent, { +  refundURI: REFUND_URI_EXAMPLE, +  qr_code: createSVG(REFUND_URI_EXAMPLE) +}); diff --git a/packages/merchant-backend-ui/src/pages/OfferRefund.tsx b/packages/merchant-backend-ui/src/pages/OfferRefund.tsx new file mode 100644 index 000000000..14c9372c2 --- /dev/null +++ b/packages/merchant-backend-ui/src/pages/OfferRefund.tsx @@ -0,0 +1,154 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ +import { Fragment, h, render, VNode } from 'preact'; +import { render as renderToString } from 'preact-render-to-string'; +import { useEffect } from 'preact/hooks'; +import { Footer } from '../components/Footer'; +import { QR } from '../components/QR'; +import "../css/pure-min.css"; +import "../css/style.css"; +import { Page, QRPlaceholder, WalletLink } from '../styled'; + +/** + * This page creates a refund offer QR code + *  + * It will build into a mustache html template for server side rendering + *  + * server side rendering params: + *  - order_status_url + *  - taler_refund_qrcode_svg + *  - taler_refund_uri + *  + * request params: + *  - refund_uri + *  - order_status_url + */ + +interface Props { +  refundURI?: string; +  order_status_url?: string; +  qr_code?: string; +} + +function Head({ order_summary }: { order_summary?: string }): VNode { +  return <Fragment> +    <meta charSet="UTF-8" /> +    <meta name="viewport" content="width=device-width, initial-scale=1.0" /> +    <noscript> +      <meta http-equiv="refresh" content="1" /> +    </noscript> +    <title>Refund available for {order_summary ? order_summary : `{{ order_summary }}`}</title> +  </Fragment> +} + +export function OfferRefund({ refundURI, qr_code, order_status_url }: Props): VNode { +  useEffect(() => { +    let checkUrl: URL; +    try { +      checkUrl = new URL(order_status_url ? order_status_url : "{{& order_status_url }}"); +    } catch (e) { +      return; +    } +    checkUrl.searchParams.set("await_refund_obtained", "yes"); +    const delayMs = 500; +    function check() { +      let retried = false; +      function retryOnce() { +        if (!retried) { +          retried = true; +          check(); +        } +      } +      const req = new XMLHttpRequest(); +      req.onreadystatechange = function () { +        if (req.readyState === XMLHttpRequest.DONE) { +          if (req.status === 200) { +            try { +              const resp = JSON.parse(req.responseText); +              if (!resp.refund_pending) { +                window.location.reload(); +              } +            } catch (e) { +              console.error("could not parse response:", e); +            } +          } +          setTimeout(retryOnce, delayMs); +        } +      }; +      req.onerror = function () { +        setTimeout(retryOnce, delayMs); +      } +      req.open("GET", checkUrl.href); +      req.send(); +    } + +    setTimeout(check, delayMs); +  }) +  return <Page> +    <section> +      <h1>Collect Taler refund</h1> +      <p> +        Scan this QR code with your Taler mobile wallet: +      </p> +      <QRPlaceholder dangerouslySetInnerHTML={{ __html: qr_code ? qr_code : `{{{ taler_refund_qrcode_svg }}}` }} /> +      <p> +        <WalletLink href={refundURI ? refundURI : `{{ taler_refund_uri }}`}> +          Or open your Taller wallet +        </WalletLink> +      </p> +      <p> +        <a href="https://wallet.taler.net/">Don't have a Taler wallet yet? Install it!</a> +      </p> +    </section> +    <Footer /> +  </Page> +} + +export function mount(): void { +  try { +    const fromLocation = new URL(window.location.href).searchParams +    const os = fromLocation.get('order_summary') || undefined; +    if (os) { +      render(<Head order_summary={os} />, document.head); +    } + +    const uri = fromLocation.get('refund_uri') || undefined; +    const osu = fromLocation.get('order_status_url') || undefined; +    const qr_code = uri ? renderToString(<QR text={uri} />) : undefined; + +    render(<OfferRefund +      refundURI={uri} order_status_url={osu} +      qr_code={qr_code} +    />, document.body); +  } catch (e) { +    console.error("got error", e); +    if (e instanceof Error) { +      document.body.innerText = `Fatal error: "${e.message}".  Please report this bug at https://bugs.gnunet.org/.`; +    } +  } +} + +export function buildTimeRendering(): { head: string, body: string } { +  return { +    head: renderToString(<Head />), +    body: renderToString(<OfferRefund />) +  } +} diff --git a/packages/merchant-backend-ui/src/pages/OfferTip.stories.tsx b/packages/merchant-backend-ui/src/pages/OfferTip.stories.tsx new file mode 100644 index 000000000..dfbf71fff --- /dev/null +++ b/packages/merchant-backend-ui/src/pages/OfferTip.stories.tsx @@ -0,0 +1,45 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { h, VNode, FunctionalComponent } from 'preact'; +import { createSVG } from '../components/QR'; +import { OfferTip as TestedComponent } from './OfferTip'; + + +export default { +  title: 'OfferTip', +  component: TestedComponent, +  argTypes: { +  }, +}; + +function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) { +  const r = (args: any) => <Component {...args} /> +  r.args = props +  return r +} + +const TIP_URI_EXAMPLE = 'taler+http://tip/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0' + +export const Example = createExample(TestedComponent, { +  tipURI: TIP_URI_EXAMPLE, +  qr_code: createSVG(TIP_URI_EXAMPLE) +}); diff --git a/packages/merchant-backend-ui/src/pages/OfferTip.tsx b/packages/merchant-backend-ui/src/pages/OfferTip.tsx new file mode 100644 index 000000000..ace1059ca --- /dev/null +++ b/packages/merchant-backend-ui/src/pages/OfferTip.tsx @@ -0,0 +1,141 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ +import { Fragment, h, render, VNode } from 'preact'; +import { render as renderToString } from 'preact-render-to-string'; +import { useEffect } from 'preact/hooks'; +import { Footer } from '../components/Footer'; +import { QR } from '../components/QR'; +import "../css/pure-min.css"; +import "../css/style.css"; +import { Page, QRPlaceholder, WalletLink } from '../styled'; +import { ShowOrderDetails } from './ShowOrderDetails'; + + +/** + * This page creates a tip offer QR code + *  + * It will build into a mustache html template for server side rendering + *  + * server side rendering params: + *  - tip_status_url + *  - taler_tip_qrcode_svg + *  - taler_tip_uri + *  + * request params: + *  - tip_uri + *  - tip_status_url + */ + +interface Props { +  tipURI?: string, +  tip_status_url?: string, +  qr_code?: string, +} + +export function Head(): VNode { +  return <Fragment> +    <meta charSet="UTF-8" /> +    <meta name="viewport" content="width=device-width, initial-scale=1.0" /> +    <noscript> +      <meta http-equiv="refresh" content="1" /> +    </noscript> +    <title>Tip available</title> +  </Fragment> +} + +export function OfferTip({ tipURI, qr_code, tip_status_url }: Props): VNode { +  useEffect(() => { +    let checkUrl: URL; +    try { +      checkUrl = new URL(tip_status_url ? tip_status_url : "{{& tip_status_url }}"); +    } catch (e) { +      return; +    } + +    const delayMs = 500; +    function check() { +      let retried = false; +      function retryOnce() { +        if (!retried) { +          retried = true; +          check(); +        } +      } +      const req = new XMLHttpRequest(); +      req.onreadystatechange = function () { +        if (req.readyState === XMLHttpRequest.DONE) { +          if (req.status === 410) { +            window.location.reload(); +          } +          setTimeout(retryOnce, delayMs); +        } +      }; +      req.onerror = function () { +        setTimeout(retryOnce, delayMs); +      } +      req.open("GET", checkUrl.href); +      req.send(); +    } + +    setTimeout(check, delayMs); +  }) +  return <Page> +    <section> +      <h1 >Collect Taler tip</h1> +      <p> +        Scan this QR code with your Taler mobile wallet: +      </p> +      <QRPlaceholder dangerouslySetInnerHTML={{ __html: qr_code ? qr_code : `{{{ taler_tip_qrcode_svg }}}` }} /> +      <p> +        <WalletLink href={tipURI ? tipURI : `{{ taler_tip_uri }}`}> +          Or open your Taller wallet +        </WalletLink> +      </p> +      <p> +        <a href="https://wallet.taler.net/">Don't have a Taler wallet yet? Install it!</a> +      </p> +    </section> +    <Footer /> +  </Page> +} + +export function mount(): void { +  try { +    const fromLocation = new URL(window.location.href).searchParams + +    const uri = fromLocation.get('tip_uri') || undefined +    const tsu = fromLocation.get('tip_status_url') || undefined + +    render(<OfferTip tipURI={uri} tip_status_url={tsu} />, document.body); +  } catch (e) { +    console.error("got error", e); +    if (e instanceof Error) { +      document.body.innerText = `Fatal error: "${e.message}".  Please report this bug at https://bugs.gnunet.org/.`; +    } +  } +} + +export function buildTimeRendering(): { head: string, body: string } { +  return { +    head: renderToString(<Head />), +    body: renderToString(<ShowOrderDetails />) +  } +} diff --git a/packages/merchant-backend-ui/src/pages/RequestPayment.stories.tsx b/packages/merchant-backend-ui/src/pages/RequestPayment.stories.tsx new file mode 100644 index 000000000..5d6d79adf --- /dev/null +++ b/packages/merchant-backend-ui/src/pages/RequestPayment.stories.tsx @@ -0,0 +1,45 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { FunctionalComponent, h } from 'preact'; +import { createSVG } from '../components/QR'; +import { RequestPayment as TestedComponent } from './RequestPayment'; + + +export default { +  title: 'RequestPayment', +  component: TestedComponent, +  argTypes: { +  }, +}; + +function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) { +  const r = (args: any) => <Component {...args} /> +  r.args = props +  return r +} + +const PAYTO_URI_EXAMPLE = 'taler+http://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0' + +export const Example = createExample(TestedComponent, { +  payURI: 'taler+http://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0', +  qr_code: createSVG(PAYTO_URI_EXAMPLE) +}); diff --git a/packages/merchant-backend-ui/src/pages/RequestPayment.tsx b/packages/merchant-backend-ui/src/pages/RequestPayment.tsx new file mode 100644 index 000000000..050755dfb --- /dev/null +++ b/packages/merchant-backend-ui/src/pages/RequestPayment.tsx @@ -0,0 +1,196 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { Fragment, h, render, VNode } from "preact"; +import { render as renderToString } from "preact-render-to-string"; +import { useEffect } from "preact/hooks"; +import { Footer } from "../components/Footer"; +import "../css/pure-min.css"; +import "../css/style.css"; +import { QR } from "../components/QR"; +import { Page, QRPlaceholder, WalletLink } from "../styled"; + +/** + * This page creates a payment request QR code + * + * It will build into a mustache html template for server side rendering + * + * server side rendering params: + *  - order_status_url + *  - taler_pay_qrcode_svg + *  - taler_pay_uri + *  - order_summary + * + * request params: + *  - pay_uri + *  - order_summary + *  - order_status_url + */ + +interface Props { +  payURI?: string; +  order_status_url?: string; +  qr_code?: string; +} + +function Head({ order_summary }: { order_summary?: string }): VNode { +  return ( +    <Fragment> +      <meta charSet="UTF-8" /> +      <meta name="viewport" content="width=device-width, initial-scale=1.0" /> +      <noscript> +        <meta http-equiv="refresh" content="1" /> +      </noscript> +      <title> +        Payment requested for{" "} +        {order_summary ? order_summary : `{{ order_summary }}`} +      </title> +    </Fragment> +  ); +} + +export function RequestPayment({ +  payURI, +  qr_code, +  order_status_url, +}: Props): VNode { +  useEffect(() => { +    const longpollDelayMs = 60 * 1000; +    let checkUrl: URL; +    try { +      checkUrl = new URL( +        order_status_url ? order_status_url : "{{& order_status_url }}" +      ); +    } catch (e) { +      return; +    } +    checkUrl.searchParams.set("timeout_s", longpollDelayMs.toString()); +    function check() { +      let retried = false; +      function retryOnce() { +        if (!retried) { +          retried = true; +          check(); +        } +      } +      const req = new XMLHttpRequest(); +      req.onreadystatechange = function () { +        if (req.readyState === XMLHttpRequest.DONE) { +          if (req.status === 200) { +            try { +              const resp = JSON.parse(req.responseText); +              if (resp.fulfillment_url) { +                window.location.replace(resp.fulfillment_url); +              } +            } catch (e) { +              console.error("could not parse response:", e); +            } +          } +          if (req.status === 202) { +            try { +              const resp = JSON.parse(req.responseText); +              if (resp.fulfillment_url) { +                window.location.replace(resp.fulfillment_url); +              } +            } catch (e) { +              console.error("could not parse response:", e); +            } +          } +          if (req.status === 402) { +            try { +              const resp = JSON.parse(req.responseText); +              if (resp.already_paid_order_id && resp.fulfillment_url) { +                window.location.replace(resp.fulfillment_url); +              } +            } catch (e) { +              console.error("could not parse response:", e); +            } +          } +          setTimeout(retryOnce, 500); +        } +      }; +      req.onerror = function () { +        setTimeout(retryOnce, 500); +      }; +      req.ontimeout = function () { +        setTimeout(retryOnce, 500); +      }; +      req.timeout = longpollDelayMs; +      req.open("GET", checkUrl.href); +      req.send(); +    } +    setTimeout(check, 500); +  }); +  return ( +    <Page> +      <section> +        <h1>Pay with Taler</h1> +        <p>Scan this QR code with your mobile wallet:</p> +        <QRPlaceholder +          dangerouslySetInnerHTML={{ +            __html: qr_code ? qr_code : `{{{ taler_pay_qrcode_svg }}}`, +          }} +        /> +        <p> +          <WalletLink href={payURI ? payURI : `{{ taler_pay_uri }}`}> +            Or open your Taller wallet +          </WalletLink> +        </p> +        <p> +          <a href="https://wallet.taler.net/"> +            Don't have a Taler wallet yet? Install it! +          </a> +        </p> +      </section> +      <Footer /> +    </Page> +  ); +} + +export function mount(): void { +  try { +    const fromLocation = new URL(window.location.href).searchParams; +    const os = fromLocation.get("order_summary") || undefined; +    if (os) { +      render(<Head order_summary={os} />, document.head); +    } + +    const uri = fromLocation.get("pay_uri") || undefined; +    const osu = fromLocation.get("order_status_url") || undefined; +    const qr_code = uri ? renderToString(<QR text={uri} />) : undefined; + +    render( +      <RequestPayment payURI={uri} order_status_url={osu} qr_code={qr_code} />, +      document.body +    ); +  } catch (e) { +    console.error("got error", e); +    if (e instanceof Error) { +      document.body.innerText = `Fatal error: "${e.message}".  Please report this bug at https://bugs.gnunet.org/.`; +    } +  } +} + +export function buildTimeRendering(): { head: string; body: string } { +  return { +    head: renderToString(<Head />), +    body: renderToString(<RequestPayment />), +  }; +} diff --git a/packages/merchant-backend-ui/src/pages/ShowOrderDetails.examples.ts b/packages/merchant-backend-ui/src/pages/ShowOrderDetails.examples.ts new file mode 100644 index 000000000..ba68397ee --- /dev/null +++ b/packages/merchant-backend-ui/src/pages/ShowOrderDetails.examples.ts @@ -0,0 +1,219 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { MerchantBackend } from '../declaration'; +import { Props } from './ShowOrderDetails'; + + +const defaultContractTerms: MerchantBackend.ContractTerms = { +  order_id: 'XRS8876388373', +  amount: 'USD:10', +  summary: 'this is a short summary', +  pay_deadline: { +    t_s: new Date().getTime() + 6 * 24 * 60 * 60 * 1000 +  }, +  merchant: { +    name: 'the merchant (inc)', +    address: { +      country_subdivision: 'Buenos Aires', +      town: 'CABA', +      country: 'Argentina' +    }, +    jurisdiction: { +      country_subdivision: 'Cordoba', +      town: 'Capital', +      country: 'Argentina' +    }, +  }, +  max_fee: 'USD:0.1', +  max_wire_fee: 'USD:0.2', +  wire_fee_amortization: 1, +  products: [], +  timestamp: { +    t_s: new Date().getTime() +  }, +  auditors: [], +  exchanges: [], +  h_wire: '', +  merchant_base_url: 'http://merchant.base.url/', +  merchant_pub: 'QWEASDQWEASD', +  nonce: 'NONCE', +  refund_deadline: { +    t_s: new Date().getTime() + 6 * 24 * 60 * 60 * 1000 +  }, +  wire_method: 'x-taler-bank', +  wire_transfer_deadline: { +    t_s: new Date().getTime() + 3 * 24 * 60 * 60 * 1000 +  }, +}; + +const inSixDays = new Date().getTime() + 6 * 24 * 60 * 60 * 1000 +const in10Minutes = new Date().getTime() + 10 * 60 * 1000 +const in15Minutes = new Date().getTime() + 15 * 60 * 1000 +const in20Minutes = new Date().getTime() + 20 * 60 * 1000 + +export const exampleData: { [name: string]: Props } = { +  Simplest: { +    order_summary: 'here goes the order summary', +    contract_terms: defaultContractTerms, +  }, +  WithRefundAmount: { +    order_summary: 'here goes the order summary', +    refund_amount: 'USD:10', +    contract_terms: defaultContractTerms, +  }, +  WithDeliveryDate: { +    order_summary: 'here goes the order summary', +    contract_terms: { +      ...defaultContractTerms, +      delivery_date: { +        t_s: inSixDays +      }, +    }, +  }, +  WithDeliveryLocation: { +    order_summary: 'here goes the order summary', +    contract_terms: { +      ...defaultContractTerms, +      delivery_location: { +        address_lines: ['addr line 1', 'addr line 2', 'addr line 3', 'addr line 4', 'addr line 5', 'addr line 6', 'addr line 7'], +        building_name: 'building-name', +        building_number: 'building-number', +        country: 'country', +        country_subdivision: 'country sub', +        district: 'district', +        post_code: 'post-code', +        street: 'street', +        town: 'town', +        town_location: 'town loc', +      }, +    }, +  }, +  WithDeliveryLocationAndDate: { +    order_summary: 'here goes the order summary', +    contract_terms: { +      ...defaultContractTerms, +      delivery_location: { +        address_lines: ['addr1', 'addr2', 'addr3', 'addr4', 'addr5', 'addr6', 'addr7'], +        building_name: 'building-name', +        building_number: 'building-number', +        country: 'country', +        country_subdivision: 'country sub', +        district: 'district', +        post_code: 'post-code', +        street: 'street', +        town: 'town', +        town_location: 'town loc', +      }, +      delivery_date: { +        t_s: inSixDays +      }, +    }, +  }, +  WithThreeProducts: { +    order_summary: 'here goes the order summary', +    contract_terms: { +      ...defaultContractTerms, +      products: [{ +        description: 'description of the first product', +        price: '5:USD', +        quantity: 1, +        delivery_date: { t_s: in10Minutes }, +        product_id: '12333', +      }, { +        description: 'another description', +        price: '10:USD', +        quantity: 5, +        unit: 't-shirt', +      }, { +        description: 'one last description', +        price: '10:USD', +        quantity: 5 +      }] +    } as MerchantBackend.ContractTerms +  }, +  WithProductWithTaxes: { +    order_summary: 'here goes the order summary', +    contract_terms: { +      ...defaultContractTerms, +      products: [{ +        description: 'description of the first product', +        price: '5:USD', +        quantity: 1, +        unit: 'beer', +        delivery_date: { t_s: in10Minutes }, +        product_id: '456', +        taxes: [{ +          name: 'VAT', tax: 'USD:1' +        }], +      }, { +        description: 'one last description', +        price: '10:USD', +        quantity: 5, +        product_id: '123', +        unit: 'beer', +        taxes: [{ +          name: 'VAT', tax: 'USD:1' +        }], +      }] +    } as MerchantBackend.ContractTerms +  }, +  WithExchangeList: { +    order_summary: 'here goes the order summary', +    contract_terms: { +      ...defaultContractTerms, +      exchanges: [{ +        master_pub: 'ABCDEFGHIJKLMNO', +        url: 'http://exchange0.taler.net' +      }, { +        master_pub: 'AAAAAAAAAAAAAAA', +        url: 'http://exchange1.taler.net' +      }, { +        master_pub: 'BBBBBBBBBBBBBBB', +        url: 'http://exchange2.taler.net' +      }] +    }, +  }, +  WithAuditorList: { +    order_summary: 'here goes the order summary', +    contract_terms: { +      ...defaultContractTerms, +      auditors: [{ +        auditor_pub: 'ABCDEFGHIJKLMNO', +        name: 'the USD auditor', +        url: 'http://auditor-usd.taler.net' +      }, { +        auditor_pub: 'OPQRSTUVWXYZABCD', +        name: 'the EUR auditor', +        url: 'http://auditor-eur.taler.net' +      }] +    }, +  }, +  WithAutoRefund: { +    order_summary: 'here goes the order summary', +    contract_terms: { +      ...defaultContractTerms, +      auto_refund: { +        d_us: 1000 * 60 * 60 * 26 + 1000 * 60 * 30 +      } +    }, +  }, +} diff --git a/packages/merchant-backend-ui/src/pages/ShowOrderDetails.stories.tsx b/packages/merchant-backend-ui/src/pages/ShowOrderDetails.stories.tsx new file mode 100644 index 000000000..6a902cc9e --- /dev/null +++ b/packages/merchant-backend-ui/src/pages/ShowOrderDetails.stories.tsx @@ -0,0 +1,49 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { FunctionalComponent, h } from 'preact'; +import { ShowOrderDetails as TestedComponent } from './ShowOrderDetails'; +import { exampleData } from './ShowOrderDetails.examples'; + +export default { +  title: 'ShowOrderDetails', +  component: TestedComponent, +  argTypes: { +  }, +  excludeStories: /.*Data$/, +}; + +function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) { +  const r = (args: any) => <Component {...args} /> +  r.args = props +  return r +} + +export const Simplest = createExample(TestedComponent, exampleData.Simplest); +export const WithRefundAmount = createExample(TestedComponent, exampleData.WithRefundAmount); +export const WithDeliveryDate = createExample(TestedComponent, exampleData.WithDeliveryDate); +export const WithDeliveryLocation = createExample(TestedComponent, exampleData.WithDeliveryLocation); +export const WithDeliveryLocationAndDate = createExample(TestedComponent, exampleData.WithDeliveryLocationAndDate); +export const WithThreeProducts = createExample(TestedComponent, exampleData.WithThreeProducts); +export const WithAuditorList = createExample(TestedComponent, exampleData.WithAuditorList); +export const WithExchangeList = createExample(TestedComponent, exampleData.WithExchangeList); +export const WithAutoRefund = createExample(TestedComponent, exampleData.WithAutoRefund); +export const WithProductWithTaxes = createExample(TestedComponent, exampleData.WithProductWithTaxes); diff --git a/packages/merchant-backend-ui/src/pages/ShowOrderDetails.tsx b/packages/merchant-backend-ui/src/pages/ShowOrderDetails.tsx new file mode 100644 index 000000000..aa62c2932 --- /dev/null +++ b/packages/merchant-backend-ui/src/pages/ShowOrderDetails.tsx @@ -0,0 +1,551 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { format, formatDuration } from "date-fns"; +import { intervalToDuration } from "date-fns/esm"; +import { Fragment, h, render, VNode } from "preact"; +import { render as renderToString } from "preact-render-to-string"; +import { Footer } from "../components/Footer"; +import "../css/pure-min.css"; +import "../css/style.css"; +import { MerchantBackend } from "../declaration"; +import { Page, InfoBox, TableExpanded, TableSimple } from "../styled"; + +/** + * This page creates a payment request QR code + * + * It will build into a mustache html template for server side rendering + * + * server side rendering params: + *  - order_summary + *  - contract_terms + *  - refund_amount + * + * request params: + *  - refund_amount + *  - contract_terms + *  - order_summary + */ + +export interface Props { +  btr?: boolean; // build time rendering flag +  order_summary?: string; +  refund_amount?: string; +  contract_terms?: MerchantBackend.ContractTerms; +} + +function Head({ order_summary }: { order_summary?: string }): VNode { +  return ( +    <Fragment> +      <meta charSet="UTF-8" /> +      <meta name="viewport" content="width=device-width, initial-scale=1.0" /> +      <noscript> +        <meta http-equiv="refresh" content="1" /> +      </noscript> +      <title> +        Status of your order for{" "} +        {order_summary ? order_summary : `{{ order_summary }}`} +      </title> +      <script>{` +      var contractTermsStr = '{{{contract_terms_json}}}'; +    `}</script> +    </Fragment> +  ); +} + +function Location({ +  templateName, +  location, +  btr, +}: { +  templateName: string; +  location: MerchantBackend.Location | undefined; +  btr?: boolean; +}) { +  //FIXME: mustache strings show be constructed in a way that ends in the final output of the html but is not present in the +  // javascript code, otherwise when mustache render engine run over the html it will also replace string in the javascript code +  // that is made to run when the browser has javascript enable leading into undefined behavior. +  // that's why in the next fields we are using concatenations to build the mustache placeholder. +  return ( +    <Fragment> +      {btr && `{{` + `#${templateName}.building_name}}`} +      <dd> +        {location?.building_name || +          (btr && `{{ ${templateName}.building_name }}`)}{" "} +        {location?.building_number || +          (btr && `{{ ${templateName}.building_number }}`)} +      </dd> +      {btr && `{{` + `/${templateName}.building_name}}`} + +      {btr && `{{` + `#${templateName}.country}}`} +      <dd> +        {location?.country || (btr && `{{ ${templateName}.country }}`)}{" "} +        {location?.country_subdivision || +          (btr && `{{ ${templateName}.country_subdivision }}`)} +      </dd> +      {btr && `{{` + `/${templateName}.country}}`} + +      {btr && `{{` + `#${templateName}.district}}`} +      <dd>{location?.district || (btr && `{{ ${templateName}.district }}`)}</dd> +      {btr && `{{` + `/${templateName}.district}}`} + +      {btr && `{{` + `#${templateName}.post_code}}`} +      <dd> +        {location?.post_code || (btr && `{{ ${templateName}.post_code }}`)} +      </dd> +      {btr && `{{` + `/${templateName}.post_code}}`} + +      {btr && `{{` + `#${templateName}.street}}`} +      <dd>{location?.street || (btr && `{{ ${templateName}.street }}`)}</dd> +      {btr && `{{` + `/${templateName}.street}}`} + +      {btr && `{{` + `#${templateName}.town}}`} +      <dd>{location?.town || (btr && `{{ ${templateName}.town }}`)}</dd> +      {btr && `{{` + `/${templateName}.town}}`} + +      {btr && `{{` + `#${templateName}.town_location}}`} +      <dd> +        {location?.town_location || +          (btr && `{{ ${templateName}.town_location }}`)} +      </dd> +      {btr && `{{` + `/${templateName}.town_location}}`} +    </Fragment> +  ); +} + +export function ShowOrderDetails({ +  order_summary, +  refund_amount, +  contract_terms, +  btr, +}: Props): VNode { +  const productList = btr +    ? [{} as MerchantBackend.Product] +    : contract_terms?.products || []; +  const auditorsList = btr +    ? [{} as MerchantBackend.Auditor] +    : contract_terms?.auditors || []; +  const exchangesList = btr +    ? [{} as MerchantBackend.Exchange] +    : contract_terms?.exchanges || []; +  const hasDeliveryInfo = +    btr || +    !!contract_terms?.delivery_date || +    !!contract_terms?.delivery_location; + +  return ( +    <Page> +      <header> +        <h1> +          Details of order{" "} +          {contract_terms?.order_id || `{{ contract_terms.order_id }}`} +        </h1> +      </header> + +      <section> +        {btr && `{{#refund_amount}}`} +        {(btr || refund_amount) && ( +          <section> +            <InfoBox> +              <b>Refunded:</b> The merchant refunded you{" "} +              <b>{refund_amount || `{{ refund_amount }}`}</b>. +            </InfoBox> +          </section> +        )} +        {btr && `{{/refund_amount}}`} + +        <section> +          <TableExpanded> +            <dt>Order summary:</dt> +            <dd>{contract_terms?.summary || `{{ contract_terms.summary }}`}</dd> +            <dt>Amount paid:</dt> +            <dd>{contract_terms?.amount || `{{ contract_terms.amount }}`}</dd> +            <dt>Order date:</dt> +            <dd> +              {contract_terms?.timestamp +                ? contract_terms?.timestamp.t_s != "never" +                  ? format( +                      contract_terms?.timestamp.t_s, +                      "dd MMM yyyy HH:mm:ss" +                    ) +                  : "never" +                : `{{ contract_terms.timestamp_str }}`}{" "} +            </dd> +            <dt>Merchant name:</dt> +            <dd> +              {contract_terms?.merchant.name || +                `{{ contract_terms.merchant.name }}`} +            </dd> +          </TableExpanded> +        </section> + +        {btr && `{{#contract_terms.hasProducts}}`} +        {!productList.length ? null : ( +          <section> +            <h2>Products purchased</h2> +            <TableSimple> +              {btr && "{{" + "#contract_terms.products" + "}}"} +              {productList.map((p, i) => { +                const taxList = btr +                  ? [{} as MerchantBackend.Tax] +                  : p.taxes || []; + +                return ( +                  <Fragment key={i}> +                    <p>{p.description || `{{description}}`}</p> +                    <dl> +                      <dt>Quantity:</dt> +                      <dd>{p.quantity || `{{quantity}}`}</dd> + +                      <dt>Price:</dt> +                      <dd>{p.price || `{{price}}`}</dd> + +                      {btr && `{{#hasTaxes}}`} +                      {!taxList.length ? null : ( +                        <Fragment> +                          {btr && "{{" + "#taxes" + "}}"} +                          {taxList.map((t, i) => { +                            return ( +                              <Fragment key={i}> +                                <dt>{t.name || `{{name}}`}</dt> +                                <dd>{t.tax || `{{tax}}`}</dd> +                              </Fragment> +                            ); +                          })} +                          {btr && "{{" + "/taxes" + "}}"} +                        </Fragment> +                      )} +                      {btr && `{{/hasTaxes}}`} + +                      {btr && `{{#delivery_date}}`} +                      {(btr || p.delivery_date) && ( +                        <Fragment> +                          <dt>Delivered on:</dt> +                          <dd> +                            {p.delivery_date +                              ? p.delivery_date.t_s != "never" +                                ? format( +                                    p.delivery_date.t_s, +                                    "dd MMM yyyy HH:mm:ss" +                                  ) +                                : "never" +                              : `{{ delivery_date_str }}`}{" "} +                          </dd> +                        </Fragment> +                      )} +                      {btr && `{{/delivery_date}}`} + +                      {btr && `{{#unit}}`} +                      {(btr || p.unit) && ( +                        <Fragment> +                          <dt>Product unit:</dt> +                          <dd>{p.unit || `{{.}}`}</dd> +                        </Fragment> +                      )} +                      {btr && `{{/unit}}`} + +                      {btr && `{{#product_id}}`} +                      {(btr || p.product_id) && ( +                        <Fragment> +                          <dt>Product ID:</dt> +                          <dd>{p.product_id || `{{.}}`}</dd> +                        </Fragment> +                      )} +                      {btr && `{{/product_id}}`} +                    </dl> +                  </Fragment> +                ); +              })} +              {btr && "{{" + "/contract_terms.products" + "}}"} +            </TableSimple> +          </section> +        )} +        {btr && `{{/contract_terms.hasProducts}}`} + +        {btr && `{{#contract_terms.has_delivery_info}}`} +        {!hasDeliveryInfo ? null : ( +          <section> +            <h2>Delivery information</h2> +            <TableExpanded> +              {btr && `{{#contract_terms.delivery_date}}`} +              {(btr || contract_terms?.delivery_date) && ( +                <Fragment> +                  <dt>Delivery date:</dt> +                  <dd> +                    {contract_terms?.delivery_date +                      ? contract_terms?.delivery_date.t_s != "never" +                        ? format( +                            contract_terms?.delivery_date.t_s, +                            "dd MMM yyyy HH:mm:ss" +                          ) +                        : "never" +                      : `{{ contract_terms.delivery_date_str }}`}{" "} +                  </dd> +                </Fragment> +              )} +              {btr && `{{/contract_terms.delivery_date}}`} + +              {btr && `{{#contract_terms.delivery_location}}`} +              {(btr || contract_terms?.delivery_location) && ( +                <Fragment> +                  <dt>Delivery address:</dt> +                  <Location +                    btr={btr} +                    location={contract_terms?.delivery_location} +                    templateName="contract_terms.delivery_location" +                  /> +                </Fragment> +              )} +              {btr && `{{/contract_terms.delivery_location}}`} +            </TableExpanded> +          </section> +        )} +        {btr && `{{/contract_terms.has_delivery_info}}`} + +        <section> +          <h2>Full payment information</h2> +          <TableExpanded> +            <dt>Amount paid:</dt> +            <dd>{contract_terms?.amount || `{{ contract_terms.amount }}`}</dd> +            <dt>Wire transfer method:</dt> +            <dd> +              {contract_terms?.wire_method || +                `{{ contract_terms.wire_method }}`} +            </dd> +            <dt>Payment deadline:</dt> +            <dd> +              {contract_terms?.pay_deadline +                ? contract_terms?.pay_deadline.t_s != "never" +                  ? format( +                      contract_terms?.pay_deadline.t_s, +                      "dd MMM yyyy HH:mm:ss" +                    ) +                  : "never" +                : `{{ contract_terms.pay_deadline_str }}`}{" "} +            </dd> +            <dt>Exchange transfer deadline:</dt> +            <dd> +              {contract_terms?.wire_transfer_deadline +                ? contract_terms?.wire_transfer_deadline.t_s != "never" +                  ? format( +                      contract_terms?.wire_transfer_deadline.t_s, +                      "dd MMM yyyy HH:mm:ss" +                    ) +                  : "never" +                : `{{ contract_terms.wire_transfer_deadline_str }}`}{" "} +            </dd> +            <dt>Maximum deposit fee:</dt> +            <dd>{contract_terms?.max_fee || `{{ contract_terms.max_fee }}`}</dd> +            <dt>Maximum wire fee:</dt> +            <dd> +              {contract_terms?.max_wire_fee || +                `{{ contract_terms.max_wire_fee }}`} +            </dd> +            <dt>Wire fee amortization:</dt> +            <dd> +              {contract_terms?.wire_fee_amortization || +                `{{ contract_terms.wire_fee_amortization }}`}{" "} +              transactions +            </dd> +          </TableExpanded> +        </section> + +        <section> +          <h2>Refund information</h2> +          <TableExpanded> +            <dt>Refund deadline:</dt> +            <dd> +              {contract_terms?.refund_deadline +                ? contract_terms?.refund_deadline.t_s != "never" +                  ? format( +                      contract_terms?.refund_deadline.t_s, +                      "dd MMM yyyy HH:mm:ss" +                    ) +                  : "never" +                : `{{ contract_terms.refund_deadline_str }}`}{" "} +            </dd> + +            {btr && `{{#contract_terms.auto_refund}}`} +            {(btr || contract_terms?.auto_refund) && ( +              <Fragment> +                <dt>Attempt autorefund for:</dt> +                <dd> +                  {contract_terms?.auto_refund +                    ? contract_terms?.auto_refund.d_us != "forever" +                      ? formatDuration( +                          intervalToDuration({ +                            start: 0, +                            end: contract_terms?.auto_refund.d_us, +                          }) +                        ) +                      : "forever" +                    : `{{ contract_terms.auto_refund_str }}`}{" "} +                </dd> +              </Fragment> +            )} +            {btr && `{{/contract_terms.auto_refund}}`} +          </TableExpanded> +        </section> + +        <section> +          <h2>Additional order details</h2> +          <TableExpanded> +            <dt>Public reorder URL:</dt> +            <dd> -- not defined yet -- </dd> +            {btr && `{{#contract_terms.fulfillment_url}}`} +            {(btr || contract_terms?.fulfillment_url) && ( +              <Fragment> +                <dt>Fulfillment URL:</dt> +                <dd> +                  {contract_terms?.fulfillment_url || +                    (btr && `{{ contract_terms.fulfillment_url }}`)} +                </dd> +              </Fragment> +            )} +            {btr && `{{/contract_terms.fulfillment_url}}`} +            {/* <dt>Fulfillment message:</dt> +          <dd> -- not defined yet -- </dd> */} +          </TableExpanded> +        </section> + +        <section> +          <h2>Full merchant information</h2> +          <TableExpanded> +            <dt>Merchant name:</dt> +            <dd> +              {contract_terms?.merchant.name || +                `{{ contract_terms.merchant.name }}`} +            </dd> +            <dt>Merchant address:</dt> +            <Location +              btr={btr} +              location={contract_terms?.merchant.address} +              templateName="contract_terms.merchant.address" +            /> +            <dt>Merchant's jurisdiction:</dt> +            <Location +              btr={btr} +              location={contract_terms?.merchant.jurisdiction} +              templateName="contract_terms.merchant.jurisdiction" +            /> +            <dt>Merchant URI:</dt> +            <dd> +              {contract_terms?.merchant_base_url || +                `{{ contract_terms.merchant_base_url }}`} +            </dd> +            <dt>Merchant's public key:</dt> +            <dd> +              {contract_terms?.merchant_pub || +                `{{ contract_terms.merchant_pub }}`} +            </dd> +            {/* <dt>Merchant's hash:</dt> +          <dd> -- not defined yet -- </dd> */} +          </TableExpanded> +        </section> + +        {btr && `{{#contract_terms.hasAuditors}}`} +        {!auditorsList.length ? null : ( +          <section> +            <h2>Auditors accepted by the merchant</h2> +            <TableExpanded> +              {btr && "{{" + "#contract_terms.auditors" + "}}"} +              {auditorsList.map((p, i) => { +                return ( +                  <Fragment key={i}> +                    <p>{p.name || `{{name}}`}</p> +                    <dt>Auditor's public key:</dt> +                    <dd>{p.auditor_pub || `{{auditor_pub}}`}</dd> +                    <dt>Auditor's URL:</dt> +                    <dd>{p.url || `{{url}}`}</dd> +                  </Fragment> +                ); +              })} +              {btr && "{{" + "/contract_terms.auditors" + "}}"} +            </TableExpanded> +          </section> +        )} +        {btr && `{{/contract_terms.hasAuditors}}`} + +        {btr && `{{#contract_terms.hasExchanges}}`} +        {!exchangesList.length ? null : ( +          <section> +            <h2>Exchanges accepted by the merchant</h2> +            <TableExpanded> +              {btr && "{{" + "#contract_terms.exchanges" + "}}"} +              {exchangesList.map((p, i) => { +                return ( +                  <Fragment key={i}> +                    <dt>Exchange's URL:</dt> +                    <dd>{p.url || `{{url}}`}</dd> +                    <dt>Public key:</dt> +                    <dd>{p.master_pub || `{{master_pub}}`}</dd> +                  </Fragment> +                ); +              })} +              {btr && "{{" + "/contract_terms.exchanges" + "}}"} +            </TableExpanded> +          </section> +        )} +        {btr && `{{/contract_terms.hasExchanges}}`} +      </section> + +      <Footer /> +    </Page> +  ); +} + +export function mount(): void { +  try { +    const fromLocation = new URL(window.location.href).searchParams; +    const os = fromLocation.get("order_summary") || undefined; +    if (os) { +      render(<Head order_summary={os} />, document.head); +    } + +    const ra = fromLocation.get("refund_amount") || undefined; +    const ct = fromLocation.get("contract_terms") || undefined; + +    let contractTerms: MerchantBackend.ContractTerms | undefined; +    try { +      contractTerms = JSON.parse((window as any).contractTermsStr); +    } catch {} + +    render( +      <ShowOrderDetails +        contract_terms={contractTerms} +        order_summary={os} +        refund_amount={ra} +      />, +      document.body +    ); +  } catch (e) { +    console.error("got error", e); +    if (e instanceof Error) { +      document.body.innerText = `Fatal error: "${e.message}".  Please report this bug at https://bugs.gnunet.org/.`; +    } +  } +} + +export function buildTimeRendering(): { head: string; body: string } { +  return { +    head: renderToString(<Head />), +    body: renderToString(<ShowOrderDetails btr />), +  }; +}  | 
