summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2020-07-27 17:09:52 +0530
committerFlorian Dold <florian.dold@gmail.com>2020-07-27 17:09:52 +0530
commitae111663f412ad7bee9029110e3ab1594ec14576 (patch)
tree7fc51632c328774c390b989e7f2e6dfd3c31751d
parent694d913d1f226b3d284258286c73a035fd43da7d (diff)
downloadwallet-core-ae111663f412ad7bee9029110e3ab1594ec14576.tar.gz
wallet-core-ae111663f412ad7bee9029110e3ab1594ec14576.tar.bz2
wallet-core-ae111663f412ad7bee9029110e3ab1594ec14576.zip
new taler:// URI syntax
-rw-r--r--src/operations/tip.ts6
-rw-r--r--src/operations/withdraw.ts8
-rw-r--r--src/util/taleruri-test.ts101
-rw-r--r--src/util/taleruri.ts222
4 files changed, 132 insertions, 205 deletions
diff --git a/src/operations/tip.ts b/src/operations/tip.ts
index d121b1cbb..17f7ee90d 100644
--- a/src/operations/tip.ts
+++ b/src/operations/tip.ts
@@ -66,9 +66,11 @@ export async function getTipStatus(
const amount = Amounts.parseOrThrow(tipPickupStatus.amount);
+ const merchantOrigin = new URL(res.merchantBaseUrl).origin;
+
let tipRecord = await ws.db.get(Stores.tips, [
res.merchantTipId,
- res.merchantOrigin,
+ merchantOrigin,
]);
if (!tipRecord) {
@@ -117,7 +119,7 @@ export async function getTipStatus(
amountLeft: Amounts.parseOrThrow(tipPickupStatus.amount_left),
exchangeUrl: tipPickupStatus.exchange_url,
nextUrl: tipPickupStatus.extra.next_url,
- merchantOrigin: res.merchantOrigin,
+ merchantOrigin: merchantOrigin,
merchantTipId: res.merchantTipId,
expirationTimestamp: tipPickupStatus.stamp_expire,
timestamp: tipPickupStatus.stamp_created,
diff --git a/src/operations/withdraw.ts b/src/operations/withdraw.ts
index f7879dfec..9f6804b2b 100644
--- a/src/operations/withdraw.ts
+++ b/src/operations/withdraw.ts
@@ -141,7 +141,11 @@ export async function getBankWithdrawalInfo(
if (!uriResult) {
throw Error(`can't parse URL ${talerWithdrawUri}`);
}
- const resp = await ws.http.get(uriResult.statusUrl);
+ const reqUrl = new URL(
+ `api/withdraw-operations/${uriResult.withdrawalOperationId}`,
+ uriResult.bankIntegrationApiBaseUrl,
+ );
+ const resp = await ws.http.get(reqUrl.href);
const status = await readSuccessResponseJsonOrThrow(
resp,
codecForWithdrawOperationStatusResponse(),
@@ -150,7 +154,7 @@ export async function getBankWithdrawalInfo(
return {
amount: Amounts.parseOrThrow(status.amount),
confirmTransferUrl: status.confirm_transfer_url,
- extractedStatusUrl: uriResult.statusUrl,
+ extractedStatusUrl: uriResult.bankIntegrationApiBaseUrl,
selectionDone: status.selection_done,
senderWire: status.sender_wire,
suggestedExchange: status.suggested_exchange,
diff --git a/src/util/taleruri-test.ts b/src/util/taleruri-test.ts
index 1510880c5..40a30bf7f 100644
--- a/src/util/taleruri-test.ts
+++ b/src/util/taleruri-test.ts
@@ -33,136 +33,93 @@ test("taler pay url parsing: wrong scheme", (t) => {
});
test("taler pay url parsing: defaults", (t) => {
- const url1 = "taler://pay/example.com/-/-/myorder";
+ const url1 = "taler://pay/example.com/myorder/";
const r1 = parsePayUri(url1);
if (!r1) {
t.fail();
return;
}
- t.is(r1.merchantBaseUrl, "https://example.com/public/");
- t.is(r1.sessionId, undefined);
+ t.is(r1.merchantBaseUrl, "https://example.com/");
+ t.is(r1.sessionId, "");
- const url2 = "taler://pay/example.com/-/-/myorder/mysession";
+ const url2 = "taler://pay/example.com/myorder/mysession";
const r2 = parsePayUri(url2);
if (!r2) {
t.fail();
return;
}
- t.is(r2.merchantBaseUrl, "https://example.com/public/");
+ t.is(r2.merchantBaseUrl, "https://example.com/");
t.is(r2.sessionId, "mysession");
});
-test("taler pay url parsing: trailing parts", (t) => {
- const url1 = "taler://pay/example.com/-/-/myorder/mysession/spam/eggs";
- const r1 = parsePayUri(url1);
- if (!r1) {
- t.fail();
- return;
- }
- t.is(r1.merchantBaseUrl, "https://example.com/public/");
- t.is(r1.sessionId, "mysession");
-});
-
test("taler pay url parsing: instance", (t) => {
- const url1 = "taler://pay/example.com/-/myinst/myorder";
+ const url1 = "taler://pay/example.com/instances/myinst/myorder/";
const r1 = parsePayUri(url1);
if (!r1) {
t.fail();
return;
}
- t.is(r1.merchantBaseUrl, "https://example.com/public/instances/myinst/");
+ t.is(r1.merchantBaseUrl, "https://example.com/instances/myinst/");
t.is(r1.orderId, "myorder");
});
-test("taler pay url parsing: path prefix and instance", (t) => {
- const url1 = "taler://pay/example.com/mypfx/myinst/myorder";
- const r1 = parsePayUri(url1);
- if (!r1) {
- t.fail();
- return;
- }
- t.is(r1.merchantBaseUrl, "https://example.com/mypfx/instances/myinst/");
-});
-
-test("taler pay url parsing: complex path prefix", (t) => {
- const url1 = "taler://pay/example.com/mypfx%2Fpublic/-/myorder";
- const r1 = parsePayUri(url1);
- if (!r1) {
- t.fail();
- return;
- }
- t.is(r1.merchantBaseUrl, "https://example.com/mypfx/public/");
- t.is(r1.orderId, "myorder");
- t.is(r1.sessionId, undefined);
-});
-
-test("taler pay uri parsing: complex path prefix and instance", (t) => {
- const url1 = "taler://pay/example.com/mypfx%2Fpublic/foo/myorder";
- const r1 = parsePayUri(url1);
- if (!r1) {
- t.fail();
- return;
- }
- t.is(r1.merchantBaseUrl, "https://example.com/mypfx/public/instances/foo/");
- t.is(r1.orderId, "myorder");
-});
test("taler refund uri parsing: non-https #1", (t) => {
- const url1 = "taler://refund/example.com/-/-/myorder?insecure=1";
+ const url1 = "taler+http://refund/example.com/myorder";
const r1 = parseRefundUri(url1);
if (!r1) {
t.fail();
return;
}
- t.is(r1.merchantBaseUrl, "http://example.com/public/");
+ t.is(r1.merchantBaseUrl, "http://example.com/");
t.is(r1.orderId, "myorder");
});
-test("taler pay uri parsing: non-https #1", (t) => {
- const url1 = "taler://pay/example.com/-/-/myorder?insecure=1";
+test("taler pay uri parsing: non-https", (t) => {
+ const url1 = "taler+http://pay/example.com/myorder/";
const r1 = parsePayUri(url1);
if (!r1) {
t.fail();
return;
}
- t.is(r1.merchantBaseUrl, "http://example.com/public/");
+ t.is(r1.merchantBaseUrl, "http://example.com/");
t.is(r1.orderId, "myorder");
});
-test("taler pay url parsing: non-https #2", (t) => {
- const url1 = "taler://pay/example.com/-/-/myorder?insecure=2";
+test("taler pay uri parsing: missing session component", (t) => {
+ const url1 = "taler+http://pay/example.com/myorder";
const r1 = parsePayUri(url1);
- if (!r1) {
+ if (r1) {
t.fail();
return;
}
- t.is(r1.merchantBaseUrl, "https://example.com/public/");
- t.is(r1.orderId, "myorder");
+ t.pass();
});
test("taler withdraw uri parsing", (t) => {
- const url1 = "taler://withdraw/bank.example.com/-/12345";
+ const url1 = "taler://withdraw/bank.example.com/12345";
const r1 = parseWithdrawUri(url1);
if (!r1) {
t.fail();
return;
}
- t.is(r1.statusUrl, "https://bank.example.com/api/withdraw-operation/12345");
+ t.is(r1.withdrawalOperationId, "12345");
+ t.is(r1.bankIntegrationApiBaseUrl, "https://bank.example.com/");
});
test("taler refund uri parsing", (t) => {
- const url1 = "taler://refund/merchant.example.com/-/-/1234";
+ const url1 = "taler://refund/merchant.example.com/1234";
const r1 = parseRefundUri(url1);
if (!r1) {
t.fail();
return;
}
- t.is(r1.merchantBaseUrl, "https://merchant.example.com/public/");
+ t.is(r1.merchantBaseUrl, "https://merchant.example.com/");
t.is(r1.orderId, "1234");
});
test("taler refund uri parsing with instance", (t) => {
- const url1 = "taler://refund/merchant.example.com/-/myinst/1234";
+ const url1 = "taler://refund/merchant.example.com/instances/myinst/1234";
const r1 = parseRefundUri(url1);
if (!r1) {
t.fail();
@@ -171,22 +128,22 @@ test("taler refund uri parsing with instance", (t) => {
t.is(r1.orderId, "1234");
t.is(
r1.merchantBaseUrl,
- "https://merchant.example.com/public/instances/myinst/",
+ "https://merchant.example.com/instances/myinst/",
);
});
test("taler tip pickup uri", (t) => {
- const url1 = "taler://tip/merchant.example.com/-/-/tipid";
+ const url1 = "taler://tip/merchant.example.com/tipid";
const r1 = parseTipUri(url1);
if (!r1) {
t.fail();
return;
}
- t.is(r1.merchantBaseUrl, "https://merchant.example.com/public/");
+ t.is(r1.merchantBaseUrl, "https://merchant.example.com/");
});
test("taler tip pickup uri with instance", (t) => {
- const url1 = "taler://tip/merchant.example.com/-/tipm/tipid";
+ const url1 = "taler://tip/merchant.example.com/instances/tipm/tipid";
const r1 = parseTipUri(url1);
if (!r1) {
t.fail();
@@ -194,13 +151,13 @@ test("taler tip pickup uri with instance", (t) => {
}
t.is(
r1.merchantBaseUrl,
- "https://merchant.example.com/public/instances/tipm/",
+ "https://merchant.example.com/instances/tipm/",
);
t.is(r1.merchantTipId, "tipid");
});
test("taler tip pickup uri with instance and prefix", (t) => {
- const url1 = "taler://tip/merchant.example.com/my%2fpfx/tipm/tipid";
+ const url1 = "taler://tip/merchant.example.com/my/pfx/tipm/tipid";
const r1 = parseTipUri(url1);
if (!r1) {
t.fail();
@@ -208,7 +165,7 @@ test("taler tip pickup uri with instance and prefix", (t) => {
}
t.is(
r1.merchantBaseUrl,
- "https://merchant.example.com/my/pfx/instances/tipm/",
+ "https://merchant.example.com/my/pfx/tipm/",
);
t.is(r1.merchantTipId, "tipid");
});
diff --git a/src/util/taleruri.ts b/src/util/taleruri.ts
index 73280b6c8..7e64dd4ca 100644
--- a/src/util/taleruri.ts
+++ b/src/util/taleruri.ts
@@ -1,6 +1,6 @@
/*
This file is part of GNU Taler
- (C) 2019 GNUnet e.V.
+ (C) 2019-2020 Taler Systems S.A.
GNU 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
@@ -17,11 +17,12 @@
export interface PayUriResult {
merchantBaseUrl: string;
orderId: string;
- sessionId?: string;
+ sessionId: string;
}
export interface WithdrawUriResult {
- statusUrl: string;
+ bankIntegrationApiBaseUrl: string;
+ withdrawalOperationId: string;
}
export interface RefundUriResult {
@@ -31,10 +32,13 @@ export interface RefundUriResult {
export interface TipUriResult {
merchantTipId: string;
- merchantOrigin: string;
merchantBaseUrl: string;
}
+/**
+ * Parse a taler[+http]://withdraw URI.
+ * Return undefined if not passed a valid URI.
+ */
export function parseWithdrawUri(s: string): WithdrawUriResult | undefined {
const pfx = "taler://withdraw/";
if (!s.toLowerCase().startsWith(pfx)) {
@@ -42,29 +46,20 @@ export function parseWithdrawUri(s: string): WithdrawUriResult | undefined {
}
const rest = s.substring(pfx.length);
+ const parts = rest.split("/");
- let [host, path, withdrawId] = rest.split("/");
-
- if (!host) {
- return undefined;
- }
-
- host = host.toLowerCase();
-
- if (!path) {
+ if (parts.length < 2) {
return undefined;
}
- if (!withdrawId) {
- return undefined;
- }
-
- if (path === "-") {
- path = "api/withdraw-operation";
- }
+ const host = parts[0].toLowerCase();
+ const pathSegments = parts.slice(1, parts.length - 1);
+ const withdrawId = parts[parts.length - 1];
+ const p = [host, ...pathSegments].join("/");
return {
- statusUrl: `https://${host}/${path}/${withdrawId}`,
+ bankIntegrationApiBaseUrl: `https://${p}/`,
+ withdrawalOperationId: withdrawId,
};
}
@@ -77,17 +72,29 @@ export const enum TalerUriType {
Unknown = "unknown",
}
+/**
+ * Classify a taler:// URI.
+ */
export function classifyTalerUri(s: string): TalerUriType {
const sl = s.toLowerCase();
if (sl.startsWith("taler://pay/")) {
return TalerUriType.TalerPay;
}
+ if (sl.startsWith("taler+http://pay/")) {
+ return TalerUriType.TalerPay;
+ }
if (sl.startsWith("taler://tip/")) {
return TalerUriType.TalerTip;
}
+ if (sl.startsWith("taler+http://tip/")) {
+ return TalerUriType.TalerTip;
+ }
if (sl.startsWith("taler://refund/")) {
return TalerUriType.TalerRefund;
}
+ if (sl.startsWith("taler+http://refund/")) {
+ return TalerUriType.TalerRefund;
+ }
if (sl.startsWith("taler://withdraw/")) {
return TalerUriType.TalerWithdraw;
}
@@ -97,146 +104,103 @@ export function classifyTalerUri(s: string): TalerUriType {
return TalerUriType.Unknown;
}
-export function parsePayUri(s: string): PayUriResult | undefined {
- const pfx = "taler://pay/";
- if (!s.toLowerCase().startsWith(pfx)) {
- return undefined;
- }
-
- const [path, search] = s.slice(pfx.length).split("?");
+interface TalerUriProtoInfo {
+ innerProto: "http" | "https";
+ rest: string;
+}
- let [host, maybePath, maybeInstance, orderId, maybeSessionid] = path.split(
- "/",
- );
- if (!host) {
+function parseProtoInfo(s: string, action: string): TalerUriProtoInfo | undefined {
+ const pfxPlain = `taler://${action}/`;
+ const pfxHttp = `taler+http://${action}/`;
+ if (s.toLowerCase().startsWith(pfxPlain)) {
+ return {
+ innerProto: "https",
+ rest: s.substring(pfxPlain.length),
+ }
+ } else if (s.toLowerCase().startsWith(pfxHttp)) {
+ return {
+ innerProto: "http",
+ rest: s.substring(pfxHttp.length),
+ }
+ } else {
return undefined;
}
+}
- host = host.toLowerCase();
-
- if (!maybePath) {
+/**
+ * Parse a taler[+http]://pay URI.
+ * Return undefined if not passed a valid URI.
+ */
+export function parsePayUri(s: string): PayUriResult | undefined {
+ const pi = parseProtoInfo(s, "pay");
+ if (!pi) {
return undefined;
}
-
- if (!orderId) {
+ const c = pi?.rest.split("?");
+ const parts = c[0].split("/");
+ if (parts.length < 3) {
return undefined;
}
-
- if (maybePath === "-") {
- maybePath = "";
- } else {
- maybePath = decodeURIComponent(maybePath) + "/";
- }
- let maybeInstancePath = "";
- if (maybeInstance !== "-") {
- maybeInstancePath = `instances/${maybeInstance}/`;
- }
-
- let protocol = "https";
- const searchParams = new URLSearchParams(search);
- if (searchParams.get("insecure") === "1") {
- protocol = "http";
- }
-
- const merchantBaseUrl =
- `${protocol}://${host}/` +
- decodeURIComponent(maybePath) +
- maybeInstancePath;
+ const host = parts[0].toLowerCase();
+ const sessionId = parts[parts.length - 1];
+ const orderId = parts[parts.length - 2];
+ const pathSegments = parts.slice(1, parts.length - 2);
+ const p = [host, ...pathSegments].join("/");
+ const merchantBaseUrl = `${pi.innerProto}://${p}/`;
return {
merchantBaseUrl,
orderId,
- sessionId: maybeSessionid,
+ sessionId: sessionId,
};
}
+/**
+ * Parse a taler[+http]://tip URI.
+ * Return undefined if not passed a valid URI.
+ */
export function parseTipUri(s: string): TipUriResult | undefined {
- const pfx = "taler://tip/";
- if (!s.toLowerCase().startsWith(pfx)) {
+ const pi = parseProtoInfo(s, "tip");
+ if (!pi) {
return undefined;
}
-
- const path = s.slice(pfx.length);
-
- let [host, maybePath, maybeInstance, tipId] = path.split("/");
-
- if (!host) {
- return undefined;
- }
-
- host = host.toLowerCase();
-
- if (!maybePath) {
- return undefined;
- }
-
- if (!tipId) {
+ const c = pi?.rest.split("?");
+ const parts = c[0].split("/");
+ if (parts.length < 2) {
return undefined;
}
-
- if (maybePath === "-") {
- maybePath = "public/";
- } else {
- maybePath = decodeURIComponent(maybePath) + "/";
- }
- let maybeInstancePath = "";
- if (maybeInstance !== "-") {
- maybeInstancePath = `instances/${maybeInstance}/`;
- }
-
- const merchantBaseUrl = `https://${host}/${maybePath}${maybeInstancePath}`;
+ const host = parts[0].toLowerCase();
+ const tipId = parts[parts.length - 1];
+ const pathSegments = parts.slice(1, parts.length - 1);
+ const p = [host, ...pathSegments].join("/");
+ const merchantBaseUrl = `${pi.innerProto}://${p}/`;
return {
- merchantTipId: tipId,
- merchantOrigin: new URL(merchantBaseUrl).origin,
merchantBaseUrl,
+ merchantTipId: tipId,
};
}
+/**
+ * Parse a taler[+http]://refund URI.
+ * Return undefined if not passed a valid URI.
+ */
export function parseRefundUri(s: string): RefundUriResult | undefined {
- const pfx = "taler://refund/";
-
- if (!s.toLowerCase().startsWith(pfx)) {
- return undefined;
- }
-
- const [path, search] = s.slice(pfx.length).split("?");
-
- let [host, maybePath, maybeInstance, orderId] = path.split("/");
-
- if (!host) {
- return undefined;
- }
-
- host = host.toLowerCase();
-
- if (!maybePath) {
+ const pi = parseProtoInfo(s, "refund");
+ if (!pi) {
return undefined;
}
-
- if (!orderId) {
+ const c = pi?.rest.split("?");
+ const parts = c[0].split("/");
+ if (parts.length < 2) {
return undefined;
}
-
- if (maybePath === "-") {
- maybePath = "";
- } else {
- maybePath = decodeURIComponent(maybePath) + "/";
- }
- let maybeInstancePath = "";
- if (maybeInstance !== "-") {
- maybeInstancePath = `instances/${maybeInstance}/`;
- }
-
- let protocol = "https";
- const searchParams = new URLSearchParams(search);
- if (searchParams.get("insecure") === "1") {
- protocol = "http";
- }
-
- const merchantBaseUrl =
- `${protocol}://${host}/` + maybePath + maybeInstancePath;
+ const host = parts[0].toLowerCase();
+ const orderId = parts[parts.length - 1];
+ const pathSegments = parts.slice(1, parts.length - 1);
+ const p = [host, ...pathSegments].join("/");
+ const merchantBaseUrl = `${pi.innerProto}://${p}/`;
return {
merchantBaseUrl,