diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/types-test.ts | 6 | ||||
-rw-r--r-- | src/types.ts | 58 | ||||
-rw-r--r-- | src/wallet.ts | 59 | ||||
-rw-r--r-- | src/webex/notify.ts | 19 | ||||
-rw-r--r-- | src/webex/pages/confirm-contract.tsx | 12 | ||||
-rw-r--r-- | src/webex/renderHtml.tsx | 14 | ||||
-rw-r--r-- | src/webex/wxApi.ts | 3 |
7 files changed, 110 insertions, 61 deletions
diff --git a/src/types-test.ts b/src/types-test.ts index a84bdaec8..3657d6d26 100644 --- a/src/types-test.ts +++ b/src/types-test.ts @@ -54,7 +54,7 @@ test("amount subtraction (saturation)", (t) => { }); -test("contract validation", (t) => { +test("contract terms validation", (t) => { const c = { H_wire: "123", amount: amt(1, 2, "EUR"), @@ -73,13 +73,13 @@ test("contract validation", (t) => { wire_method: "test", }; - types.Contract.checked(c); + types.ContractTerms.checked(c); const c1 = JSON.parse(JSON.stringify(c)); c1.exchanges = []; try { - types.Contract.checked(c1); + types.ContractTerms.checked(c1); } catch (e) { t.pass(); return; diff --git a/src/types.ts b/src/types.ts index 82777f96b..0f817ccb6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -903,10 +903,10 @@ export interface WalletBalanceEntry { * Contract terms from a merchant. */ @Checkable.Class({validate: true}) -export class Contract { +export class ContractTerms { validate() { if (this.exchanges.length === 0) { - throw Error("no exchanges in contract"); + throw Error("no exchanges in contract terms"); } } @@ -1042,7 +1042,7 @@ export class Contract { * Verify that a value matches the schema of this class and convert it into a * member. */ - static checked: (obj: any) => Contract; + static checked: (obj: any) => ContractTerms; } @@ -1054,8 +1054,8 @@ export class ProposalRecord { /** * The contract that was offered by the merchant. */ - @Checkable.Value(Contract) - contractTerms: Contract; + @Checkable.Value(ContractTerms) + contractTerms: ContractTerms; /** * Signature by the merchant over the contract details. @@ -1398,3 +1398,51 @@ export interface HistoryRecord { } +/** + * Payment body sent to the merchant's /pay. + */ +export interface PayReq { + /** + * Coins with signature. + */ + coins: CoinPaySig[]; + + /** + * The merchant public key, used to uniquely + * identify the merchant instance. + */ + merchant_pub: string; + + /** + * Order ID that's being payed for. + */ + order_id: string; + + /** + * Exchange that the coins are from. + */ + exchange: string; +} + + +/** + * 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. + */ +export interface QueryPaymentNotFound { + found: false; +} + +/** + * Query payment response when the payment wasn't found. + */ +export interface QueryPaymentFound { + found: true; + contractTermsHash: string; + contractTerms: ContractTerms; + payReq: PayReq; +} diff --git a/src/wallet.ts b/src/wallet.ts index 8dc9a75a4..0bbab15e9 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -47,11 +47,10 @@ import { Amounts, Auditor, CheckPayResult, - CoinPaySig, CoinRecord, CoinStatus, ConfirmPayResult, - Contract, + ContractTerms, CreateReserveResponse, CurrencyRecord, Denomination, @@ -63,10 +62,12 @@ import { HistoryLevel, HistoryRecord, Notifier, - ProposalRecord, PayCoinInfo, + PayReq, PaybackConfirmation, PreCoinRecord, + ProposalRecord, + QueryPaymentResult, RefreshSessionRecord, ReserveCreationInfo, ReserveRecord, @@ -272,16 +273,9 @@ export class ConfirmReserveRequest { } -interface PayReq { - coins: CoinPaySig[]; - merchant_pub: string; - order_id: string; - exchange: string; -} - interface TransactionRecord { - contractHash: string; - contract: Contract; + contractTermsHash: string; + contractTerms: ContractTerms; payReq: PayReq; merchantSig: string; @@ -518,11 +512,11 @@ export namespace Stores { class TransactionsStore extends Store<TransactionRecord> { constructor() { - super("transactions", {keyPath: "contractHash"}); + super("transactions", {keyPath: "contractTermsHash"}); } - fulfillmentUrlIndex = new Index<string, TransactionRecord>(this, "fulfillment_url", "contract.fulfillment_url"); - orderIdIndex = new Index<string, TransactionRecord>(this, "order_id", "contract.order_id"); + fulfillmentUrlIndex = new Index<string, TransactionRecord>(this, "fulfillment_url", "contractTerms.fulfillment_url"); + orderIdIndex = new Index<string, TransactionRecord>(this, "order_id", "contractTerms.order_id"); } class DenominationsStore extends Store<DenominationRecord> { @@ -832,7 +826,7 @@ export class Wallet { /** * Record all information that is necessary to - * pay for a contract in the wallet's database. + * pay for a proposal in the wallet's database. */ private async recordConfirmPay(proposal: ProposalRecord, payCoinInfo: PayCoinInfo, @@ -844,8 +838,8 @@ export class Wallet { order_id: proposal.contractTerms.order_id, }; const t: TransactionRecord = { - contract: proposal.contractTerms, - contractHash: proposal.contractTermsHash, + contractTerms: proposal.contractTerms, + contractTermsHash: proposal.contractTermsHash, finished: false, merchantSig: proposal.merchantSig, payReq, @@ -854,7 +848,7 @@ export class Wallet { const historyEntry: HistoryRecord = { detail: { amount: proposal.contractTerms.amount, - contractHash: proposal.contractTermsHash, + contractTermsHash: proposal.contractTermsHash, fulfillmentUrl: proposal.contractTerms.fulfillment_url, merchantName: proposal.contractTerms.merchant.name, }, @@ -980,7 +974,7 @@ export class Wallet { * Retrieve information required to pay for a contract, where the * contract is identified via the fulfillment url. */ - async queryPayment(url: string): Promise<any> { + async queryPayment(url: string): Promise<QueryPaymentResult> { console.log("query for payment", url); const t = await this.q().getIndexed(Stores.transactions.fulfillmentUrlIndex, url); @@ -988,17 +982,16 @@ export class Wallet { if (!t) { console.log("query for payment failed"); return { - success: false, + found: false, }; } console.log("query for payment succeeded:", t); - const resp = { - H_contract: t.contractHash, - contract: t.contract, + return { + contractTermsHash: t.contractTermsHash, + contractTerms: t.contractTerms, payReq: t.payReq, - success: true, + found: true, }; - return resp; } @@ -1804,9 +1797,9 @@ export class Wallet { if (t.finished) { return balance; } - const entry = ensureEntry(balance, t.contract.amount.currency); + const entry = ensureEntry(balance, t.contractTerms.amount.currency); entry.pendingPayment = Amounts.add(entry.pendingPayment, - t.contract.amount).amount; + t.contractTerms.amount).amount; return balance; } @@ -2171,7 +2164,7 @@ export class Wallet { .toArray(); } - async hashContract(contract: Contract): Promise<string> { + async hashContract(contract: ContractTerms): Promise<string> { return this.cryptoApi.hashString(canonicalJson(contract)); } @@ -2193,16 +2186,16 @@ export class Wallet { } - async paymentSucceeded(contractHash: string, merchantSig: string): Promise<any> { + async paymentSucceeded(contractTermsHash: string, merchantSig: string): Promise<any> { const doPaymentSucceeded = async() => { const t = await this.q().get<TransactionRecord>(Stores.transactions, - contractHash); + contractTermsHash); if (!t) { console.error("contract not found"); return; } - const merchantPub = t.contract.merchant_pub; - const valid = this.cryptoApi.isValidPaymentSignature(merchantSig, contractHash, merchantPub); + const merchantPub = t.contractTerms.merchant_pub; + const valid = this.cryptoApi.isValidPaymentSignature(merchantSig, contractTermsHash, merchantPub); if (!valid) { console.error("merchant payment signature invalid"); // FIXME: properly display error diff --git a/src/webex/notify.ts b/src/webex/notify.ts index 2f38658bd..09c5ae002 100644 --- a/src/webex/notify.ts +++ b/src/webex/notify.ts @@ -28,6 +28,8 @@ import URI = require("urijs"); import wxApi = require("./wxApi"); +import { QueryPaymentResult } from "../types"; + declare var cloneInto: any; let logVerbose: boolean = false; @@ -96,7 +98,12 @@ function setStyles(installed: boolean) { } -function handlePaymentResponse(walletResp: any) { +function handlePaymentResponse(maybeFoundResponse: QueryPaymentResult) { + if (!maybeFoundResponse.found) { + console.log("pay-failed", {hint: "payment not found in the wallet"}); + return; + } + const walletResp = maybeFoundResponse; /** * Handle a failed payment. * @@ -115,7 +122,7 @@ function handlePaymentResponse(walletResp: any) { } timeoutHandle = window.setTimeout(onTimeout, 200); - await wxApi.paymentFailed(walletResp.H_contract); + await wxApi.paymentFailed(walletResp.contractTermsHash); if (timeoutHandle !== null) { clearTimeout(timeoutHandle); timeoutHandle = null; @@ -131,7 +138,7 @@ function handlePaymentResponse(walletResp: any) { let timeoutHandle: number|null = null; function sendPay() { r = new XMLHttpRequest(); - r.open("post", walletResp.contract.pay_url); + r.open("post", walletResp.contractTerms.pay_url); r.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); r.send(JSON.stringify(walletResp.payReq)); r.onload = async () => { @@ -142,8 +149,8 @@ function handlePaymentResponse(walletResp: any) { case 200: const merchantResp = JSON.parse(r.responseText); logVerbose && console.log("got success from pay_url"); - await wxApi.paymentSucceeded(walletResp.H_contract, merchantResp.sig); - const nextUrl = walletResp.contract.fulfillment_url; + await wxApi.paymentSucceeded(walletResp.contractTermsHash, merchantResp.sig); + const nextUrl = walletResp.contractTerms.fulfillment_url; logVerbose && console.log("taler-payment-succeeded done, going to", nextUrl); window.location.href = nextUrl; window.location.reload(true); @@ -318,7 +325,7 @@ function talerPay(msg: any): Promise<any> { const url = new URI(document.location.href).fragment("").href(); const res = await wxApi.queryPayment(url); logVerbose && console.log("taler-pay: got response", res); - if (res && res.payReq) { + if (res && res.found && res.payReq) { resolve(res); return; } diff --git a/src/webex/pages/confirm-contract.tsx b/src/webex/pages/confirm-contract.tsx index c5513f7c6..e80aed19d 100644 --- a/src/webex/pages/confirm-contract.tsx +++ b/src/webex/pages/confirm-contract.tsx @@ -25,12 +25,12 @@ */ import * as i18n from "../../i18n"; import { - Contract, + ContractTerms, ExchangeRecord, ProposalRecord, } from "../../types"; -import { renderContract } from "../renderHtml"; +import { renderContractTerms } from "../renderHtml"; import * as wxApi from "../wxApi"; import * as React from "react"; @@ -43,7 +43,7 @@ interface DetailState { } interface DetailProps { - contract: Contract; + contractTerms: ContractTerms; collapsed: boolean; exchanges: null|ExchangeRecord[]; } @@ -82,7 +82,7 @@ class Details extends React.Component<DetailProps, DetailState> { <div> {i18n.str`Accepted exchanges:`} <ul> - {this.props.contract.exchanges.map( + {this.props.contractTerms.exchanges.map( (e) => <li>{`${e.url}: ${e.master_pub}`}</li>)} </ul> {i18n.str`Exchanges in the wallet:`} @@ -185,7 +185,7 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt return ( <div> <div> - {renderContract(c)} + {renderContractTerms(c)} </div> <button onClick={() => this.doPayment()} disabled={this.state.payDisabled} @@ -195,7 +195,7 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt <div> {(this.state.error ? <p className="errorbox">{this.state.error}</p> : <p />)} </div> - <Details exchanges={this.state.exchanges} contract={c} collapsed={!this.state.error}/> + <Details exchanges={this.state.exchanges} contractTerms={c} collapsed={!this.state.error}/> </div> ); } diff --git a/src/webex/renderHtml.tsx b/src/webex/renderHtml.tsx index 70cd61d62..4dd7baded 100644 --- a/src/webex/renderHtml.tsx +++ b/src/webex/renderHtml.tsx @@ -27,7 +27,7 @@ import { amountToPretty } from "../helpers"; import * as i18n from "../i18n"; import { - Contract, + ContractTerms, } from "../types"; import * as React from "react"; @@ -35,14 +35,14 @@ import * as React from "react"; /** * Render contract terms for the end user to view. */ -export function renderContract(contract: Contract): JSX.Element { +export function renderContractTerms(contractTerms: ContractTerms): JSX.Element { let merchantName; - if (contract.merchant && contract.merchant.name) { - merchantName = <strong>{contract.merchant.name}</strong>; + if (contractTerms.merchant && contractTerms.merchant.name) { + merchantName = <strong>{contractTerms.merchant.name}</strong>; } else { - merchantName = <strong>(pub: {contract.merchant_pub})</strong>; + merchantName = <strong>(pub: {contractTerms.merchant_pub})</strong>; } - const amount = <strong>{amountToPretty(contract.amount)}</strong>; + const amount = <strong>{amountToPretty(contractTerms.amount)}</strong>; return ( <div> @@ -53,7 +53,7 @@ export function renderContract(contract: Contract): JSX.Element { </i18n.Translate> <p>{i18n.str`You are about to purchase:`}</p> <ul> - {contract.products.map( + {contractTerms.products.map( (p: any, i: number) => (<li key={i}>{`${p.description}: ${amountToPretty(p.price)}`}</li>)) } </ul> diff --git a/src/webex/wxApi.ts b/src/webex/wxApi.ts index 4babb2a79..1968b6575 100644 --- a/src/webex/wxApi.ts +++ b/src/webex/wxApi.ts @@ -33,6 +33,7 @@ import { PreCoinRecord, ReserveCreationInfo, ReserveRecord, + QueryPaymentResult, } from "../types"; import { MessageType, MessageMap } from "./messages"; @@ -213,7 +214,7 @@ export function confirmReserve(reservePub: string): Promise<void> { /** * Query for a payment by fulfillment URL. */ -export function queryPayment(url: string): Promise<any> { +export function queryPayment(url: string): Promise<QueryPaymentResult> { return callBackend("query-payment", { url }); } |