cli refunds
This commit is contained in:
parent
5ec344290e
commit
5a7269b20d
@ -179,6 +179,20 @@ program
|
|||||||
wallet.stop();
|
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
|
program
|
||||||
.command("pay-uri <pay-uri")
|
.command("pay-uri <pay-uri")
|
||||||
.option("-y, --yes", "automatically answer yes to prompts")
|
.option("-y, --yes", "automatically answer yes to prompts")
|
||||||
|
@ -26,6 +26,10 @@ export interface WithdrawUriResult {
|
|||||||
statusUrl: string;
|
statusUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RefundUriResult {
|
||||||
|
refundUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TipUriResult {
|
export interface TipUriResult {
|
||||||
tipPickupUrl: string;
|
tipPickupUrl: string;
|
||||||
tipId: string;
|
tipId: string;
|
||||||
@ -155,3 +159,52 @@ export function parseTipUri(s: string): TipUriResult | undefined {
|
|||||||
merchantOrigin: new URI(tipPickupUrl).origin(),
|
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,
|
AcceptWithdrawalResponse,
|
||||||
} from "./walletTypes";
|
} from "./walletTypes";
|
||||||
import { openPromise } from "./promiseUtils";
|
import { openPromise } from "./promiseUtils";
|
||||||
import { parsePayUri, parseWithdrawUri, parseTipUri } from "./taleruri";
|
import { parsePayUri, parseWithdrawUri, parseTipUri, parseRefundUri } from "./taleruri";
|
||||||
|
|
||||||
interface SpeculativePayData {
|
interface SpeculativePayData {
|
||||||
payCoinInfo: PayCoinInfo;
|
payCoinInfo: PayCoinInfo;
|
||||||
@ -3109,7 +3109,7 @@ export class Wallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async acceptRefundResponse(
|
private async acceptRefundResponse(
|
||||||
refundResponse: MerchantRefundResponse,
|
refundResponse: MerchantRefundResponse,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const refundPermissions = refundResponse.refund_permissions;
|
const refundPermissions = refundResponse.refund_permissions;
|
||||||
@ -3149,8 +3149,7 @@ export class Wallet {
|
|||||||
.finish();
|
.finish();
|
||||||
this.notifier.notify();
|
this.notifier.notify();
|
||||||
|
|
||||||
// Start submitting it but don't wait for it here.
|
await this.submitRefunds(hc);
|
||||||
this.submitRefunds(hc);
|
|
||||||
|
|
||||||
return hc;
|
return hc;
|
||||||
}
|
}
|
||||||
@ -3159,7 +3158,15 @@ export class Wallet {
|
|||||||
* Accept a refund, return the contract hash for the contract
|
* Accept a refund, return the contract hash for the contract
|
||||||
* that was involved in the refund.
|
* 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");
|
Wallet.enableTracing && console.log("processing refund");
|
||||||
let resp;
|
let resp;
|
||||||
try {
|
try {
|
||||||
|
@ -30,7 +30,7 @@ import { ExchangeRecord, ProposalDownloadRecord } from "../../dbTypes";
|
|||||||
import { ContractTerms } from "../../talerTypes";
|
import { ContractTerms } from "../../talerTypes";
|
||||||
import { CheckPayResult, PreparePayResult } from "../../walletTypes";
|
import { CheckPayResult, PreparePayResult } from "../../walletTypes";
|
||||||
|
|
||||||
import { renderAmount } from "../renderHtml";
|
import { renderAmount, ProgressButton } from "../renderHtml";
|
||||||
import * as wxApi from "../wxApi";
|
import * as wxApi from "../wxApi";
|
||||||
|
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
@ -44,6 +44,7 @@ function TalerPayDialog({ talerPayUri }: { talerPayUri: string }) {
|
|||||||
const [payStatus, setPayStatus] = useState<PreparePayResult | undefined>();
|
const [payStatus, setPayStatus] = useState<PreparePayResult | undefined>();
|
||||||
const [payErrMsg, setPayErrMsg] = useState<string | undefined>("");
|
const [payErrMsg, setPayErrMsg] = useState<string | undefined>("");
|
||||||
const [numTries, setNumTries] = useState(0);
|
const [numTries, setNumTries] = useState(0);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
let totalFees: Amounts.AmountJson | undefined = undefined;
|
let totalFees: Amounts.AmountJson | undefined = undefined;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -99,6 +100,7 @@ function TalerPayDialog({ talerPayUri }: { talerPayUri: string }) {
|
|||||||
const doPayment = async () => {
|
const doPayment = async () => {
|
||||||
setNumTries(numTries + 1);
|
setNumTries(numTries + 1);
|
||||||
try {
|
try {
|
||||||
|
setLoading(true);
|
||||||
const res = await wxApi.confirmPay(payStatus!.proposalId!, undefined);
|
const res = await wxApi.confirmPay(payStatus!.proposalId!, undefined);
|
||||||
document.location.href = res.nextUrl;
|
document.location.href = res.nextUrl;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -140,12 +142,11 @@ function TalerPayDialog({ talerPayUri }: { talerPayUri: string }) {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<button
|
<ProgressButton
|
||||||
className="pure-button button-success"
|
loading={loading}
|
||||||
onClick={() => doPayment()}
|
onClick={() => doPayment()}>
|
||||||
>
|
|
||||||
{i18n.str`Confirm payment`}
|
{i18n.str`Confirm payment`}
|
||||||
</button>
|
</ProgressButton>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -188,8 +188,12 @@ async function main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const contractTermsHash = query.contractTermsHash;
|
const talerRefundUri = query.talerRefundUri;
|
||||||
const refundUrl = query.refundUrl;
|
if (!talerRefundUri) {
|
||||||
|
console.error("taler refund URI requred");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ReactDOM.render(<RefundStatusView contractTermsHash={contractTermsHash} refundUrl={refundUrl} />, container);
|
ReactDOM.render(<RefundStatusView contractTermsHash={contractTermsHash} refundUrl={refundUrl} />, container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,35 +29,12 @@ import * as i18n from "../../i18n";
|
|||||||
|
|
||||||
import { acceptTip, getReserveCreationInfo, getTipStatus } from "../wxApi";
|
import { acceptTip, getReserveCreationInfo, getTipStatus } from "../wxApi";
|
||||||
|
|
||||||
import { WithdrawDetailView, renderAmount } from "../renderHtml";
|
import { WithdrawDetailView, renderAmount, ProgressButton } from "../renderHtml";
|
||||||
|
|
||||||
import * as Amounts from "../../amounts";
|
import * as Amounts from "../../amounts";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { TipStatus } from "../../walletTypes";
|
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 }) {
|
function TipDisplay(props: { talerTipUri: string }) {
|
||||||
const [tipStatus, setTipStatus] = useState<TipStatus | undefined>(undefined);
|
const [tipStatus, setTipStatus] = useState<TipStatus | undefined>(undefined);
|
||||||
@ -110,9 +87,9 @@ function TipDisplay(props: { talerTipUri: string }) {
|
|||||||
operation.
|
operation.
|
||||||
</p>
|
</p>
|
||||||
<form className="pure-form">
|
<form className="pure-form">
|
||||||
<LoadingButton loading={loading} onClick={() => accept()}>
|
<ProgressButton loading={loading} onClick={() => accept()}>
|
||||||
AcceptTip
|
AcceptTip
|
||||||
</LoadingButton>
|
</ProgressButton>
|
||||||
{" "}
|
{" "}
|
||||||
<button className="pure-button" type="button" onClick={() => discard()}>
|
<button className="pure-button" type="button" onClick={() => discard()}>
|
||||||
Discard tip
|
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":
|
case "get-full-refund-fees":
|
||||||
return needsWallet().getFullRefundFees(detail.refundPermissions);
|
return needsWallet().getFullRefundFees(detail.refundPermissions);
|
||||||
case "accept-refund":
|
case "accept-refund":
|
||||||
return needsWallet().acceptRefund(detail.refundUrl);
|
return needsWallet().applyRefund(detail.refundUrl);
|
||||||
case "get-tip-status": {
|
case "get-tip-status": {
|
||||||
return needsWallet().getTipStatus(detail.talerTipUri);
|
return needsWallet().getTipStatus(detail.talerTipUri);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user