/* 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()); } } }