/*
This file is part of GNU Taler
(C) 2022-2024 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
*/
import {
HttpStatusCode,
LibtoolVersion,
PaginationParams,
TalerMerchantApi,
codecForAbortResponse,
codecForAccountAddResponse,
codecForAccountKycRedirects,
codecForAccountsSummaryResponse,
codecForBankAccountEntry,
codecForClaimResponse,
codecForInstancesResponse,
codecForInventorySummaryResponse,
codecForMerchantConfig,
codecForMerchantOrderPrivateStatusResponse,
codecForMerchantRefundResponse,
codecForOrderHistory,
codecForOtpDeviceDetails,
codecForOtpDeviceSummaryResponse,
codecForOutOfStockResponse,
codecForPaidRefundStatusResponse,
codecForPaymentResponse,
codecForPostOrderResponse,
codecForProductDetail,
codecForQueryInstancesResponse,
codecForStatusGoto,
codecForStatusPaid,
codecForStatusStatusUnpaid,
codecForTansferList,
codecForTemplateDetails,
codecForTemplateSummaryResponse,
codecForTokenFamiliesList,
codecForTokenFamilyDetails,
codecForWalletRefundResponse,
codecForWalletTemplateDetails,
codecForWebhookDetails,
codecForWebhookSummaryResponse,
opEmptySuccess,
opKnownAlternativeFailure,
opKnownHttpFailure,
} from "@gnu-taler/taler-util";
import {
HttpRequestLibrary,
HttpResponse,
createPlatformHttpLib,
} from "@gnu-taler/taler-util/http";
import { opSuccessFromHttp, opUnknownFailure } from "../operation.js";
import {
CacheEvictor,
addMerchantPaginationParams,
nullEvictor,
} from "./utils.js";
export enum TalerMerchantInstanceCacheEviction {
CREATE_ORDER,
}
export enum TalerMerchantManagementCacheEviction {
CREATE_INSTANCE,
}
/**
* Protocol version spoken with the core bank.
*
* Endpoint must be ordered in the same way that in the docs
* Response code (http and taler) must have the same order that in the docs
* That way is easier to see changes
*
* Uses libtool's current:revision:age versioning.
*/
export class TalerMerchantInstanceHttpClient {
public readonly PROTOCOL_VERSION = "10:0:6";
readonly httpLib: HttpRequestLibrary;
readonly cacheEvictor: CacheEvictor;
constructor(
readonly baseUrl: string,
httpClient?: HttpRequestLibrary,
cacheEvictor?: CacheEvictor,
) {
this.httpLib = httpClient ?? createPlatformHttpLib();
this.cacheEvictor = cacheEvictor ?? nullEvictor;
}
isCompatible(version: string): boolean {
const compare = LibtoolVersion.compare(this.PROTOCOL_VERSION, version);
return compare?.compatible ?? false;
}
/**
* https://docs.taler.net/core/api-merchant.html#get--config
*
*/
async getConfig() {
const url = new URL(`config`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForMerchantConfig());
default:
return opUnknownFailure(resp, await resp.text());
}
}
//
// Wallet API
//
/**
* https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-orders-$ORDER_ID-claim
*/
async claimOrder(orderId: string, body: TalerMerchantApi.ClaimRequest) {
const url = new URL(`orders/${orderId}/claim`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForClaimResponse());
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-orders-$ORDER_ID-pay
*/
async makePayment(orderId: string, body: TalerMerchantApi.PayRequest) {
const url = new URL(`orders/${orderId}/pay`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForPaymentResponse());
case HttpStatusCode.BadRequest:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.PaymentRequired:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Forbidden:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.RequestTimeout:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Gone:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.PreconditionFailed:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.BadGateway:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.GatewayTimeout:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-orders-$ORDER_ID
*/
async getPaymentStatus(
orderId: string,
params: TalerMerchantApi.PaymentStatusRequestParams = {},
) {
const url = new URL(`orders/${orderId}`, this.baseUrl);
if (params.allowRefundedForRepurchase !== undefined) {
url.searchParams.set(
"allow_refunded_for_repurchase",
params.allowRefundedForRepurchase ? "YES" : "NO",
);
}
if (params.awaitRefundObtained !== undefined) {
url.searchParams.set(
"await_refund_obtained",
params.allowRefundedForRepurchase ? "YES" : "NO",
);
}
if (params.claimToken !== undefined) {
url.searchParams.set("token", params.claimToken);
}
if (params.contractTermHash !== undefined) {
url.searchParams.set("h_contract", params.contractTermHash);
}
if (params.refund !== undefined) {
url.searchParams.set("refund", params.refund);
}
if (params.sessionId !== undefined) {
url.searchParams.set("session_id", params.sessionId);
}
if (params.timeout !== undefined) {
url.searchParams.set("timeout_ms", String(params.timeout));
}
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
// body,
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForStatusPaid());
case HttpStatusCode.Accepted:
return opSuccessFromHttp(resp, codecForStatusGoto());
// case HttpStatusCode.Found: not possible since content is not HTML
case HttpStatusCode.PaymentRequired:
return opSuccessFromHttp(resp, codecForStatusStatusUnpaid());
case HttpStatusCode.Forbidden:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.NotAcceptable:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#demonstrating-payment
*/
async demostratePayment(orderId: string, body: TalerMerchantApi.PaidRequest) {
const url = new URL(`orders/${orderId}/paid`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForPaidRefundStatusResponse());
case HttpStatusCode.BadRequest:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Forbidden:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#aborting-incomplete-payments
*/
async abortIncompletePayment(
orderId: string,
body: TalerMerchantApi.AbortRequest,
) {
const url = new URL(`orders/${orderId}/abort`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForAbortResponse());
case HttpStatusCode.BadRequest:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Forbidden:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#obtaining-refunds
*/
async obtainRefund(
orderId: string,
body: TalerMerchantApi.WalletRefundRequest,
) {
const url = new URL(`orders/${orderId}/refund`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForWalletRefundResponse());
case HttpStatusCode.BadRequest:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Forbidden:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
//
// Management
//
/**
* https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-auth
*/
async updateCurrentInstanceAuthentication(
body: TalerMerchantApi.InstanceAuthConfigurationMessage,
) {
const url = new URL(`private/auth`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
});
//
switch (resp.status) {
case HttpStatusCode.Ok:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCE]-private
*/
async updateCurrentInstance(
body: TalerMerchantApi.InstanceReconfigurationMessage,
) {
const url = new URL(`private`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "PATCH",
body,
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private
*
*/
async getCurrentInstance() {
const url = new URL(`private`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForQueryInstancesResponse());
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private
*/
async deleteCurrentInstance(params: { purge?: boolean }) {
const url = new URL(`private`, this.baseUrl);
if (params.purge) {
url.searchParams.set("purge", "YES");
}
const resp = await this.httpLib.fetch(url.href, {
method: "DELETE",
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.Unauthorized:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get--instances-$INSTANCE-private-kyc
*/
async getCurrentIntanceKycStatus(
params: TalerMerchantApi.GetKycStatusRequestParams,
) {
const url = new URL(`private/kyc`, this.baseUrl);
if (params.wireHash) {
url.searchParams.set("h_wire", params.wireHash);
}
if (params.exchangeURL) {
url.searchParams.set("exchange_url", params.exchangeURL);
}
if (params.timeout) {
url.searchParams.set("timeout_ms", String(params.timeout));
}
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Accepted:
return opSuccessFromHttp(resp, codecForAccountKycRedirects());
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.BadGateway:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.ServiceUnavailable:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
//
// Bank Accounts
//
/**
* https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-accounts
*/
async addAccount(body: TalerMerchantApi.AccountAddDetails) {
const url = new URL(`private/accounts`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForAccountAddResponse());
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCE]-private-accounts-$H_WIRE
*/
async updateAccount(
wireAccount: string,
body: TalerMerchantApi.AccountPatchDetails,
) {
const url = new URL(`private/accounts/${wireAccount}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "PATCH",
body,
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-accounts
*/
async listAccounts() {
const url = new URL(`private/accounts`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForAccountsSummaryResponse());
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-accounts-$H_WIRE
*/
async getAccount(wireAccount: string) {
const url = new URL(`private/accounts/${wireAccount}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForBankAccountEntry());
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-accounts-$H_WIRE
*/
async deleteAccount(wireAccount: string) {
const url = new URL(`private/accounts/${wireAccount}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "DELETE",
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
//
// Inventory Management
//
/**
* https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-products
*/
async addProduct(body: TalerMerchantApi.ProductAddDetail) {
const url = new URL(`private/products`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCE]-private-products-$PRODUCT_ID
*/
async updateProduct(
productId: string,
body: TalerMerchantApi.ProductAddDetail,
) {
const url = new URL(`private/products/${productId}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "PATCH",
body,
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-products
*/
async listProducts(params?: PaginationParams) {
const url = new URL(`private/products`, this.baseUrl);
addMerchantPaginationParams(url, params);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForInventorySummaryResponse());
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-products-$PRODUCT_ID
*/
async getProduct(productId: string) {
const url = new URL(`private/products/${productId}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForProductDetail());
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#reserving-inventory
*/
async lockProduct(productId: string) {
const url = new URL(`private/products/${productId}/lock`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Gone:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#removing-products-from-inventory
*/
async removeProduct(productId: string) {
const url = new URL(`private/products/${productId}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "DELETE",
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
//
// Payment processing
//
/**
* https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-orders
*/
async createOrder(body: TalerMerchantApi.PostOrderRequest) {
const url = new URL(`private/orders`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
});
return this.procesOrderCreationResponse(resp)
}
private async procesOrderCreationResponse(resp: HttpResponse) {
switch (resp.status) {
case HttpStatusCode.Ok: {
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.CREATE_ORDER)
return opSuccessFromHttp(resp, codecForPostOrderResponse())
}
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Gone:
return opKnownAlternativeFailure(
resp,
resp.status,
codecForOutOfStockResponse(),
);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#inspecting-orders
*/
async listOrders(params: TalerMerchantApi.ListOrdersRequestParams = {}) {
const url = new URL(`private/orders`, this.baseUrl);
if (params.date) {
url.searchParams.set("date_s", String(params.date));
}
if (params.fulfillmentUrl) {
url.searchParams.set("fulfillment_url", params.fulfillmentUrl);
}
if (params.paid) {
url.searchParams.set("paid", "YES");
}
if (params.refunded) {
url.searchParams.set("refunded", "YES");
}
if (params.sessionId) {
url.searchParams.set("session_id", params.sessionId);
}
if (params.timeout) {
url.searchParams.set("timeout", String(params.timeout));
}
if (params.wired) {
url.searchParams.set("wired", "YES");
}
addMerchantPaginationParams(url, params);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForOrderHistory());
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-orders-$ORDER_ID
*/
async getOrder(
orderId: string,
params: TalerMerchantApi.GetOrderRequestParams = {},
) {
const url = new URL(`private/orders/${orderId}`, this.baseUrl);
if (params.allowRefundedForRepurchase) {
url.searchParams.set("allow_refunded_for_repurchase", "YES");
}
if (params.sessionId) {
url.searchParams.set("session_id", params.sessionId);
}
if (params.timeout) {
url.searchParams.set("timeout_ms", String(params.timeout));
}
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(
resp,
codecForMerchantOrderPrivateStatusResponse(),
);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.BadGateway:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.GatewayTimeout:
return opKnownAlternativeFailure(
resp,
resp.status,
codecForOutOfStockResponse(),
);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#private-order-data-cleanup
*/
async forgetOrder(orderId: string, body: TalerMerchantApi.ForgetRequest) {
const url = new URL(`private/orders/${orderId}/forget`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "PATCH",
body,
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opEmptySuccess(resp);
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.BadRequest:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-orders-$ORDER_ID
*/
async deleteOrder(orderId: string) {
const url = new URL(`private/orders/${orderId}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "DELETE",
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
//
// Refunds
//
/**
* https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-orders-$ORDER_ID-refund
*/
async addRefund(orderId: string, body: TalerMerchantApi.RefundRequest) {
const url = new URL(`private/orders/${orderId}/refund`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForMerchantRefundResponse());
case HttpStatusCode.Forbidden:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Gone:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
//
// Wire Transfer
//
/**
* https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-transfers
*/
async informWireTransfer(body: TalerMerchantApi.TransferInformation) {
const url = new URL(`private/transfers`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-transfers
*/
async listWireTransfers(
params: TalerMerchantApi.ListWireTransferRequestParams = {},
) {
const url = new URL(`private/transfers`, this.baseUrl);
if (params.after) {
url.searchParams.set("after", String(params.after));
}
if (params.before) {
url.searchParams.set("before", String(params.before));
}
if (params.paytoURI) {
url.searchParams.set("payto_uri", params.paytoURI);
}
if (params.verified) {
url.searchParams.set("verified", "YES");
}
addMerchantPaginationParams(url, params);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForTansferList());
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-transfers-$TID
*/
async deleteWireTransfer(transferId: string) {
const url = new URL(`private/transfers/${transferId}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "DELETE",
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
//
// OTP Devices
//
/**
* https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-otp-devices
*/
async addOtpDevice(body: TalerMerchantApi.OtpDeviceAddDetails) {
const url = new URL(`private/otp-devices`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCE]-private-otp-devices-$DEVICE_ID
*/
async updateOtpDevice(
deviceId: string,
body: TalerMerchantApi.OtpDevicePatchDetails,
) {
const url = new URL(`private/otp-devices/${deviceId}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "PATCH",
body,
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-otp-devices
*/
async listOtpDevices() {
const url = new URL(`private/otp-devices`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForOtpDeviceSummaryResponse());
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-otp-devices-$DEVICE_ID
*/
async getOtpDevice(
deviceId: string,
params: TalerMerchantApi.GetOtpDeviceRequestParams = {},
) {
const url = new URL(`private/otp-devices/${deviceId}`, this.baseUrl);
if (params.faketime) {
url.searchParams.set("faketime", String(params.faketime));
}
if (params.price) {
url.searchParams.set("price", params.price);
}
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForOtpDeviceDetails());
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-otp-devices-$DEVICE_ID
*/
async deleteOtpDevice(deviceId: string) {
const url = new URL(`private/otp-devices/${deviceId}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "DELETE",
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
//
// Templates
//
/**
* https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-templates
*/
async addTemplate(body: TalerMerchantApi.TemplateAddDetails) {
const url = new URL(`private/templates`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCE]-private-templates-$TEMPLATE_ID
*/
async updateTemplate(
templateId: string,
body: TalerMerchantApi.TemplatePatchDetails,
) {
const url = new URL(`private/templates/${templateId}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "PATCH",
body,
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#inspecting-template
*/
async listTemplates() {
const url = new URL(`private/templates`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForTemplateSummaryResponse());
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-templates-$TEMPLATE_ID
*/
async getTemplate(templateId: string) {
const url = new URL(`private/templates/${templateId}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForTemplateDetails());
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-templates-$TEMPLATE_ID
*/
async deleteTemplate(templateId: string) {
const url = new URL(`private/templates/${templateId}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "DELETE",
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-templates-$TEMPLATE_ID
*/
async useTemplateGetInfo(templateId: string) {
const url = new URL(`templates/${templateId}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForWalletTemplateDetails());
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCES]-templates-$TEMPLATE_ID
*/
async useTemplateCreateOrder(
templateId: string,
body: TalerMerchantApi.UsingTemplateDetails,
) {
const url = new URL(`templates/${templateId}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
});
return this.procesOrderCreationResponse(resp)
}
//
// Webhooks
//
/**
* https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCES]-private-webhooks
*/
async addWebhook(body: TalerMerchantApi.WebhookAddDetails) {
const url = new URL(`private/webhooks`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCES]-private-webhooks-$WEBHOOK_ID
*/
async updateWebhook(
webhookId: string,
body: TalerMerchantApi.WebhookPatchDetails,
) {
const url = new URL(`private/webhooks/${webhookId}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "PATCH",
body,
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCES]-private-webhooks
*/
async listWebhooks() {
const url = new URL(`private/webhooks`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opSuccessFromHttp(resp, codecForWebhookSummaryResponse());
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCES]-private-webhooks-$WEBHOOK_ID
*/
async getWebhook(webhookId: string) {
const url = new URL(`private/webhooks/${webhookId}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opSuccessFromHttp(resp, codecForWebhookDetails());
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCES]-private-webhooks-$WEBHOOK_ID
*/
async removeWebhook(webhookId: string) {
const url = new URL(`private/webhooks/${webhookId}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "DELETE",
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
//
// token families
//
/**
* https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCES]-private-tokenfamilies
*/
async createTokenFamily(body: TalerMerchantApi.TokenFamilyCreateRequest) {
const url = new URL(`private/tokenfamilies`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCES]-private-tokenfamilies-$TOKEN_FAMILY_SLUG
*/
async updateTokenFamily(
tokenSlug: string,
body: TalerMerchantApi.TokenFamilyUpdateRequest,
) {
const url = new URL(`private/tokenfamilies/${tokenSlug}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForTokenFamilyDetails());
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCES]-private-tokenfamilies
*/
async listTokenFamilies() {
const url = new URL(`private/tokenfamilies`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForTokenFamiliesList());
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCES]-private-tokenfamilies-$TOKEN_FAMILY_SLUG
*/
async getTokenFamily(tokenSlug: string) {
const url = new URL(`private/tokenfamilies/${tokenSlug}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForTokenFamilyDetails());
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCES]-private-tokenfamilies-$TOKEN_FAMILY_SLUG
*/
async deleteTokenFamily(tokenSlug: string) {
const url = new URL(`private/tokenfamilies/${tokenSlug}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "DELETE",
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* Get the auth api against the current instance
*
* https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-token
* https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-token
*/
getAuthenticationAPI(): URL {
return new URL(`private/`, this.baseUrl);
}
}
export class TalerMerchantManagementHttpClient extends TalerMerchantInstanceHttpClient {
readonly cacheManagementEvictor: CacheEvictor;
constructor(
readonly baseUrl: string,
httpClient?: HttpRequestLibrary,
cacheManagementEvictor?: CacheEvictor,
cacheEvictor?: CacheEvictor,
) {
super(baseUrl, httpClient, cacheEvictor);
this.cacheManagementEvictor = cacheManagementEvictor ?? nullEvictor;
}
getSubInstanceAPI(instanceId: string) {
return new URL(`instances/${instanceId}`, this.baseUrl);
}
//
// Instance Management
//
/**
* https://docs.taler.net/core/api-merchant.html#post--management-instances
*/
async createInstance(body: TalerMerchantApi.InstanceConfigurationMessage) {
const url = new URL(`management/instances`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
});
switch (resp.status) {
case HttpStatusCode.NoContent: {
this.cacheManagementEvictor.notifySuccess(TalerMerchantManagementCacheEviction.CREATE_INSTANCE)
return opEmptySuccess(resp);
}
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#post--management-instances-$INSTANCE-auth
*/
async updateInstanceAuthentication(
body: TalerMerchantApi.InstanceAuthConfigurationMessage,
) {
const url = new URL(`management/instances`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#patch--management-instances-$INSTANCE
*/
async updateInstance(
instanceId: string,
body: TalerMerchantApi.InstanceReconfigurationMessage,
) {
const url = new URL(`management/instances/${instanceId}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "PATCH",
body,
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get--management-instances
*/
async listInstances() {
const url = new URL(`management/instances`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForInstancesResponse());
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get--management-instances-$INSTANCE
*
*/
async getInstance(instanceId: string) {
const url = new URL(`management/instances/${instanceId}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForQueryInstancesResponse());
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#delete--management-instances-$INSTANCE
*/
async deleteInstance(instanceId: string, params: { purge?: boolean } = {}) {
const url = new URL(`management/instances/${instanceId}`, this.baseUrl);
if (params.purge) {
url.searchParams.set("purge", "YES");
}
const resp = await this.httpLib.fetch(url.href, {
method: "DELETE",
});
switch (resp.status) {
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.Unauthorized:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
/**
* https://docs.taler.net/core/api-merchant.html#get--management-instances-$INSTANCE-kyc
*/
async getIntanceKycStatus(
instanceId: string,
params: TalerMerchantApi.GetKycStatusRequestParams,
) {
const url = new URL(`management/instances/${instanceId}/kyc`, this.baseUrl);
if (params.wireHash) {
url.searchParams.set("h_wire", params.wireHash);
}
if (params.exchangeURL) {
url.searchParams.set("exchange_url", params.exchangeURL);
}
if (params.timeout) {
url.searchParams.set("timeout_ms", String(params.timeout));
}
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
case HttpStatusCode.Accepted:
return opSuccessFromHttp(resp, codecForAccountKycRedirects());
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.BadGateway:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.ServiceUnavailable:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await resp.text());
}
}
}