aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/content_scripts/notify.ts240
-rw-r--r--src/renderHtml.tsx7
-rw-r--r--src/wallet.ts54
-rw-r--r--src/wxBackend.ts78
m---------web-common0
5 files changed, 218 insertions, 161 deletions
diff --git a/src/content_scripts/notify.ts b/src/content_scripts/notify.ts
index fda5d3fec..10b988c43 100644
--- a/src/content_scripts/notify.ts
+++ b/src/content_scripts/notify.ts
@@ -90,6 +90,19 @@ namespace TalerNotify {
});
}
+ function queryPayment(query: any): Promise<any> {
+ // current URL without fragment
+ const walletMsg = {
+ type: "query-payment",
+ detail: query,
+ };
+ return new Promise((resolve, reject) => {
+ chrome.runtime.sendMessage(walletMsg, (resp: any) => {
+ resolve(resp);
+ });
+ });
+ }
+
function putHistory(historyEntry: any): Promise<void> {
const walletMsg = {
type: "put-history-entry",
@@ -109,16 +122,20 @@ namespace TalerNotify {
type: "save-offer",
detail: {
offer: {
- contract: offer.contract,
- merchant_sig: offer.merchant_sig,
- H_contract: offer.H_contract,
+ contract: offer.data,
+ merchant_sig: offer.sig,
+ H_contract: offer.hash,
offer_time: new Date().getTime() / 1000
},
},
};
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage(walletMsg, (resp: any) => {
- resolve(resp);
+ if (resp && resp.error) {
+ reject(resp);
+ } else {
+ resolve(resp);
+ }
});
});
}
@@ -141,17 +158,10 @@ namespace TalerNotify {
}
});
- if (resp && resp.type === "fetch") {
- logVerbose && console.log("it's fetch");
- taler.internalOfferContractFrom(resp.contractUrl);
+ if (resp && resp.type == "pay") {
+ logVerbose && console.log("doing taler.pay with", resp.payDetail);
+ taler.internalPay(resp.payDetail);
document.documentElement.style.visibility = "hidden";
-
- } else if (resp && resp.type === "execute") {
- logVerbose && console.log("it's execute");
- document.documentElement.style.visibility = "hidden";
- taler.internalExecutePayment(resp.contractHash,
- resp.payUrl,
- resp.offerUrl);
}
});
}
@@ -163,6 +173,104 @@ namespace TalerNotify {
(detail: any, sendResponse: (msg: any) => void): void;
}
+ function downloadContract(url: string): Promise<any> {
+ // FIXME: include and check nonce!
+ return new Promise((resolve, reject) => {
+ const contract_request = new XMLHttpRequest();
+ console.log("downloading contract from '" + url + "'")
+ contract_request.open("GET", url, true);
+ contract_request.onload = function (e) {
+ if (contract_request.readyState == 4) {
+ if (contract_request.status == 200) {
+ console.log("response text:",
+ contract_request.responseText);
+ var contract_wrapper = JSON.parse(contract_request.responseText);
+ if (!contract_wrapper) {
+ console.error("response text was invalid json");
+ let detail = {hint: "invalid json", status: contract_request.status, body: contract_request.responseText};
+ reject(detail);
+ return;
+ }
+ resolve(contract_wrapper);
+ } else {
+ let detail = {hint: "contract download failed", status: contract_request.status, body: contract_request.responseText};
+ reject(detail);
+ return;
+ }
+ }
+ };
+ contract_request.onerror = function (e) {
+ let detail = {hint: "contract download failed", status: contract_request.status, body: contract_request.responseText};
+ reject(detail);
+ return;
+ };
+ contract_request.send();
+ });
+ }
+
+ async function processProposal(proposal: any) {
+ if (!proposal.data) {
+ console.error("field proposal.data field missing");
+ return;
+ }
+
+ if (!proposal.hash) {
+ console.error("proposal.hash field missing");
+ return;
+ }
+
+ let contractHash = await hashContract(proposal.data);
+
+ if (contractHash != proposal.hash) {
+ console.error("merchant-supplied contract hash is wrong");
+ return;
+ }
+
+ let resp = await checkRepurchase(proposal.data);
+
+ if (resp.error) {
+ console.error("wallet backend error", resp);
+ return;
+ }
+
+ if (resp.isRepurchase) {
+ logVerbose && console.log("doing repurchase");
+ console.assert(resp.existingFulfillmentUrl);
+ console.assert(resp.existingContractHash);
+ window.location.href = subst(resp.existingFulfillmentUrl,
+ resp.existingContractHash);
+
+ } else {
+
+ let merchantName = "(unknown)";
+ try {
+ merchantName = proposal.data.merchant.name;
+ } catch (e) {
+ // bad contract / name not included
+ }
+
+ let historyEntry = {
+ timestamp: (new Date).getTime(),
+ subjectId: `contract-${contractHash}`,
+ type: "offer-contract",
+ detail: {
+ contractHash,
+ merchantName,
+ }
+ };
+ await putHistory(historyEntry);
+ let offerId = await saveOffer(proposal);
+
+ const uri = URI(chrome.extension.getURL(
+ "/src/pages/confirm-contract.html"));
+ const params = {
+ offerId: offerId.toString(),
+ };
+ const target = uri.query(params).href();
+ document.location.replace(target);
+ }
+ }
+
function registerHandlers() {
/**
* Add a handler for a DOM event, which automatically
@@ -237,70 +345,28 @@ namespace TalerNotify {
const proposal = msg.contract_wrapper;
- if (!proposal.data) {
- console.error("field proposal.data field missing");
- return;
- }
+ processProposal(proposal);
+ });
- if (!proposal.hash) {
- console.error("proposal.hash field missing");
+ addHandler("taler-pay", async(msg: any, sendResponse: any) => {
+ let res = await queryPayment(msg.contract_query);
+ logVerbose && console.log("taler-pay: got response", res);
+ if (res && res.payReq) {
+ sendResponse(res);
return;
}
-
- let contractHash = await hashContract(proposal.data);
-
- if (contractHash != proposal.hash) {
- console.error("merchant-supplied contract hash is wrong");
+ if (msg.contract_url) {
+ let proposal = await downloadContract(msg.contract_url);
+ await processProposal(proposal);
return;
}
- let resp = await checkRepurchase(proposal.data);
-
- if (resp.error) {
- console.error("wallet backend error", resp);
+ if (msg.offer_url) {
+ document.location.href = msg.offer_url;
return;
}
- if (resp.isRepurchase) {
- logVerbose && console.log("doing repurchase");
- console.assert(resp.existingFulfillmentUrl);
- console.assert(resp.existingContractHash);
- window.location.href = subst(resp.existingFulfillmentUrl,
- resp.existingContractHash);
-
- } else {
-
- let merchantName = "(unknown)";
- try {
- merchantName = proposal.data.merchant.name;
- } catch (e) {
- // bad contract / name not included
- }
-
- let historyEntry = {
- timestamp: (new Date).getTime(),
- subjectId: `contract-${contractHash}`,
- type: "offer-contract",
- detail: {
- contractHash,
- merchantName,
- }
- };
- await putHistory(historyEntry);
- let offerId = await saveOffer(proposal);
-
- const uri = URI(chrome.extension.getURL(
- "/src/pages/confirm-contract.html"));
- const params = {
- offerId: offerId.toString(),
- };
- const target = uri.query(params).href();
- if (msg.replace_navigation === true) {
- document.location.replace(target);
- } else {
- document.location.href = target;
- }
- }
+ console.log("can't proceed with payment, no way to get contract specified");
});
addHandler("taler-payment-failed", (msg: any, sendResponse: any) => {
@@ -331,41 +397,5 @@ namespace TalerNotify {
sendResponse();
})
});
-
- addHandler("taler-get-payment", (msg: any, sendResponse: any) => {
- const walletMsg = {
- type: "execute-payment",
- detail: {
- H_contract: msg.H_contract,
- },
- };
-
- chrome.runtime.sendMessage(walletMsg, (resp) => {
- if (resp.rateLimitExceeded) {
- console.error("rate limit exceeded, check for redirect loops");
- }
-
- if (!resp.success) {
- if (msg.offering_url) {
- window.location.href = msg.offering_url;
- } else {
- console.error("execute-payment failed", resp);
- }
- return;
- }
- let contract = resp.contract;
- if (!contract) {
- throw Error("contract missing");
- }
-
- // We have the details for then payment, the merchant page
- // is responsible to give it to the merchant.
- sendResponse({
- H_contract: msg.H_contract,
- contract: resp.contract,
- payment: resp.payReq,
- });
- });
- });
}
}
diff --git a/src/renderHtml.tsx b/src/renderHtml.tsx
index 40b48094e..79e101b17 100644
--- a/src/renderHtml.tsx
+++ b/src/renderHtml.tsx
@@ -30,7 +30,12 @@ export function prettyAmount(amount: AmountJson) {
}
export function renderContract(contract: Contract): JSX.Element {
- let merchantName = <strong>{contract.merchant.name}</strong>;
+ let merchantName;
+ if (contract.merchant && contract.merchant.name) {
+ merchantName = <strong>{contract.merchant.name}</strong>;
+ } else {
+ merchantName = <strong>(pub: {contract.merchant_pub})</strong>;
+ }
let amount = <strong>{prettyAmount(contract.amount)}</strong>;
return (
diff --git a/src/wallet.ts b/src/wallet.ts
index 988ed32df..1c9de0170 100644
--- a/src/wallet.ts
+++ b/src/wallet.ts
@@ -164,20 +164,10 @@ export interface HistoryRecord {
interface PayReq {
- amount: AmountJson;
coins: CoinPaySig[];
- H_contract: string;
- max_fee: AmountJson;
- merchant_sig: string;
+ merchant_pub: string;
+ order_id: string;
exchange: string;
- refund_deadline: string;
- timestamp: string;
- pay_deadline: string;
- /**
- * Merchant instance identifier that should receive the
- * payment, if applicable.
- */
- instance?: string;
}
interface TransactionRecord {
@@ -352,6 +342,8 @@ export namespace Stores {
"contract.merchant_pub",
"contract.repurchase_correlation_id"
]);
+ fulfillmentUrlIndex = new Index<string,TransactionRecord>(this, "fulfillment_url", "contract.fulfillment_url");
+ orderIdIndex = new Index<string,TransactionRecord>(this, "order_id", "contract.order_id");
}
class DenominationsStore extends Store<DenominationRecord> {
@@ -552,16 +544,10 @@ export class Wallet {
payCoinInfo: PayCoinInfo,
chosenExchange: string): Promise<void> {
let payReq: PayReq = {
- amount: offer.contract.amount,
coins: payCoinInfo.map((x) => x.sig),
- H_contract: offer.H_contract,
- max_fee: offer.contract.max_fee,
- merchant_sig: offer.merchant_sig,
- exchange: URI(chosenExchange).href(),
- refund_deadline: offer.contract.refund_deadline,
- pay_deadline: offer.contract.pay_deadline,
- timestamp: offer.contract.timestamp,
- instance: offer.contract.merchant.instance
+ merchant_pub: offer.contract.merchant_pub,
+ order_id: offer.contract.order_id,
+ exchange: chosenExchange,
};
let t: TransactionRecord = {
contractHash: offer.H_contract,
@@ -679,18 +665,36 @@ export class Wallet {
* Retrieve all necessary information for looking up the contract
* with the given hash.
*/
- async executePayment(H_contract: string): Promise<any> {
- let t = await this.q().get<TransactionRecord>(Stores.transactions,
- H_contract);
+ async queryPayment(query: any): Promise<any> {
+ let t: TransactionRecord | undefined;
+
+ console.log("query for payment", query);
+
+ switch (query.type) {
+ case "fulfillment_url":
+ t = await this.q().getIndexed(Stores.transactions.fulfillmentUrlIndex, query.value);
+ break;
+ case "order_id":
+ t = await this.q().getIndexed(Stores.transactions.orderIdIndex, query.value);
+ break;
+ case "hash":
+ t = await this.q().get<TransactionRecord>(Stores.transactions, query.value);
+ break;
+ default:
+ throw Error("invalid type");
+ }
+
if (!t) {
+ console.log("query for payment failed");
return {
success: false,
- contractFound: false,
}
}
+ console.log("query for payment succeeded:", t);
let resp = {
success: true,
payReq: t.payReq,
+ H_contract: t.contractHash,
contract: t.contract,
};
return resp;
diff --git a/src/wxBackend.ts b/src/wxBackend.ts
index 637ab5d0e..50e068946 100644
--- a/src/wxBackend.ts
+++ b/src/wxBackend.ts
@@ -139,20 +139,20 @@ function makeHandlers(db: IDBDatabase,
}
return wallet.checkPay(offer);
},
- ["execute-payment"]: function (detail: any, sender: MessageSender) {
+ ["query-payment"]: function (detail: any, sender: MessageSender) {
if (sender.tab && sender.tab.id) {
rateLimitCache[sender.tab.id]++;
if (rateLimitCache[sender.tab.id] > 10) {
- console.warn("rate limit for execute payment exceeded");
+ console.warn("rate limit for query-payment exceeded");
let msg = {
- error: "rate limit exceeded for execute-payment",
+ error: "rate limit exceeded for query-payment",
rateLimitExceeded: true,
hint: "Check for redirect loops",
};
return Promise.resolve(msg);
}
}
- return wallet.executePayment(detail.H_contract);
+ return wallet.queryPayment(detail);
},
["exchange-info"]: function (detail) {
if (!detail.baseUrl) {
@@ -179,8 +179,10 @@ function makeHandlers(db: IDBDatabase,
if (!offer) {
return Promise.resolve({ error: "offer missing" });
}
- console.log("handling safe-offer");
- return wallet.saveOffer(offer);
+ console.log("handling safe-offer", detail);
+ // FIXME: fully migrate to new terminology
+ let checkedOffer = OfferRecord.checked(offer);
+ return wallet.saveOffer(checkedOffer);
},
["reserve-creation-info"]: function (detail, sender) {
if (!detail.baseUrl || typeof detail.baseUrl !== "string") {
@@ -317,8 +319,7 @@ class ChromeNotifier implements Notifier {
*/
let paymentRequestCookies: { [n: number]: any } = {};
-function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[],
- url: string, tabId: number): any {
+function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: string, tabId: number): any {
const headers: { [s: string]: string } = {};
for (let kv of headerList) {
if (kv.value) {
@@ -326,35 +327,52 @@ function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[],
}
}
- const contractUrl = headers["x-taler-contract-url"];
- if (contractUrl !== undefined) {
- paymentRequestCookies[tabId] = { type: "fetch", contractUrl };
- return;
+ let fields = {
+ contract_url: headers["x-taler-contract-url"],
+ contract_query: headers["x-taler-contract-query"],
+ offer_url: headers["x-taler-offer-url"],
+ pay_url: headers["x-taler-pay-url"],
}
- const contractHash = headers["x-taler-contract-hash"];
+ let n: number = 0;
- if (contractHash !== undefined) {
- const payUrl = headers["x-taler-pay-url"];
- if (payUrl === undefined) {
- console.log("malformed 402, X-Taler-Pay-Url missing");
- return;
+ for (let key of Object.keys(fields)) {
+ if ((fields as any)[key]) {
+ n++;
}
+ }
- // Offer URL is optional
- const offerUrl = headers["x-taler-offer-url"];
- paymentRequestCookies[tabId] = {
- type: "execute",
- offerUrl,
- payUrl,
- contractHash
- };
- return;
+ if (n == 0) {
+ // looks like it's not a taler request, it might be
+ // for a different payment system (or the shop is buggy)
+ console.log("ignoring non-taler 402 response");
+ }
+
+ let contract_query = undefined;
+ // parse " type [ ':' value ] " format
+ if (fields.contract_query) {
+ let res = /[-a-zA-Z0-9_.,]+(:.*)?/.exec(fields.contract_query);
+ if (res) {
+ contract_query = {type: res[0], value: res[1]};
+ if (contract_query.type == "fulfillment_url" && !contract_query.value) {
+ contract_query.value = url;
+ }
+ }
}
- // looks like it's not a taler request, it might be
- // for a different payment system (or the shop is buggy)
- console.log("ignoring non-taler 402 response");
+ let payDetail = {
+ contract_query,
+ contract_url: fields.contract_url,
+ offer_url: fields.offer_url,
+ pay_url: fields.pay_url,
+ };
+
+ console.log("got pay detail", payDetail)
+
+ paymentRequestCookies[tabId] = {
+ type: "pay",
+ payDetail,
+ };
}
diff --git a/web-common b/web-common
-Subproject d4de1c912ecaac7991067027b352de61b237c0c
+Subproject 4831e664d69759da288625911c053d145aa1b68