/* 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 { AccessToken, FailCasesByMethod, HttpStatusCode, LibtoolVersion, PaginationParams, ResultByMethod, 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, readTalerErrorResponse, } from "@gnu-taler/taler-util/http"; import { opSuccessFromHttp, opUnknownFailure } from "../operation.js"; import { CacheEvictor, addMerchantPaginationParams, makeBearerTokenAuthHeader, nullEvictor, } from "./utils.js"; export type TalerMerchantInstanceResultByMethod< prop extends keyof TalerMerchantInstanceHttpClient, > = ResultByMethod; export type TalerMerchantInstanceErrorsByMethod< prop extends keyof TalerMerchantInstanceHttpClient, > = FailCasesByMethod; export enum TalerMerchantInstanceCacheEviction { CREATE_ORDER, UPDATE_ORDER, DELETE_ORDER, UPDATE_CURRENT_INSTANCE, DELETE_CURRENT_INSTANCE, CREATE_BANK_ACCOUNT, UPDATE_BANK_ACCOUNT, DELETE_BANK_ACCOUNT, CREATE_PRODUCT, UPDATE_PRODUCT, DELETE_PRODUCT, CREATE_TRANSFER, DELETE_TRANSFER, CREATE_DEVICE, UPDATE_DEVICE, DELETE_DEVICE, CREATE_TEMPLATE, UPDATE_TEMPLATE, DELETE_TEMPLATE, CREATE_WEBHOOK, UPDATE_WEBHOOK, DELETE_WEBHOOK, CREATE_TOKENFAMILY, UPDATE_TOKENFAMILY, DELETE_TOKENFAMILY, LAST, } export enum TalerMerchantManagementCacheEviction { CREATE_INSTANCE = TalerMerchantInstanceCacheEviction.LAST + 1, UPDATE_INSTANCE, DELETE_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()); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } // // 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: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.UPDATE_ORDER, ); 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 readTalerErrorResponse(resp)); } } /** * 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: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.UPDATE_ORDER, ); 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 readTalerErrorResponse(resp)); } } /** * 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 readTalerErrorResponse(resp)); } } /** * 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: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.UPDATE_ORDER, ); 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 readTalerErrorResponse(resp)); } } /** * 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: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.UPDATE_ORDER, ); 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 readTalerErrorResponse(resp)); } } /** * 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: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.UPDATE_ORDER, ); 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 readTalerErrorResponse(resp)); } } // // Management // /** * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-auth */ async updateCurrentInstanceAuthentication( token: AccessToken | undefined, body: TalerMerchantApi.InstanceAuthConfigurationMessage, ) { const url = new URL(`private/auth`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "POST", body, headers, }); switch (resp.status) { case HttpStatusCode.Ok: // FIXME: missing in docs return opEmptySuccess(resp); case HttpStatusCode.NoContent: return opEmptySuccess(resp); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCE]-private */ async updateCurrentInstance( token: AccessToken | undefined, body: TalerMerchantApi.InstanceReconfigurationMessage, ) { const url = new URL(`private`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "PATCH", body, headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.UPDATE_CURRENT_INSTANCE, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private * */ async getCurrentInstanceDetails(token: AccessToken) { const url = new URL(`private`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForQueryInstancesResponse()); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private */ async deleteCurrentInstance( token: AccessToken | undefined, params: { purge?: boolean } = {}, ) { const url = new URL(`private`, this.baseUrl); if (params.purge !== undefined) { url.searchParams.set("purge", params.purge ? "YES" : "NO"); } const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "DELETE", headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.DELETE_CURRENT_INSTANCE, ); 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 readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#get--instances-$INSTANCE-private-kyc */ async getCurrentIntanceKycStatus( token: AccessToken | undefined, 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 headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.Accepted: return opSuccessFromHttp(resp, codecForAccountKycRedirects()); case HttpStatusCode.NoContent: return opEmptySuccess(resp); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.BadGateway: return opKnownAlternativeFailure( resp, resp.status, codecForAccountKycRedirects(), ); case HttpStatusCode.ServiceUnavailable: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.GatewayTimeout: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } // // Bank Accounts // /** * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-accounts */ async addBankAccount( token: AccessToken | undefined, body: TalerMerchantApi.AccountAddDetails, ) { const url = new URL(`private/accounts`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "POST", body, headers, }); switch (resp.status) { case HttpStatusCode.Ok: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.CREATE_BANK_ACCOUNT, ); return opSuccessFromHttp(resp, codecForAccountAddResponse()); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs 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 readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCE]-private-accounts-$H_WIRE */ async updateBankAccount( token: AccessToken | undefined, wireAccount: string, body: TalerMerchantApi.AccountPatchDetails, ) { const url = new URL(`private/accounts/${wireAccount}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "PATCH", body, headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.UPDATE_BANK_ACCOUNT, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-accounts */ async listBankAccounts(token: AccessToken, params?: PaginationParams) { const url = new URL(`private/accounts`, this.baseUrl); // addMerchantPaginationParams(url, params); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForAccountsSummaryResponse()); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-accounts-$H_WIRE */ async getBankAccountDetails( token: AccessToken | undefined, wireAccount: string, ) { const url = new URL(`private/accounts/${wireAccount}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForBankAccountEntry()); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-accounts-$H_WIRE */ async deleteBankAccount(token: AccessToken | undefined, wireAccount: string) { const url = new URL(`private/accounts/${wireAccount}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "DELETE", headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.DELETE_BANK_ACCOUNT, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } // // Inventory Management // /** * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-products */ async addProduct( token: AccessToken | undefined, body: TalerMerchantApi.ProductAddDetail, ) { const url = new URL(`private/products`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "POST", body, headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.CREATE_PRODUCT, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Conflict: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCE]-private-products-$PRODUCT_ID */ async updateProduct( token: AccessToken | undefined, productId: string, body: TalerMerchantApi.ProductPatchDetail, ) { const url = new URL(`private/products/${productId}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "PATCH", body, headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.UPDATE_PRODUCT, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs 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 readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-products */ async listProducts( token: AccessToken | undefined, params?: PaginationParams, ) { const url = new URL(`private/products`, this.baseUrl); addMerchantPaginationParams(url, params); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForInventorySummaryResponse()); case HttpStatusCode.Unauthorized: // FIXME: not in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-products-$PRODUCT_ID */ async getProductDetails(token: AccessToken | undefined, productId: string) { const url = new URL(`private/products/${productId}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForProductDetail()); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#reserving-inventory */ async lockProduct( token: AccessToken | undefined, productId: string, body: TalerMerchantApi.LockRequest, ) { const url = new URL(`private/products/${productId}/lock`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "POST", body, headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.UPDATE_PRODUCT, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Gone: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#removing-products-from-inventory */ async deleteProduct(token: AccessToken | undefined, productId: string) { const url = new URL(`private/products/${productId}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "DELETE", headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.DELETE_PRODUCT, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs 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 readTalerErrorResponse(resp)); } } // // Payment processing // /** * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-orders */ async createOrder( token: AccessToken | undefined, body: TalerMerchantApi.PostOrderRequest, ) { const url = new URL(`private/orders`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "POST", body, headers, }); 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.Unauthorized: // FIXME: missing in docs 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 readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#inspecting-orders */ async listOrders( token: AccessToken | undefined, 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 !== undefined) { url.searchParams.set("paid", params.paid ? "YES" : "NO"); } if (params.refunded !== undefined) { url.searchParams.set("refunded", params.refunded ? "YES" : "NO"); } if (params.sessionId) { url.searchParams.set("session_id", params.sessionId); } if (params.timeout) { url.searchParams.set("timeout", String(params.timeout)); } if (params.wired !== undefined) { url.searchParams.set("wired", params.wired ? "YES" : "NO"); } addMerchantPaginationParams(url, params); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForOrderHistory()); case HttpStatusCode.NotFound: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-orders-$ORDER_ID */ async getOrderDetails( token: AccessToken | undefined, orderId: string, params: TalerMerchantApi.GetOrderRequestParams = {}, ) { const url = new URL(`private/orders/${orderId}`, this.baseUrl); if (params.allowRefundedForRepurchase !== undefined) { url.searchParams.set( "allow_refunded_for_repurchase", params.allowRefundedForRepurchase ? "YES" : "NO", ); } if (params.sessionId) { url.searchParams.set("session_id", params.sessionId); } if (params.timeout) { url.searchParams.set("timeout_ms", String(params.timeout)); } const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp( resp, codecForMerchantOrderPrivateStatusResponse(), ); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Unauthorized: // FIXME: missing in docs 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 readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#private-order-data-cleanup */ async forgetOrder( token: AccessToken | undefined, orderId: string, body: TalerMerchantApi.ForgetRequest, ) { const url = new URL(`private/orders/${orderId}/forget`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "PATCH", body, headers, }); switch (resp.status) { case HttpStatusCode.Ok: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.UPDATE_ORDER, ); return opEmptySuccess(resp); } case HttpStatusCode.NoContent: return opEmptySuccess(resp); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, 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 readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-orders-$ORDER_ID */ async deleteOrder(token: AccessToken | undefined, orderId: string) { const url = new URL(`private/orders/${orderId}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "DELETE", headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.DELETE_ORDER, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs 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 readTalerErrorResponse(resp)); } } // // Refunds // /** * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-orders-$ORDER_ID-refund */ async addRefund( token: AccessToken | undefined, orderId: string, body: TalerMerchantApi.RefundRequest, ) { const url = new URL(`private/orders/${orderId}/refund`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "POST", body, headers, }); switch (resp.status) { case HttpStatusCode.Ok: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.UPDATE_ORDER, ); return opSuccessFromHttp(resp, codecForMerchantRefundResponse()); } case HttpStatusCode.Forbidden: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Unauthorized: // FIXME: missing in docs 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 readTalerErrorResponse(resp)); } } // // Wire Transfer // /** * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-transfers */ async informWireTransfer( token: AccessToken | undefined, body: TalerMerchantApi.TransferInformation, ) { const url = new URL(`private/transfers`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "POST", body, headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.CREATE_TRANSFER, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs 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 readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-transfers */ async listWireTransfers( token: AccessToken | undefined, 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 !== undefined) { url.searchParams.set("verified", params.verified ? "YES" : "NO"); } addMerchantPaginationParams(url, params); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForTansferList()); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-transfers-$TID */ async deleteWireTransfer(token: AccessToken | undefined, transferId: string) { const url = new URL(`private/transfers/${transferId}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "DELETE", headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.DELETE_TRANSFER, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs 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 readTalerErrorResponse(resp)); } } // // OTP Devices // /** * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-otp-devices */ async addOtpDevice( token: AccessToken | undefined, body: TalerMerchantApi.OtpDeviceAddDetails, ) { const url = new URL(`private/otp-devices`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "POST", body, headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.CREATE_DEVICE, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCE]-private-otp-devices-$DEVICE_ID */ async updateOtpDevice( token: AccessToken | undefined, deviceId: string, body: TalerMerchantApi.OtpDevicePatchDetails, ) { const url = new URL(`private/otp-devices/${deviceId}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "PATCH", body, headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.UPDATE_DEVICE, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs 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 readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-otp-devices */ async listOtpDevices( token: AccessToken | undefined, params?: PaginationParams, ) { const url = new URL(`private/otp-devices`, this.baseUrl); addMerchantPaginationParams(url, params); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForOtpDeviceSummaryResponse()); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-otp-devices-$DEVICE_ID */ async getOtpDeviceDetails( token: AccessToken | undefined, 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 headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForOtpDeviceDetails()); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-otp-devices-$DEVICE_ID */ async deleteOtpDevice(token: AccessToken | undefined, deviceId: string) { const url = new URL(`private/otp-devices/${deviceId}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "DELETE", headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.DELETE_DEVICE, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } // // Templates // /** * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-templates */ async addTemplate( token: AccessToken | undefined, body: TalerMerchantApi.TemplateAddDetails, ) { const url = new URL(`private/templates`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "POST", body, headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.CREATE_TEMPLATE, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCE]-private-templates-$TEMPLATE_ID */ async updateTemplate( token: AccessToken | undefined, templateId: string, body: TalerMerchantApi.TemplatePatchDetails, ) { const url = new URL(`private/templates/${templateId}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "PATCH", body, headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.UPDATE_TEMPLATE, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs 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 readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#inspecting-template */ async listTemplates( token: AccessToken | undefined, params?: PaginationParams, ) { const url = new URL(`private/templates`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForTemplateSummaryResponse()); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-templates-$TEMPLATE_ID */ async getTemplateDetails(token: AccessToken | undefined, templateId: string) { const url = new URL(`private/templates/${templateId}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForTemplateDetails()); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-templates-$TEMPLATE_ID */ async deleteTemplate(token: AccessToken | undefined, templateId: string) { const url = new URL(`private/templates/${templateId}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "DELETE", headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.DELETE_TEMPLATE, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * 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 readTalerErrorResponse(resp)); } } /** * 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( token: AccessToken | undefined, body: TalerMerchantApi.WebhookAddDetails, ) { const url = new URL(`private/webhooks`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "POST", body, headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.CREATE_WEBHOOK, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCES]-private-webhooks-$WEBHOOK_ID */ async updateWebhook( token: AccessToken | undefined, webhookId: string, body: TalerMerchantApi.WebhookPatchDetails, ) { const url = new URL(`private/webhooks/${webhookId}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "PATCH", body, headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.UPDATE_WEBHOOK, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs 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 readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCES]-private-webhooks */ async listWebhooks( token: AccessToken | undefined, params?: PaginationParams, ) { const url = new URL(`private/webhooks`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForWebhookSummaryResponse()); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCES]-private-webhooks-$WEBHOOK_ID */ async getWebhookDetails(token: AccessToken | undefined, webhookId: string) { const url = new URL(`private/webhooks/${webhookId}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.NoContent: return opSuccessFromHttp(resp, codecForWebhookDetails()); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCES]-private-webhooks-$WEBHOOK_ID */ async deleteWebhook(token: AccessToken | undefined, webhookId: string) { const url = new URL(`private/webhooks/${webhookId}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "DELETE", headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.DELETE_WEBHOOK, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } // // token families // /** * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCES]-private-tokenfamilies */ async createTokenFamily( token: AccessToken | undefined, body: TalerMerchantApi.TokenFamilyCreateRequest, ) { const url = new URL(`private/tokenfamilies`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "POST", body, headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.CREATE_TOKENFAMILY, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCES]-private-tokenfamilies-$TOKEN_FAMILY_SLUG */ async updateTokenFamily( token: AccessToken | undefined, tokenSlug: string, body: TalerMerchantApi.TokenFamilyUpdateRequest, ) { const url = new URL(`private/tokenfamilies/${tokenSlug}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "POST", body, headers, }); switch (resp.status) { case HttpStatusCode.Ok: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.UPDATE_TOKENFAMILY, ); return opSuccessFromHttp(resp, codecForTokenFamilyDetails()); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCES]-private-tokenfamilies */ async listTokenFamilies( token: AccessToken | undefined, params?: PaginationParams, ) { const url = new URL(`private/tokenfamilies`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForTokenFamiliesList()); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCES]-private-tokenfamilies-$TOKEN_FAMILY_SLUG */ async getTokenFamilyDetails( token: AccessToken | undefined, tokenSlug: string, ) { const url = new URL(`private/tokenfamilies/${tokenSlug}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForTokenFamilyDetails()); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCES]-private-tokenfamilies-$TOKEN_FAMILY_SLUG */ async deleteTokenFamily(token: AccessToken | undefined, tokenSlug: string) { const url = new URL(`private/tokenfamilies/${tokenSlug}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "DELETE", headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.DELETE_TOKENFAMILY, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * 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 type TalerMerchantManagementResultByMethod< prop extends keyof TalerMerchantManagementHttpClient, > = ResultByMethod; export type TalerMerchantManagementErrorsByMethod< prop extends keyof TalerMerchantManagementHttpClient, > = FailCasesByMethod; export class TalerMerchantManagementHttpClient extends TalerMerchantInstanceHttpClient { readonly cacheManagementEvictor: CacheEvictor< TalerMerchantInstanceCacheEviction | TalerMerchantManagementCacheEviction >; constructor( readonly baseUrl: string, httpClient?: HttpRequestLibrary, // cacheManagementEvictor?: CacheEvictor, cacheEvictor?: CacheEvictor< TalerMerchantInstanceCacheEviction | TalerMerchantManagementCacheEviction >, ) { super(baseUrl, httpClient, cacheEvictor); this.cacheManagementEvictor = cacheEvictor ?? 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( token: AccessToken | undefined, body: TalerMerchantApi.InstanceConfigurationMessage, ) { const url = new URL(`management/instances`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "POST", body, headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheManagementEvictor.notifySuccess( TalerMerchantManagementCacheEviction.CREATE_INSTANCE, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Conflict: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#post--management-instances-$INSTANCE-auth */ async updateInstanceAuthentication( token: AccessToken | undefined, instanceId: string, body: TalerMerchantApi.InstanceAuthConfigurationMessage, ) { const url = new URL( `management/instances/${instanceId}/auth`, this.baseUrl, ); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "POST", body, headers, }); switch (resp.status) { case HttpStatusCode.NoContent: return opEmptySuccess(resp); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#patch--management-instances-$INSTANCE */ async updateInstance( token: AccessToken | undefined, instanceId: string, body: TalerMerchantApi.InstanceReconfigurationMessage, ) { const url = new URL(`management/instances/${instanceId}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "PATCH", body, headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheManagementEvictor.notifySuccess( TalerMerchantManagementCacheEviction.UPDATE_INSTANCE, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#get--management-instances */ async listInstances( token: AccessToken | undefined, params?: PaginationParams, ) { const url = new URL(`management/instances`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForInstancesResponse()); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#get--management-instances-$INSTANCE * */ async getInstanceDetails(token: AccessToken | undefined, instanceId: string) { const url = new URL(`management/instances/${instanceId}`, this.baseUrl); const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForQueryInstancesResponse()); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#delete--management-instances-$INSTANCE */ async deleteInstance( token: AccessToken | undefined, instanceId: string, params: { purge?: boolean } = {}, ) { const url = new URL(`management/instances/${instanceId}`, this.baseUrl); if (params.purge !== undefined) { url.searchParams.set("purge", params.purge ? "YES" : "NO"); } const headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "DELETE", headers, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheManagementEvictor.notifySuccess( TalerMerchantManagementCacheEviction.DELETE_INSTANCE, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: // FIXME: missing in docs 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 readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-merchant.html#get--management-instances-$INSTANCE-kyc */ async getIntanceKycStatus( token: AccessToken | undefined, 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 headers: Record = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers, }); switch (resp.status) { case HttpStatusCode.Accepted: return opSuccessFromHttp(resp, codecForAccountKycRedirects()); case HttpStatusCode.NoContent: return opEmptySuccess(resp); case HttpStatusCode.NotFound: return opEmptySuccess(resp); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, 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 readTalerErrorResponse(resp)); } } }