From 1671d9a508b803af31762bcd9508e70eb40e7b48 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Fri, 19 Jan 2018 01:27:27 +0100 Subject: [PATCH] refactor tipping, adjust to new redirect-based API --- src/dbTypes.ts | 5 + src/i18n/de.po | 8 +- src/i18n/en-US.po | 8 +- src/i18n/fr.po | 8 +- src/i18n/it.po | 8 +- src/i18n/taler-wallet-webex.pot | 8 +- src/wallet.ts | 184 ++++++++++----------------- src/walletTypes.ts | 141 +------------------- src/webex/messages.ts | 16 +-- src/webex/pages/confirm-contract.tsx | 75 +++++++---- src/webex/pages/tip.tsx | 16 +-- src/webex/wxApi.ts | 39 ++---- src/webex/wxBackend.ts | 67 ++++------ 13 files changed, 192 insertions(+), 391 deletions(-) diff --git a/src/dbTypes.ts b/src/dbTypes.ts index 609c85265..035c100a9 100644 --- a/src/dbTypes.ts +++ b/src/dbTypes.ts @@ -574,6 +574,11 @@ export interface TipRecord { */ accepted: boolean; + /** + * Have we picked up the tip record from the merchant already? + */ + pickedUp: boolean; + /** * The tipped amount. */ diff --git a/src/i18n/de.po b/src/i18n/de.po index 39f1f56e6..1a003c17d 100644 --- a/src/i18n/de.po +++ b/src/i18n/de.po @@ -42,13 +42,13 @@ msgstr "" msgid "Exchanges in the wallet:" msgstr "" -#: src/webex/pages/confirm-contract.tsx:188 +#: src/webex/pages/confirm-contract.tsx:200 #, c-format msgid "You have insufficient funds of the requested currency in your wallet." msgstr "" #. tslint:disable-next-line:max-line-length -#: src/webex/pages/confirm-contract.tsx:190 +#: src/webex/pages/confirm-contract.tsx:202 #, c-format msgid "" "You do not have any funds from an exchange that is accepted by this " @@ -56,12 +56,12 @@ msgid "" "wallet." msgstr "" -#: src/webex/pages/confirm-contract.tsx:251 +#: src/webex/pages/confirm-contract.tsx:280 #, c-format msgid "The merchant%1$s offers you to purchase:\n" msgstr "" -#: src/webex/pages/confirm-contract.tsx:272 +#: src/webex/pages/confirm-contract.tsx:301 #, fuzzy, c-format msgid "Confirm payment" msgstr "Bezahlung bestätigen" diff --git a/src/i18n/en-US.po b/src/i18n/en-US.po index 2fdb451db..3d3fd4332 100644 --- a/src/i18n/en-US.po +++ b/src/i18n/en-US.po @@ -42,13 +42,13 @@ msgstr "" msgid "Exchanges in the wallet:" msgstr "" -#: src/webex/pages/confirm-contract.tsx:188 +#: src/webex/pages/confirm-contract.tsx:200 #, c-format msgid "You have insufficient funds of the requested currency in your wallet." msgstr "" #. tslint:disable-next-line:max-line-length -#: src/webex/pages/confirm-contract.tsx:190 +#: src/webex/pages/confirm-contract.tsx:202 #, c-format msgid "" "You do not have any funds from an exchange that is accepted by this " @@ -56,12 +56,12 @@ msgid "" "wallet." msgstr "" -#: src/webex/pages/confirm-contract.tsx:251 +#: src/webex/pages/confirm-contract.tsx:280 #, c-format msgid "The merchant%1$s offers you to purchase:\n" msgstr "" -#: src/webex/pages/confirm-contract.tsx:272 +#: src/webex/pages/confirm-contract.tsx:301 #, c-format msgid "Confirm payment" msgstr "" diff --git a/src/i18n/fr.po b/src/i18n/fr.po index 5d47a1f74..08f4a9d0c 100644 --- a/src/i18n/fr.po +++ b/src/i18n/fr.po @@ -42,13 +42,13 @@ msgstr "" msgid "Exchanges in the wallet:" msgstr "" -#: src/webex/pages/confirm-contract.tsx:188 +#: src/webex/pages/confirm-contract.tsx:200 #, c-format msgid "You have insufficient funds of the requested currency in your wallet." msgstr "" #. tslint:disable-next-line:max-line-length -#: src/webex/pages/confirm-contract.tsx:190 +#: src/webex/pages/confirm-contract.tsx:202 #, c-format msgid "" "You do not have any funds from an exchange that is accepted by this " @@ -56,12 +56,12 @@ msgid "" "wallet." msgstr "" -#: src/webex/pages/confirm-contract.tsx:251 +#: src/webex/pages/confirm-contract.tsx:280 #, c-format msgid "The merchant%1$s offers you to purchase:\n" msgstr "" -#: src/webex/pages/confirm-contract.tsx:272 +#: src/webex/pages/confirm-contract.tsx:301 #, c-format msgid "Confirm payment" msgstr "" diff --git a/src/i18n/it.po b/src/i18n/it.po index 5d47a1f74..08f4a9d0c 100644 --- a/src/i18n/it.po +++ b/src/i18n/it.po @@ -42,13 +42,13 @@ msgstr "" msgid "Exchanges in the wallet:" msgstr "" -#: src/webex/pages/confirm-contract.tsx:188 +#: src/webex/pages/confirm-contract.tsx:200 #, c-format msgid "You have insufficient funds of the requested currency in your wallet." msgstr "" #. tslint:disable-next-line:max-line-length -#: src/webex/pages/confirm-contract.tsx:190 +#: src/webex/pages/confirm-contract.tsx:202 #, c-format msgid "" "You do not have any funds from an exchange that is accepted by this " @@ -56,12 +56,12 @@ msgid "" "wallet." msgstr "" -#: src/webex/pages/confirm-contract.tsx:251 +#: src/webex/pages/confirm-contract.tsx:280 #, c-format msgid "The merchant%1$s offers you to purchase:\n" msgstr "" -#: src/webex/pages/confirm-contract.tsx:272 +#: src/webex/pages/confirm-contract.tsx:301 #, c-format msgid "Confirm payment" msgstr "" diff --git a/src/i18n/taler-wallet-webex.pot b/src/i18n/taler-wallet-webex.pot index 5d47a1f74..08f4a9d0c 100644 --- a/src/i18n/taler-wallet-webex.pot +++ b/src/i18n/taler-wallet-webex.pot @@ -42,13 +42,13 @@ msgstr "" msgid "Exchanges in the wallet:" msgstr "" -#: src/webex/pages/confirm-contract.tsx:188 +#: src/webex/pages/confirm-contract.tsx:200 #, c-format msgid "You have insufficient funds of the requested currency in your wallet." msgstr "" #. tslint:disable-next-line:max-line-length -#: src/webex/pages/confirm-contract.tsx:190 +#: src/webex/pages/confirm-contract.tsx:202 #, c-format msgid "" "You do not have any funds from an exchange that is accepted by this " @@ -56,12 +56,12 @@ msgid "" "wallet." msgstr "" -#: src/webex/pages/confirm-contract.tsx:251 +#: src/webex/pages/confirm-contract.tsx:280 #, c-format msgid "The merchant%1$s offers you to purchase:\n" msgstr "" -#: src/webex/pages/confirm-contract.tsx:272 +#: src/webex/pages/confirm-contract.tsx:301 #, c-format msgid "Confirm payment" msgstr "" diff --git a/src/wallet.ts b/src/wallet.ts index 7c2914926..9498fe820 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -99,7 +99,6 @@ import { NextUrlResult, Notifier, PayCoinInfo, - QueryPaymentResult, ReserveCreationInfo, ReturnCoinsRequest, SenderWireInfos, @@ -652,8 +651,8 @@ export class Wallet { contractTerms: proposal.contractTerms, contractTermsHash: proposal.contractTermsHash, finished: false, - lastSessionSig: undefined, lastSessionId: undefined, + lastSessionSig: undefined, merchantSig: proposal.merchantSig, payReq, refundsDone: {}, @@ -717,7 +716,11 @@ export class Wallet { return id; } - private async submitPay(purchase: PurchaseRecord, sessionId: string | undefined): Promise { + async submitPay(contractTermsHash: string, sessionId: string | undefined): Promise { + const purchase = await this.q().get(Stores.purchases, contractTermsHash); + if (!purchase) { + throw Error("Purchase not found: " + contractTermsHash); + } let resp; const payReq = { ...purchase.payReq, session_id: sessionId }; try { @@ -764,7 +767,7 @@ export class Wallet { let purchase = await this.q().get(Stores.purchases, proposal.contractTermsHash); if (purchase) { - return this.submitPay(purchase, sessionId); + return this.submitPay(purchase.contractTermsHash, sessionId); } const res = await this.getCoinsForPayment({ @@ -796,7 +799,7 @@ export class Wallet { purchase = await this.recordConfirmPay(sd.proposal, sd.payCoinInfo, sd.exchangeUrl); } - return this.submitPay(purchase, sessionId); + return this.submitPay(purchase.contractTermsHash, sessionId); } @@ -885,52 +888,17 @@ export class Wallet { * Retrieve information required to pay for a contract, where the * contract is identified via the fulfillment url. */ - async queryPaymentByFulfillmentUrl(url: string): Promise { + async queryPaymentByFulfillmentUrl(url: string): Promise { console.log("query for payment", url); const t = await this.q().getIndexed(Stores.purchases.fulfillmentUrlIndex, url); if (!t) { console.log("query for payment failed"); - return { - found: false, - }; + return undefined; } console.log("query for payment succeeded:", t); - return { - contractTerms: t.contractTerms, - contractTermsHash: t.contractTermsHash, - found: true, - lastSessionId: t.lastSessionId, - lastSessionSig: t.lastSessionSig, - payReq: t.payReq, - }; - } - - /** - * Retrieve information required to pay for a contract, where the - * contract is identified via the contract terms hash. - */ - async queryPaymentByContractTermsHash(contractTermsHash: string): Promise { - console.log("query for payment", contractTermsHash); - - const t = await this.q().get(Stores.purchases, contractTermsHash); - - if (!t) { - console.log("query for payment failed"); - return { - found: false, - }; - } - console.log("query for payment succeeded:", t); - return { - contractTerms: t.contractTerms, - contractTermsHash: t.contractTermsHash, - found: true, - lastSessionSig: t.lastSessionSig, - lastSessionId: t.lastSessionId, - payReq: t.payReq, - }; + return t; } @@ -2723,46 +2691,11 @@ export class Wallet { } /** - * Get planchets for a tip. Creates new planchets if they don't exist already - * for this tip. The tip is uniquely identified by the merchant's domain and the tip id. + * Workaround for merchant bug (#5258) */ - async getTipPlanchets(merchantDomain: string, - tipId: string, - amount: AmountJson, - deadline: number, - exchangeUrl: string, - nextUrl: string): Promise { - let tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]); - if (!tipRecord) { - await this.updateExchangeFromUrl(exchangeUrl); - const denomsForWithdraw = await this.getVerifiedWithdrawDenomList(exchangeUrl, amount); - const planchets = await Promise.all(denomsForWithdraw.map(d => this.cryptoApi.createTipPlanchet(d))); - const coinPubs: string[] = planchets.map(x => x.coinPub); - const now = (new Date()).getTime(); - tipRecord = { - accepted: false, - amount, - coinPubs, - deadline, - exchangeUrl, - merchantDomain, - nextUrl, - planchets, - timestamp: now, - tipId, - }; - await this.q().put(Stores.tips, tipRecord).finish(); - } - // Planchets in the form that the merchant expects - const planchetDetail: TipPlanchetDetail[] = tipRecord.planchets.map((p) => ({ - coin_ev: p.coinEv, - denom_pub_hash: p.denomPubHash, - })); - return planchetDetail; - } + private tipPickupWorkaround: { [tipId: string]: boolean } = {}; - - async processTip(tipToken: TipToken): Promise { + async processTip(tipToken: TipToken): Promise { console.log("got tip token", tipToken); const deadlineSec = getTalerStampSec(tipToken.expiration); @@ -2770,55 +2703,61 @@ export class Wallet { throw Error("tipping failed (invalid expiration)"); } - const merchantDomain = new URI(document.location.href).origin(); - let walletResp; - walletResp = await this.getTipPlanchets(merchantDomain, - tipToken.tip_id, - tipToken.amount, - deadlineSec, - tipToken.exchange_url, - tipToken.next_url); + const merchantDomain = new URI(tipToken.pickup_url).origin(); + let tipRecord = await this.q().get(Stores.tips, [tipToken.tip_id, merchantDomain]); - const planchets = walletResp; - - if (!planchets) { - console.log("failed tip", walletResp); - throw Error("processing tip failed"); + if (tipRecord && tipRecord.pickedUp) { + return tipRecord; } + await this.updateExchangeFromUrl(tipToken.exchange_url); + const denomsForWithdraw = await this.getVerifiedWithdrawDenomList(tipToken.exchange_url, tipToken.amount); + const planchets = await Promise.all(denomsForWithdraw.map(d => this.cryptoApi.createTipPlanchet(d))); + const coinPubs: string[] = planchets.map(x => x.coinPub); + const now = (new Date()).getTime(); + tipRecord = { + accepted: false, + amount: tipToken.amount, + coinPubs, + deadline: deadlineSec, + exchangeUrl: tipToken.exchange_url, + merchantDomain, + nextUrl: tipToken.next_url, + pickedUp: false, + planchets, + timestamp: now, + tipId: tipToken.tip_id, + }; + + // Planchets in the form that the merchant expects + const planchetsDetail: TipPlanchetDetail[] = tipRecord.planchets.map((p) => ({ + coin_ev: p.coinEv, + denom_pub_hash: p.denomPubHash, + })); let merchantResp; + await this.q().put(Stores.tips, tipRecord).finish(); + + if (this.tipPickupWorkaround[tipRecord.tipId]) { + // Be careful to not accidentally download twice (#5258) + return tipRecord; + } + try { const config = { validateStatus: (s: number) => s === 200, }; - const req = { planchets, tip_id: tipToken.tip_id }; + const req = { planchets: planchetsDetail, tip_id: tipToken.tip_id }; merchantResp = await axios.post(tipToken.pickup_url, req, config); } catch (e) { console.log("tipping failed", e); throw e; } - try { - this.processTipResponse(merchantDomain, tipToken.tip_id, merchantResp.data); - } catch (e) { - console.log("processTipResponse failed", e); - throw e; - } + this.tipPickupWorkaround[tipToken.tip_id] = true; - return; - } + const response = TipResponse.checked(merchantResp.data); - /** - * Accept a merchant's response to a tip pickup and start withdrawing the coins. - * These coins will not appear in the wallet yet. - */ - async processTipResponse(merchantDomain: string, tipId: string, response: TipResponse): Promise { - const tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]); - if (!tipRecord) { - throw Error("tip not found"); - } - console.log("processing tip response", response); if (response.reserve_sigs.length !== tipRecord.planchets.length) { throw Error("number of tip responses does not match requested planchets"); } @@ -2840,12 +2779,21 @@ export class Wallet { await this.q().put(Stores.precoins, preCoin); this.processPreCoin(preCoin); } + + tipRecord.pickedUp = true; + + await this.q().put(Stores.tips, tipRecord).finish(); + + return tipRecord; } + /** * Start using the coins from a tip. */ - async acceptTip(merchantDomain: string, tipId: string): Promise { + async acceptTip(tipToken: TipToken): Promise { + const tipId = tipToken.tip_id; + const merchantDomain = new URI(tipToken.pickup_url).origin(); const tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]); if (!tipRecord) { throw Error("tip not found"); @@ -2875,11 +2823,9 @@ export class Wallet { this.notifier.notify(); } - async getTipStatus(merchantDomain: string, tipId: string): Promise { - const tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]); - if (!tipRecord) { - throw Error("tip not found"); - } + + async getTipStatus(tipToken: TipToken): Promise { + const tipRecord = await this.processTip(tipToken); const rci = await this.getReserveCreationInfo(tipRecord.exchangeUrl, tipRecord.amount); const tipStatus: TipStatus = { rci, diff --git a/src/walletTypes.ts b/src/walletTypes.ts index c98717ac2..aba7dbfba 100644 --- a/src/walletTypes.ts +++ b/src/walletTypes.ts @@ -41,7 +41,6 @@ import { CoinPaySig, ContractTerms, PayReq, - TipResponse, } from "./talerTypes"; @@ -280,12 +279,6 @@ export interface HistoryRecord { } -/** - * Response to a query payment request. Tagged union over the 'found' field. - */ -export type QueryPaymentResult = QueryPaymentNotFound | QueryPaymentFound; - - /** * Query payment response when the payment was found. */ @@ -304,6 +297,7 @@ export interface QueryPaymentFound { lastSessionSig?: string; lastSessionId?: string; payReq: PayReq; + proposalId: number; } @@ -438,7 +432,6 @@ export interface CoinWithDenom { denom: DenominationRecord; } - /** * Status of processing a tip. */ @@ -448,138 +441,6 @@ export interface TipStatus { } -/** - * Request to the wallet for the status of processing a tip. - */ -@Checkable.Class() -export class TipStatusRequest { - /** - * Identifier of the tip. - */ - @Checkable.String - tipId: string; - - /** - * Merchant domain. Within each merchant domain, the tip identifier - * uniquely identifies a tip. - */ - @Checkable.String - merchantDomain: string; - - /** - * Create a TipStatusRequest from untyped JSON. - */ - static checked: (obj: any) => TipStatusRequest; -} - -/** - * Request to the wallet to accept a tip. - */ -@Checkable.Class() -export class AcceptTipRequest { - /** - * Identifier of the tip. - */ - @Checkable.String - tipId: string; - - /** - * Merchant domain. Within each merchant domain, the tip identifier - * uniquely identifies a tip. - */ - @Checkable.String - merchantDomain: string; - - /** - * Create an AcceptTipRequest from untyped JSON. - * Validates the schema and throws on error. - */ - static checked: (obj: any) => AcceptTipRequest; -} - - -/** - * Request for the wallet to process a tip response from a merchant. - */ -@Checkable.Class() -export class ProcessTipResponseRequest { - /** - * Identifier of the tip. - */ - @Checkable.String - tipId: string; - - /** - * Merchant domain. Within each merchant domain, the tip identifier - * uniquely identifies a tip. - */ - @Checkable.String - merchantDomain: string; - - /** - * Tip response from the merchant. - */ - @Checkable.Value(() => TipResponse) - tipResponse: TipResponse; - - /** - * Create an AcceptTipRequest from untyped JSON. - * Validates the schema and throws on error. - */ - static checked: (obj: any) => ProcessTipResponseRequest; -} - - -/** - * Request for the wallet to generate tip planchets. - */ -@Checkable.Class() -export class GetTipPlanchetsRequest { - /** - * Identifier of the tip. - */ - @Checkable.String - tipId: string; - - /** - * Merchant domain. Within each merchant domain, the tip identifier - * uniquely identifies a tip. - */ - @Checkable.String - merchantDomain: string; - - /** - * Amount of the tip. - */ - @Checkable.Optional(Checkable.Value(() => AmountJson)) - amount: AmountJson; - - /** - * Deadline for picking up the tip. - */ - @Checkable.Number - deadline: number; - - /** - * Exchange URL that must be used to pick up the tip. - */ - @Checkable.String - exchangeUrl: string; - - /** - * URL to nagivate to after processing the tip. - */ - @Checkable.String - nextUrl: string; - - /** - * Create an AcceptTipRequest from untyped JSON. - * Validates the schema and throws on error. - */ - static checked: (obj: any) => GetTipPlanchetsRequest; -} - - /** * Badge that shows activity for the wallet. */ 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 { @@ -135,6 +136,7 @@ class ContractPrompt extends React.ComponentError: either contractUrl or proposalId must be given; } + if (this.state.replaying) { + return Re-submitting existing payment; + } if (this.state.proposalId === undefined) { return Downloading contract terms; } @@ -245,26 +260,40 @@ class ContractPrompt extends React.Component{renderAmount(c.amount)}; console.log("payStatus", this.state.payStatus); - return ( -
-
- - The merchant {merchantName} {" "} - offers you to purchase: - + + let products = null; + if (c.products.length) { + products = ( + <> + The following items are included:
    {c.products.map( (p: any, i: number) => (
  • {p.description}: {renderAmount(p.price)}
  • )) }
- {(this.state.payStatus && this.state.payStatus.coinSelection) - ?

- The total price is {amount}{" "} - (plus {renderAmount(this.state.payStatus.coinSelection.totalFees)} fees). -

- : -

The total price is {amount}.

- } + + ); + } + return ( + <> +
+ + The merchant {merchantName} {" "} + offers you to purchase: + +
+ {c.summary} +
+ + {products} + {(this.state.payStatus && this.state.payStatus.coinSelection) + ?

+ The total price is {amount}{" "} + (plus {renderAmount(this.state.payStatus.coinSelection.totalFees)} fees). +

+ : +

The total price is {amount}.

+ }
-
+ ); } } @@ -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 { } 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 { 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 {

Tip Received!

You received a tip of {renderAmount(ts.tip.amount)} from - {this.props.merchantDomain}.

+ {ts.tip.merchantDomain}.

{ts.tip.accepted ?

You've accepted this tip! Go back to merchant

: 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(, + ReactDOM.render(, 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"; @@ -221,6 +219,13 @@ export function confirmPay(proposalId: number, sessionId: string | undefined): P return callBackend("confirm-pay", { proposalId, sessionId }); } +/** + * Replay paying for a purchase. + */ +export function submitPay(contractTermsHash: string, sessionId: string | undefined): Promise { + return callBackend("submit-pay", { contractTermsHash, sessionId }); +} + /** * Hash a contract. Throws if its not a valid contract. */ @@ -238,7 +243,7 @@ export function confirmReserve(reservePub: string): Promise { /** * Query for a payment by fulfillment URL. */ -export function queryPaymentByFulfillmentUrl(url: string): Promise { +export function queryPaymentByFulfillmentUrl(url: string): Promise { return callBackend("query-payment", { url }); } @@ -323,38 +328,20 @@ 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 { - 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 { - return callBackend("get-tip-status", { merchantDomain, tipId }); +export function getTipStatus(tipToken: TipToken): Promise { + return callBackend("get-tip-status", { tipToken }); } /** * Mark a tip as accepted by the user. */ -export function acceptTip(merchantDomain: string, tipId: string): Promise { - return callBackend("accept-tip", { merchantDomain, tipId }); +export function acceptTip(tipToken: TipToken): Promise { + 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 { - 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 { + 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 { if (nextUrl) {