fix terminology, better types

This commit is contained in:
Florian Dold 2017-06-01 18:46:07 +02:00
parent 26467674ba
commit 29b107f937
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
8 changed files with 219 additions and 90 deletions

File diff suppressed because one or more lines are too long

View File

@ -54,7 +54,7 @@ test("amount subtraction (saturation)", (t) => {
}); });
test("contract validation", (t) => { test("contract terms validation", (t) => {
const c = { const c = {
H_wire: "123", H_wire: "123",
amount: amt(1, 2, "EUR"), amount: amt(1, 2, "EUR"),
@ -73,13 +73,13 @@ test("contract validation", (t) => {
wire_method: "test", wire_method: "test",
}; };
types.Contract.checked(c); types.ContractTerms.checked(c);
const c1 = JSON.parse(JSON.stringify(c)); const c1 = JSON.parse(JSON.stringify(c));
c1.exchanges = []; c1.exchanges = [];
try { try {
types.Contract.checked(c1); types.ContractTerms.checked(c1);
} catch (e) { } catch (e) {
t.pass(); t.pass();
return; return;

View File

@ -903,10 +903,10 @@ export interface WalletBalanceEntry {
* Contract terms from a merchant. * Contract terms from a merchant.
*/ */
@Checkable.Class({validate: true}) @Checkable.Class({validate: true})
export class Contract { export class ContractTerms {
validate() { validate() {
if (this.exchanges.length === 0) { 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 * Verify that a value matches the schema of this class and convert it into a
* member. * 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. * The contract that was offered by the merchant.
*/ */
@Checkable.Value(Contract) @Checkable.Value(ContractTerms)
contractTerms: Contract; contractTerms: ContractTerms;
/** /**
* Signature by the merchant over the contract details. * 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;
}

View File

@ -47,11 +47,10 @@ import {
Amounts, Amounts,
Auditor, Auditor,
CheckPayResult, CheckPayResult,
CoinPaySig,
CoinRecord, CoinRecord,
CoinStatus, CoinStatus,
ConfirmPayResult, ConfirmPayResult,
Contract, ContractTerms,
CreateReserveResponse, CreateReserveResponse,
CurrencyRecord, CurrencyRecord,
Denomination, Denomination,
@ -63,10 +62,12 @@ import {
HistoryLevel, HistoryLevel,
HistoryRecord, HistoryRecord,
Notifier, Notifier,
ProposalRecord,
PayCoinInfo, PayCoinInfo,
PayReq,
PaybackConfirmation, PaybackConfirmation,
PreCoinRecord, PreCoinRecord,
ProposalRecord,
QueryPaymentResult,
RefreshSessionRecord, RefreshSessionRecord,
ReserveCreationInfo, ReserveCreationInfo,
ReserveRecord, ReserveRecord,
@ -272,16 +273,9 @@ export class ConfirmReserveRequest {
} }
interface PayReq {
coins: CoinPaySig[];
merchant_pub: string;
order_id: string;
exchange: string;
}
interface TransactionRecord { interface TransactionRecord {
contractHash: string; contractTermsHash: string;
contract: Contract; contractTerms: ContractTerms;
payReq: PayReq; payReq: PayReq;
merchantSig: string; merchantSig: string;
@ -518,11 +512,11 @@ export namespace Stores {
class TransactionsStore extends Store<TransactionRecord> { class TransactionsStore extends Store<TransactionRecord> {
constructor() { constructor() {
super("transactions", {keyPath: "contractHash"}); super("transactions", {keyPath: "contractTermsHash"});
} }
fulfillmentUrlIndex = new Index<string, TransactionRecord>(this, "fulfillment_url", "contract.fulfillment_url"); fulfillmentUrlIndex = new Index<string, TransactionRecord>(this, "fulfillment_url", "contractTerms.fulfillment_url");
orderIdIndex = new Index<string, TransactionRecord>(this, "order_id", "contract.order_id"); orderIdIndex = new Index<string, TransactionRecord>(this, "order_id", "contractTerms.order_id");
} }
class DenominationsStore extends Store<DenominationRecord> { class DenominationsStore extends Store<DenominationRecord> {
@ -832,7 +826,7 @@ export class Wallet {
/** /**
* Record all information that is necessary to * 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, private async recordConfirmPay(proposal: ProposalRecord,
payCoinInfo: PayCoinInfo, payCoinInfo: PayCoinInfo,
@ -844,8 +838,8 @@ export class Wallet {
order_id: proposal.contractTerms.order_id, order_id: proposal.contractTerms.order_id,
}; };
const t: TransactionRecord = { const t: TransactionRecord = {
contract: proposal.contractTerms, contractTerms: proposal.contractTerms,
contractHash: proposal.contractTermsHash, contractTermsHash: proposal.contractTermsHash,
finished: false, finished: false,
merchantSig: proposal.merchantSig, merchantSig: proposal.merchantSig,
payReq, payReq,
@ -854,7 +848,7 @@ export class Wallet {
const historyEntry: HistoryRecord = { const historyEntry: HistoryRecord = {
detail: { detail: {
amount: proposal.contractTerms.amount, amount: proposal.contractTerms.amount,
contractHash: proposal.contractTermsHash, contractTermsHash: proposal.contractTermsHash,
fulfillmentUrl: proposal.contractTerms.fulfillment_url, fulfillmentUrl: proposal.contractTerms.fulfillment_url,
merchantName: proposal.contractTerms.merchant.name, merchantName: proposal.contractTerms.merchant.name,
}, },
@ -980,7 +974,7 @@ export class Wallet {
* Retrieve information required to pay for a contract, where the * Retrieve information required to pay for a contract, where the
* contract is identified via the fulfillment url. * 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); console.log("query for payment", url);
const t = await this.q().getIndexed(Stores.transactions.fulfillmentUrlIndex, url); const t = await this.q().getIndexed(Stores.transactions.fulfillmentUrlIndex, url);
@ -988,17 +982,16 @@ export class Wallet {
if (!t) { if (!t) {
console.log("query for payment failed"); console.log("query for payment failed");
return { return {
success: false, found: false,
}; };
} }
console.log("query for payment succeeded:", t); console.log("query for payment succeeded:", t);
const resp = { return {
H_contract: t.contractHash, contractTermsHash: t.contractTermsHash,
contract: t.contract, contractTerms: t.contractTerms,
payReq: t.payReq, payReq: t.payReq,
success: true, found: true,
}; };
return resp;
} }
@ -1804,9 +1797,9 @@ export class Wallet {
if (t.finished) { if (t.finished) {
return balance; return balance;
} }
const entry = ensureEntry(balance, t.contract.amount.currency); const entry = ensureEntry(balance, t.contractTerms.amount.currency);
entry.pendingPayment = Amounts.add(entry.pendingPayment, entry.pendingPayment = Amounts.add(entry.pendingPayment,
t.contract.amount).amount; t.contractTerms.amount).amount;
return balance; return balance;
} }
@ -2171,7 +2164,7 @@ export class Wallet {
.toArray(); .toArray();
} }
async hashContract(contract: Contract): Promise<string> { async hashContract(contract: ContractTerms): Promise<string> {
return this.cryptoApi.hashString(canonicalJson(contract)); 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 doPaymentSucceeded = async() => {
const t = await this.q().get<TransactionRecord>(Stores.transactions, const t = await this.q().get<TransactionRecord>(Stores.transactions,
contractHash); contractTermsHash);
if (!t) { if (!t) {
console.error("contract not found"); console.error("contract not found");
return; return;
} }
const merchantPub = t.contract.merchant_pub; const merchantPub = t.contractTerms.merchant_pub;
const valid = this.cryptoApi.isValidPaymentSignature(merchantSig, contractHash, merchantPub); const valid = this.cryptoApi.isValidPaymentSignature(merchantSig, contractTermsHash, merchantPub);
if (!valid) { if (!valid) {
console.error("merchant payment signature invalid"); console.error("merchant payment signature invalid");
// FIXME: properly display error // FIXME: properly display error

View File

@ -28,6 +28,8 @@ import URI = require("urijs");
import wxApi = require("./wxApi"); import wxApi = require("./wxApi");
import { QueryPaymentResult } from "../types";
declare var cloneInto: any; declare var cloneInto: any;
let logVerbose: boolean = false; 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. * Handle a failed payment.
* *
@ -115,7 +122,7 @@ function handlePaymentResponse(walletResp: any) {
} }
timeoutHandle = window.setTimeout(onTimeout, 200); timeoutHandle = window.setTimeout(onTimeout, 200);
await wxApi.paymentFailed(walletResp.H_contract); await wxApi.paymentFailed(walletResp.contractTermsHash);
if (timeoutHandle !== null) { if (timeoutHandle !== null) {
clearTimeout(timeoutHandle); clearTimeout(timeoutHandle);
timeoutHandle = null; timeoutHandle = null;
@ -131,7 +138,7 @@ function handlePaymentResponse(walletResp: any) {
let timeoutHandle: number|null = null; let timeoutHandle: number|null = null;
function sendPay() { function sendPay() {
r = new XMLHttpRequest(); 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.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
r.send(JSON.stringify(walletResp.payReq)); r.send(JSON.stringify(walletResp.payReq));
r.onload = async () => { r.onload = async () => {
@ -142,8 +149,8 @@ function handlePaymentResponse(walletResp: any) {
case 200: case 200:
const merchantResp = JSON.parse(r.responseText); const merchantResp = JSON.parse(r.responseText);
logVerbose && console.log("got success from pay_url"); logVerbose && console.log("got success from pay_url");
await wxApi.paymentSucceeded(walletResp.H_contract, merchantResp.sig); await wxApi.paymentSucceeded(walletResp.contractTermsHash, merchantResp.sig);
const nextUrl = walletResp.contract.fulfillment_url; const nextUrl = walletResp.contractTerms.fulfillment_url;
logVerbose && console.log("taler-payment-succeeded done, going to", nextUrl); logVerbose && console.log("taler-payment-succeeded done, going to", nextUrl);
window.location.href = nextUrl; window.location.href = nextUrl;
window.location.reload(true); window.location.reload(true);
@ -318,7 +325,7 @@ function talerPay(msg: any): Promise<any> {
const url = new URI(document.location.href).fragment("").href(); const url = new URI(document.location.href).fragment("").href();
const res = await wxApi.queryPayment(url); const res = await wxApi.queryPayment(url);
logVerbose && console.log("taler-pay: got response", res); logVerbose && console.log("taler-pay: got response", res);
if (res && res.payReq) { if (res && res.found && res.payReq) {
resolve(res); resolve(res);
return; return;
} }

View File

@ -25,12 +25,12 @@
*/ */
import * as i18n from "../../i18n"; import * as i18n from "../../i18n";
import { import {
Contract, ContractTerms,
ExchangeRecord, ExchangeRecord,
ProposalRecord, ProposalRecord,
} from "../../types"; } from "../../types";
import { renderContract } from "../renderHtml"; import { renderContractTerms } from "../renderHtml";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
import * as React from "react"; import * as React from "react";
@ -43,7 +43,7 @@ interface DetailState {
} }
interface DetailProps { interface DetailProps {
contract: Contract; contractTerms: ContractTerms;
collapsed: boolean; collapsed: boolean;
exchanges: null|ExchangeRecord[]; exchanges: null|ExchangeRecord[];
} }
@ -82,7 +82,7 @@ class Details extends React.Component<DetailProps, DetailState> {
<div> <div>
{i18n.str`Accepted exchanges:`} {i18n.str`Accepted exchanges:`}
<ul> <ul>
{this.props.contract.exchanges.map( {this.props.contractTerms.exchanges.map(
(e) => <li>{`${e.url}: ${e.master_pub}`}</li>)} (e) => <li>{`${e.url}: ${e.master_pub}`}</li>)}
</ul> </ul>
{i18n.str`Exchanges in the wallet:`} {i18n.str`Exchanges in the wallet:`}
@ -185,7 +185,7 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
return ( return (
<div> <div>
<div> <div>
{renderContract(c)} {renderContractTerms(c)}
</div> </div>
<button onClick={() => this.doPayment()} <button onClick={() => this.doPayment()}
disabled={this.state.payDisabled} disabled={this.state.payDisabled}
@ -195,7 +195,7 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
<div> <div>
{(this.state.error ? <p className="errorbox">{this.state.error}</p> : <p />)} {(this.state.error ? <p className="errorbox">{this.state.error}</p> : <p />)}
</div> </div>
<Details exchanges={this.state.exchanges} contract={c} collapsed={!this.state.error}/> <Details exchanges={this.state.exchanges} contractTerms={c} collapsed={!this.state.error}/>
</div> </div>
); );
} }

View File

@ -27,7 +27,7 @@
import { amountToPretty } from "../helpers"; import { amountToPretty } from "../helpers";
import * as i18n from "../i18n"; import * as i18n from "../i18n";
import { import {
Contract, ContractTerms,
} from "../types"; } from "../types";
import * as React from "react"; import * as React from "react";
@ -35,14 +35,14 @@ import * as React from "react";
/** /**
* Render contract terms for the end user to view. * 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; let merchantName;
if (contract.merchant && contract.merchant.name) { if (contractTerms.merchant && contractTerms.merchant.name) {
merchantName = <strong>{contract.merchant.name}</strong>; merchantName = <strong>{contractTerms.merchant.name}</strong>;
} else { } 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 ( return (
<div> <div>
@ -53,7 +53,7 @@ export function renderContract(contract: Contract): JSX.Element {
</i18n.Translate> </i18n.Translate>
<p>{i18n.str`You are about to purchase:`}</p> <p>{i18n.str`You are about to purchase:`}</p>
<ul> <ul>
{contract.products.map( {contractTerms.products.map(
(p: any, i: number) => (<li key={i}>{`${p.description}: ${amountToPretty(p.price)}`}</li>)) (p: any, i: number) => (<li key={i}>{`${p.description}: ${amountToPretty(p.price)}`}</li>))
} }
</ul> </ul>

View File

@ -33,6 +33,7 @@ import {
PreCoinRecord, PreCoinRecord,
ReserveCreationInfo, ReserveCreationInfo,
ReserveRecord, ReserveRecord,
QueryPaymentResult,
} from "../types"; } from "../types";
import { MessageType, MessageMap } from "./messages"; import { MessageType, MessageMap } from "./messages";
@ -213,7 +214,7 @@ export function confirmReserve(reservePub: string): Promise<void> {
/** /**
* Query for a payment by fulfillment URL. * 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 }); return callBackend("query-payment", { url });
} }