From 74579ac2f2a4dd46487fb4d2bb2f2b26961a54f1 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 26 Jun 2023 12:54:54 -0300 Subject: [PATCH] withdraw exchange URI --- packages/taler-util/src/taleruri.test.ts | 41 +++++++++++++++++ packages/taler-util/src/taleruri.ts | 56 ++++++++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/packages/taler-util/src/taleruri.test.ts b/packages/taler-util/src/taleruri.test.ts index ffc56a723..3244bbbd9 100644 --- a/packages/taler-util/src/taleruri.test.ts +++ b/packages/taler-util/src/taleruri.test.ts @@ -22,10 +22,12 @@ import { parseRefundUri, parseRestoreUri, parseTipUri, + parseWithdrawExchangeUri, parseWithdrawUri, stringifyPayPushUri, stringifyPayUri, stringifyRestoreUri, + stringifyWithdrawExchange, } from "./taleruri.js"; test("taler pay url parsing: wrong scheme", (t) => { @@ -329,3 +331,42 @@ test("taler restore URI (stringify)", (t) => { "taler://restore/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0/http%3A%2F%2Fprov1.example.com%2F,https%3A%2F%2Fprov2.example.com%3A234%2F", ); }); + +test("taler withdraw exchange URI (parse)", (t) => { + const r1 = parseWithdrawExchangeUri( + "taler://withdraw-exchange/exchange.demo.taler.net/someroot/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0?a=KUDOS%3A2", + ); + if (!r1) { + t.fail(); + return; + } + t.deepEqual( + r1.exchangePub, + "GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0", + ); + t.deepEqual(r1.exchangeBaseUrl, "https://exchange.demo.taler.net/someroot/"); + t.deepEqual(r1.amount, "KUDOS:2"); +}); + +test("taler withdraw exchange URI (stringify)", (t) => { + const url = stringifyWithdrawExchange({ + exchangeBaseUrl: "https://exchange.demo.taler.net", + exchangePub: "GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0", + }); + t.deepEqual( + url, + "taler://withdraw-exchange/exchange.demo.taler.net/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0", + ); +}); + +test("taler withdraw exchange URI with amount (stringify)", (t) => { + const url = stringifyWithdrawExchange({ + exchangeBaseUrl: "https://exchange.demo.taler.net", + exchangePub: "GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0", + amount: "KUDOS:19", + }); + t.deepEqual( + url, + "taler://withdraw-exchange/exchange.demo.taler.net/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0?a=KUDOS%3A19", + ); +}); diff --git a/packages/taler-util/src/taleruri.ts b/packages/taler-util/src/taleruri.ts index cb0d74a12..fc140811b 100644 --- a/packages/taler-util/src/taleruri.ts +++ b/packages/taler-util/src/taleruri.ts @@ -15,6 +15,7 @@ */ import { canonicalizeBaseUrl } from "./helpers.js"; +import { AmountString } from "./taler-types.js"; import { URLSearchParams, URL } from "./url.js"; export type TalerUri = @@ -28,6 +29,7 @@ export type TalerUri = | TipUriResult | WithdrawUriResult | ExchangeUri + | WithdrawExchangeUri | AuditorUri; export interface PayUriResult { @@ -99,6 +101,13 @@ export interface BackupRestoreUri { providers: Array; } +export interface WithdrawExchangeUri { + type: TalerUriAction.WithdrawExchange; + exchangeBaseUrl: string; + exchangePub: string; + amount?: AmountString; +} + /** * Parse a taler[+http]://withdraw URI. * Return undefined if not passed a valid URI. @@ -166,6 +175,7 @@ export enum TalerUriAction { Auditor = "auditor", Restore = "restore", DevExperiment = "dev-experiment", + WithdrawExchange = "withdraw-exchange", } interface TalerUriProtoInfo { @@ -207,6 +217,7 @@ const parsers: { [A in TalerUriAction]: Parser } = { [TalerUriAction.DevExperiment]: parseDevExperimentUri, [TalerUriAction.Exchange]: parseExchangeUri, [TalerUriAction.Auditor]: parseAuditorUri, + [TalerUriAction.WithdrawExchange]: parseWithdrawExchangeUri, }; export function parseTalerUri(string: string): TalerUri | undefined { @@ -253,6 +264,9 @@ export function stringifyTalerUri(uri: TalerUri): string { case TalerUriAction.Exchange: { return stringifyExchangeUri(uri); } + case TalerUriAction.WithdrawExchange: { + return stringifyWithdrawExchange(uri); + } case TalerUriAction.Auditor: { return stringifyAuditorUri(uri); } @@ -432,6 +446,37 @@ export function parseExchangeUri(s: string): ExchangeUri | undefined { exchangePub, }; } + +export function parseWithdrawExchangeUri( + s: string, +): WithdrawExchangeUri | undefined { + const pi = parseProtoInfo(s, "withdraw-exchange"); + if (!pi) { + return undefined; + } + const c = pi?.rest.split("?"); + const parts = c[0].split("/"); + if (parts.length < 2) { + return undefined; + } + const host = parts[0].toLowerCase(); + const exchangePub = parts[parts.length - 1]; + const pathSegments = parts.slice(1, parts.length - 1); + const hostAndSegments = [host, ...pathSegments].join("/"); + const exchangeBaseUrl = canonicalizeBaseUrl( + `${pi.innerProto}://${hostAndSegments}/`, + ); + const q = new URLSearchParams(c[1] ?? ""); + const amount = q.get("a") ?? undefined; + + return { + type: TalerUriAction.WithdrawExchange, + exchangeBaseUrl, + exchangePub, + amount, + }; +} + export function parseAuditorUri(s: string): AuditorUri | undefined { const pi = parseProtoInfo(s, "auditor"); if (!pi) { @@ -622,6 +667,17 @@ export function stringifyRestoreUri({ return `taler://restore/${walletRootPriv}/${list}`; } +export function stringifyWithdrawExchange({ + exchangeBaseUrl, + exchangePub, + amount, +}: Omit): string { + const { proto, path, query } = getUrlInfo(exchangeBaseUrl, { + a: amount, + }); + return `${proto}://withdraw-exchange/${path}${exchangePub}${query}`; +} + export function stringifyDevExperimentUri({ devExperimentId, }: Omit): string {