-missing files

This commit is contained in:
Florian Dold 2023-09-06 11:44:21 +02:00
parent 324d9f871c
commit 07d71eb297
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
2 changed files with 365 additions and 0 deletions

View File

@ -0,0 +1,334 @@
/*
This file is part of GNU Taler
(C) 2023 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 {
createPlatformHttpLib,
expectSuccessResponseOrThrow,
readSuccessResponseJsonOrThrow,
} from "./http.js";
import { FacadeCredentials } from "./libeufin-api-types.js";
import { Logger } from "./logging.js";
import {
MerchantReserveCreateConfirmation,
codecForMerchantReserveCreateConfirmation,
TippingReserveStatus,
MerchantInstancesResponse,
MerchantPostOrderRequest,
MerchantPostOrderResponse,
codecForMerchantPostOrderResponse,
MerchantOrderPrivateStatusResponse,
codecForMerchantOrderPrivateStatusResponse,
RewardCreateRequest,
RewardCreateConfirmation,
MerchantTemplateAddDetails,
} from "./merchant-api-types.js";
import { AmountString } from "./taler-types.js";
import { TalerProtocolDuration } from "./time.js";
const logger = new Logger("MerchantApiClient.ts");
export interface MerchantAuthConfiguration {
method: "external" | "token";
token?: string;
}
// FIXME: Why do we need this? Describe / fix!
export interface PartialMerchantInstanceConfig {
auth?: MerchantAuthConfiguration;
id: string;
name: string;
paytoUris: string[];
address?: unknown;
jurisdiction?: unknown;
defaultWireTransferDelay?: TalerProtocolDuration;
defaultPayDelay?: TalerProtocolDuration;
}
export interface CreateMerchantTippingReserveRequest {
// Amount that the merchant promises to put into the reserve
initial_balance: AmountString;
// Exchange the merchant intends to use for tipping
exchange_url: string;
// Desired wire method, for example "iban" or "x-taler-bank"
wire_method: string;
}
export interface DeleteTippingReserveArgs {
reservePub: string;
purge?: boolean;
}
export interface MerchantInstanceConfig {
accounts: MerchantBankAccount[];
auth: MerchantAuthConfiguration;
id: string;
name: string;
address: unknown;
jurisdiction: unknown;
use_stefan: boolean;
default_wire_transfer_delay: TalerProtocolDuration;
default_pay_delay: TalerProtocolDuration;
}
interface MerchantBankAccount {
// The payto:// URI where the wallet will send coins.
payto_uri: string;
// Optional base URL for a facade where the
// merchant backend can see incoming wire
// transfers to reconcile its accounting
// with that of the exchange. Used by
// taler-merchant-wirewatch.
credit_facade_url?: string;
// Credentials for accessing the credit facade.
credit_facade_credentials?: FacadeCredentials;
}
export interface MerchantInstanceConfig {
accounts: MerchantBankAccount[];
auth: MerchantAuthConfiguration;
id: string;
name: string;
address: unknown;
jurisdiction: unknown;
use_stefan: boolean;
default_wire_transfer_delay: TalerProtocolDuration;
default_pay_delay: TalerProtocolDuration;
}
export interface PrivateOrderStatusQuery {
instance?: string;
orderId: string;
sessionId?: string;
}
/**
* Client for the GNU Taler merchant backend.
*/
export class MerchantApiClient {
/**
* Base URL for the particular instance that this merchant API client
* is for.
*/
private baseUrl: string;
readonly auth: MerchantAuthConfiguration;
constructor(baseUrl: string, auth?: MerchantAuthConfiguration) {
this.baseUrl = baseUrl;
this.auth = auth ?? {
method: "external",
};
}
httpClient = createPlatformHttpLib({
allowHttp: true,
enableThrottling: false,
});
async changeAuth(auth: MerchantAuthConfiguration): Promise<void> {
const url = new URL("private/auth", this.baseUrl);
const res = await this.httpClient.fetch(url.href, {
method: "POST",
body: auth,
headers: this.makeAuthHeader(),
});
await expectSuccessResponseOrThrow(res);
}
async deleteTippingReserve(req: DeleteTippingReserveArgs): Promise<void> {
const url = new URL(`private/reserves/${req.reservePub}`, this.baseUrl);
if (req.purge) {
url.searchParams.set("purge", "YES");
}
const resp = await this.httpClient.fetch(url.href, {
method: "DELETE",
headers: this.makeAuthHeader(),
});
logger.info(`delete status: ${resp.status}`);
return;
}
async createTippingReserve(
req: CreateMerchantTippingReserveRequest,
): Promise<MerchantReserveCreateConfirmation> {
const url = new URL("private/reserves", this.baseUrl);
const resp = await this.httpClient.fetch(url.href, {
method: "POST",
body: req,
headers: this.makeAuthHeader(),
});
const respData = readSuccessResponseJsonOrThrow(
resp,
codecForMerchantReserveCreateConfirmation(),
);
return respData;
}
async getPrivateInstanceInfo(): Promise<any> {
const url = new URL("private", this.baseUrl);
const resp = await this.httpClient.fetch(url.href, {
method: "GET",
headers: this.makeAuthHeader(),
});
return await resp.json();
}
async getPrivateTipReserves(): Promise<TippingReserveStatus> {
const url = new URL("private/reserves", this.baseUrl);
const resp = await this.httpClient.fetch(url.href, {
method: "GET",
headers: this.makeAuthHeader(),
});
// FIXME: Validate!
return await resp.json();
}
async deleteInstance(instanceId: string) {
const url = new URL(`management/instances/${instanceId}`, this.baseUrl);
const resp = await this.httpClient.fetch(url.href, {
method: "DELETE",
headers: this.makeAuthHeader(),
});
await expectSuccessResponseOrThrow(resp);
}
async createInstance(req: MerchantInstanceConfig): Promise<void> {
const url = new URL("management/instances", this.baseUrl);
await this.httpClient.fetch(url.href, {
method: "POST",
body: req,
headers: this.makeAuthHeader(),
});
}
async getInstances(): Promise<MerchantInstancesResponse> {
const url = new URL("management/instances", this.baseUrl);
const resp = await this.httpClient.fetch(url.href, {
headers: this.makeAuthHeader(),
});
return resp.json();
}
async getInstanceFullDetails(instanceId: string): Promise<any> {
const url = new URL(`management/instances/${instanceId}`, this.baseUrl);
try {
const resp = await this.httpClient.fetch(url.href, {
headers: this.makeAuthHeader(),
});
return resp.json();
} catch (e) {
throw e;
}
}
async createOrder(
req: MerchantPostOrderRequest,
): Promise<MerchantPostOrderResponse> {
let url = new URL("private/orders", this.baseUrl);
const resp = await this.httpClient.fetch(url.href, {
method: "POST",
body: req,
headers: this.makeAuthHeader(),
});
return readSuccessResponseJsonOrThrow(
resp,
codecForMerchantPostOrderResponse(),
);
}
async queryPrivateOrderStatus(
query: PrivateOrderStatusQuery,
): Promise<MerchantOrderPrivateStatusResponse> {
const reqUrl = new URL(`private/orders/${query.orderId}`, this.baseUrl);
if (query.sessionId) {
reqUrl.searchParams.set("session_id", query.sessionId);
}
const resp = await this.httpClient.fetch(reqUrl.href, {
headers: this.makeAuthHeader(),
});
return readSuccessResponseJsonOrThrow(
resp,
codecForMerchantOrderPrivateStatusResponse(),
);
}
async giveTip(req: RewardCreateRequest): Promise<RewardCreateConfirmation> {
const reqUrl = new URL(`private/tips`, this.baseUrl);
const resp = await this.httpClient.fetch(reqUrl.href, {
method: "POST",
body: req,
});
// FIXME: validate
return resp.json();
}
async queryTippingReserves(): Promise<TippingReserveStatus> {
const reqUrl = new URL(`private/reserves`, this.baseUrl);
const resp = await this.httpClient.fetch(reqUrl.href, {
headers: this.makeAuthHeader(),
});
// FIXME: validate
return resp.json();
}
async giveRefund(r: {
instance: string;
orderId: string;
amount: string;
justification: string;
}): Promise<{ talerRefundUri: string }> {
const reqUrl = new URL(`private/orders/${r.orderId}/refund`, this.baseUrl);
const resp = await this.httpClient.fetch(reqUrl.href, {
method: "POST",
body: {
refund: r.amount,
reason: r.justification,
},
});
const respBody = await resp.json();
return {
talerRefundUri: respBody.taler_refund_uri,
};
}
async createTemplate(req: MerchantTemplateAddDetails) {
let url = new URL("private/templates", this.baseUrl);
const resp = await this.httpClient.fetch(url.href, {
method: "POST",
body: req,
headers: this.makeAuthHeader(),
});
if (resp.status !== 204) {
throw Error("failed to create template");
}
}
private makeAuthHeader(): Record<string, string> {
switch (this.auth.method) {
case "external":
return {};
case "token":
return {
Authorization: `Bearer ${this.auth.token}`,
};
}
}
}

View File

@ -0,0 +1,31 @@
/*
This file is part of GNU Taler
(C) 2023 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/>
*/
export type FacadeCredentials =
| NoFacadeCredentials
| BasicAuthFacadeCredentials;
export interface NoFacadeCredentials {
type: "none";
}
export interface BasicAuthFacadeCredentials {
type: "basic";
// Username to use to authenticate
username: string;
// Password to use to authenticate
password: string;
}