779 lines
20 KiB
TypeScript
779 lines
20 KiB
TypeScript
/*
|
|
This file is part of GNU Taler
|
|
(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
|
|
Foundation; either version 3, or (at your option) any later version.
|
|
|
|
GNU 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
|
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
|
*/
|
|
|
|
import { canonicalizeBaseUrl } from "./helpers.js";
|
|
import { AmountString } from "./taler-types.js";
|
|
import { URLSearchParams, URL } from "./url.js";
|
|
|
|
export type TalerUri =
|
|
| PayUriResult
|
|
| PayTemplateUriResult
|
|
| DevExperimentUri
|
|
| PayPullUriResult
|
|
| PayPushUriResult
|
|
| BackupRestoreUri
|
|
| RefundUriResult
|
|
| TipUriResult
|
|
| WithdrawUriResult
|
|
| ExchangeUri
|
|
| WithdrawExchangeUri
|
|
| AuditorUri;
|
|
|
|
export interface PayUriResult {
|
|
type: TalerUriAction.Pay;
|
|
merchantBaseUrl: string;
|
|
orderId: string;
|
|
sessionId: string;
|
|
claimToken?: string;
|
|
noncePriv?: string;
|
|
}
|
|
|
|
export interface PayTemplateUriResult {
|
|
type: TalerUriAction.PayTemplate;
|
|
merchantBaseUrl: string;
|
|
templateId: string;
|
|
templateParams: Record<string, string>;
|
|
}
|
|
|
|
export interface WithdrawUriResult {
|
|
type: TalerUriAction.Withdraw;
|
|
bankIntegrationApiBaseUrl: string;
|
|
withdrawalOperationId: string;
|
|
}
|
|
|
|
export interface RefundUriResult {
|
|
type: TalerUriAction.Refund;
|
|
merchantBaseUrl: string;
|
|
orderId: string;
|
|
}
|
|
|
|
export interface TipUriResult {
|
|
type: TalerUriAction.Tip;
|
|
merchantBaseUrl: string;
|
|
merchantTipId: string;
|
|
}
|
|
|
|
export interface ExchangeUri {
|
|
type: TalerUriAction.Exchange;
|
|
exchangeBaseUrl: string;
|
|
exchangePub: string;
|
|
}
|
|
|
|
export interface AuditorUri {
|
|
type: TalerUriAction.Auditor;
|
|
auditorBaseUrl: string;
|
|
auditorPub: string;
|
|
}
|
|
|
|
export interface PayPushUriResult {
|
|
type: TalerUriAction.PayPush;
|
|
exchangeBaseUrl: string;
|
|
contractPriv: string;
|
|
}
|
|
|
|
export interface PayPullUriResult {
|
|
type: TalerUriAction.PayPull;
|
|
exchangeBaseUrl: string;
|
|
contractPriv: string;
|
|
}
|
|
|
|
export interface DevExperimentUri {
|
|
type: TalerUriAction.DevExperiment;
|
|
devExperimentId: string;
|
|
}
|
|
|
|
export interface BackupRestoreUri {
|
|
type: TalerUriAction.Restore;
|
|
walletRootPriv: string;
|
|
providers: Array<string>;
|
|
}
|
|
|
|
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.
|
|
*/
|
|
export function parseWithdrawUri(s: string): WithdrawUriResult | undefined {
|
|
const pi = parseProtoInfo(s, "withdraw");
|
|
if (!pi) {
|
|
return undefined;
|
|
}
|
|
const parts = pi.rest.split("/");
|
|
|
|
if (parts.length < 2) {
|
|
return undefined;
|
|
}
|
|
|
|
const host = parts[0].toLowerCase();
|
|
const pathSegments = parts.slice(1, parts.length - 1);
|
|
/**
|
|
* The statement below does not tolerate a slash-ended URI.
|
|
* This results in (1) the withdrawalId being passed as the
|
|
* empty string, and (2) the bankIntegrationApi ending with the
|
|
* actual withdrawal operation ID. That can be fixed by
|
|
* trimming the parts-list. FIXME
|
|
*/
|
|
const withdrawId = parts[parts.length - 1];
|
|
const p = [host, ...pathSegments].join("/");
|
|
|
|
return {
|
|
type: TalerUriAction.Withdraw,
|
|
bankIntegrationApiBaseUrl: canonicalizeBaseUrl(`${pi.innerProto}://${p}/`),
|
|
withdrawalOperationId: withdrawId,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @deprecated use TalerUriAction
|
|
*/
|
|
export enum TalerUriType {
|
|
TalerPay = "taler-pay",
|
|
TalerTemplate = "taler-template",
|
|
TalerPayTemplate = "taler-pay-template",
|
|
TalerWithdraw = "taler-withdraw",
|
|
TalerTip = "taler-tip",
|
|
TalerRefund = "taler-refund",
|
|
TalerPayPush = "taler-pay-push",
|
|
TalerPayPull = "taler-pay-pull",
|
|
TalerRecovery = "taler-recovery",
|
|
TalerDevExperiment = "taler-dev-experiment",
|
|
Unknown = "unknown",
|
|
}
|
|
|
|
const talerActionPayPull = "pay-pull";
|
|
const talerActionPayPush = "pay-push";
|
|
const talerActionPayTemplate = "pay-template";
|
|
|
|
export enum TalerUriAction {
|
|
Pay = "pay",
|
|
Withdraw = "withdraw",
|
|
Refund = "refund",
|
|
Tip = "tip",
|
|
PayPull = "pay-pull",
|
|
PayPush = "pay-push",
|
|
PayTemplate = "pay-template",
|
|
Exchange = "exchange",
|
|
Auditor = "auditor",
|
|
Restore = "restore",
|
|
DevExperiment = "dev-experiment",
|
|
WithdrawExchange = "withdraw-exchange",
|
|
}
|
|
|
|
interface TalerUriProtoInfo {
|
|
innerProto: "http" | "https";
|
|
rest: string;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
type Parser = (s: string) => TalerUri | undefined;
|
|
const parsers: { [A in TalerUriAction]: Parser } = {
|
|
[TalerUriAction.Pay]: parsePayUri,
|
|
[TalerUriAction.PayPull]: parsePayPullUri,
|
|
[TalerUriAction.PayPush]: parsePayPushUri,
|
|
[TalerUriAction.PayTemplate]: parsePayTemplateUri,
|
|
[TalerUriAction.Restore]: parseRestoreUri,
|
|
[TalerUriAction.Refund]: parseRefundUri,
|
|
[TalerUriAction.Tip]: parseTipUri,
|
|
[TalerUriAction.Withdraw]: parseWithdrawUri,
|
|
[TalerUriAction.DevExperiment]: parseDevExperimentUri,
|
|
[TalerUriAction.Exchange]: parseExchangeUri,
|
|
[TalerUriAction.Auditor]: parseAuditorUri,
|
|
[TalerUriAction.WithdrawExchange]: parseWithdrawExchangeUri,
|
|
};
|
|
|
|
export function parseTalerUri(string: string): TalerUri | undefined {
|
|
const https = string.startsWith("taler://");
|
|
const http = string.startsWith("taler+http://");
|
|
if (!https && !http) return undefined;
|
|
const actionStart = https ? 8 : 13;
|
|
const actionEnd = string.indexOf("/", actionStart + 1);
|
|
const action = string.substring(actionStart, actionEnd);
|
|
const found = Object.values(TalerUriAction).find((x) => x === action);
|
|
if (!found) return undefined;
|
|
return parsers[found](string);
|
|
}
|
|
|
|
export function stringifyTalerUri(uri: TalerUri): string {
|
|
switch (uri.type) {
|
|
case TalerUriAction.DevExperiment: {
|
|
return stringifyDevExperimentUri(uri);
|
|
}
|
|
case TalerUriAction.Pay: {
|
|
return stringifyPayUri(uri);
|
|
}
|
|
case TalerUriAction.PayPull: {
|
|
return stringifyPayPullUri(uri);
|
|
}
|
|
case TalerUriAction.PayPush: {
|
|
return stringifyPayPushUri(uri);
|
|
}
|
|
case TalerUriAction.PayTemplate: {
|
|
return stringifyPayTemplateUri(uri);
|
|
}
|
|
case TalerUriAction.Restore: {
|
|
return stringifyRestoreUri(uri);
|
|
}
|
|
case TalerUriAction.Refund: {
|
|
return stringifyRefundUri(uri);
|
|
}
|
|
case TalerUriAction.Tip: {
|
|
return stringifyTipUri(uri);
|
|
}
|
|
case TalerUriAction.Withdraw: {
|
|
return stringifyWithdrawUri(uri);
|
|
}
|
|
case TalerUriAction.Exchange: {
|
|
return stringifyExchangeUri(uri);
|
|
}
|
|
case TalerUriAction.WithdrawExchange: {
|
|
return stringifyWithdrawExchange(uri);
|
|
}
|
|
case TalerUriAction.Auditor: {
|
|
return stringifyAuditorUri(uri);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
const c = pi?.rest.split("?");
|
|
const q = new URLSearchParams(c[1] ?? "");
|
|
const claimToken = q.get("c") ?? undefined;
|
|
const noncePriv = q.get("n") ?? undefined;
|
|
const parts = c[0].split("/");
|
|
if (parts.length < 3) {
|
|
return undefined;
|
|
}
|
|
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 = canonicalizeBaseUrl(`${pi.innerProto}://${p}/`);
|
|
|
|
return {
|
|
type: TalerUriAction.Pay,
|
|
merchantBaseUrl,
|
|
orderId,
|
|
sessionId,
|
|
claimToken,
|
|
noncePriv,
|
|
};
|
|
}
|
|
|
|
export function parsePayTemplateUri(
|
|
uriString: string,
|
|
): PayTemplateUriResult | undefined {
|
|
const pi = parseProtoInfo(uriString, talerActionPayTemplate);
|
|
if (!pi) {
|
|
return undefined;
|
|
}
|
|
const c = pi.rest.split("?");
|
|
|
|
const parts = c[0].split("/");
|
|
if (parts.length < 2) {
|
|
return undefined;
|
|
}
|
|
|
|
const q = new URLSearchParams(c[1] ?? "");
|
|
const params: Record<string, string> = {};
|
|
q.forEach((v, k) => {
|
|
params[k] = v;
|
|
});
|
|
|
|
const host = parts[0].toLowerCase();
|
|
const templateId = parts[parts.length - 1];
|
|
const pathSegments = parts.slice(1, parts.length - 1);
|
|
const hostAndSegments = [host, ...pathSegments].join("/");
|
|
const merchantBaseUrl = canonicalizeBaseUrl(
|
|
`${pi.innerProto}://${hostAndSegments}/`,
|
|
);
|
|
|
|
return {
|
|
type: TalerUriAction.PayTemplate,
|
|
merchantBaseUrl,
|
|
templateId,
|
|
templateParams: params,
|
|
};
|
|
}
|
|
|
|
export function parsePayPushUri(s: string): PayPushUriResult | undefined {
|
|
const pi = parseProtoInfo(s, talerActionPayPush);
|
|
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 contractPriv = parts[parts.length - 1];
|
|
const pathSegments = parts.slice(1, parts.length - 1);
|
|
const hostAndSegments = [host, ...pathSegments].join("/");
|
|
const exchangeBaseUrl = canonicalizeBaseUrl(
|
|
`${pi.innerProto}://${hostAndSegments}/`,
|
|
);
|
|
|
|
return {
|
|
type: TalerUriAction.PayPush,
|
|
exchangeBaseUrl,
|
|
contractPriv,
|
|
};
|
|
}
|
|
|
|
export function parsePayPullUri(s: string): PayPullUriResult | undefined {
|
|
const pi = parseProtoInfo(s, talerActionPayPull);
|
|
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 contractPriv = parts[parts.length - 1];
|
|
const pathSegments = parts.slice(1, parts.length - 1);
|
|
const hostAndSegments = [host, ...pathSegments].join("/");
|
|
const exchangeBaseUrl = canonicalizeBaseUrl(
|
|
`${pi.innerProto}://${hostAndSegments}/`,
|
|
);
|
|
|
|
return {
|
|
type: TalerUriAction.PayPull,
|
|
exchangeBaseUrl,
|
|
contractPriv,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Parse a taler[+http]://tip URI.
|
|
* Return undefined if not passed a valid URI.
|
|
*/
|
|
export function parseTipUri(s: string): TipUriResult | undefined {
|
|
const pi = parseProtoInfo(s, "tip");
|
|
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 tipId = parts[parts.length - 1];
|
|
const pathSegments = parts.slice(1, parts.length - 1);
|
|
const hostAndSegments = [host, ...pathSegments].join("/");
|
|
const merchantBaseUrl = canonicalizeBaseUrl(
|
|
`${pi.innerProto}://${hostAndSegments}/`,
|
|
);
|
|
|
|
return {
|
|
type: TalerUriAction.Tip,
|
|
merchantBaseUrl,
|
|
merchantTipId: tipId,
|
|
};
|
|
}
|
|
|
|
export function parseExchangeUri(s: string): ExchangeUri | undefined {
|
|
const pi = parseProtoInfo(s, "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}/`,
|
|
);
|
|
|
|
return {
|
|
type: TalerUriAction.Exchange,
|
|
exchangeBaseUrl,
|
|
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) {
|
|
return undefined;
|
|
}
|
|
const c = pi?.rest.split("?");
|
|
const parts = c[0].split("/");
|
|
if (parts.length < 2) {
|
|
return undefined;
|
|
}
|
|
const host = parts[0].toLowerCase();
|
|
const auditorPub = parts[parts.length - 1];
|
|
const pathSegments = parts.slice(1, parts.length - 1);
|
|
const hostAndSegments = [host, ...pathSegments].join("/");
|
|
const auditorBaseUrl = canonicalizeBaseUrl(
|
|
`${pi.innerProto}://${hostAndSegments}/`,
|
|
);
|
|
|
|
return {
|
|
type: TalerUriAction.Auditor,
|
|
auditorBaseUrl,
|
|
auditorPub,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Parse a taler[+http]://refund URI.
|
|
* Return undefined if not passed a valid URI.
|
|
*/
|
|
export function parseRefundUri(s: string): RefundUriResult | undefined {
|
|
const pi = parseProtoInfo(s, "refund");
|
|
if (!pi) {
|
|
return undefined;
|
|
}
|
|
const c = pi?.rest.split("?");
|
|
const parts = c[0].split("/");
|
|
if (parts.length < 3) {
|
|
return undefined;
|
|
}
|
|
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 hostAndSegments = [host, ...pathSegments].join("/");
|
|
const merchantBaseUrl = canonicalizeBaseUrl(
|
|
`${pi.innerProto}://${hostAndSegments}/`,
|
|
);
|
|
|
|
return {
|
|
type: TalerUriAction.Refund,
|
|
merchantBaseUrl,
|
|
orderId,
|
|
};
|
|
}
|
|
|
|
export function parseDevExperimentUri(s: string): DevExperimentUri | undefined {
|
|
const pi = parseProtoInfo(s, "dev-experiment");
|
|
const c = pi?.rest.split("?");
|
|
if (!c) {
|
|
return undefined;
|
|
}
|
|
const parts = c[0].split("/");
|
|
return {
|
|
type: TalerUriAction.DevExperiment,
|
|
devExperimentId: parts[0],
|
|
};
|
|
}
|
|
|
|
export function parseRestoreUri(uri: string): BackupRestoreUri | undefined {
|
|
const pi = parseProtoInfo(uri, "restore");
|
|
if (!pi) {
|
|
return undefined;
|
|
}
|
|
const c = pi.rest.split("?");
|
|
const parts = c[0].split("/");
|
|
if (parts.length < 2) {
|
|
return undefined;
|
|
}
|
|
|
|
const walletRootPriv = parts[0];
|
|
if (!walletRootPriv) return undefined;
|
|
const providers = new Array<string>();
|
|
parts[1].split(",").map((name) => {
|
|
const url = canonicalizeBaseUrl(
|
|
`${pi.innerProto}://${decodeURIComponent(name)}/`,
|
|
);
|
|
providers.push(url);
|
|
});
|
|
return {
|
|
type: TalerUriAction.Restore,
|
|
walletRootPriv,
|
|
providers,
|
|
};
|
|
}
|
|
|
|
// ================================================
|
|
// To string functions
|
|
// ================================================
|
|
|
|
/**
|
|
* @deprecated use stringifyRecoveryUri
|
|
*/
|
|
export function constructRecoveryUri(args: {
|
|
walletRootPriv: string;
|
|
providers: string[];
|
|
}): string {
|
|
return stringifyRestoreUri(args);
|
|
}
|
|
|
|
/**
|
|
* @deprecated stringifyPayPullUri
|
|
*/
|
|
export function constructPayPullUri(args: {
|
|
exchangeBaseUrl: string;
|
|
contractPriv: string;
|
|
}): string {
|
|
return stringifyPayPullUri(args);
|
|
}
|
|
|
|
/**
|
|
* @deprecated use stringifyPayPushUri
|
|
*/
|
|
export function constructPayPushUri(args: {
|
|
exchangeBaseUrl: string;
|
|
contractPriv: string;
|
|
}): string {
|
|
return stringifyPayPushUri(args);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @deprecated use stringifyPayUri
|
|
*/
|
|
export function constructPayUri(
|
|
merchantBaseUrl: string,
|
|
orderId: string,
|
|
sessionId: string,
|
|
claimToken?: string,
|
|
noncePriv?: string,
|
|
): string {
|
|
return stringifyPayUri({
|
|
merchantBaseUrl,
|
|
orderId,
|
|
sessionId,
|
|
claimToken,
|
|
noncePriv,
|
|
});
|
|
}
|
|
|
|
export function stringifyPayUri({
|
|
merchantBaseUrl,
|
|
orderId,
|
|
sessionId,
|
|
claimToken,
|
|
noncePriv,
|
|
}: Omit<PayUriResult, "type">): string {
|
|
const { proto, path, query } = getUrlInfo(merchantBaseUrl, {
|
|
c: claimToken,
|
|
n: noncePriv,
|
|
});
|
|
return `${proto}://pay/${path}${orderId}/${sessionId}${query}`;
|
|
}
|
|
|
|
export function stringifyPayPullUri({
|
|
contractPriv,
|
|
exchangeBaseUrl,
|
|
}: Omit<PayPullUriResult, "type">): string {
|
|
const { proto, path } = getUrlInfo(exchangeBaseUrl);
|
|
return `${proto}://pay-pull/${path}${contractPriv}`;
|
|
}
|
|
|
|
export function stringifyPayPushUri({
|
|
contractPriv,
|
|
exchangeBaseUrl,
|
|
}: Omit<PayPushUriResult, "type">): string {
|
|
const { proto, path } = getUrlInfo(exchangeBaseUrl);
|
|
|
|
return `${proto}://pay-push/${path}${contractPriv}`;
|
|
}
|
|
|
|
export function stringifyRestoreUri({
|
|
providers,
|
|
walletRootPriv,
|
|
}: Omit<BackupRestoreUri, "type">): string {
|
|
const list = providers
|
|
.map((url) => `${encodeURIComponent(new URL(url).href)}`)
|
|
.join(",");
|
|
return `taler://restore/${walletRootPriv}/${list}`;
|
|
}
|
|
|
|
export function stringifyWithdrawExchange({
|
|
exchangeBaseUrl,
|
|
exchangePub,
|
|
amount,
|
|
}: Omit<WithdrawExchangeUri, "type">): string {
|
|
const { proto, path, query } = getUrlInfo(exchangeBaseUrl, {
|
|
a: amount,
|
|
});
|
|
return `${proto}://withdraw-exchange/${path}${exchangePub}${query}`;
|
|
}
|
|
|
|
export function stringifyDevExperimentUri({
|
|
devExperimentId,
|
|
}: Omit<DevExperimentUri, "type">): string {
|
|
return `taler://dev-experiment/${devExperimentId}`;
|
|
}
|
|
|
|
export function stringifyPayTemplateUri({
|
|
merchantBaseUrl,
|
|
templateId,
|
|
templateParams,
|
|
}: Omit<PayTemplateUriResult, "type">): string {
|
|
const { proto, path, query } = getUrlInfo(merchantBaseUrl, templateParams);
|
|
return `${proto}://pay-template/${path}${templateId}${query}`;
|
|
}
|
|
export function stringifyRefundUri({
|
|
merchantBaseUrl,
|
|
orderId,
|
|
}: Omit<RefundUriResult, "type">): string {
|
|
const { proto, path } = getUrlInfo(merchantBaseUrl);
|
|
return `${proto}://refund/${path}${orderId}`;
|
|
}
|
|
export function stringifyTipUri({
|
|
merchantBaseUrl,
|
|
merchantTipId,
|
|
}: Omit<TipUriResult, "type">): string {
|
|
const { proto, path } = getUrlInfo(merchantBaseUrl);
|
|
return `${proto}://tip/${path}${merchantTipId}`;
|
|
}
|
|
|
|
export function stringifyExchangeUri({
|
|
exchangeBaseUrl,
|
|
exchangePub,
|
|
}: Omit<ExchangeUri, "type">): string {
|
|
const { proto, path } = getUrlInfo(exchangeBaseUrl);
|
|
return `${proto}://exchange/${path}${exchangePub}`;
|
|
}
|
|
|
|
export function stringifyAuditorUri({
|
|
auditorBaseUrl,
|
|
auditorPub,
|
|
}: Omit<AuditorUri, "type">): string {
|
|
const { proto, path } = getUrlInfo(auditorBaseUrl);
|
|
return `${proto}://auditor/${path}${auditorPub}`;
|
|
}
|
|
|
|
export function stringifyWithdrawUri({
|
|
bankIntegrationApiBaseUrl,
|
|
withdrawalOperationId,
|
|
}: Omit<WithdrawUriResult, "type">): string {
|
|
const { proto, path } = getUrlInfo(bankIntegrationApiBaseUrl);
|
|
return `${proto}://withdraw/${path}${withdrawalOperationId}`;
|
|
}
|
|
|
|
/**
|
|
* Use baseUrl to defined http or https
|
|
* create path using host+port+pathname
|
|
* use params to create a query parameter string or empty
|
|
*
|
|
* @param baseUrl
|
|
* @param params
|
|
* @returns
|
|
*/
|
|
function getUrlInfo(
|
|
baseUrl: string,
|
|
params: Record<string, string | undefined> = {},
|
|
): { proto: string; path: string; query: string } {
|
|
const url = new URL(baseUrl);
|
|
let proto: string;
|
|
if (url.protocol === "https:") {
|
|
proto = "taler";
|
|
} else if (url.protocol === "http:") {
|
|
proto = "taler+http";
|
|
} else {
|
|
throw Error(`Unsupported URL protocol in ${baseUrl}`);
|
|
}
|
|
let path = url.hostname;
|
|
if (url.port) {
|
|
path = path + ":" + url.port;
|
|
}
|
|
if (url.pathname) {
|
|
path = path + url.pathname;
|
|
}
|
|
if (!path.endsWith("/")) {
|
|
path = path + "/";
|
|
}
|
|
|
|
const qp = new URLSearchParams();
|
|
let withParams = false;
|
|
Object.entries(params).forEach(([name, value]) => {
|
|
if (value) {
|
|
withParams = true;
|
|
qp.append(name, value);
|
|
}
|
|
});
|
|
const query = withParams ? "?" + qp.toString() : "";
|
|
|
|
return { proto, path, query };
|
|
}
|