cli refunds
This commit is contained in:
parent
5ec344290e
commit
5a7269b20d
@ -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")
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user