diff options
Diffstat (limited to 'packages/taler-util/src/http-client/merchant.ts')
-rw-r--r-- | packages/taler-util/src/http-client/merchant.ts | 305 |
1 files changed, 286 insertions, 19 deletions
diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts index 10afdc8eb..e6af6dfe8 100644 --- a/packages/taler-util/src/http-client/merchant.ts +++ b/packages/taler-util/src/http-client/merchant.ts @@ -19,19 +19,24 @@ import { FailCasesByMethod, HttpStatusCode, LibtoolVersion, + OperationFail, + OperationOk, PaginationParams, ResultByMethod, + TalerError, + TalerErrorCode, TalerMerchantApi, + TalerMerchantConfigResponse, codecForAbortResponse, codecForAccountAddResponse, codecForAccountKycRedirects, codecForAccountsSummaryResponse, codecForBankAccountDetail, - codecForBankAccountEntry, + codecForCategoryListResponse, + codecForCategoryProductList, codecForClaimResponse, codecForInstancesResponse, codecForInventorySummaryResponse, - codecForMerchantConfig, codecForMerchantOrderPrivateStatusResponse, codecForMerchantPosProductDetail, codecForMerchantRefundResponse, @@ -40,6 +45,7 @@ import { codecForOtpDeviceSummaryResponse, codecForOutOfStockResponse, codecForPaidRefundStatusResponse, + codecForPaymentDeniedLegallyResponse, codecForPaymentResponse, codecForPostOrderResponse, codecForProductDetail, @@ -47,6 +53,8 @@ import { codecForStatusGoto, codecForStatusPaid, codecForStatusStatusUnpaid, + codecForTalerCommonConfigResponse, + codecForTalerMerchantConfigResponse, codecForTansferList, codecForTemplateDetails, codecForTemplateSummaryResponse, @@ -64,12 +72,13 @@ import { HttpRequestLibrary, HttpResponse, createPlatformHttpLib, + readSuccessResponseJsonOrThrow, readTalerErrorResponse, } from "@gnu-taler/taler-util/http"; import { opSuccessFromHttp, opUnknownFailure } from "../operation.js"; import { + addPaginationParams, CacheEvictor, - addMerchantPaginationParams, makeBearerTokenAuthHeader, nullEvictor, } from "./utils.js"; @@ -93,6 +102,9 @@ export enum TalerMerchantInstanceCacheEviction { CREATE_PRODUCT, UPDATE_PRODUCT, DELETE_PRODUCT, + CREATE_CATEGORY, + UPDATE_CATEGORY, + DELETE_CATEGORY, CREATE_TRANSFER, DELETE_TRANSFER, CREATE_DEVICE, @@ -109,11 +121,13 @@ export enum TalerMerchantInstanceCacheEviction { DELETE_TOKENFAMILY, LAST, } + export enum TalerMerchantManagementCacheEviction { CREATE_INSTANCE = TalerMerchantInstanceCacheEviction.LAST + 1, UPDATE_INSTANCE, DELETE_INSTANCE, } + /** * Protocol version spoken with the core bank. * @@ -124,7 +138,7 @@ export enum TalerMerchantManagementCacheEviction { * Uses libtool's current:revision:age versioning. */ export class TalerMerchantInstanceHttpClient { - public readonly PROTOCOL_VERSION = "15:0:0"; + public readonly PROTOCOL_VERSION = "17:0:1"; readonly httpLib: HttpRequestLibrary; readonly cacheEvictor: CacheEvictor<TalerMerchantInstanceCacheEviction>; @@ -145,17 +159,48 @@ export class TalerMerchantInstanceHttpClient { /** * https://docs.taler.net/core/api-merchant.html#get--config - * */ - async getConfig() { + async getConfig(): Promise< + | OperationFail<HttpStatusCode.NotFound> + | OperationOk<TalerMerchantConfigResponse> + > { 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.Ok: { + const minBody = await readSuccessResponseJsonOrThrow( + resp, + codecForTalerCommonConfigResponse(), + ); + const expectedName = "taler-merchant"; + if (minBody.name !== expectedName) { + throw TalerError.fromUncheckedDetail({ + code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, + requestUrl: resp.requestUrl, + httpStatusCode: resp.status, + detail: `Unexpected server component name (got ${minBody.name}, expected ${expectedName}})`, + }); + } + if (!this.isCompatible(minBody.version)) { + throw TalerError.fromUncheckedDetail({ + code: TalerErrorCode.GENERIC_CLIENT_UNSUPPORTED_PROTOCOL_VERSION, + requestUrl: resp.requestUrl, + httpStatusCode: resp.status, + detail: `Unsupported protocol version, client supports ${this.PROTOCOL_VERSION}, server supports ${minBody.version}`, + }); + } + // Now that we've checked the basic body, re-parse the full response. + const body = await readSuccessResponseJsonOrThrow( + resp, + codecForTalerMerchantConfigResponse(), + ); + return { + type: "ok", + body, + }; + } case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: @@ -232,6 +277,12 @@ export class TalerMerchantInstanceHttpClient { return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.GatewayTimeout: return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.UnavailableForLegalReasons: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForPaymentDeniedLegallyResponse(), + ); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } @@ -387,6 +438,12 @@ export class TalerMerchantInstanceHttpClient { return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.UnavailableForLegalReasons: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForPaymentDeniedLegallyResponse(), + ); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } @@ -467,7 +524,7 @@ export class TalerMerchantInstanceHttpClient { * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private * */ - async getCurrentInstanceDetails(token: AccessToken) { + async getCurrentInstanceDetails(token: AccessToken | undefined) { const url = new URL(`private`, this.baseUrl); const headers: Record<string, string> = {}; @@ -560,6 +617,8 @@ export class TalerMerchantInstanceHttpClient { }); switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForAccountKycRedirects()); case HttpStatusCode.Accepted: return opSuccessFromHttp(resp, codecForAccountKycRedirects()); case HttpStatusCode.NoContent: @@ -662,10 +721,13 @@ export class TalerMerchantInstanceHttpClient { /** * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-accounts */ - async listBankAccounts(token: AccessToken, params?: PaginationParams) { + async listBankAccounts( + token: AccessToken | undefined, + params?: PaginationParams, + ) { const url = new URL(`private/accounts`, this.baseUrl); - // addMerchantPaginationParams(url, params); + // addPaginationParams(url, params); const headers: Record<string, string> = {}; if (token) { @@ -754,6 +816,173 @@ export class TalerMerchantInstanceHttpClient { // /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-categories + */ + async listCategories( + token: AccessToken | undefined, + params?: PaginationParams, + ) { + const url = new URL(`private/categories`, this.baseUrl); + + // addPaginationParams(url, params); + + const headers: Record<string, string> = {}; + 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, codecForCategoryListResponse()); + case HttpStatusCode.NotFound: + 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-categories-$CATEGORY_ID + */ + async getCategoryDetails(token: AccessToken | undefined, cId: string) { + const url = new URL(`private/categories/${cId}`, this.baseUrl); + + const headers: Record<string, string> = {}; + 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, codecForCategoryProductList()); + 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#post-[-instances-$INSTANCE]-private-categories + */ + async addCategory( + token: AccessToken | undefined, + body: TalerMerchantApi.CategoryCreateRequest, + ) { + const url = new URL(`private/categories`, this.baseUrl); + + const headers: Record<string, string> = {}; + 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_CATEGORY, + ); + 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#post-[-instances-$INSTANCE]-private-categories + */ + async updateCategory( + token: AccessToken | undefined, + cid: string, + body: TalerMerchantApi.CategoryCreateRequest, + ) { + const url = new URL(`private/categories/${cid}`, this.baseUrl); + + const headers: Record<string, string> = {}; + 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_CATEGORY, + ); + 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#delete-[-instances-$INSTANCE]-private-categories-$CATEGORY_ID + */ + async deleteCategory(token: AccessToken | undefined, cId: string) { + const url = new URL(`private/categories/${cId}`, this.baseUrl); + + const headers: Record<string, string> = {}; + 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_CATEGORY, + ); + 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#post-[-instances-$INSTANCE]-private-products */ async addProduct( @@ -837,7 +1066,7 @@ export class TalerMerchantInstanceHttpClient { ) { const url = new URL(`private/products`, this.baseUrl); - addMerchantPaginationParams(url, params); + addPaginationParams(url, params); const headers: Record<string, string> = {}; if (token) { @@ -913,7 +1142,7 @@ export class TalerMerchantInstanceHttpClient { } /** - * https://docs.taler.net/core/api-merchant.html#reserving-inventory + * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-products-$PRODUCT_ID-lock */ async lockProduct( token: AccessToken | undefined, @@ -951,7 +1180,7 @@ export class TalerMerchantInstanceHttpClient { } /** - * https://docs.taler.net/core/api-merchant.html#removing-products-from-inventory + * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-products-$PRODUCT_ID */ async deleteProduct(token: AccessToken | undefined, productId: string) { const url = new URL(`private/products/${productId}`, this.baseUrl); @@ -1020,6 +1249,12 @@ export class TalerMerchantInstanceHttpClient { return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.UnavailableForLegalReasons: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForPaymentDeniedLegallyResponse(), + ); case HttpStatusCode.Conflict: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Gone: @@ -1063,7 +1298,7 @@ export class TalerMerchantInstanceHttpClient { if (params.wired !== undefined) { url.searchParams.set("wired", params.wired ? "YES" : "NO"); } - addMerchantPaginationParams(url, params); + addPaginationParams(url, params); const headers: Record<string, string> = {}; if (token) { @@ -1257,6 +1492,12 @@ export class TalerMerchantInstanceHttpClient { return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Conflict: return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.UnavailableForLegalReasons: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForPaymentDeniedLegallyResponse(), + ); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } @@ -1324,7 +1565,7 @@ export class TalerMerchantInstanceHttpClient { if (params.verified !== undefined) { url.searchParams.set("verified", params.verified ? "YES" : "NO"); } - addMerchantPaginationParams(url, params); + addPaginationParams(url, params); const headers: Record<string, string> = {}; if (token) { @@ -1465,7 +1706,7 @@ export class TalerMerchantInstanceHttpClient { ) { const url = new URL(`private/otp-devices`, this.baseUrl); - addMerchantPaginationParams(url, params); + addPaginationParams(url, params); const headers: Record<string, string> = {}; if (token) { @@ -1638,7 +1879,7 @@ export class TalerMerchantInstanceHttpClient { ) { const url = new URL(`private/templates`, this.baseUrl); - addMerchantPaginationParams(url, params); + addPaginationParams(url, params); const headers: Record<string, string> = {}; if (token) { @@ -2370,3 +2611,29 @@ export class TalerMerchantManagementHttpClient extends TalerMerchantInstanceHttp } } } + +// 2024-09-23T01:23:14.421Z http.ts INFO malformed error response (status 200): { +// "kyc_data": [ +// { +// "payto_uri": "payto://iban/DE1327812254798?receiver-name=the%20name%20of%20merchant", +// "exchange_url": "http://exchange.taler.test:1180/", +// "no_keys": false, +// "auth_conflict": false, +// "exchange_http_status": 204, +// "limits": [], +// "payto_kycauths": [ +// "payto://iban/DE9714548806481?receiver-name=Exchanger+Normal&subject=54DR9R0CEWA1A7FK3QWABJ1PRBCD2X6S418Y5DE0P9Q1ASKTX770" +// ] +// }, +// { +// "payto_uri": "payto://iban/DE1327812254798?receiver-name=the%20name%20of%20merchant", +// "exchange_url": "https://exchange.demo.taler.net/", +// "no_keys": false, +// "auth_conflict": false, +// "exchange_http_status": 400, +// "exchange_code": 26, +// "access_token": "0000000000000000000000000000000000000000000000000000", +// "limits": [] +// } +// ] +// } |