diff options
Diffstat (limited to 'packages/taler-util/src/http-client')
-rw-r--r-- | packages/taler-util/src/http-client/authentication.ts | 33 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/bank-core.ts | 46 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/bank-integration.ts | 2 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/bank-revenue.ts | 32 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/bank-wire.ts | 108 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/challenger.ts | 2 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/exchange.ts | 780 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/merchant.ts | 71 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/officer-account.ts | 35 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/utils.ts | 70 |
10 files changed, 883 insertions, 296 deletions
diff --git a/packages/taler-util/src/http-client/authentication.ts b/packages/taler-util/src/http-client/authentication.ts index fa48f3da6..7e3297ff0 100644 --- a/packages/taler-util/src/http-client/authentication.ts +++ b/packages/taler-util/src/http-client/authentication.ts @@ -88,9 +88,37 @@ export class TalerAuthenticationHttpClient { } /** - * + * FIXME: merge this with createAccessTokenBearer the protocol of both + * services need to reply the same + * * @returns */ + async createAccessTokenBearer_BANK(token: AccessToken, body: TokenRequest) { + const url = new URL(`token`, this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + headers: { + Authorization: makeBearerTokenAuthHeader(token), + }, + body, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForTokenSuccessResponse()); + //FIXME: missing in docs + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * + * @returns + */ async createAccessTokenBearer(token: AccessToken, body: TokenRequest) { const url = new URL(`token`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { @@ -125,6 +153,9 @@ export class TalerAuthenticationHttpClient { case HttpStatusCode.Ok: return opEmptySuccess(resp); //FIXME: missing in docs + case HttpStatusCode.NoContent: + return opEmptySuccess(resp); + //FIXME: missing in docs case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: diff --git a/packages/taler-util/src/http-client/bank-core.ts b/packages/taler-util/src/http-client/bank-core.ts index 2639018a8..2fef0adb6 100644 --- a/packages/taler-util/src/http-client/bank-core.ts +++ b/packages/taler-util/src/http-client/bank-core.ts @@ -68,7 +68,6 @@ import { } from "../types-taler-corebank.js"; import { CacheEvictor, - IdempotencyRetry, addLongPollingParam, addPaginationParams, makeBearerTokenAuthHeader, @@ -129,7 +128,8 @@ export class TalerCoreBankHttpClient { * */ async getConfig(): Promise< - OperationFail<HttpStatusCode.NotFound> | OperationOk<TalerCorebankApi.TalerCorebankConfigResponse> + | OperationFail<HttpStatusCode.NotFound> + | OperationOk<TalerCorebankApi.TalerCorebankConfigResponse> > { const url = new URL(`config`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { @@ -230,6 +230,10 @@ export class TalerCoreBankHttpClient { return opKnownTalerFailure(details.code, details); case TalerErrorCode.BANK_MISSING_TAN_INFO: return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_PASSWORD_TOO_SHORT: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_PASSWORD_TOO_LONG: + return opKnownTalerFailure(details.code, details); default: return opUnknownFailure(resp, details); } @@ -326,6 +330,10 @@ export class TalerCoreBankHttpClient { return opKnownTalerFailure(details.code, details); case TalerErrorCode.BANK_MISSING_TAN_INFO: return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_PASSWORD_TOO_SHORT: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_PASSWORD_TOO_LONG: + return opKnownTalerFailure(details.code, details); default: return opUnknownFailure(resp, details); } @@ -538,7 +546,6 @@ export class TalerCoreBankHttpClient { async createTransaction( auth: UserAndToken, body: TalerCorebankApi.CreateTransactionRequest, - idempotencyCheck: IdempotencyRetry | undefined, cid?: string, ): Promise< //manually definition all return types because of recursion @@ -554,9 +561,6 @@ export class TalerCoreBankHttpClient { | OperationFail<TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED> > { const url = new URL(`accounts/${auth.username}/transactions`, this.baseUrl); - if (idempotencyCheck) { - body.request_uid = idempotencyCheck.uid; - } const resp = await this.httpLib.fetch(url.href, { method: "POST", headers: { @@ -592,11 +596,7 @@ export class TalerCoreBankHttpClient { case TalerErrorCode.BANK_UNALLOWED_DEBIT: return opKnownTalerFailure(details.code, details); case TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED: - if (!idempotencyCheck) { - return opKnownTalerFailure(details.code, details); - } - const nextRetry = idempotencyCheck.next(); - return this.createTransaction(auth, body, nextRetry, cid); + return opKnownTalerFailure(details.code, details); default: return opUnknownFailure(resp, details); } @@ -648,7 +648,12 @@ export class TalerCoreBankHttpClient { * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-withdrawals-$WITHDRAWAL_ID-confirm * */ - async confirmWithdrawalById(auth: UserAndToken, wid: string, cid?: string) { + async confirmWithdrawalById( + auth: UserAndToken, + body: TalerCorebankApi.BankAccountConfirmWithdrawalRequest, + wid: string, + cid?: string, + ) { const url = new URL( `accounts/${auth.username}/withdrawals/${wid}/confirm`, this.baseUrl, @@ -659,6 +664,7 @@ export class TalerCoreBankHttpClient { Authorization: makeBearerTokenAuthHeader(auth.token), "X-Challenge-Id": cid, }, + body, }); switch (resp.status) { case HttpStatusCode.Accepted: @@ -683,6 +689,10 @@ export class TalerCoreBankHttpClient { return opKnownTalerFailure(details.code, details); case TalerErrorCode.BANK_UNALLOWED_DEBIT: return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_AMOUNT_DIFFERS: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_AMOUNT_REQUIRED: + return opKnownTalerFailure(details.code, details); default: return opUnknownFailure(resp, details); } @@ -794,10 +804,10 @@ export class TalerCoreBankHttpClient { switch (details.code) { case TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED: return opKnownTalerFailure(details.code, details); - case TalerErrorCode.BANK_CONVERSION_AMOUNT_TO_SMALL: - return opKnownTalerFailure(details.code, details); case TalerErrorCode.BANK_BAD_CONVERSION: return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_CONVERSION_AMOUNT_TO_SMALL: + return opKnownTalerFailure(details.code, details); case TalerErrorCode.BANK_UNALLOWED_DEBIT: return opKnownTalerFailure(details.code, details); case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: @@ -816,7 +826,13 @@ export class TalerCoreBankHttpClient { } } case HttpStatusCode.NotImplemented: - return opKnownHttpFailure(resp.status, resp); + const details = await readTalerErrorResponse(resp); + switch (details.code) { + case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED: + return opKnownTalerFailure(details.code, details); + default: + return opKnownHttpFailure(resp.status, resp); + } default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } diff --git a/packages/taler-util/src/http-client/bank-integration.ts b/packages/taler-util/src/http-client/bank-integration.ts index 8e98bb783..9bcdac683 100644 --- a/packages/taler-util/src/http-client/bank-integration.ts +++ b/packages/taler-util/src/http-client/bank-integration.ts @@ -155,7 +155,7 @@ export class TalerBankIntegrationHttpClient { return opKnownTalerFailure(details.code, details); case TalerErrorCode.BANK_AMOUNT_DIFFERS: return opKnownTalerFailure(details.code, details); - case TalerErrorCode.BANK_AMOUNT_REQUIRED: + case TalerErrorCode.BANK_UNALLOWED_DEBIT: return opKnownTalerFailure(details.code, details); default: return opUnknownFailure(resp, details); diff --git a/packages/taler-util/src/http-client/bank-revenue.ts b/packages/taler-util/src/http-client/bank-revenue.ts index 6acff91f6..dfa007fa1 100644 --- a/packages/taler-util/src/http-client/bank-revenue.ts +++ b/packages/taler-util/src/http-client/bank-revenue.ts @@ -30,12 +30,21 @@ import { opSuccessFromHttp, opUnknownFailure, } from "../operation.js"; -import { LongPollParams, PaginationParams } from "../types-taler-common.js"; +import { + AccessToken, + LongPollParams, + PaginationParams, +} from "../types-taler-common.js"; import { codecForRevenueConfig, codecForRevenueIncomingHistory, } from "../types-taler-revenue.js"; -import { addLongPollingParam, addPaginationParams } from "./utils.js"; +import { + addLongPollingParam, + addPaginationParams, + BasicOrTokenAuth, + createAuthorizationHeader, +} from "./utils.js"; export type TalerBankRevenueResultByMethod< prop extends keyof TalerRevenueHttpClient, @@ -44,10 +53,7 @@ export type TalerBankRevenueErrorsByMethod< prop extends keyof TalerRevenueHttpClient, > = FailCasesByMethod<TalerRevenueHttpClient, prop>; -type UsernameAndPassword = { - username: string; - password: string; -}; + /** * The API is used by the merchant (or other parties) to query * for incoming transactions to their account. @@ -62,7 +68,7 @@ export class TalerRevenueHttpClient { this.httpLib = httpClient ?? createPlatformHttpLib(); } - public readonly PROTOCOL_VERSION = "0:0:0"; + public readonly PROTOCOL_VERSION = "1:0:0"; isCompatible(version: string): boolean { const compare = LibtoolVersion.compare(this.PROTOCOL_VERSION, version); @@ -73,14 +79,12 @@ export class TalerRevenueHttpClient { * https://docs.taler.net/core/api-bank-revenue.html#get--config * */ - async getConfig(auth?: UsernameAndPassword) { + async getConfig(auth?: BasicOrTokenAuth) { const url = new URL(`config`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { method: "GET", headers: { - Authorization: auth - ? makeBasicAuthHeader(auth.username, auth.password) - : undefined, + Authorization: createAuthorizationHeader(auth), }, }); switch (resp.status) { @@ -100,7 +104,7 @@ export class TalerRevenueHttpClient { * @returns */ async getHistory( - auth?: UsernameAndPassword, + auth?: BasicOrTokenAuth, params?: PaginationParams & LongPollParams, ) { const url = new URL(`history`, this.baseUrl); @@ -109,9 +113,7 @@ export class TalerRevenueHttpClient { const resp = await this.httpLib.fetch(url.href, { method: "GET", headers: { - Authorization: auth - ? makeBasicAuthHeader(auth.username, auth.password) - : undefined, + Authorization: createAuthorizationHeader(auth), }, }); switch (resp.status) { diff --git a/packages/taler-util/src/http-client/bank-wire.ts b/packages/taler-util/src/http-client/bank-wire.ts index 84df50208..c002bd125 100644 --- a/packages/taler-util/src/http-client/bank-wire.ts +++ b/packages/taler-util/src/http-client/bank-wire.ts @@ -34,6 +34,7 @@ import { codecForIncomingHistory, codecForOutgoingHistory, codecForTransferResponse, + codecForBankWireTransferList, } from "../types-taler-wire-gateway.js"; import { addLongPollingParam, addPaginationParams } from "./utils.js"; @@ -89,7 +90,10 @@ export class TalerWireGatewayHttpClient { * https://docs.taler.net/core/api-bank-wire.html#post--transfer * */ - async transfer(auth: string, body: TalerWireGatewayApi.TransferRequest) { + async makeWireTransfer( + auth: string, + body: TalerWireGatewayApi.TransferRequest, + ) { const url = new URL(`transfer`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { method: "POST", @@ -117,6 +121,79 @@ export class TalerWireGatewayHttpClient { } /** + * https://docs.taler.net/core/api-bank-wire.html#get--transfers + * + */ + async getTransfers( + auth: string, + params?: { + status?: TalerWireGatewayApi.WireTransferStatus; + } & PaginationParams, + ) { + const url = new URL(`transfers`, this.baseUrl); + if (params) { + if (params.status) { + url.searchParams.set("status", params.status); + } + } + addPaginationParams(url, params); + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + headers: { + Authorization: makeBasicAuthHeader(this.username, auth), + }, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForBankWireTransferList()); + //FIXME: account should not be returned or make it optional + case HttpStatusCode.NoContent: + return opFixedSuccess({ + transfers: [], + debit_account: undefined, + }); + //FIXME: show more details in docs + case HttpStatusCode.BadRequest: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + //FIXME: show more details in docs + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-bank-wire.html#get--transfers-$ROW_ID + * + */ + async getTransferStatus(auth: string, rowId?: number) { + const url = new URL(`transfers/${String(rowId)}`, this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + headers: { + Authorization: makeBasicAuthHeader(this.username, auth), + }, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForBankWireTransferList()); + //FIXME: account should not be returned or make it optional + case HttpStatusCode.BadRequest: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + //FIXME: show more details in docs + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** * https://docs.taler.net/core/api-bank-wire.html#get--history-incoming * */ @@ -227,4 +304,33 @@ export class TalerWireGatewayHttpClient { return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } + + /** + * https://docs.taler.net/core/api-bank-wire.html#post--admin-add-kycauth + * + */ + async addKycAuth(auth: string, body: TalerWireGatewayApi.AddIncomingRequest) { + const url = new URL(`admin/add-kycauth`, this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + headers: { + Authorization: makeBasicAuthHeader(this.username, auth), + }, + body, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForAddIncomingResponse()); + //FIXME: show more details in docs + case HttpStatusCode.BadRequest: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + //FIXME: show more details in docs + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } } diff --git a/packages/taler-util/src/http-client/challenger.ts b/packages/taler-util/src/http-client/challenger.ts index 1b0fda300..1c6f54be2 100644 --- a/packages/taler-util/src/http-client/challenger.ts +++ b/packages/taler-util/src/http-client/challenger.ts @@ -63,7 +63,7 @@ export enum ChallengerCacheEviction { export class ChallengerHttpClient { httpLib: HttpRequestLibrary; cacheEvictor: CacheEvictor<ChallengerCacheEviction>; - public readonly PROTOCOL_VERSION = "1:0:0"; + public readonly PROTOCOL_VERSION = "2:0:0"; constructor( readonly baseUrl: string, diff --git a/packages/taler-util/src/http-client/exchange.ts b/packages/taler-util/src/http-client/exchange.ts index 9b7635cb4..7526fd8a0 100644 --- a/packages/taler-util/src/http-client/exchange.ts +++ b/packages/taler-util/src/http-client/exchange.ts @@ -1,3 +1,19 @@ +/* + 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 <http://www.gnu.org/licenses/> + */ + import { HttpRequestLibrary, readSuccessResponseJsonOrThrow, @@ -17,16 +33,18 @@ import { opKnownAlternativeFailure, opKnownHttpFailure, opSuccessFromHttp, - opUnknownFailure + opUnknownFailure, } from "../operation.js"; +import { Codec, codecForAny } from "../codec.js"; import { TalerSignaturePurpose, + bufferForUint64, buildSigPS, decodeCrock, eddsaSign, encodeCrock, stringToBytes, - timestampRoundedToBuffer + timestampRoundedToBuffer, } from "../taler-crypto.js"; import { AccessToken, @@ -35,10 +53,12 @@ import { PaginationParams, ReserveAccount, SigningKey, - codecForTalerCommonConfigResponse + codecForTalerCommonConfigResponse, } from "../types-taler-common.js"; import { AmlDecisionRequest, + BatchWithdrawResponse, + ExchangeBatchWithdrawRequest, ExchangeVersionResponse, KycRequirementInformationId, WalletKycRequest, @@ -52,13 +72,14 @@ import { codecForExchangeKeys, codecForKycProcessClientInformation, codecForKycProcessStartInformation, - codecForLegitimizationNeededResponse + codecForLegitimizationNeededResponse, } from "../types-taler-exchange.js"; -import { CacheEvictor, addMerchantPaginationParams, nullEvictor } from "./utils.js"; +import { CacheEvictor, addPaginationParams, nullEvictor } from "./utils.js"; import { TalerError } from "../errors.js"; import { TalerErrorCode } from "../taler-error-codes.js"; import { codecForEmptyObject } from "../types-taler-wallet.js"; +import { canonicalJson } from "../helpers.js"; export type TalerExchangeResultByMethod< prop extends keyof TalerExchangeHttpClient, @@ -68,14 +89,17 @@ export type TalerExchangeErrorsByMethod< > = FailCasesByMethod<TalerExchangeHttpClient, prop>; export enum TalerExchangeCacheEviction { - CREATE_DESCISION, + UPLOAD_KYC_FORM, + MAKE_AML_DECISION, } +declare const __pubId: unique symbol; +export type ReservePub = string & { [__pubId]: true }; /** */ export class TalerExchangeHttpClient { httpLib: HttpRequestLibrary; - public readonly PROTOCOL_VERSION = "20:0:0"; + public readonly PROTOCOL_VERSION = "21:0:0"; cacheEvictor: CacheEvictor<TalerExchangeCacheEviction>; constructor( @@ -91,6 +115,20 @@ export class TalerExchangeHttpClient { const compare = LibtoolVersion.compare(this.PROTOCOL_VERSION, version); return compare?.compatible ?? false; } + + // TERMS + + /** + * https://docs.taler.net/core/api-exchange.html#get--seed + * + */ + /** + * https://docs.taler.net/core/api-exchange.html#get--seed + * + */ + + // EXCHANGE INFORMATION + /** * https://docs.taler.net/core/api-exchange.html#get--seed * @@ -163,6 +201,7 @@ export class TalerExchangeHttpClient { return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } + /** * https://docs.taler.net/core/api-merchant.html#get--config * @@ -181,10 +220,390 @@ export class TalerExchangeHttpClient { } } - // TERMS + // + // MANAGEMENT + // + + /** + * https://docs.taler.net/core/api-exchange.html#get--management-keys + * + */ + async getFutureKeys(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-keys + * + */ + async signFutureKeys(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-denominations-$H_DENOM_PUB-revoke + * + */ + async revokeFutureDenominationKeys(): Promise<never> { + throw Error("not yet implemented"); + } + /** + * https://docs.taler.net/core/api-exchange.html#post--management-signkeys-$EXCHANGE_PUB-revoke + * + */ + async revokeFutureSigningKeys(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-auditors + * + */ + async enableAuditor(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-auditors-$AUDITOR_PUB-disable + * + */ + async disableAuditor(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-wire-fee + * + */ + async configWireFee(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-global-fees + * + */ + async configGlobalFees(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-wire + * + */ + async enableWireMethod(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-wire-disable + * + */ + async disableWireMethod(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-drain + * + */ + async drainProfits(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-aml-officers + * + */ + async updateOfficer(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-partners + * + */ + async enablePartner(): Promise<never> { + throw Error("not yet implemented"); + } + + // + // AUDITOR + // + + /** + * https://docs.taler.net/core/api-exchange.html#post--auditors-$AUDITOR_PUB-$H_DENOM_PUB + * + */ + async addAuditor(): Promise<never> { + throw Error("not yet implemented"); + } + + // + // WITHDRAWAL + // + + /** + * https://docs.taler.net/core/api-exchange.html#get--reserves-$RESERVE_PUB + * + */ + async getReserveInfo(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--csr-withdraw + * + */ + async prepareCsrWithdawal(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--reserves-$RESERVE_PUB-batch-withdraw + * + */ + async withdraw(rid: ReservePub, body: ExchangeBatchWithdrawRequest) { + const url = new URL(`reserves/${rid}/batch-withdraw`, this.baseUrl); + + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body, + }); + + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp( + resp, + codecForAny() as Codec<BatchWithdrawResponse>, + ); + case HttpStatusCode.Forbidden: + 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); + case HttpStatusCode.Gone: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.UnavailableForLegalReasons: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForLegitimizationNeededResponse(), + ); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#withdraw-with-age-restriction + * + */ + async withdrawWithAge(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--age-withdraw-$ACH-reveal + * + */ + async revealCoinsForAge(): Promise<never> { + throw Error("not yet implemented"); + } + + // + // RESERVE HISTORY + // + + /** + * https://docs.taler.net/core/api-exchange.html#get--reserves-$RESERVE_PUB-history + * + */ + async getResverveHistory(): Promise<never> { + throw Error("not yet implemented"); + } // - // KYC operations + // COIN HISTORY + // + + /** + * https://docs.taler.net/core/api-exchange.html#get--coins-$COIN_PUB-history + * + */ + async getCoinHistory(): Promise<never> { + throw Error("not yet implemented"); + } + + // + // DEPOSIT + // + + /** + * https://docs.taler.net/core/api-exchange.html#post--batch-deposit + * + */ + async deposit(): Promise<never> { + throw Error("not yet implemented"); + } + + // + // REFRESH + // + + /** + * https://docs.taler.net/core/api-exchange.html#post--csr-melt + * + */ + async prepareCsrMelt(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--coins-$COIN_PUB-melt + * + */ + async meltCoin(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--refreshes-$RCH-reveal + * + */ + async releaveCoin(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#get--coins-$COIN_PUB-link + * + */ + async linkCoin(): Promise<never> { + throw Error("not yet implemented"); + } + + // + // RECOUP + // + + /** + * https://docs.taler.net/core/api-exchange.html#post--coins-$COIN_PUB-recoup + * + */ + async recoupReserveCoin(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--coins-$COIN_PUB-recoup-refresh + * + */ + async recoupRefreshCoin(): Promise<never> { + throw Error("not yet implemented"); + } + + // WIRE TRANSFER + + /** + * https://docs.taler.net/core/api-exchange.html#get--transfers-$WTID + * + */ + async getWireTransferInfo(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#get--deposits-$H_WIRE-$MERCHANT_PUB-$H_CONTRACT_TERMS-$COIN_PUB + * + */ + async getWireTransferIdForDeposit(): Promise<never> { + throw Error("not yet implemented"); + } + + // REFUND + + /** + * https://docs.taler.net/core/api-exchange.html#post--coins-$COIN_PUB-refund + * + */ + async refund(): Promise<never> { + throw Error("not yet implemented"); + } + + // WALLET TO WALLET + + /** + * https://docs.taler.net/core/api-exchange.html#get--purses-$PURSE_PUB-merge + * + */ + async getPurseInfoAtMerge(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#get--purses-$PURSE_PUB-deposit + * + */ + async getPurseInfoAtDeposit(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--purses-$PURSE_PUB-create + * + */ + async createPurseFromDeposit(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#delete--purses-$PURSE_PUB + * + */ + async deletePurse(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--purses-$PURSE_PUB-merge + * + */ + async mergePurse(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--reserves-$RESERVE_PUB-purse + * + */ + async createPurseFromReserve(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--purses-$PURSE_PUB-deposit + * + */ + async depositIntoPurse(): Promise<never> { + throw Error("not yet implemented"); + } + + // WADS + + /** + * https://docs.taler.net/core/api-exchange.html#get--wads-$WAD_ID + * + */ + async getWadInfo(): Promise<never> { + throw Error("not yet implemented"); + } + + // + // KYC // /** @@ -198,7 +617,7 @@ export class TalerExchangeHttpClient { balance, reserve_pub: account.id, reserve_sig: encodeCrock(account.signingKey), - } + }; const resp = await this.httpLib.fetch(url.href, { method: "POST", @@ -213,29 +632,41 @@ export class TalerExchangeHttpClient { case HttpStatusCode.Forbidden: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.UnavailableForLegalReasons: - return opKnownAlternativeFailure(resp, resp.status, codecForLegitimizationNeededResponse()); + return opKnownAlternativeFailure( + resp, + resp.status, + codecForLegitimizationNeededResponse(), + ); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** - * https://docs.taler.net/core/api-exchange.html#post--kyc-wallet + * https://docs.taler.net/core/api-exchange.html#get--kyc-check-$H_PAYTO * */ - async checkKycStatus(account: ReserveAccount, requirementId: number, params: { - timeout?: number, - } = {}) { - const url = new URL(`kyc-check/${String(requirementId)}`, this.baseUrl); + async checkKycStatus( + signingKey: SigningKey, + paytoHash: string, + params: { + timeout?: number; + awaitAuth?: boolean; + } = {}, + ) { + const url = new URL(`kyc-check/${paytoHash}`, this.baseUrl); if (params.timeout !== undefined) { url.searchParams.set("timeout_ms", String(params.timeout)); } + if (params.awaitAuth !== undefined) { + url.searchParams.set("await_auth", params.awaitAuth ? "YES" : "NO"); + } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers: { - "Account-Owner-Signature": buildKYCQuerySignature(account.signingKey), + "Account-Owner-Signature": buildKYCQuerySignature(signingKey), }, }); @@ -243,9 +674,9 @@ export class TalerExchangeHttpClient { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForAccountKycStatus()); case HttpStatusCode.Accepted: - return opKnownAlternativeFailure(resp, resp.status, codecForAccountKycStatus()); + return opSuccessFromHttp(resp, codecForAccountKycStatus()); case HttpStatusCode.NoContent: - return opEmptySuccess(resp); + return opFixedSuccess(undefined); case HttpStatusCode.Forbidden: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: @@ -258,12 +689,16 @@ export class TalerExchangeHttpClient { } /** - * https://docs.taler.net/core/api-exchange.html#get--kyc-info-$ACCESS_TOKEN - * - */ - async checkKycInfo(token: AccessToken, known: KycRequirementInformationId[], params: { - timeout?: number, - } = {}) { + * https://docs.taler.net/core/api-exchange.html#get--kyc-info-$ACCESS_TOKEN + * + */ + async checkKycInfo( + token: AccessToken, + known: KycRequirementInformationId[], + params: { + timeout?: number; + } = {}, + ) { const url = new URL(`kyc-info/${token}`, this.baseUrl); if (params.timeout !== undefined) { @@ -273,15 +708,25 @@ export class TalerExchangeHttpClient { const resp = await this.httpLib.fetch(url.href, { method: "GET", headers: { - "If-None-Match": known.length ? known.join(",") : undefined - } + "If-None-Match": known.length ? known.join(",") : undefined, + }, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForKycProcessClientInformation()); + case HttpStatusCode.Accepted: + return opKnownAlternativeFailure( + resp, + HttpStatusCode.Accepted, + codecForEmptyObject(), + ); case HttpStatusCode.NoContent: - return opKnownAlternativeFailure(resp, HttpStatusCode.NoContent, codecForEmptyObject()); + return opKnownAlternativeFailure( + resp, + HttpStatusCode.NoContent, + codecForEmptyObject(), + ); case HttpStatusCode.NotModified: return opKnownHttpFailure(resp.status, resp); default: @@ -289,7 +734,6 @@ export class TalerExchangeHttpClient { } } - /** * https://docs.taler.net/core/api-exchange.html#post--kyc-upload-$ID * @@ -303,8 +747,12 @@ export class TalerExchangeHttpClient { }); switch (resp.status) { - case HttpStatusCode.NoContent: + case HttpStatusCode.NoContent: { + this.cacheEvictor.notifySuccess( + TalerExchangeCacheEviction.UPLOAD_KYC_FORM, + ); return opEmptySuccess(resp); + } case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Conflict: @@ -320,13 +768,15 @@ export class TalerExchangeHttpClient { * https://docs.taler.net/core/api-exchange.html#post--kyc-start-$ID * */ - async startKycProcess(requirement: KycRequirementInformationId, body: object = {}) { + async startExternalKycProcess( + requirement: KycRequirementInformationId, + body: object = {}, + ) { const url = new URL(`kyc-start/${requirement}`, this.baseUrl); - const resp = await this.httpLib.fetch(url.href, { method: "POST", - body + body, }); switch (resp.status) { @@ -343,119 +793,40 @@ export class TalerExchangeHttpClient { } } + /** + * https://docs.taler.net/core/api-exchange.html#get--kyc-proof-$PROVIDER_NAME?state=$H_PAYTO + * + */ + async completeExternalKycProcess( + provider: string, + state: string, + code: string, + ) { + const url = new URL( + `kyc-proof/${provider}?state=${state}&code=${code}`, + this.baseUrl, + ); + + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + redirect: "manual", + }); + + switch (resp.status) { + case HttpStatusCode.SeeOther: + return opEmptySuccess(resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + // // AML operations // /** - * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-decisions-$STATE - * - */ - // async getDecisionsByState( - // auth: OfficerAccount, - // state: TalerExchangeApi.AmlState, - // pagination?: PaginationParams, - // ) { - // const url = new URL( - // `aml/${auth.id}/decisions/${TalerExchangeApi.AmlState[state]}`, - // this.baseUrl, - // ); - // addPaginationParams(url, pagination); - - // const resp = await this.httpLib.fetch(url.href, { - // method: "GET", - // headers: { - // "Taler-AML-Officer-Signature": buildQuerySignature(auth.signingKey), - // }, - // }); - - // switch (resp.status) { - // case HttpStatusCode.Ok: - // return opSuccessFromHttp(resp, codecForAmlRecords()); - // case HttpStatusCode.NoContent: - // return opFixedSuccess({ records: [] }); - // //this should be unauthorized - // case HttpStatusCode.Forbidden: - // return opKnownHttpFailure(resp.status, 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-exchange.html#get--aml-$OFFICER_PUB-decision-$H_PAYTO - // * - // */ - // async getDecisionDetails(auth: OfficerAccount, account: string) { - // const url = new URL(`aml/${auth.id}/decision/${account}`, this.baseUrl); - - // const resp = await this.httpLib.fetch(url.href, { - // method: "GET", - // headers: { - // "Taler-AML-Officer-Signature": buildQuerySignature(auth.signingKey), - // }, - // }); - - // switch (resp.status) { - // case HttpStatusCode.Ok: - // return opSuccessFromHttp(resp, codecForAmlDecisionDetails()); - // case HttpStatusCode.NoContent: - // return opFixedSuccess({ aml_history: [], kyc_attributes: [] }); - // //this should be unauthorized - // case HttpStatusCode.Forbidden: - // return opKnownHttpFailure(resp.status, 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-exchange.html#post--aml-$OFFICER_PUB-decision - // * - // */ - // async addDecisionDetails( - // auth: OfficerAccount, - // decision: Omit<TalerExchangeApi.AmlDecision, "officer_sig">, - // ) { - // const url = new URL(`aml/${auth.id}/decision`, this.baseUrl); - - // const body = buildDecisionSignature(auth.signingKey, decision); - // const resp = await this.httpLib.fetch(url.href, { - // method: "POST", - // body, - // }); - - // switch (resp.status) { - // case HttpStatusCode.NoContent: - // return opEmptySuccess(resp); - // //FIXME: this should be unauthorized - // case HttpStatusCode.Forbidden: - // return opKnownHttpFailure(resp.status, resp); - // case HttpStatusCode.Unauthorized: - // return opKnownHttpFailure(resp.status, resp); - // //FIXME: this two need to be split by error code - // 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-exchange.html#get--aml-$OFFICER_PUB-measures * */ @@ -481,26 +852,23 @@ export class TalerExchangeHttpClient { * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-measures * */ - async getAmlKycStatistics(auth: OfficerAccount, name: string, filter: { - since?: Date - until?: Date - } = {}) { + async getAmlKycStatistics( + auth: OfficerAccount, + name: string, + filter: { + since?: Date; + until?: Date; + } = {}, + ) { const url = new URL(`aml/${auth.id}/kyc-statistics/${name}`, this.baseUrl); if (filter.since !== undefined) { - url.searchParams.set( - "start_date", - String(filter.since.getTime()) - ); + url.searchParams.set("start_date", String(filter.since.getTime())); } if (filter.until !== undefined) { - url.searchParams.set( - "end_date", - String(filter.until.getTime()) - ); + url.searchParams.set("end_date", String(filter.until.getTime())); } - const resp = await this.httpLib.fetch(url.href, { method: "GET", headers: { @@ -519,14 +887,17 @@ export class TalerExchangeHttpClient { * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-decisions * */ - async getAmlDecisions(auth: OfficerAccount, params: PaginationParams & { - account?: string, - active?: boolean, - investigation?: boolean, - } = {}) { + async getAmlDecisions( + auth: OfficerAccount, + params: PaginationParams & { + account?: string; + active?: boolean; + investigation?: boolean; + } = {}, + ) { const url = new URL(`aml/${auth.id}/decisions`, this.baseUrl); - addMerchantPaginationParams(url, params); + addPaginationParams(url, params); if (params.account !== undefined) { url.searchParams.set("h_payto", params.account); } @@ -534,7 +905,10 @@ export class TalerExchangeHttpClient { url.searchParams.set("active", params.active ? "YES" : "NO"); } if (params.investigation !== undefined) { - url.searchParams.set("investigation", params.investigation ? "YES" : "NO"); + url.searchParams.set( + "investigation", + params.investigation ? "YES" : "NO", + ); } const resp = await this.httpLib.fetch(url.href, { @@ -564,10 +938,14 @@ export class TalerExchangeHttpClient { * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-attributes-$H_PAYTO * */ - async getAmlAttributesForAccount(auth: OfficerAccount, account: string, params: PaginationParams = {}) { + async getAmlAttributesForAccount( + auth: OfficerAccount, + account: string, + params: PaginationParams = {}, + ) { const url = new URL(`aml/${auth.id}/attributes/${account}`, this.baseUrl); - addMerchantPaginationParams(url, params); + addPaginationParams(url, params); const resp = await this.httpLib.fetch(url.href, { method: "GET", headers: { @@ -591,12 +969,14 @@ export class TalerExchangeHttpClient { } } - /** * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-attributes-$H_PAYTO * */ - async makeAmlDesicion(auth: OfficerAccount, decision: Omit<AmlDecisionRequest, "officer_sig">) { + async makeAmlDesicion( + auth: OfficerAccount, + decision: Omit<AmlDecisionRequest, "officer_sig">, + ) { const url = new URL(`aml/${auth.id}/decision`, this.baseUrl); const body = buildAMLDecisionSignature(auth.signingKey, decision); @@ -609,8 +989,12 @@ export class TalerExchangeHttpClient { }); switch (resp.status) { - case HttpStatusCode.NoContent: + case HttpStatusCode.NoContent: { + this.cacheEvictor.notifySuccess( + TalerExchangeCacheEviction.MAKE_AML_DECISION, + ); return opEmptySuccess(resp); + } case HttpStatusCode.Forbidden: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: @@ -622,20 +1006,57 @@ export class TalerExchangeHttpClient { } } + // RESERVE control + + /** + * https://docs.taler.net/core/api-exchange.html#post--reserves-$RESERVE_PUB-open + * + */ + async reserveOpen(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#get--reserves-attest-$RESERVE_PUB + * + */ + async getReserveAttributes(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--reserves-attest-$RESERVE_PUB + * + */ + async signReserveAttributes(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--reserves-$RESERVE_PUB-close + * + */ + async closeReserve(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#delete--reserves-$RESERVE_PUB + * + */ + async deleteReserve(): Promise<never> { + throw Error("not yet implemented"); + } } function buildKYCQuerySignature(key: SigningKey): string { - const sigBlob = buildSigPS( - TalerSignaturePurpose.AML_QUERY, - ).build(); + const sigBlob = buildSigPS(TalerSignaturePurpose.KYC_AUTH).build(); return encodeCrock(eddsaSign(sigBlob, key)); } function buildAMLQuerySignature(key: SigningKey): string { - const sigBlob = buildSigPS( - TalerSignaturePurpose.AML_QUERY, - ).build(); + const sigBlob = buildSigPS(TalerSignaturePurpose.AML_QUERY).build(); return encodeCrock(eddsaSign(sigBlob, key)); } @@ -647,16 +1068,21 @@ function buildAMLDecisionSignature( const zero = new Uint8Array(new ArrayBuffer(64)); const sigBlob = buildSigPS(TalerSignaturePurpose.AML_DECISION) - //TODO: new need the null terminator, also in the exchange - .put(hash(stringToBytes(decision.justification))) //check null .put(timestampRoundedToBuffer(decision.decision_time)) - // .put(amountToBuffer(decision.new_threshold)) .put(decodeCrock(decision.h_payto)) - .put(zero) //kyc_requirement - // .put(bufferForUint32(decision.new_state)) + .put(hash(stringToBytes(decision.justification))) + .put(hash(stringToBytes(canonicalJson(decision.properties) + "\0"))) + .put(hash(stringToBytes(canonicalJson(decision.new_rules) + "\0"))) + .put( + decision.new_measures != null + ? hash(stringToBytes(decision.new_measures)) + : zero, + ) + .put(bufferForUint64(decision.keep_investigating ? 1 : 0)) .build(); const officer_sig = encodeCrock(eddsaSign(sigBlob, key)); + return { ...decision, officer_sig, diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts index e765d286b..e6af6dfe8 100644 --- a/packages/taler-util/src/http-client/merchant.ts +++ b/packages/taler-util/src/http-client/merchant.ts @@ -45,6 +45,7 @@ import { codecForOtpDeviceSummaryResponse, codecForOutOfStockResponse, codecForPaidRefundStatusResponse, + codecForPaymentDeniedLegallyResponse, codecForPaymentResponse, codecForPostOrderResponse, codecForProductDetail, @@ -76,8 +77,8 @@ import { } from "@gnu-taler/taler-util/http"; import { opSuccessFromHttp, opUnknownFailure } from "../operation.js"; import { + addPaginationParams, CacheEvictor, - addMerchantPaginationParams, makeBearerTokenAuthHeader, nullEvictor, } from "./utils.js"; @@ -137,7 +138,7 @@ export enum TalerMerchantManagementCacheEviction { * Uses libtool's current:revision:age versioning. */ export class TalerMerchantInstanceHttpClient { - public readonly PROTOCOL_VERSION = "16:0:0"; + public readonly PROTOCOL_VERSION = "17:0:1"; readonly httpLib: HttpRequestLibrary; readonly cacheEvictor: CacheEvictor<TalerMerchantInstanceCacheEviction>; @@ -276,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)); } @@ -431,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)); } @@ -604,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: @@ -712,7 +727,7 @@ export class TalerMerchantInstanceHttpClient { ) { const url = new URL(`private/accounts`, this.baseUrl); - // addMerchantPaginationParams(url, params); + // addPaginationParams(url, params); const headers: Record<string, string> = {}; if (token) { @@ -809,7 +824,7 @@ export class TalerMerchantInstanceHttpClient { ) { const url = new URL(`private/categories`, this.baseUrl); - // addMerchantPaginationParams(url, params); + // addPaginationParams(url, params); const headers: Record<string, string> = {}; if (token) { @@ -1051,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) { @@ -1234,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: @@ -1277,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) { @@ -1471,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)); } @@ -1538,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) { @@ -1679,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) { @@ -1852,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) { @@ -2584,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": [] +// } +// ] +// } diff --git a/packages/taler-util/src/http-client/officer-account.ts b/packages/taler-util/src/http-client/officer-account.ts index 612fd815e..f5f55ea1b 100644 --- a/packages/taler-util/src/http-client/officer-account.ts +++ b/packages/taler-util/src/http-client/officer-account.ts @@ -17,11 +17,8 @@ import { EncryptionNonce, LockedAccount, - LockedReserve, OfficerAccount, OfficerId, - ReserveAccount, - ReserveId, SigningKey, createEddsaKeyPair, decodeCrock, @@ -31,7 +28,7 @@ import { encryptWithDerivedKey, getRandomBytesF, kdf, - stringToBytes, + stringToBytes } from "@gnu-taler/taler-util"; /** @@ -100,36 +97,6 @@ export async function createNewOfficerAccount( } /** - * Restore previous session and unlock account with password - * - * @param salt string from which crypto params will be derived - * @param key secured private key - * @param password password for the private key - * @returns - */ -export async function unlockWalletKycAccount( - account: LockedReserve, - password: string, -): Promise<ReserveAccount> { - const rawKey = decodeCrock(account); - const rawPassword = stringToBytes(password); - - const signingKey = (await decryptWithDerivedKey( - rawKey, - rawPassword, - password, - ).catch((e) => { - throw new UnwrapKeyError(e instanceof Error ? e.message : String(e)); - })) as SigningKey; - - const publicKey = eddsaGetPublic(signingKey); - - const accountId = encodeCrock(publicKey) as ReserveId; - - return { id: accountId, signingKey }; -} - -/** * Create new account (secured private key) * secured with the given password * diff --git a/packages/taler-util/src/http-client/utils.ts b/packages/taler-util/src/http-client/utils.ts index baea3ce6f..a69eb0847 100644 --- a/packages/taler-util/src/http-client/utils.ts +++ b/packages/taler-util/src/http-client/utils.ts @@ -46,29 +46,38 @@ export function makeBearerTokenAuthHeader(token: AccessToken): string { return `Bearer ${token}`; } +export type BasicOrTokenAuth = BasicAuth | TokenAuth; + +export type BasicAuth = { + type: "basic"; + username: string; + password: string; +}; + +export type TokenAuth = { + type: "bearer"; + token: AccessToken; +}; + +export function createAuthorizationHeader(auth?: BasicOrTokenAuth): string | undefined { + if (!auth) return undefined; + switch (auth.type) { + case "basic": { + return makeBasicAuthHeader(auth.username, auth.password); + } + case "bearer": { + return makeBearerTokenAuthHeader(auth.token); + } + } + return undefined; +} + /** * https://bugs.gnunet.org/view.php?id=7949 */ export function addPaginationParams(url: URL, pagination?: PaginationParams) { if (!pagination) return; if (pagination.offset) { - url.searchParams.set("start", pagination.offset); - } - const order = !pagination || pagination.order === "asc" ? 1 : -1; - const limit = - !pagination || !pagination.limit || pagination.limit === 0 - ? 5 - : Math.abs(pagination.limit); - //always send delta - url.searchParams.set("delta", String(order * limit)); -} - -export function addMerchantPaginationParams( - url: URL, - pagination?: PaginationParams, -) { - if (!pagination) return; - if (pagination.offset) { url.searchParams.set("offset", pagination.offset); } const order = !pagination || pagination.order === "asc" ? 1 : -1; @@ -76,14 +85,14 @@ export function addMerchantPaginationParams( !pagination || !pagination.limit || pagination.limit === 0 ? 5 : Math.abs(pagination.limit); - //always send delta + //always send limit url.searchParams.set("limit", String(order * limit)); } export function addLongPollingParam(url: URL, param?: LongPollParams) { if (!param) return; if (param.timeoutMs) { - url.searchParams.set("long_poll_ms", String(param.timeoutMs)); + url.searchParams.set("timeout_ms", String(param.timeoutMs)); } } @@ -95,26 +104,3 @@ export const nullEvictor: CacheEvictor<unknown> = { notifySuccess: () => Promise.resolve(), }; -export class IdempotencyRetry { - public readonly uid: string; - public readonly timesLeft: number; - public readonly maxTries: number; - - private constructor(timesLeft: number, maxTimesLeft: number) { - this.timesLeft = timesLeft; - this.maxTries = maxTimesLeft; - this.uid = encodeCrock(getRandomBytes(32)); - } - - static tryFiveTimes() { - return new IdempotencyRetry(5, 5); - } - - next(): IdempotencyRetry | undefined { - const left = this.timesLeft - 1; - if (left <= 0) { - return undefined; - } - return new IdempotencyRetry(left, this.maxTries); - } -} |