-missing files
This commit is contained in:
parent
324d9f871c
commit
07d71eb297
334
packages/taler-util/src/MerchantApiClient.ts
Normal file
334
packages/taler-util/src/MerchantApiClient.ts
Normal 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}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
packages/taler-util/src/libeufin-api-types.ts
Normal file
31
packages/taler-util/src/libeufin-api-types.ts
Normal 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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user