new fulfillment protocol

This commit is contained in:
Florian Dold 2016-02-01 15:10:20 +01:00
parent b150470eb6
commit 42a0076f59
19 changed files with 2487 additions and 401 deletions

View File

@ -17,89 +17,100 @@
// query the availability of Taler. // query the availability of Taler.
/// <reference path="../lib/decl/chrome/chrome.d.ts" /> /// <reference path="../lib/decl/chrome/chrome.d.ts" />
"use strict"; "use strict";
console.log("Taler injected"); // Make sure we don't pollute the namespace too much.
function subst(url, H_contract) { var TalerNotify;
url = url.replace("${H_contract}", H_contract); (function (TalerNotify) {
url = url.replace("${$}", "$"); console.log("Taler injected");
return url; function subst(url, H_contract) {
} url = url.replace("${H_contract}", H_contract);
document.addEventListener("taler-probe", function (e) { url = url.replace("${$}", "$");
var evt = new Event("taler-wallet-present"); return url;
document.dispatchEvent(evt);
console.log("handshake done");
});
document.addEventListener("taler-create-reserve", function (e) {
var $ = function (x) { return document.getElementById(x); };
console.log("taler-create-reserve with " + JSON.stringify(e.detail));
var form_uri = $(e.detail.form_id).action;
// TODO: validate event fields
// TODO: also send extra bank-defined form fields
var params = {
post_url: URI(form_uri).absoluteTo(document.location.href).href(),
// TODO: This should change in the future, we should not deal with the
// amount as a bank-specific string here.
amount_str: $(e.detail.input_amount).value,
// TODO: This double indirection is way too much ...
field_amount: $(e.detail.input_amount).name,
field_reserve_pub: $(e.detail.input_pub).name,
field_mint: $(e.detail.mint_rcv).name,
};
var uri = URI(chrome.extension.getURL("pages/confirm-create-reserve.html"));
document.location.href = uri.query(params).href();
});
document.addEventListener("taler-contract", function (e) {
// XXX: the merchant should just give us the parsed data ...
var offer = JSON.parse(e.detail);
var uri = URI(chrome.extension.getURL("pages/confirm-contract.html"));
var params = {
offer: JSON.stringify(offer),
merchantPageUrl: document.location.href,
cookie: document.cookie,
};
document.location.href = uri.query(params).href();
});
document.addEventListener('taler-execute-payment', function (e) {
console.log("got taler-execute-payment in content page");
if (!e.detail.pay_url) {
console.log("field 'pay_url' missing in taler-execute-payment event");
return;
} }
var payUrl = e.detail.pay_url; var $ = function (x) { return document.getElementById(x); };
var msg = { document.addEventListener("taler-probe", function (e) {
type: "execute-payment", var evt = new Event("taler-wallet-present");
detail: { document.dispatchEvent(evt);
H_contract: e.detail.H_contract, console.log("handshake done");
}, });
}; document.addEventListener("taler-create-reserve", function (e) {
chrome.runtime.sendMessage(msg, function (resp) { console.log("taler-create-reserve with " + JSON.stringify(e.detail));
if (!resp.success) { var form_uri = $(e.detail.form_id).action;
console.log("failure!"); // TODO: validate event fields
// TODO: also send extra bank-defined form fields
var params = {
post_url: URI(form_uri).absoluteTo(document.location.href).href(),
// TODO: This should change in the future, we should not deal with the
// amount as a bank-specific string here.
amount_str: $(e.detail.input_amount).value,
// TODO: This double indirection is way too much ...
field_amount: $(e.detail.input_amount).name,
field_reserve_pub: $(e.detail.input_pub).name,
field_mint: $(e.detail.mint_rcv).name,
};
var uri = URI(chrome.extension.getURL("pages/confirm-create-reserve.html"));
document.location.href = uri.query(params).href();
});
document.addEventListener("taler-contract", function (e) {
// XXX: the merchant should just give us the parsed data ...
var offer = JSON.parse(e.detail);
var uri = URI(chrome.extension.getURL("pages/confirm-contract.html"));
var params = {
offer: JSON.stringify(offer),
merchantPageUrl: document.location.href,
};
document.location.href = uri.query(params).href();
});
document.addEventListener('taler-execute-payment', function (e) {
console.log("got taler-execute-payment in content page");
if (!e.detail.pay_url) {
console.log("field 'pay_url' missing in taler-execute-payment event");
return; return;
} }
var contract = resp.contract; var payUrl = e.detail.pay_url;
if (!contract) { var msg = {
throw Error("contract missing"); type: "execute-payment",
} detail: {
var payReq = Object.assign({}, resp.payReq); H_contract: e.detail.H_contract,
if (e.detail.require_contract) { },
payReq.contract = contract;
}
console.log("Making request to ", payUrl);
var r = new XMLHttpRequest();
r.open('post', payUrl);
r.send(JSON.stringify(payReq));
r.onload = function () {
switch (r.status) {
case 200:
console.log("going to", contract.fulfillment_url);
window.location.href = subst(contract.fulfillment_url, e.detail.H_contract);
window.location.reload(true);
break;
default:
console.log("Unexpected status code for $pay_url:", r.status);
break;
}
}; };
chrome.runtime.sendMessage(msg, function (resp) {
console.log("got resp");
console.dir(resp);
if (!resp.success) {
console.log("got event detial:");
console.dir(e.detail);
if (e.detail.offering_url) {
console.log("offering url", e.detail.offering_url);
window.location.href = e.detail.offering_url;
}
else {
console.error("execute-payment failed");
}
return;
}
var contract = resp.contract;
if (!contract) {
throw Error("contract missing");
}
console.log("Making request to ", payUrl);
var r = new XMLHttpRequest();
r.open('post', payUrl);
r.send(JSON.stringify(resp.payReq));
r.onload = function () {
switch (r.status) {
case 200:
console.log("going to", contract.fulfillment_url);
// TODO: Is this the right thing? Does the reload
// TODO: override setting location.href?
window.location.href = subst(contract.fulfillment_url, e.detail.H_contract);
window.location.reload(true);
break;
default:
console.log("Unexpected status code for $pay_url:", r.status);
break;
}
};
});
}); });
}); })(TalerNotify || (TalerNotify = {}));
//# sourceMappingURL=notify.js.map //# sourceMappingURL=notify.js.map

View File

@ -21,99 +21,108 @@
"use strict"; "use strict";
console.log("Taler injected");
function subst(url: string, H_contract) { // Make sure we don't pollute the namespace too much.
url = url.replace("${H_contract}", H_contract); namespace TalerNotify {
url = url.replace("${$}", "$"); console.log("Taler injected");
return url;
}
function subst(url: string, H_contract) {
document.addEventListener("taler-probe", function(e) { url = url.replace("${H_contract}", H_contract);
let evt = new Event("taler-wallet-present"); url = url.replace("${$}", "$");
document.dispatchEvent(evt); return url;
console.log("handshake done");
});
document.addEventListener("taler-create-reserve", function(e: CustomEvent) {
let $ = (x) => document.getElementById(x);
console.log("taler-create-reserve with " + JSON.stringify(e.detail));
let form_uri = (<HTMLFormElement>$(e.detail.form_id)).action;
// TODO: validate event fields
// TODO: also send extra bank-defined form fields
let params = {
post_url: URI(form_uri).absoluteTo(document.location.href).href(),
// TODO: This should change in the future, we should not deal with the
// amount as a bank-specific string here.
amount_str: (<HTMLInputElement>$(e.detail.input_amount)).value,
// TODO: This double indirection is way too much ...
field_amount: (<HTMLInputElement>$(e.detail.input_amount)).name,
field_reserve_pub: (<HTMLInputElement>$(e.detail.input_pub)).name,
field_mint: (<HTMLInputElement>$(e.detail.mint_rcv)).name,
};
let uri = URI(chrome.extension.getURL("pages/confirm-create-reserve.html"));
document.location.href = uri.query(params).href();
});
document.addEventListener("taler-contract", function(e: CustomEvent) {
// XXX: the merchant should just give us the parsed data ...
let offer = JSON.parse(e.detail);
let uri = URI(chrome.extension.getURL("pages/confirm-contract.html"));
let params = {
offer: JSON.stringify(offer),
merchantPageUrl: document.location.href,
cookie: document.cookie,
};
document.location.href = uri.query(params).href();
});
document.addEventListener('taler-execute-payment', function(e: CustomEvent) {
console.log("got taler-execute-payment in content page");
if (!e.detail.pay_url) {
console.log("field 'pay_url' missing in taler-execute-payment event");
return;
} }
let payUrl = e.detail.pay_url;
let msg = { let $ = (x) => document.getElementById(x);
type: "execute-payment",
detail: { document.addEventListener("taler-probe", function(e) {
H_contract: e.detail.H_contract, let evt = new Event("taler-wallet-present");
}, document.dispatchEvent(evt);
}; console.log("handshake done");
chrome.runtime.sendMessage(msg, (resp) => { });
if (!resp.success) {
console.log("failure!"); document.addEventListener("taler-create-reserve", function(e: CustomEvent) {
console.log("taler-create-reserve with " + JSON.stringify(e.detail));
let form_uri = (<HTMLFormElement>$(e.detail.form_id)).action;
// TODO: validate event fields
// TODO: also send extra bank-defined form fields
let params = {
post_url: URI(form_uri).absoluteTo(document.location.href).href(),
// TODO: This should change in the future, we should not deal with the
// amount as a bank-specific string here.
amount_str: (<HTMLInputElement>$(e.detail.input_amount)).value,
// TODO: This double indirection is way too much ...
field_amount: (<HTMLInputElement>$(e.detail.input_amount)).name,
field_reserve_pub: (<HTMLInputElement>$(e.detail.input_pub)).name,
field_mint: (<HTMLInputElement>$(e.detail.mint_rcv)).name,
};
let uri = URI(chrome.extension.getURL("pages/confirm-create-reserve.html"));
document.location.href = uri.query(params).href();
});
document.addEventListener("taler-contract", function(e: CustomEvent) {
// XXX: the merchant should just give us the parsed data ...
let offer = JSON.parse(e.detail);
let uri = URI(chrome.extension.getURL("pages/confirm-contract.html"));
let params = {
offer: JSON.stringify(offer),
merchantPageUrl: document.location.href,
};
document.location.href = uri.query(params).href();
});
document.addEventListener('taler-execute-payment', function(e: CustomEvent) {
console.log("got taler-execute-payment in content page");
if (!e.detail.pay_url) {
console.log("field 'pay_url' missing in taler-execute-payment event");
return; return;
} }
let contract = resp.contract; let payUrl = e.detail.pay_url;
if (!contract) { let msg = {
throw Error("contract missing"); type: "execute-payment",
} detail: {
H_contract: e.detail.H_contract,
let payReq = Object.assign({}, resp.payReq); },
if (e.detail.require_contract) {
payReq.contract = contract;
}
console.log("Making request to ", payUrl);
let r = new XMLHttpRequest();
r.open('post', payUrl);
r.send(JSON.stringify(payReq));
r.onload = () => {
switch (r.status) {
case 200:
console.log("going to", contract.fulfillment_url);
window.location.href = subst(contract.fulfillment_url,
e.detail.H_contract);
window.location.reload(true);
break;
default:
console.log("Unexpected status code for $pay_url:", r.status);
break;
}
}; };
chrome.runtime.sendMessage(msg, (resp) => {
console.log("got resp");
console.dir(resp);
if (!resp.success) {
console.log("got event detial:");
console.dir(e.detail);
if (e.detail.offering_url) {
console.log("offering url", e.detail.offering_url);
window.location.href = e.detail.offering_url;
} else {
console.error("execute-payment failed");
}
return;
}
let contract = resp.contract;
if (!contract) {
throw Error("contract missing");
}
console.log("Making request to ", payUrl);
let r = new XMLHttpRequest();
r.open('post', payUrl);
r.send(JSON.stringify(resp.payReq));
r.onload = () => {
switch (r.status) {
case 200:
console.log("going to", contract.fulfillment_url);
// TODO: Is this the right thing? Does the reload
// TODO: override setting location.href?
window.location.href = subst(contract.fulfillment_url,
e.detail.H_contract);
window.location.reload(true);
break;
default:
console.log("Unexpected status code for $pay_url:", r.status);
break;
}
};
});
}); });
}); }

2178
extension/lib/decl/node.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,136 +0,0 @@
/*
This file is part of TALER
(C) 2016 GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/
"use strict";
/**
* Decorators for type-checking JSON into
* an object.
* @module Checkable
* @author Florian Dold
*/
export namespace Checkable {
let chkSym = Symbol("checkable");
function checkNumber(target, prop): any {
if ((typeof target) !== "number") {
throw Error("number expected for " + prop.propertyKey);
}
return target;
}
function checkString(target, prop): any {
if (typeof target !== "string") {
throw Error("string expected for " + prop.propertyKey);
}
return target;
}
function checkAnyObject(target, prop): any {
if (typeof target !== "object") {
throw Error("object expected for " + prop.propertyKey);
}
return target;
}
function checkValue(target, prop): any {
let type = prop.type;
if (!type) {
throw Error("assertion failed");
}
let v = target;
if (!v || typeof v !== "object") {
throw Error("expected object for " + prop.propertyKey);
}
let props = type.prototype[chkSym].props;
let remainingPropNames = new Set(Object.getOwnPropertyNames(v));
let obj = new type();
for (let prop of props) {
if (!remainingPropNames.has(prop.propertyKey)) {
throw Error("Property missing: " + prop.propertyKey);
}
if (!remainingPropNames.delete(prop.propertyKey)) {
throw Error("assertion failed");
}
let propVal = v[prop.propertyKey];
obj[prop.propertyKey] = prop.checker(propVal, prop);
}
if (remainingPropNames.size != 0) {
throw Error("superfluous properties " + JSON.stringify(Array.from(
remainingPropNames.values())));
}
return obj;
}
export function Class(target) {
target.checked = (v) => {
return checkValue(v, {
propertyKey: "(root)",
type: target,
checker: checkValue
});
};
return target;
}
export function Value(type) {
function deco(target: Object, propertyKey: string | symbol): void {
let chk = mkChk(target);
chk.props.push({
propertyKey: propertyKey,
checker: checkValue,
type: type
});
}
return deco;
}
export function List(type) {
function deco(target: Object, propertyKey: string | symbol): void {
throw Error("not implemented");
}
return deco;
}
export function Number(target: Object, propertyKey: string | symbol): void {
let chk = mkChk(target);
chk.props.push({propertyKey: propertyKey, checker: checkNumber});
}
export function AnyObject(target: Object, propertyKey: string | symbol): void {
let chk = mkChk(target);
chk.props.push({propertyKey: propertyKey, checker: checkAnyObject});
}
export function String(target: Object, propertyKey: string | symbol): void {
let chk = mkChk(target);
chk.props.push({propertyKey: propertyKey, checker: checkString});
}
function mkChk(target) {
let chk = target[chkSym];
if (!chk) {
chk = {props: []};
target[chkSym] = chk;
}
return chk;
}
}

View File

@ -14,7 +14,7 @@
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/ */
import {AmountJson_interface} from "./types"; import {AmountJson} from "./types";
import * as EmscWrapper from "../emscripten/emsc"; import * as EmscWrapper from "../emscripten/emsc";
/** /**
@ -288,7 +288,7 @@ arenaStack.push(new SyncArena());
export class Amount extends ArenaObject { export class Amount extends ArenaObject {
constructor(args?: AmountJson_interface, arena?: Arena) { constructor(args?: AmountJson, arena?: Arena) {
super(arena); super(arena);
if (args) { if (args) {
this.nativePtr = emscAlloc.get_amount(args.value, this.nativePtr = emscAlloc.get_amount(args.value,

View File

@ -14,8 +14,6 @@
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/ */
/// <reference path="../decl/urijs/URIjs.d.ts" />
/** /**
* Database query abstractions. * Database query abstractions.

View File

@ -33,10 +33,10 @@ export interface Keys {
} }
export interface Denomination { export interface Denomination {
value: AmountJson_interface; value: AmountJson;
denom_pub: string; denom_pub: string;
fee_withdraw: AmountJson_interface; fee_withdraw: AmountJson;
fee_deposit: AmountJson_interface; fee_deposit: AmountJson;
} }
export interface PreCoin { export interface PreCoin {
@ -48,7 +48,7 @@ export interface PreCoin {
withdrawSig: string; withdrawSig: string;
coinEv: string; coinEv: string;
mintBaseUrl: string; mintBaseUrl: string;
coinValue: AmountJson_interface; coinValue: AmountJson;
} }
export interface Coin { export interface Coin {
@ -56,17 +56,18 @@ export interface Coin {
coinPriv: string; coinPriv: string;
denomPub: string; denomPub: string;
denomSig: string; denomSig: string;
currentAmount: AmountJson_interface; currentAmount: AmountJson;
mintBaseUrl: string; mintBaseUrl: string;
} }
export interface AmountJson_interface { export interface AmountJson {
value: number; value: number;
fraction: number fraction: number
currency: string; currency: string;
} }
export interface ConfirmReserveRequest { export interface ConfirmReserveRequest {
/** /**
* Name of the form field for the amount. * Name of the form field for the amount.

View File

@ -22,7 +22,6 @@
*/ */
import {Amount} from "./emscriptif" import {Amount} from "./emscriptif"
import {AmountJson_interface} from "./types";
import {CoinWithDenom} from "./types"; import {CoinWithDenom} from "./types";
import {DepositRequestPS_Args} from "./emscriptif"; import {DepositRequestPS_Args} from "./emscriptif";
import {HashCode} from "./emscriptif"; import {HashCode} from "./emscriptif";
@ -45,46 +44,25 @@ import {PreCoin} from "./types";
import {rsaUnblind} from "./emscriptif"; import {rsaUnblind} from "./emscriptif";
import {RsaSignature} from "./emscriptif"; import {RsaSignature} from "./emscriptif";
import {Mint} from "./types"; import {Mint} from "./types";
import {Checkable} from "./checkable";
import {HttpResponse} from "./http"; import {HttpResponse} from "./http";
import {RequestException} from "./http"; import {RequestException} from "./http";
import {Query} from "./query"; import {Query} from "./query";
import {AmountJson} from "./types";
"use strict"; "use strict";
@Checkable.Class
class AmountJson {
@Checkable.Number
value: number;
@Checkable.Number
fraction: number;
@Checkable.String
currency: string;
static check: (v: any) => AmountJson;
}
@Checkable.Class
class CoinPaySig { class CoinPaySig {
@Checkable.String
coin_sig: string; coin_sig: string;
@Checkable.String
coin_pub: string; coin_pub: string;
@Checkable.String
ub_sig: string; ub_sig: string;
@Checkable.String
denom_pub: string; denom_pub: string;
@Checkable.Value(AmountJson)
f: AmountJson; f: AmountJson;
static check: (v: any) => CoinPaySig;
} }
@ -104,17 +82,17 @@ interface MintInfo {
interface Offer { interface Offer {
contract: Contract; contract: Contract;
sig: string; merchant_sig: string;
H_contract: string; H_contract: string;
} }
interface Contract { interface Contract {
H_wire: string; H_wire: string;
amount: AmountJson_interface; amount: AmountJson;
auditors: string[]; auditors: string[];
expiry: string, expiry: string,
locations: string[]; locations: string[];
max_fee: AmountJson_interface; max_fee: AmountJson;
merchant: any; merchant: any;
merchant_pub: string; merchant_pub: string;
mints: MintInfo[]; mints: MintInfo[];
@ -122,6 +100,7 @@ interface Contract {
refund_deadline: string; refund_deadline: string;
timestamp: string; timestamp: string;
transaction_id: number; transaction_id: number;
fulfillment_url: string;
} }
@ -130,7 +109,7 @@ interface CoinPaySig_interface {
coin_pub: string; coin_pub: string;
ub_sig: string; ub_sig: string;
denom_pub: string; denom_pub: string;
f: AmountJson_interface; f: AmountJson;
} }
@ -177,7 +156,7 @@ function canonicalizeBaseUrl(url) {
return x.href() return x.href()
} }
function parsePrettyAmount(pretty: string): AmountJson_interface { function parsePrettyAmount(pretty: string): AmountJson {
const res = /([0-9]+)(.[0-9]+)?\s*(\w+)/.exec(pretty); const res = /([0-9]+)(.[0-9]+)?\s*(\w+)/.exec(pretty);
if (!res) { if (!res) {
return null; return null;
@ -291,8 +270,8 @@ export class Wallet {
* @param depositFeeLimit * @param depositFeeLimit
* @param allowedMints * @param allowedMints
*/ */
getPossibleMintCoins(paymentAmount: AmountJson_interface, getPossibleMintCoins(paymentAmount: AmountJson,
depositFeeLimit: AmountJson_interface, depositFeeLimit: AmountJson,
allowedMints: MintInfo[]): Promise<MintCoins> { allowedMints: MintInfo[]): Promise<MintCoins> {
@ -366,15 +345,17 @@ export class Wallet {
executePay(offer: Offer, executePay(offer: Offer,
payCoinInfo: PayCoinInfo, payCoinInfo: PayCoinInfo,
chosenMint: string): Promise<void> { chosenMint: string): Promise<any> {
let payReq = {}; let payReq = {};
payReq["H_wire"] = offer.contract.H_wire; payReq["amount"] = offer.contract.amount;
payReq["H_contract"] = offer.H_contract;
payReq["transaction_id"] = offer.contract.transaction_id;
payReq["refund_deadline"] = offer.contract.refund_deadline;
payReq["mint"] = URI(chosenMint).href();
payReq["coins"] = payCoinInfo.map((x) => x.sig); payReq["coins"] = payCoinInfo.map((x) => x.sig);
payReq["H_contract"] = offer.H_contract;
payReq["max_fee"] = offer.contract.max_fee;
payReq["merchant_sig"] = offer.merchant_sig;
payReq["mint"] = URI(chosenMint).href();
payReq["refund_deadline"] = offer.contract.refund_deadline;
payReq["timestamp"] = offer.contract.timestamp; payReq["timestamp"] = offer.contract.timestamp;
payReq["transaction_id"] = offer.contract.transaction_id;
let t: Transaction = { let t: Transaction = {
contractHash: offer.H_contract, contractHash: offer.H_contract,
contract: offer.contract, contract: offer.contract,
@ -387,7 +368,8 @@ export class Wallet {
detail: { detail: {
merchantName: offer.contract.merchant.name, merchantName: offer.contract.merchant.name,
amount: offer.contract.amount, amount: offer.contract.amount,
contractHash: offer.H_contract contractHash: offer.H_contract,
fulfillmentUrl: offer.contract.fulfillment_url
} }
}; };
@ -395,7 +377,12 @@ export class Wallet {
.put("transactions", t) .put("transactions", t)
.put("history", historyEntry) .put("history", historyEntry)
.putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin)) .putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin))
.finish(); .finish()
.then(() => {
return {
success: true
};
});
} }
confirmPay(offer: Offer): Promise<any> { confirmPay(offer: Offer): Promise<any> {
@ -405,23 +392,31 @@ export class Wallet {
offer.contract.mints) offer.contract.mints)
}).then((mcs) => { }).then((mcs) => {
if (Object.keys(mcs).length == 0) { if (Object.keys(mcs).length == 0) {
throw Error("Not enough coins."); return {
success: false,
message: "Not enough coins",
};
} }
let mintUrl = Object.keys(mcs)[0]; let mintUrl = Object.keys(mcs)[0];
let ds = Wallet.signDeposit(offer, mcs[mintUrl]); let ds = Wallet.signDeposit(offer, mcs[mintUrl]);
return this.executePay(offer, ds, mintUrl); return this
.executePay(offer, ds, mintUrl);
}); });
} }
doPayment(H_contract): Promise<PaymentResponse> { doPayment(H_contract): Promise<any> {
return Promise.resolve().then(() => { return Promise.resolve().then(() => {
return Query(this.db) return Query(this.db)
.get("transactions", H_contract) .get("transactions", H_contract)
.then((t) => { .then((t) => {
if (!t) { if (!t) {
throw Error("contract not found"); return {
success: false,
contractFound: false,
}
} }
let resp: PaymentResponse = { let resp = {
success: true,
payReq: t.payReq, payReq: t.payReq,
contract: t.contract, contract: t.contract,
}; };
@ -682,8 +677,10 @@ export class Wallet {
let next = () => { let next = () => {
if (workList.length == 0) { if (workList.length == 0) {
resolve(); resolve();
return;
} }
let d = workList.pop(); let d = workList.pop();
console.log("withdrawing", JSON.stringify(d));
this.withdraw(d, reserve) this.withdraw(d, reserve)
.then(() => next()) .then(() => next())
.catch((e) => { .catch((e) => {
@ -760,7 +757,7 @@ export class Wallet {
getBalances(): Promise<any> { getBalances(): Promise<any> {
function collectBalances(c: Coin, byCurrency) { function collectBalances(c: Coin, byCurrency) {
let acc: AmountJson_interface = byCurrency[c.currentAmount.currency]; let acc: AmountJson = byCurrency[c.currentAmount.currency];
if (!acc) { if (!acc) {
acc = Amount.getZero(c.currentAmount.currency).toJson(); acc = Amount.getZero(c.currentAmount.currency).toJson();
} }
@ -786,4 +783,4 @@ export class Wallet {
.iter("history", {indexName: "timestamp"}) .iter("history", {indexName: "timestamp"})
.reduce(collect, []) .reduce(collect, [])
} }
} }

View File

@ -66,8 +66,8 @@ System.register(["./wallet", "./db", "./http"], function(exports_1) {
}, },
_a["confirm-pay"] = function (db, detail, sendResponse) { _a["confirm-pay"] = function (db, detail, sendResponse) {
wallet.confirmPay(detail.offer, detail.merchantPageUrl) wallet.confirmPay(detail.offer, detail.merchantPageUrl)
.then(function () { .then(function (r) {
sendResponse({ success: true }); sendResponse(r);
}) })
.catch(function (e) { .catch(function (e) {
console.error("exception during 'confirm-pay'"); console.error("exception during 'confirm-pay'");
@ -79,11 +79,7 @@ System.register(["./wallet", "./db", "./http"], function(exports_1) {
_a["execute-payment"] = function (db, detail, sendResponse) { _a["execute-payment"] = function (db, detail, sendResponse) {
wallet.doPayment(detail.H_contract) wallet.doPayment(detail.H_contract)
.then(function (r) { .then(function (r) {
sendResponse({ sendResponse(r);
success: true,
payReq: r.payReq,
contract: r.contract,
});
}) })
.catch(function (e) { .catch(function (e) {
console.error("exception during 'execute-payment'"); console.error("exception during 'execute-payment'");

View File

@ -83,8 +83,8 @@ function makeHandlers(wallet) {
}, },
["confirm-pay"]: function(db, detail, sendResponse) { ["confirm-pay"]: function(db, detail, sendResponse) {
wallet.confirmPay(detail.offer, detail.merchantPageUrl) wallet.confirmPay(detail.offer, detail.merchantPageUrl)
.then(() => { .then((r) => {
sendResponse({success: true}) sendResponse(r)
}) })
.catch((e) => { .catch((e) => {
console.error("exception during 'confirm-pay'"); console.error("exception during 'confirm-pay'");
@ -96,11 +96,7 @@ function makeHandlers(wallet) {
["execute-payment"]: function(db, detail, sendResponse) { ["execute-payment"]: function(db, detail, sendResponse) {
wallet.doPayment(detail.H_contract) wallet.doPayment(detail.H_contract)
.then((r) => { .then((r) => {
sendResponse({ sendResponse(r);
success: true,
payReq: r.payReq,
contract: r.contract,
});
}) })
.catch((e) => { .catch((e) => {
console.error("exception during 'execute-payment'"); console.error("exception during 'execute-payment'");

View File

@ -14,8 +14,8 @@
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/ */
export function substituteFulfillmentUrl(url: string, offer) { export function substituteFulfillmentUrl(url: string, vars) {
url = url.replace("${H_contract}", offer.H_contract); url = url.replace("${H_contract}", vars.H_contract);
url = url.replace("${$}", "$"); url = url.replace("${$}", "$");
return url; return url;
} }

View File

@ -42,7 +42,6 @@
"background": { "background": {
"scripts": [ "scripts": [
"lib/vendor/URI.js", "lib/vendor/URI.js",
"lib/vendor/handlebars-v4.0.5.js",
"lib/emscripten/libwrapper.js", "lib/emscripten/libwrapper.js",
"lib/vendor/system-csp-production.src.js", "lib/vendor/system-csp-production.src.js",
"background/main.js" "background/main.js"

View File

@ -11,23 +11,6 @@
<script src="../lib/module-trampoline.js"></script> <script src="../lib/module-trampoline.js"></script>
<link rel="stylesheet" type="text/css" href="../style/wallet.css"> <link rel="stylesheet" type="text/css" href="../style/wallet.css">
<script id="contract-template" type="text/x-handlebars-template">
Your contract includes these products:
<ul>
{{#each products}}
<li>{{description}}: {{prettyAmount price}}</li>
{{/each}}
</ul>
<p />
</script>
<script id="error-template" type="text/x-handlebars-template">
Payment was not successful: {{error}}
</script>
</head>
<body> <body>
<header> <header>
<div id="logo"></div> <div id="logo"></div>

View File

@ -27,13 +27,15 @@ System.register(["../lib/web-common"], function(exports_1) {
var offer = JSON.parse(query.offer); var offer = JSON.parse(query.offer);
console.dir(offer); console.dir(offer);
var contract = offer.contract; var contract = offer.contract;
var error = null;
var Contract = { var Contract = {
view: function (ctrl) { view: function (ctrl) {
return [ return [
m("p", (_a = ["Hello, this is the wallet. The merchant \"", "\"\n wants to enter a contract over ", "\n with you."], _a.raw = ["Hello, this is the wallet. The merchant \"", "\"\n wants to enter a contract over ", "\n with you."], i18n(_a, contract.merchant.name, prettyAmount(contract.amount)))), m("p", (_a = ["Hello, this is the wallet. The merchant \"", "\"\n wants to enter a contract over ", "\n with you."], _a.raw = ["Hello, this is the wallet. The merchant \"", "\"\n wants to enter a contract over ", "\n with you."], i18n(_a, contract.merchant.name, prettyAmount(contract.amount)))),
m("p", (_b = ["The contract contains the following products:"], _b.raw = ["The contract contains the following products:"], i18n(_b))), m("p", (_b = ["The contract contains the following products:"], _b.raw = ["The contract contains the following products:"], i18n(_b))),
m('ul', _.map(contract.products, function (p) { return m("li", p.description + ": " + prettyAmount(p.price)); })), m('ul', _.map(contract.products, function (p) { return m("li", p.description + ": " + prettyAmount(p.price)); })),
m("button", { onclick: doPayment }, (_c = ["Confirm Payment"], _c.raw = ["Confirm Payment"], i18n(_c))) m("button", { onclick: doPayment }, (_c = ["Confirm Payment"], _c.raw = ["Confirm Payment"], i18n(_c))),
m("p", error ? error : []),
]; ];
var _a, _b, _c; var _a, _b, _c;
} }
@ -46,6 +48,8 @@ System.register(["../lib/web-common"], function(exports_1) {
chrome.runtime.sendMessage({ type: 'confirm-pay', detail: d }, function (resp) { chrome.runtime.sendMessage({ type: 'confirm-pay', detail: d }, function (resp) {
if (!resp.success) { if (!resp.success) {
console.log("confirm-pay error", JSON.stringify(resp)); console.log("confirm-pay error", JSON.stringify(resp));
error = resp.message;
m.redraw();
return; return;
} }
var c = d.offer.contract; var c = d.offer.contract;

View File

@ -33,6 +33,7 @@ export function main() {
let offer = JSON.parse(query.offer); let offer = JSON.parse(query.offer);
console.dir(offer); console.dir(offer);
let contract = offer.contract; let contract = offer.contract;
let error = null;
var Contract = { var Contract = {
view(ctrl) { view(ctrl) {
@ -47,7 +48,8 @@ export function main() {
_.map(contract.products, _.map(contract.products,
(p: any) => m("li", (p: any) => m("li",
`${p.description}: ${prettyAmount(p.price)}`))), `${p.description}: ${prettyAmount(p.price)}`))),
m("button", {onclick: doPayment}, i18n`Confirm Payment`) m("button", {onclick: doPayment}, i18n`Confirm Payment`),
m("p", error ? error : []),
]; ];
} }
}; };
@ -62,6 +64,8 @@ export function main() {
chrome.runtime.sendMessage({type: 'confirm-pay', detail: d}, (resp) => { chrome.runtime.sendMessage({type: 'confirm-pay', detail: d}, (resp) => {
if (!resp.success) { if (!resp.success) {
console.log("confirm-pay error", JSON.stringify(resp)); console.log("confirm-pay error", JSON.stringify(resp));
error = resp.message;
m.redraw();
return; return;
} }
let c = d.offer.contract; let c = d.offer.contract;

View File

@ -8,13 +8,13 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-01-27 01:05+0100\n" "POT-Creation-Date: 2016-01-27 01:51+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: example/test.ts:3 #: example/test.ts:3
@ -95,3 +95,13 @@ msgid ""
"asdf" "asdf"
msgstr "" msgstr ""
#: example/test.ts:42
#, csharp-format
msgid "This message appears twice"
msgstr ""
#: example/test.ts:45
#, csharp-format
msgid "This message appears twice"
msgstr ""

View File

@ -18,7 +18,11 @@ It has multiple lines, and a trailing empty line.
*/ */
console.log(/*lol*/i18n.foo`Hello7,${123} World${42}`); console.log(/*lol*/i18n.foo`Hello7,${123} World${42}`);
console.log(i18n.foo`${"foo"}Hello8,${123} World${42}`);
i18n.plural()
console.log(i18n`${"foo"}Hello8,${123} World${42}`);
/* /*
@ -40,3 +44,10 @@ it should be wrapped and stuff`);
// This is a single line comment // This is a single line comment
console.log(i18n`Hello12 this is a long long string it will go over multiple lines and in the pofile it should be wrapped and stuff. asdf asdf asdf asdf asdf asdf asdf asdf adsf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf`); console.log(i18n`Hello12 this is a long long string it will go over multiple lines and in the pofile it should be wrapped and stuff. asdf asdf asdf asdf asdf asdf asdf asdf adsf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf`);
// First occurence
console.log(i18n`This message appears twice`);
// Second occurence
console.log(i18n`This message appears twice`);

View File

@ -20,6 +20,8 @@
"use strict"; "use strict";
import {substituteFulfillmentUrl} from "../lib/web-common";
declare var m: any; declare var m: any;
declare var i18n: any; declare var i18n: any;
@ -106,11 +108,22 @@ function formatAmount(amount) {
return `${v.toFixed(2)} ${amount.currency}`; return `${v.toFixed(2)} ${amount.currency}`;
} }
function abbrevKey(s: string) { function abbrevKey(s: string) {
return m("span.abbrev", {title: s}, (s.slice(0, 5) + "..")) return m("span.abbrev", {title: s}, (s.slice(0, 5) + ".."))
} }
function retryPayment(url, contractHash) {
return function() {
chrome.tabs.create({
"url": substituteFulfillmentUrl(url,
{H_contract: contractHash})
});
}
}
function formatHistoryItem(historyItem) { function formatHistoryItem(historyItem) {
const d = historyItem.detail; const d = historyItem.detail;
const t = historyItem.timestamp; const t = historyItem.timestamp;
@ -124,10 +137,14 @@ function formatHistoryItem(historyItem) {
return m("p", return m("p",
i18n`Withdraw at ${formatTimestamp(t)}`); i18n`Withdraw at ${formatTimestamp(t)}`);
case "pay": case "pay":
let url = substituteFulfillmentUrl(d.fulfillmentUrl,
{H_contract: d.contractHash});
return m("p", return m("p",
[ [
i18n`Payment for ${formatAmount(d.amount)} to merchant ${d.merchantName}. `, i18n`Payment for ${formatAmount(d.amount)} to merchant ${d.merchantName}. `,
m("a[href=javascript:;]", "Retry") m(`a`,
{href: url, onclick: openTab(url)},
"Retry")
]); ]);
default: default:
return m("p", i18n`Unknown event (${historyItem.type})`); return m("p", i18n`Unknown event (${historyItem.type})`);
@ -204,3 +221,12 @@ function openExtensionPage(page) {
} }
} }
function openTab(page) {
return function() {
chrome.tabs.create({
"url": page
});
}
}

View File

@ -11,7 +11,6 @@
"lib/i18n.ts", "lib/i18n.ts",
"lib/refs.ts", "lib/refs.ts",
"lib/web-common.ts", "lib/web-common.ts",
"lib/wallet/checkable.ts",
"lib/wallet/db.ts", "lib/wallet/db.ts",
"lib/wallet/emscriptif.ts", "lib/wallet/emscriptif.ts",
"lib/wallet/http.ts", "lib/wallet/http.ts",