implement flicker-free refunds
This commit is contained in:
parent
1671d9a508
commit
ae177549a5
@ -2532,6 +2532,10 @@ 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 acceptRefund(refundUrl: string): Promise<string> {
|
||||||
console.log("processing refund");
|
console.log("processing refund");
|
||||||
let resp;
|
let resp;
|
||||||
@ -2598,7 +2602,8 @@ export class Wallet {
|
|||||||
return refundPermissions[0].h_contract_terms;
|
return refundPermissions[0].h_contract_terms;
|
||||||
}
|
}
|
||||||
|
|
||||||
async submitRefunds(contractTermsHash: string): Promise<void> {
|
|
||||||
|
private async submitRefunds(contractTermsHash: string): Promise<void> {
|
||||||
const purchase = await this.q().get(Stores.purchases, contractTermsHash);
|
const purchase = await this.q().get(Stores.purchases, contractTermsHash);
|
||||||
if (!purchase) {
|
if (!purchase) {
|
||||||
console.error("not submitting refunds, contract terms not found:", contractTermsHash);
|
console.error("not submitting refunds, contract terms not found:", contractTermsHash);
|
||||||
@ -2644,7 +2649,6 @@ export class Wallet {
|
|||||||
return c;
|
return c;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
await this.q()
|
await this.q()
|
||||||
.mutate(Stores.purchases, contractTermsHash, transformPurchase)
|
.mutate(Stores.purchases, contractTermsHash, transformPurchase)
|
||||||
.mutate(Stores.coins, perm.coin_pub, transformCoin)
|
.mutate(Stores.coins, perm.coin_pub, transformCoin)
|
||||||
|
@ -195,6 +195,10 @@ export interface MessageMap {
|
|||||||
request: { contractTermsHash: string, sessionId: string | undefined };
|
request: { contractTermsHash: string, sessionId: string | undefined };
|
||||||
response: void;
|
response: void;
|
||||||
};
|
};
|
||||||
|
"accept-refund": {
|
||||||
|
request: { refundUrl: string }
|
||||||
|
response: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,10 +35,12 @@ import { AmountDisplay } from "../renderHtml";
|
|||||||
import * as wxApi from "../wxApi";
|
import * as wxApi from "../wxApi";
|
||||||
|
|
||||||
interface RefundStatusViewProps {
|
interface RefundStatusViewProps {
|
||||||
contractTermsHash: string;
|
contractTermsHash?: string;
|
||||||
|
refundUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RefundStatusViewState {
|
interface RefundStatusViewState {
|
||||||
|
contractTermsHash?: string;
|
||||||
purchase?: dbTypes.PurchaseRecord;
|
purchase?: dbTypes.PurchaseRecord;
|
||||||
refundFees?: AmountJson;
|
refundFees?: AmountJson;
|
||||||
gotResult: boolean;
|
gotResult: boolean;
|
||||||
@ -102,13 +104,22 @@ class RefundStatusView extends React.Component<RefundStatusViewProps, RefundStat
|
|||||||
}
|
}
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
|
if (!this.props.contractTermsHash && !this.props.refundUrl) {
|
||||||
|
return (
|
||||||
|
<div id="main">
|
||||||
|
<span>Error: Neither contract terms hash nor refund url given.</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
const purchase = this.state.purchase;
|
const purchase = this.state.purchase;
|
||||||
if (!purchase) {
|
if (!purchase) {
|
||||||
|
let message;
|
||||||
if (this.state.gotResult) {
|
if (this.state.gotResult) {
|
||||||
return <span>No purchase with contract terms hash {this.props.contractTermsHash} found</span>;
|
message = <span>No purchase with contract terms hash {this.props.contractTermsHash} found</span>;
|
||||||
} else {
|
} else {
|
||||||
return <span>...</span>;
|
message = <span>...</span>;
|
||||||
}
|
}
|
||||||
|
return <div id="main">{message}</div>;
|
||||||
}
|
}
|
||||||
const merchantName = purchase.contractTerms.merchant.name || "(unknown)";
|
const merchantName = purchase.contractTerms.merchant.name || "(unknown)";
|
||||||
const summary = purchase.contractTerms.summary || purchase.contractTerms.order_id;
|
const summary = purchase.contractTerms.summary || purchase.contractTerms.order_id;
|
||||||
@ -128,7 +139,16 @@ class RefundStatusView extends React.Component<RefundStatusViewProps, RefundStat
|
|||||||
}
|
}
|
||||||
|
|
||||||
async update() {
|
async update() {
|
||||||
const purchase = await wxApi.getPurchase(this.props.contractTermsHash);
|
let contractTermsHash = this.state.contractTermsHash;
|
||||||
|
if (!contractTermsHash) {
|
||||||
|
const refundUrl = this.props.refundUrl;
|
||||||
|
if (!refundUrl) {
|
||||||
|
console.error("neither contractTermsHash nor refundUrl is given");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
contractTermsHash = await wxApi.acceptRefund(refundUrl);
|
||||||
|
}
|
||||||
|
const purchase = await wxApi.getPurchase(contractTermsHash);
|
||||||
console.log("got purchase", purchase);
|
console.log("got purchase", purchase);
|
||||||
const refundsDone = Object.keys(purchase.refundsDone).map((x) => purchase.refundsDone[x]);
|
const refundsDone = Object.keys(purchase.refundsDone).map((x) => purchase.refundsDone[x]);
|
||||||
const refundFees = await wxApi.getFullRefundFees( {refundPermissions: refundsDone });
|
const refundFees = await wxApi.getFullRefundFees( {refundPermissions: refundsDone });
|
||||||
@ -147,8 +167,9 @@ async function main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const contractTermsHash = query.contractTermsHash || "(none)";
|
const contractTermsHash = query.contractTermsHash;
|
||||||
ReactDOM.render(<RefundStatusView contractTermsHash={contractTermsHash} />, container);
|
const refundUrl = query.refundUrl;
|
||||||
|
ReactDOM.render(<RefundStatusView contractTermsHash={contractTermsHash} refundUrl={refundUrl} />, container);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => main());
|
document.addEventListener("DOMContentLoaded", () => main());
|
||||||
|
@ -363,3 +363,10 @@ export function talerPay(msg: any): Promise<void> {
|
|||||||
export function downloadProposal(url: string): Promise<number> {
|
export function downloadProposal(url: string): Promise<number> {
|
||||||
return callBackend("download-proposal", { url });
|
return callBackend("download-proposal", { url });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download a refund and accept it.
|
||||||
|
*/
|
||||||
|
export function acceptRefund(refundUrl: string): Promise<string> {
|
||||||
|
return callBackend("accept-refund", { refundUrl });
|
||||||
|
}
|
||||||
|
@ -292,6 +292,8 @@ function handleMessage(sender: MessageSender,
|
|||||||
}
|
}
|
||||||
case "get-full-refund-fees":
|
case "get-full-refund-fees":
|
||||||
return needsWallet().getFullRefundFees(detail.refundPermissions);
|
return needsWallet().getFullRefundFees(detail.refundPermissions);
|
||||||
|
case "accept-refund":
|
||||||
|
return needsWallet().acceptRefund(detail.refundUrl);
|
||||||
case "get-tip-status": {
|
case "get-tip-status": {
|
||||||
const tipToken = TipToken.checked(detail.tipToken);
|
const tipToken = TipToken.checked(detail.tipToken);
|
||||||
return needsWallet().getTipStatus(tipToken);
|
return needsWallet().getTipStatus(tipToken);
|
||||||
@ -430,8 +432,8 @@ async function talerPay(fields: any, url: string, tabId: number): Promise<string
|
|||||||
}
|
}
|
||||||
if (fields.refund_url) {
|
if (fields.refund_url) {
|
||||||
console.log("processing refund");
|
console.log("processing refund");
|
||||||
const hc = await w.acceptRefund(fields.refund_url);
|
const uri = new URI(chrome.extension.getURL("/src/webex/pages/refund.html"));
|
||||||
return chrome.extension.getURL(`/src/webex/pages/refund.html?contractTermsHash=${hc}`);
|
return uri.query({ refundUrl: fields.refund_url }).href();
|
||||||
}
|
}
|
||||||
if (fields.tip) {
|
if (fields.tip) {
|
||||||
const uri = new URI(chrome.extension.getURL("/src/webex/pages/tip.html"));
|
const uri = new URI(chrome.extension.getURL("/src/webex/pages/tip.html"));
|
||||||
@ -507,10 +509,24 @@ function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: stri
|
|||||||
return { redirectUrl: uri.href() };
|
return { redirectUrl: uri.href() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Synchronous fast path for refund
|
||||||
|
if (fields.refund_url) {
|
||||||
|
console.log("processing refund");
|
||||||
|
const uri = new URI(chrome.extension.getURL("/src/webex/pages/refund.html"));
|
||||||
|
uri.query({ refundUrl: fields.refund_url });
|
||||||
|
return { redirectUrl: uri.href };
|
||||||
|
}
|
||||||
|
|
||||||
// We need to do some asynchronous operation, we can't directly redirect
|
// We need to do some asynchronous operation, we can't directly redirect
|
||||||
talerPay(fields, url, tabId).then((nextUrl) => {
|
talerPay(fields, url, tabId).then((nextUrl) => {
|
||||||
if (nextUrl) {
|
if (nextUrl) {
|
||||||
chrome.tabs.update(tabId, { url: nextUrl });
|
// We use chrome.tabs.executeScript instead of chrome.tabs.update
|
||||||
|
// because the latter is buggy when it does not execute in the same
|
||||||
|
// (micro-?)task as the header callback.
|
||||||
|
chrome.tabs.executeScript({
|
||||||
|
code: `document.location.href = decodeURIComponent("${encodeURI(nextUrl)}");`,
|
||||||
|
runAt: "document_start",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user