taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 24c6d46d34de6b42fe6434d0a5ab4f405c86f27d
parent f7a5cd2022eee5991eb5e2504e4d469e5470892e
Author: Sebastian <sebasjm@gmail.com>
Date:   Thu,  1 Aug 2024 12:16:34 -0300

implement merchant v16

Diffstat:
Mpackages/taler-util/src/http-client/merchant.ts | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mpackages/taler-util/src/types-taler-merchant.ts | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
2 files changed, 283 insertions(+), 11 deletions(-)

diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts @@ -32,6 +32,8 @@ import { codecForAccountKycRedirects, codecForAccountsSummaryResponse, codecForBankAccountDetail, + codecForCategoryListResponse, + codecForCategoryProductList, codecForClaimResponse, codecForInstancesResponse, codecForInventorySummaryResponse, @@ -99,6 +101,9 @@ export enum TalerMerchantInstanceCacheEviction { CREATE_PRODUCT, UPDATE_PRODUCT, DELETE_PRODUCT, + CREATE_CATEGORY, + UPDATE_CATEGORY, + DELETE_CATEGORY, CREATE_TRANSFER, DELETE_TRANSFER, CREATE_DEVICE, @@ -132,7 +137,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 = "16:0:0"; readonly httpLib: HttpRequestLibrary; readonly cacheEvictor: CacheEvictor<TalerMerchantInstanceCacheEviction>; @@ -793,6 +798,172 @@ export class TalerMerchantInstanceHttpClient { // /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-categories + */ + async listCategories(token: AccessToken, params?: PaginationParams) { + const url = new URL(`private/categories`, this.baseUrl); + + // addMerchantPaginationParams(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( @@ -952,7 +1123,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, @@ -990,7 +1161,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); diff --git a/packages/taler-util/src/types-taler-merchant.ts b/packages/taler-util/src/types-taler-merchant.ts @@ -1455,6 +1455,64 @@ export interface BankAccountDetail { active?: boolean; } +export interface CategoryListResponse { + // Array with all of the categories we know. + categories: CategoryListEntry[]; +} + +export interface CategoryListEntry { + // Unique number for the category. + category_id: Integer; + + // Name of the category. + name: string; + + // Translations of the name into various + // languages. + name_i18n?: { [lang_tag: string]: string }; + + // Number of products in this category. + // A product can be in more than one category. + product_count: Integer; +} + +export interface CategoryProductList { + // Name of the category. + name: string; + + // Translations of the name into various + // languages. + name_i18n?: { [lang_tag: string]: string }; + + // The products in this category. + products: ProductSummary[]; +} + +export interface ProductSummary { + // Product ID to use. + product_id: string; + + // Human-readable product description. + description: string; + + // Map from IETF BCP 47 language tags to localized descriptions. + description_i18n?: { [lang_tag: string]: string }; +} + +export interface CategoryCreateRequest { + // Name of the category. + name: string; + + // Translations of the name into various + // languages. + name_i18n?: { [lang_tag: string]: string }; +} + +export interface CategoryCreatedResponse { + // Number of the newly created category. + category_id: Integer; +} + export interface ProductAddDetail { // Product ID to use. product_id: string; @@ -1465,6 +1523,11 @@ export interface ProductAddDetail { // Map from IETF BCP 47 language tags to localized descriptions. description_i18n?: { [lang_tag: string]: string }; + // Categories into which the product belongs. + // Used in the POS-endpoint. + // Since API version **v16**. + categories?: Integer[]; + // Unit in which the product is measured (liters, kilograms, packages, etc.). unit: string; @@ -1504,6 +1567,11 @@ export interface ProductPatchDetail { // Map from IETF BCP 47 language tags to localized descriptions. description_i18n?: { [lang_tag: string]: string }; + // Categories into which the product belongs. + // Used in the POS-endpoint. + // Since API version **v16**. + categories?: Integer[]; + // Unit in which the product is measured (liters, kilograms, packages, etc.). unit: string; @@ -1623,6 +1691,10 @@ export interface ProductDetail { // Unit in which the product is measured (liters, kilograms, packages, etc.). unit: string; + // Categories into which the product belongs. + // Since API version **v16**. + categories: Integer[]; + // The price for one unit of the product. Zero is used // to imply that this product is not sold separately, or // that the price is not fixed, and must be supplied by the @@ -2734,14 +2806,15 @@ const codecForExchangeConfigInfo = (): Codec<ExchangeConfigInfo> => .property("master_pub", codecForString()) .build("TalerMerchantApi.ExchangeConfigInfo"); -export const codecForTalerMerchantConfigResponse = (): Codec<TalerMerchantConfigResponse> => - buildCodecForObject<TalerMerchantConfigResponse>() - .property("name", codecForConstString("taler-merchant")) - .property("currency", codecForString()) - .property("version", codecForString()) - .property("currencies", codecForMap(codecForCurrencySpecificiation())) - .property("exchanges", codecForList(codecForExchangeConfigInfo())) - .build("TalerMerchantApi.VersionResponse"); +export const codecForTalerMerchantConfigResponse = + (): Codec<TalerMerchantConfigResponse> => + buildCodecForObject<TalerMerchantConfigResponse>() + .property("name", codecForConstString("taler-merchant")) + .property("currency", codecForString()) + .property("version", codecForString()) + .property("currencies", codecForMap(codecForCurrencySpecificiation())) + .property("exchanges", codecForList(codecForExchangeConfigInfo())) + .build("TalerMerchantApi.VersionResponse"); export const codecForClaimResponse = (): Codec<ClaimResponse> => buildCodecForObject<ClaimResponse>() @@ -2941,6 +3014,33 @@ export const codecForBankAccountDetail = (): Codec<BankAccountDetail> => .property("active", codecOptional(codecForBoolean())) .build("TalerMerchantApi.BankAccountEntry"); +export const codecForCategoryListResponse = (): Codec<CategoryListResponse> => + buildCodecForObject<CategoryListResponse>() + .property("categories", codecForList(codecForCategoryListEntry())) + .build("TalerMerchantApi.CategoryListResponse"); + +export const codecForCategoryListEntry = (): Codec<CategoryListEntry> => + buildCodecForObject<CategoryListEntry>() + .property("category_id", codecForNumber()) + .property("name", codecForString()) + .property("name_i18n", codecForInternationalizedString()) + .property("product_count", codecForNumber()) + .build("TalerMerchantApi.CategoryListEntry"); + +export const codecForCategoryProductList = (): Codec<CategoryProductList> => + buildCodecForObject<CategoryProductList>() + .property("name", codecForString()) + .property("name_i18n", codecForInternationalizedString()) + .property("products", codecForList(codecForProductSummary())) + .build("TalerMerchantApi.CategoryProductList"); + +export const codecForProductSummary = (): Codec<ProductSummary> => + buildCodecForObject<ProductSummary>() + .property("product_id", codecForString()) + .property("description", codecForString()) + .property("description_i18n", codecForInternationalizedString()) + .build("TalerMerchantApi.ProductSummary"); + export const codecForInventorySummaryResponse = (): Codec<InventorySummaryResponse> => buildCodecForObject<InventorySummaryResponse>() @@ -2990,6 +3090,7 @@ export const codecForProductDetail = (): Codec<ProductDetail> => .property("unit", codecForString()) .property("price", codecForAmountString()) .property("image", codecForString()) + .property("categories", codecForList(codecForNumber())) .property("taxes", codecOptional(codecForList(codecForTax()))) .property("address", codecOptional(codecForLocation())) .property("next_restock", codecOptional(codecForTimestamp))