diff options
-rw-r--r-- | src/headless/taler-wallet-cli.ts | 14 | ||||
-rw-r--r-- | src/taleruri.ts | 53 | ||||
-rw-r--r-- | src/wallet.ts | 17 | ||||
-rw-r--r-- | src/webex/pages/pay.tsx | 13 | ||||
-rw-r--r-- | src/webex/pages/refund.tsx | 8 | ||||
-rw-r--r-- | src/webex/pages/tip.tsx | 29 | ||||
-rw-r--r-- | src/webex/renderHtml.tsx | 25 | ||||
-rw-r--r-- | src/webex/wxBackend.ts | 2 |
8 files changed, 121 insertions, 40 deletions
diff --git a/src/headless/taler-wallet-cli.ts b/src/headless/taler-wallet-cli.ts index 86eaec64c..bfa1ac4b9 100644 --- a/src/headless/taler-wallet-cli.ts +++ b/src/headless/taler-wallet-cli.ts @@ -179,6 +179,20 @@ program wallet.stop(); }); + + + program + .command("refund-uri <refund-uri>") + .action(async (refundUri, cmdObj) => { + applyVerbose(program.verbose); + console.log("getting refund", refundUri); + const wallet = await getDefaultNodeWallet({ + persistentStoragePath: walletDbPath, + }); + await wallet.applyRefund(refundUri); + wallet.stop(); + }); + program .command("pay-uri <pay-uri") .option("-y, --yes", "automatically answer yes to prompts") diff --git a/src/taleruri.ts b/src/taleruri.ts index f5fc77421..bd01abb65 100644 --- a/src/taleruri.ts +++ b/src/taleruri.ts @@ -26,6 +26,10 @@ export interface WithdrawUriResult { statusUrl: string; } +export interface RefundUriResult { + refundUrl: string; +} + export interface TipUriResult { tipPickupUrl: string; tipId: string; @@ -155,3 +159,52 @@ export function parseTipUri(s: string): TipUriResult | undefined { merchantOrigin: new URI(tipPickupUrl).origin(), }; } + +export function parseRefundUri(s: string): RefundUriResult | undefined { + const parsedUri = new URI(s); + if (parsedUri.scheme() != "taler") { + return undefined; + } + if (parsedUri.authority() != "refund") { + return undefined; + } + + let [ + _, + host, + maybePath, + maybeInstance, + orderId, + ] = parsedUri.path().split("/"); + + if (!host) { + return undefined; + } + + if (!maybePath) { + return undefined; + } + + if (!orderId) { + return undefined; + } + + if (maybePath === "-") { + maybePath = "public/refund"; + } else { + maybePath = decodeURIComponent(maybePath); + } + if (maybeInstance === "-") { + maybeInstance = "default"; + } + + const refundUrl = new URI( + "https://" + host + "/" + decodeURIComponent(maybePath), + ) + .addQuery({ instance: maybeInstance, order_id: orderId }) + .href(); + + return { + refundUrl, + }; +}
\ No newline at end of file diff --git a/src/wallet.ts b/src/wallet.ts index fd1be5293..00a82f92c 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -109,7 +109,7 @@ import { AcceptWithdrawalResponse, } from "./walletTypes"; import { openPromise } from "./promiseUtils"; -import { parsePayUri, parseWithdrawUri, parseTipUri } from "./taleruri"; +import { parsePayUri, parseWithdrawUri, parseTipUri, parseRefundUri } from "./taleruri"; interface SpeculativePayData { payCoinInfo: PayCoinInfo; @@ -3109,7 +3109,7 @@ export class Wallet { } } - async acceptRefundResponse( + private async acceptRefundResponse( refundResponse: MerchantRefundResponse, ): Promise<string> { const refundPermissions = refundResponse.refund_permissions; @@ -3149,8 +3149,7 @@ export class Wallet { .finish(); this.notifier.notify(); - // Start submitting it but don't wait for it here. - this.submitRefunds(hc); + await this.submitRefunds(hc); return hc; } @@ -3159,7 +3158,15 @@ export class Wallet { * Accept a refund, return the contract hash for the contract * that was involved in the refund. */ - async acceptRefund(refundUrl: string): Promise<string> { + async applyRefund(talerRefundUri: string): Promise<string> { + const parseResult = parseRefundUri(talerRefundUri); + + if (!parseResult) { + throw Error("invalid refund URI"); + } + + const refundUrl = parseResult.refundUrl; + Wallet.enableTracing && console.log("processing refund"); let resp; try { diff --git a/src/webex/pages/pay.tsx b/src/webex/pages/pay.tsx index d929426c4..1561dd95f 100644 --- a/src/webex/pages/pay.tsx +++ b/src/webex/pages/pay.tsx @@ -30,7 +30,7 @@ import { ExchangeRecord, ProposalDownloadRecord } from "../../dbTypes"; import { ContractTerms } from "../../talerTypes"; import { CheckPayResult, PreparePayResult } from "../../walletTypes"; -import { renderAmount } from "../renderHtml"; +import { renderAmount, ProgressButton } from "../renderHtml"; import * as wxApi from "../wxApi"; import React, { useState, useEffect } from "react"; @@ -44,6 +44,7 @@ function TalerPayDialog({ talerPayUri }: { talerPayUri: string }) { const [payStatus, setPayStatus] = useState<PreparePayResult | undefined>(); const [payErrMsg, setPayErrMsg] = useState<string | undefined>(""); const [numTries, setNumTries] = useState(0); + const [loading, setLoading] = useState(false); let totalFees: Amounts.AmountJson | undefined = undefined; useEffect(() => { @@ -99,6 +100,7 @@ function TalerPayDialog({ talerPayUri }: { talerPayUri: string }) { const doPayment = async () => { setNumTries(numTries + 1); try { + setLoading(true); const res = await wxApi.confirmPay(payStatus!.proposalId!, undefined); document.location.href = res.nextUrl; } catch (e) { @@ -140,12 +142,11 @@ function TalerPayDialog({ talerPayUri }: { talerPayUri: string }) { </div> ) : ( <div> - <button - className="pure-button button-success" - onClick={() => doPayment()} - > + <ProgressButton + loading={loading} + onClick={() => doPayment()}> {i18n.str`Confirm payment`} - </button> + </ProgressButton> </div> )} </div> diff --git a/src/webex/pages/refund.tsx b/src/webex/pages/refund.tsx index 6bc1a136e..57d740486 100644 --- a/src/webex/pages/refund.tsx +++ b/src/webex/pages/refund.tsx @@ -188,8 +188,12 @@ async function main() { return; } - const contractTermsHash = query.contractTermsHash; - const refundUrl = query.refundUrl; + const talerRefundUri = query.talerRefundUri; + if (!talerRefundUri) { + console.error("taler refund URI requred"); + return; + } + ReactDOM.render(<RefundStatusView contractTermsHash={contractTermsHash} refundUrl={refundUrl} />, container); } diff --git a/src/webex/pages/tip.tsx b/src/webex/pages/tip.tsx index a3f5c38c3..0a066053b 100644 --- a/src/webex/pages/tip.tsx +++ b/src/webex/pages/tip.tsx @@ -29,35 +29,12 @@ import * as i18n from "../../i18n"; import { acceptTip, getReserveCreationInfo, getTipStatus } from "../wxApi"; -import { WithdrawDetailView, renderAmount } from "../renderHtml"; +import { WithdrawDetailView, renderAmount, ProgressButton } from "../renderHtml"; import * as Amounts from "../../amounts"; import { useState, useEffect } from "react"; import { TipStatus } from "../../walletTypes"; -interface LoadingButtonProps { - loading: boolean; -} - -function LoadingButton( - props: - & React.PropsWithChildren<LoadingButtonProps> - & React.DetailedHTMLProps< - React.ButtonHTMLAttributes<HTMLButtonElement>, - HTMLButtonElement - >, -) { - return ( - <button - className="pure-button pure-button-primary" - type="button" - {...props} - > - {props.loading ? <span><object className="svg-icon svg-baseline" data="/img/spinner-bars.svg" /></span> : null} - {props.children} - </button> - ); -} function TipDisplay(props: { talerTipUri: string }) { const [tipStatus, setTipStatus] = useState<TipStatus | undefined>(undefined); @@ -110,9 +87,9 @@ function TipDisplay(props: { talerTipUri: string }) { operation. </p> <form className="pure-form"> - <LoadingButton loading={loading} onClick={() => accept()}> + <ProgressButton loading={loading} onClick={() => accept()}> AcceptTip - </LoadingButton> + </ProgressButton> {" "} <button className="pure-button" type="button" onClick={() => discard()}> Discard tip diff --git a/src/webex/renderHtml.tsx b/src/webex/renderHtml.tsx index e4686adee..867fb440f 100644 --- a/src/webex/renderHtml.tsx +++ b/src/webex/renderHtml.tsx @@ -316,3 +316,28 @@ export class ExpanderText extends ImplicitStateComponent<ExpanderTextProps> { } } + +export interface LoadingButtonProps { + loading: boolean; +} + +export function ProgressButton( + props: + & React.PropsWithChildren<LoadingButtonProps> + & React.DetailedHTMLProps< + React.ButtonHTMLAttributes<HTMLButtonElement>, + HTMLButtonElement + >, +) { + return ( + <button + className="pure-button pure-button-primary" + type="button" + {...props} + > + {props.loading ? <span><object className="svg-icon svg-baseline" data="/img/spinner-bars.svg" /></span> : null} + {" "} + {props.children} + </button> + ); +}
\ No newline at end of file diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts index 5bff4fe0a..70a7557e3 100644 --- a/src/webex/wxBackend.ts +++ b/src/webex/wxBackend.ts @@ -292,7 +292,7 @@ function handleMessage( case "get-full-refund-fees": return needsWallet().getFullRefundFees(detail.refundPermissions); case "accept-refund": - return needsWallet().acceptRefund(detail.refundUrl); + return needsWallet().applyRefund(detail.refundUrl); case "get-tip-status": { return needsWallet().getTipStatus(detail.talerTipUri); } |