diff options
author | Florian Dold <florian.dold@gmail.com> | 2018-01-19 01:27:27 +0100 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2018-01-19 01:27:27 +0100 |
commit | 1671d9a508b803af31762bcd9508e70eb40e7b48 (patch) | |
tree | 24d79103d0661c9edafd1d6371692b726b2f0ef3 /src/webex | |
parent | 2f68e9e50e83c55ca46e9d4d72956d6525d0fa8c (diff) |
refactor tipping, adjust to new redirect-based API
Diffstat (limited to 'src/webex')
-rw-r--r-- | src/webex/messages.ts | 16 | ||||
-rw-r--r-- | src/webex/pages/confirm-contract.tsx | 73 | ||||
-rw-r--r-- | src/webex/pages/tip.tsx | 16 | ||||
-rw-r--r-- | src/webex/wxApi.ts | 39 | ||||
-rw-r--r-- | src/webex/wxBackend.ts | 67 |
5 files changed, 100 insertions, 111 deletions
diff --git a/src/webex/messages.ts b/src/webex/messages.ts index 0fcd6047e..e1bd6f12c 100644 --- a/src/webex/messages.ts +++ b/src/webex/messages.ts @@ -171,20 +171,12 @@ export interface MessageMap { request: { refundPermissions: talerTypes.RefundPermission[] }; response: void; }; - "get-tip-planchets": { - request: walletTypes.GetTipPlanchetsRequest; - response: void; - }; - "process-tip-response": { - request: walletTypes.ProcessTipResponseRequest; - response: void; - }; "accept-tip": { - request: walletTypes.AcceptTipRequest; + request: { tipToken: talerTypes.TipToken }; response: void; }; "get-tip-status": { - request: walletTypes.TipStatusRequest; + request: { tipToken: talerTypes.TipToken }; response: void; }; "clear-notification": { @@ -199,6 +191,10 @@ export interface MessageMap { request: any; response: void; }; + "submit-pay": { + request: { contractTermsHash: string, sessionId: string | undefined }; + response: void; + }; } /** diff --git a/src/webex/pages/confirm-contract.tsx b/src/webex/pages/confirm-contract.tsx index cd58d712a..2ec131052 100644 --- a/src/webex/pages/confirm-contract.tsx +++ b/src/webex/pages/confirm-contract.tsx @@ -122,6 +122,7 @@ interface ContractPromptState { */ holdCheck: boolean; payStatus?: CheckPayResult; + replaying: boolean; } class ContractPrompt extends React.Component<ContractPromptProps, ContractPromptState> { @@ -135,6 +136,7 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt payDisabled: true, proposal: null, proposalId: props.proposalId, + replaying: false, }; } @@ -150,13 +152,23 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt if (this.props.resourceUrl) { const p = await wxApi.queryPaymentByFulfillmentUrl(this.props.resourceUrl); console.log("query for resource url", this.props.resourceUrl, "result", p); - if (p.found && (p.lastSessionSig === undefined || p.lastSessionSig === this.props.sessionId)) { - const nextUrl = new URI(p.contractTerms.fulfillment_url); - nextUrl.addSearch("order_id", p.contractTerms.order_id); - if (p.lastSessionSig) { - nextUrl.addSearch("session_sig", p.lastSessionSig); + if (p) { + if (p.lastSessionSig === undefined || p.lastSessionSig === this.props.sessionId) { + const nextUrl = new URI(p.contractTerms.fulfillment_url); + nextUrl.addSearch("order_id", p.contractTerms.order_id); + if (p.lastSessionSig) { + nextUrl.addSearch("session_sig", p.lastSessionSig); + } + location.replace(nextUrl.href()); + return; + } else { + // We're in a new session + this.setState({ replaying: true }); + const payResult = await wxApi.submitPay(p.contractTermsHash, this.props.sessionId); + console.log("payResult", payResult); + location.replace(payResult.nextUrl); + return; } - location.href = nextUrl.href(); } } let proposalId = this.props.proposalId; @@ -230,6 +242,9 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt if (this.props.contractUrl === undefined && this.props.proposalId === undefined) { return <span>Error: either contractUrl or proposalId must be given</span>; } + if (this.state.replaying) { + return <span>Re-submitting existing payment</span>; + } if (this.state.proposalId === undefined) { return <span>Downloading contract terms</span>; } @@ -245,26 +260,40 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt } const amount = <strong>{renderAmount(c.amount)}</strong>; console.log("payStatus", this.state.payStatus); + + let products = null; + if (c.products.length) { + products = ( + <> + <span>The following items are included:</span> + <ul> + {c.products.map( + (p: any, i: number) => (<li key={i}>{p.description}: {renderAmount(p.price)}</li>)) + } + </ul> + </> + ); + } return ( - <div> + <> <div> <i18n.Translate wrap="p"> The merchant <span>{merchantName}</span> {" "} offers you to purchase: </i18n.Translate> - <ul> - {c.products.map( - (p: any, i: number) => (<li key={i}>{p.description}: {renderAmount(p.price)}</li>)) - } - </ul> - {(this.state.payStatus && this.state.payStatus.coinSelection) - ? <p> - The total price is <span>{amount}</span>{" "} - (plus <span>{renderAmount(this.state.payStatus.coinSelection.totalFees)}</span> fees). - </p> - : - <p>The total price is <span>{amount}</span>.</p> - } + <div style={{"text-align": "center"}}> + <strong>{c.summary}</strong> + </div> + <strong></strong> + {products} + {(this.state.payStatus && this.state.payStatus.coinSelection) + ? <p> + The total price is <span>{amount}</span>{" "} + (plus <span>{renderAmount(this.state.payStatus.coinSelection.totalFees)}</span> fees). + </p> + : + <p>The total price is <span>{amount}</span>.</p> + } </div> <button className="pure-button button-success" disabled={this.state.payDisabled} @@ -280,7 +309,7 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt {(this.state.error ? <p className="errorbox">{this.state.error}</p> : <p />)} </div> <Details exchanges={this.state.exchanges} contractTerms={c} collapsed={!this.state.error}/> - </div> + </> ); } } @@ -296,10 +325,8 @@ document.addEventListener("DOMContentLoaded", () => { } catch { // ignore error } - const sessionId = query.sessionId; const contractUrl = query.contractUrl; - const resourceUrl = query.resourceUrl; ReactDOM.render( diff --git a/src/webex/pages/tip.tsx b/src/webex/pages/tip.tsx index 7f96401c5..578ae6aa4 100644 --- a/src/webex/pages/tip.tsx +++ b/src/webex/pages/tip.tsx @@ -39,11 +39,11 @@ import { } from "../renderHtml"; import * as Amounts from "../../amounts"; +import { TipToken } from "../../talerTypes"; import { TipStatus } from "../../walletTypes"; interface TipDisplayProps { - merchantDomain: string; - tipId: string; + tipToken: TipToken; } interface TipDisplayState { @@ -58,7 +58,7 @@ class TipDisplay extends React.Component<TipDisplayProps, TipDisplayState> { } async update() { - const tipStatus = await getTipStatus(this.props.merchantDomain, this.props.tipId); + const tipStatus = await getTipStatus(this.props.tipToken); this.setState({ tipStatus }); } @@ -96,7 +96,7 @@ class TipDisplay extends React.Component<TipDisplayProps, TipDisplayState> { accept() { this.setState({ working: true}); - acceptTip(this.props.merchantDomain, this.props.tipId); + acceptTip(this.props.tipToken); } renderButtons() { @@ -126,7 +126,7 @@ class TipDisplay extends React.Component<TipDisplayProps, TipDisplayState> { <div> <h2>Tip Received!</h2> <p>You received a tip of <strong>{renderAmount(ts.tip.amount)}</strong> from <span> </span> - <strong>{this.props.merchantDomain}</strong>.</p> + <strong>{ts.tip.merchantDomain}</strong>.</p> {ts.tip.accepted ? <p>You've accepted this tip! <a href={ts.tip.nextUrl}>Go back to merchant</a></p> : this.renderButtons() @@ -142,11 +142,9 @@ async function main() { const url = new URI(document.location.href); const query: any = URI.parseQuery(url.query()); - const merchantDomain = query.merchant_domain; - const tipId = query.tip_id; - const props: TipDisplayProps = { tipId, merchantDomain }; + const tipToken = TipToken.checked(JSON.parse(query.tip_token)); - ReactDOM.render(<TipDisplay {...props} />, + ReactDOM.render(<TipDisplay tipToken={tipToken} />, document.getElementById("container")!); } catch (e) { diff --git a/src/webex/wxApi.ts b/src/webex/wxApi.ts index 84c44dbaa..566f45265 100644 --- a/src/webex/wxApi.ts +++ b/src/webex/wxApi.ts @@ -35,7 +35,6 @@ import { import { CheckPayResult, ConfirmPayResult, - QueryPaymentResult, ReserveCreationInfo, SenderWireInfos, TipStatus, @@ -44,8 +43,7 @@ import { import { RefundPermission, - TipPlanchetDetail, - TipResponse, + TipToken, } from "../talerTypes"; import { MessageMap, MessageType } from "./messages"; @@ -222,6 +220,13 @@ export function confirmPay(proposalId: number, sessionId: string | undefined): P } /** + * Replay paying for a purchase. + */ +export function submitPay(contractTermsHash: string, sessionId: string | undefined): Promise<ConfirmPayResult> { + return callBackend("submit-pay", { contractTermsHash, sessionId }); +} + +/** * Hash a contract. Throws if its not a valid contract. */ export function hashContract(contract: object): Promise<string> { @@ -238,7 +243,7 @@ export function confirmReserve(reservePub: string): Promise<void> { /** * Query for a payment by fulfillment URL. */ -export function queryPaymentByFulfillmentUrl(url: string): Promise<QueryPaymentResult> { +export function queryPaymentByFulfillmentUrl(url: string): Promise<PurchaseRecord> { return callBackend("query-payment", { url }); } @@ -324,37 +329,19 @@ export function getFullRefundFees(args: { refundPermissions: RefundPermission[] /** - * Get or generate planchets to give the merchant that wants to tip us. - */ -export function getTipPlanchets(merchantDomain: string, - tipId: string, - amount: AmountJson, - deadline: number, - exchangeUrl: string, - nextUrl: string): Promise<TipPlanchetDetail[]> { - return callBackend("get-tip-planchets", { merchantDomain, tipId, amount, deadline, exchangeUrl, nextUrl }); -} - -/** * Get the status of processing a tip. */ -export function getTipStatus(merchantDomain: string, tipId: string): Promise<TipStatus> { - return callBackend("get-tip-status", { merchantDomain, tipId }); +export function getTipStatus(tipToken: TipToken): Promise<TipStatus> { + return callBackend("get-tip-status", { tipToken }); } /** * Mark a tip as accepted by the user. */ -export function acceptTip(merchantDomain: string, tipId: string): Promise<TipStatus> { - return callBackend("accept-tip", { merchantDomain, tipId }); +export function acceptTip(tipToken: TipToken): Promise<TipStatus> { + return callBackend("accept-tip", { tipToken }); } -/** - * Process a response from the merchant for a tip request. - */ -export function processTipResponse(merchantDomain: string, tipId: string, tipResponse: TipResponse): Promise<void> { - return callBackend("process-tip-response", { merchantDomain, tipId, tipResponse }); -} /** * Clear notifications that the wallet shows to the user. diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts index a4f534af9..26b8ff2cf 100644 --- a/src/webex/wxBackend.ts +++ b/src/webex/wxBackend.ts @@ -34,15 +34,10 @@ import { import { AmountJson } from "../amounts"; import { - AcceptTipRequest, ConfirmReserveRequest, CreateReserveRequest, - GetTipPlanchetsRequest, Notifier, - ProcessTipResponseRequest, - QueryPaymentFound, ReturnCoinsRequest, - TipStatusRequest, } from "../walletTypes"; import { @@ -50,6 +45,7 @@ import { } from "../wallet"; import { + PurchaseRecord, Stores, WALLET_DB_VERSION, } from "../dbTypes"; @@ -136,6 +132,12 @@ function handleMessage(sender: MessageSender, } return needsWallet().confirmPay(detail.proposalId, detail.sessionId); } + case "submit-pay": { + if (typeof detail.contractTermsHash !== "string") { + throw Error("contractTermsHash must be a string"); + } + return needsWallet().submitPay(detail.contractTermsHash, detail.sessionId); + } case "check-pay": { if (typeof detail.proposalId !== "number") { throw Error("proposalId must be number"); @@ -291,25 +293,12 @@ function handleMessage(sender: MessageSender, case "get-full-refund-fees": return needsWallet().getFullRefundFees(detail.refundPermissions); case "get-tip-status": { - const req = TipStatusRequest.checked(detail); - return needsWallet().getTipStatus(req.merchantDomain, req.tipId); + const tipToken = TipToken.checked(detail.tipToken); + return needsWallet().getTipStatus(tipToken); } case "accept-tip": { - const req = AcceptTipRequest.checked(detail); - return needsWallet().acceptTip(req.merchantDomain, req.tipId); - } - case "process-tip-response": { - const req = ProcessTipResponseRequest.checked(detail); - return needsWallet().processTipResponse(req.merchantDomain, req.tipId, req.tipResponse); - } - case "get-tip-planchets": { - const req = GetTipPlanchetsRequest.checked(detail); - return needsWallet().getTipPlanchets(req.merchantDomain, - req.tipId, - req.amount, - req.deadline, - req.exchangeUrl, - req.nextUrl); + const tipToken = TipToken.checked(detail.tipToken); + return needsWallet().acceptTip(tipToken); } case "clear-notification": { return needsWallet().clearNotification(); @@ -410,7 +399,7 @@ async function talerPay(fields: any, url: string, tabId: number): Promise<string const w = currentWallet; - const goToPayment = (p: QueryPaymentFound): string => { + const goToPayment = (p: PurchaseRecord): string => { const nextUrl = new URI(p.contractTerms.fulfillment_url); nextUrl.addSearch("order_id", p.contractTerms.order_id); if (p.lastSessionSig) { @@ -422,14 +411,7 @@ async function talerPay(fields: any, url: string, tabId: number): Promise<string if (fields.resource_url) { const p = await w.queryPaymentByFulfillmentUrl(fields.resource_url); console.log("query for resource url", fields.resource_url, "result", p); - if (p.found && (fields.session_id === undefined || fields.session_id === p.lastSessionId)) { - return goToPayment(p); - } - } - if (fields.contract_hash) { - const p = await w.queryPaymentByContractTermsHash(fields.contract_hash); - if (p.found) { - goToPayment(p); + if (p && (fields.session_id === undefined || fields.session_id === p.lastSessionId)) { return goToPayment(p); } } @@ -452,15 +434,8 @@ async function talerPay(fields: any, url: string, tabId: number): Promise<string return chrome.extension.getURL(`/src/webex/pages/refund.html?contractTermsHash=${hc}`); } if (fields.tip) { - const tipToken = TipToken.checked(fields.tip); - w.processTip(tipToken); - // Go to tip dialog page, where the user can confirm the tip or - // decline if they are not happy with the exchange. - const merchantDomain = new URI(url).origin(); const uri = new URI(chrome.extension.getURL("/src/webex/pages/tip.html")); - const params = { tip_id: tipToken.tip_id, merchant_domain: merchantDomain }; - const redirectUrl = uri.query(params).href(); - return redirectUrl; + return uri.query({ tip_token: fields.tip }).href(); } return undefined; } @@ -486,7 +461,6 @@ function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: stri } const fields = { - contract_hash: headers["x-taler-contract-hash"], contract_url: headers["x-taler-contract-url"], offer_url: headers["x-taler-offer-url"], refund_url: headers["x-taler-refund-url"], @@ -506,15 +480,15 @@ function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: stri console.log("got pay detail", fields); - // Fast path for existing payment + // Synchronous fast path for existing payment if (fields.resource_url) { const result = currentWallet.getNextUrlFromResourceUrl(fields.resource_url); if (result && (fields.session_id === undefined || fields.session_id === result.lastSessionId)) { return { redirectUrl: result.nextUrl }; } } - // Fast path for new contract - if (!fields.contract_hash && fields.contract_url) { + // Synchronous fast path for new contract + if (fields.contract_url) { const uri = new URI(chrome.extension.getURL("/src/webex/pages/confirm-contract.html")); uri.addSearch("contractUrl", fields.contract_url); if (fields.session_id) { @@ -526,6 +500,13 @@ function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: stri return { redirectUrl: uri.href() }; } + // Synchronous fast path for tip + if (fields.tip) { + const uri = new URI(chrome.extension.getURL("/src/webex/pages/tip.html")); + uri.query({ tip_token: fields.tip }); + return { redirectUrl: uri.href() }; + } + // We need to do some asynchronous operation, we can't directly redirect talerPay(fields, url, tabId).then((nextUrl) => { if (nextUrl) { |