diff options
author | Özgür Kesim <oec-taler@kesim.org> | 2023-10-06 16:33:05 +0200 |
---|---|---|
committer | Özgür Kesim <oec-taler@kesim.org> | 2023-10-06 16:33:05 +0200 |
commit | fe7b51ef2736edbf04f5bbd9d19f2a2d04baccc2 (patch) | |
tree | 66c68c8d6a666f6e74dc663c9ee4f07879f6626c /packages/taler-util/src | |
parent | 35611f0bf9cf67638b171c2a300fab1797d3d8f0 (diff) | |
parent | 97d7be7503168f4f3bbd05905d32aa76ca1636b2 (diff) |
Merge branch 'master' into age-withdraw
Diffstat (limited to 'packages/taler-util/src')
-rw-r--r-- | packages/taler-util/src/MerchantApiClient.ts | 2 | ||||
-rw-r--r-- | packages/taler-util/src/amounts.ts | 5 | ||||
-rw-r--r-- | packages/taler-util/src/bank-api-client.ts | 173 | ||||
-rw-r--r-- | packages/taler-util/src/errors.ts | 2 | ||||
-rw-r--r-- | packages/taler-util/src/payto.ts | 11 | ||||
-rw-r--r-- | packages/taler-util/src/taler-types.ts | 13 | ||||
-rw-r--r-- | packages/taler-util/src/talerconfig.ts | 6 | ||||
-rw-r--r-- | packages/taler-util/src/time.ts | 6 | ||||
-rw-r--r-- | packages/taler-util/src/transactions-types.ts | 24 | ||||
-rw-r--r-- | packages/taler-util/src/wallet-types.ts | 44 |
10 files changed, 207 insertions, 79 deletions
diff --git a/packages/taler-util/src/MerchantApiClient.ts b/packages/taler-util/src/MerchantApiClient.ts index ccbbf79b3..988872ae7 100644 --- a/packages/taler-util/src/MerchantApiClient.ts +++ b/packages/taler-util/src/MerchantApiClient.ts @@ -269,7 +269,7 @@ export class MerchantApiClient { } async giveTip(req: RewardCreateRequest): Promise<RewardCreateConfirmation> { - const reqUrl = new URL(`private/tips`, this.baseUrl); + const reqUrl = new URL(`private/rewards`, this.baseUrl); const resp = await this.httpClient.fetch(reqUrl.href, { method: "POST", body: req, diff --git a/packages/taler-util/src/amounts.ts b/packages/taler-util/src/amounts.ts index a8df3679f..04343b8e9 100644 --- a/packages/taler-util/src/amounts.ts +++ b/packages/taler-util/src/amounts.ts @@ -345,9 +345,12 @@ export class Amounts { /** * Parse an amount like 'EUR:20.5' for 20 Euros and 50 ct. + * + * Currency name size limit is 11 of ASCII letters + * Fraction size limit is 8 */ static parse(s: string): AmountJson | undefined { - const res = s.match(/^([a-zA-Z0-9_*-]+):([0-9]+)([.][0-9]+)?$/); + const res = s.match(/^([a-zA-Z]{1,11}):([0-9]+)([.][0-9]{1,8})?$/); if (!res) { return undefined; } diff --git a/packages/taler-util/src/bank-api-client.ts b/packages/taler-util/src/bank-api-client.ts index cc4123500..a8cd4b0da 100644 --- a/packages/taler-util/src/bank-api-client.ts +++ b/packages/taler-util/src/bank-api-client.ts @@ -146,11 +146,91 @@ export class WireGatewayApiClient { } } +export interface ChallengeContactData { + // E-Mail address + email?: string; + + // Phone number. + phone?: string; +} + +export interface AccountBalance { + amount: AmountString; + credit_debit_indicator: "credit" | "debit"; +} + +export interface RegisterAccountRequest { + // Username + username: string; + + // Password. + password: string; + + // Legal name of the account owner + name: string; + + // Defaults to false. + is_public?: boolean; + + // Is this a taler exchange account? + // If true: + // - incoming transactions to the account that do not + // have a valid reserve public key are automatically + // - the account provides the taler-wire-gateway-api endpoints + // Defaults to false. + is_taler_exchange?: boolean; + + // Addresses where to send the TAN for transactions. + // Currently only used for cashouts. + // If missing, cashouts will fail. + // In the future, might be used for other transactions + // as well. + challenge_contact_data?: ChallengeContactData; + + // 'payto' address pointing a bank account + // external to the libeufin-bank. + // Payments will be sent to this bank account + // when the user wants to convert the local currency + // back to fiat currency outside libeufin-bank. + cashout_payto_uri?: string; + + // Internal payto URI of this bank account. + // Used mostly for testing. + internal_payto_uri?: string; +} + +export interface AccountData { + // Legal name of the account owner. + name: string; + + // Available balance on the account. + balance: AccountBalance; + + // payto://-URI of the account. + payto_uri: string; + + // Number indicating the max debit allowed for the requesting user. + debit_threshold: AmountString; + + contact_data?: ChallengeContactData; + + // 'payto' address pointing the bank account + // where to send cashouts. This field is optional + // because not all the accounts are required to participate + // in the merchants' circuit. One example is the exchange: + // that never cashouts. Registering these accounts can + // be done via the access API. + cashout_payto_uri?: string; +} + +export interface ConfirmWithdrawalArgs { + withdrawalOperationId: string; +} + /** - * This API look like it belongs to harness - * but it will be nice to have in utils to be used by others + * Client for the Taler corebank API. */ -export class BankAccessApiClient { +export class TalerCorebankApiClient { httpLib: HttpRequestLibrary; constructor( @@ -184,7 +264,7 @@ export class BankAccessApiClient { const resp = await this.httpLib.fetch(url.href, { headers: this.makeAuthHeader(), }); - return await resp.json(); + return readSuccessResponseJsonOrThrow(resp, codecForAny()); } async getTransactions(username: string): Promise<void> { @@ -215,24 +295,53 @@ export class BankAccessApiClient { return await readSuccessResponseJsonOrThrow(resp, codecForAny()); } - async registerAccount( - username: string, - password: string, - options: { - iban?: string; - } = {}, - ): Promise<BankUser> { - const url = new URL("testing/register", this.baseUrl); + async registerAccountExtended(req: RegisterAccountRequest): Promise<void> { + const url = new URL("accounts", this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body: req, + }); + + if ( + resp.status !== 200 && + resp.status !== 201 && + resp.status !== 202 && + resp.status !== 204 + ) { + logger.error(`unexpected status ${resp.status} from POST ${url.href}`); + logger.error(`${j2s(await resp.json())}`); + throw TalerError.fromDetail( + TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, + { + httpStatusCode: resp.status, + }, + ); + } + } + + /** + * Register a new account and return information about it. + * + * This is a helper, as it does both the registration and the + * account info query. + */ + async registerAccount(username: string, password: string): Promise<BankUser> { + const url = new URL("accounts", this.baseUrl); const resp = await this.httpLib.fetch(url.href, { method: "POST", body: { username, password, - iban: options?.iban, + name: username, }, }); - let paytoUri = `payto://x-taler-bank/localhost/${username}`; - if (resp.status !== 200 && resp.status !== 202 && resp.status !== 204) { + if ( + resp.status !== 200 && + resp.status !== 201 && + resp.status !== 202 && + resp.status !== 204 + ) { + logger.error(`unexpected status ${resp.status} from POST ${url.href}`); logger.error(`${j2s(await resp.json())}`); throw TalerError.fromDetail( TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, @@ -241,31 +350,29 @@ export class BankAccessApiClient { }, ); } - try { - // Pybank has no body, thus this might throw. - const respJson = await resp.json(); - // LibEuFin demobank returns payto URI in response - if (respJson.paytoUri) { - paytoUri = respJson.paytoUri; - } - } catch (e) { - // Do nothing - } + // FIXME: Corebank should directly return this info! + const infoUrl = new URL(`accounts/${username}`, this.baseUrl); + const infoResp = await this.httpLib.fetch(infoUrl.href, { + headers: { + Authorization: makeBasicAuthHeader(username, password), + }, + }); + // FIXME: Validate! + const acctInfo: AccountData = await readSuccessResponseJsonOrThrow( + infoResp, + codecForAny(), + ); return { password, username, - accountPaytoUri: paytoUri, + accountPaytoUri: acctInfo.payto_uri, }; } async createRandomBankUser(): Promise<BankUser> { const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase(); const password = "pw-" + encodeCrock(getRandomBytes(10)).toLowerCase(); - // FIXME: This is just a temporary workaround, because demobank is running out of short IBANs - const iban = generateIban("DE", 15); - return await this.registerAccount(username, password, { - iban, - }); + return await this.registerAccount(username, password); } async createWithdrawalOperation( @@ -288,10 +395,10 @@ export class BankAccessApiClient { async confirmWithdrawalOperation( username: string, - wopi: WithdrawalOperationInfo, + wopi: ConfirmWithdrawalArgs, ): Promise<void> { const url = new URL( - `accounts/${username}/withdrawals/${wopi.withdrawal_id}/confirm`, + `withdrawals/${wopi.withdrawalOperationId}/confirm`, this.baseUrl, ); logger.info(`confirming withdrawal operation via ${url.href}`); diff --git a/packages/taler-util/src/errors.ts b/packages/taler-util/src/errors.ts index 4399dbcf2..07a402413 100644 --- a/packages/taler-util/src/errors.ts +++ b/packages/taler-util/src/errors.ts @@ -78,7 +78,7 @@ export interface DetailsMap { stack?: string; }; [TalerErrorCode.WALLET_BANK_INTEGRATION_PROTOCOL_VERSION_INCOMPATIBLE]: { - exchangeProtocolVersion: string; + bankProtocolVersion: string; walletProtocolVersion: string; }; [TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN]: { diff --git a/packages/taler-util/src/payto.ts b/packages/taler-util/src/payto.ts index 60c4ba838..85870afcd 100644 --- a/packages/taler-util/src/payto.ts +++ b/packages/taler-util/src/payto.ts @@ -89,12 +89,13 @@ export function buildPayto( return result; } case "iban": { + const uppercased = first.toUpperCase() const result: PaytoUriIBAN = { isKnown: true, targetType: "iban", - iban: first, + iban: uppercased, params: {}, - targetPath: !second ? first : `${second}/${first}`, + targetPath: !second ? uppercased : `${second}/${uppercased}`, }; return result; } @@ -200,13 +201,13 @@ export function parsePaytoUri(s: string): PaytoUri | undefined { let iban: string | undefined = undefined; let bic: string | undefined = undefined; if (parts.length === 1) { - iban = parts[0]; + iban = parts[0].toUpperCase(); } if (parts.length === 2) { bic = parts[0]; - iban = parts[1]; + iban = parts[1].toUpperCase(); } else { - iban = targetPath; + iban = targetPath.toUpperCase(); } return { isKnown: true, diff --git a/packages/taler-util/src/taler-types.ts b/packages/taler-util/src/taler-types.ts index 8a0608008..ebb0291f5 100644 --- a/packages/taler-util/src/taler-types.ts +++ b/packages/taler-util/src/taler-types.ts @@ -2005,22 +2005,13 @@ export interface BatchDepositSuccess { // Array of deposit confirmation signatures from the exchange // Entries must be in the same order the coins were given // in the batch deposit request. - exchange_sigs: DepositConfirmationSignature[]; + exchange_sig: EddsaSignatureString; } -export const codecForDepositConfirmationSignature = - (): Codec<DepositConfirmationSignature> => - buildCodecForObject<DepositConfirmationSignature>() - .property("exchange_sig", codecForString()) - .build("DepositConfirmationSignature"); - export const codecForBatchDepositSuccess = (): Codec<BatchDepositSuccess> => buildCodecForObject<BatchDepositSuccess>() .property("exchange_pub", codecForString()) - .property( - "exchange_sigs", - codecForList(codecForDepositConfirmationSignature()), - ) + .property("exchange_sig", codecForString()) .property("exchange_timestamp", codecForTimestamp) .property("transaction_base_url", codecOptional(codecForString())) .build("BatchDepositSuccess"); diff --git a/packages/taler-util/src/talerconfig.ts b/packages/taler-util/src/talerconfig.ts index e9eb71279..f817d9bcb 100644 --- a/packages/taler-util/src/talerconfig.ts +++ b/packages/taler-util/src/talerconfig.ts @@ -143,9 +143,9 @@ export function expandPath(path: string): string { export function pathsub( x: string, lookup: (s: string, depth: number) => string | undefined, - depth = 0, + recursionDepth = 0, ): string { - if (depth >= 128) { + if (recursionDepth >= 128) { throw Error("recursion in path substitution"); } let s = x; @@ -201,7 +201,7 @@ export function pathsub( } else { const m = /^[a-zA-Z-_][a-zA-Z0-9-_]*/.exec(s.substring(l + 1)); if (m && m[0]) { - const r = lookup(m[0], depth + 1); + const r = lookup(m[0], recursionDepth + 1); if (r !== undefined) { s = s.substring(0, l) + r + s.substring(l + 1 + m[0].length); l = l + r.length; diff --git a/packages/taler-util/src/time.ts b/packages/taler-util/src/time.ts index 46ed37637..c677d52ae 100644 --- a/packages/taler-util/src/time.ts +++ b/packages/taler-util/src/time.ts @@ -52,6 +52,10 @@ export interface TalerProtocolTimestamp { readonly _flavor?: typeof flavor_TalerProtocolTimestamp; } +/** + * Precise timestamp, typically used in the wallet-core + * API but not in other Taler APIs so far. + */ export interface TalerPreciseTimestamp { /** * Seconds (as integer) since epoch. @@ -88,7 +92,7 @@ export namespace TalerPreciseTimestamp { export function fromMilliseconds(ms: number): TalerPreciseTimestamp { return { t_s: Math.floor(ms / 1000), - off_us: Math.floor((ms - Math.floor(ms / 100) * 1000) * 1000), + off_us: Math.floor((ms - Math.floor(ms / 1000) * 1000) * 1000), }; } } diff --git a/packages/taler-util/src/transactions-types.ts b/packages/taler-util/src/transactions-types.ts index 304183ceb..63db206bd 100644 --- a/packages/taler-util/src/transactions-types.ts +++ b/packages/taler-util/src/transactions-types.ts @@ -67,7 +67,7 @@ export interface TransactionsRequest { */ includeRefreshes?: boolean; - filterByState?: TransactionStateFilter + filterByState?: TransactionStateFilter; } export interface TransactionState { @@ -629,6 +629,17 @@ export interface TransactionRefresh extends TransactionCommon { refreshOutputAmount: AmountString; } +export interface DepositTransactionTrackingState { + // Raw wire transfer identifier of the deposit. + wireTransferId: string; + // When was the wire transfer given to the bank. + timestampExecuted: TalerProtocolTimestamp; + // Total amount transfer for this wtid (including fees) + amountRaw: AmountString; + // Wire fee amount for this exchange + wireFee: AmountString; +} + /** * Deposit transaction, which effectively sends * money from this wallet somewhere else. @@ -662,16 +673,7 @@ export interface TransactionDeposit extends TransactionCommon { */ deposited: boolean; - trackingState: Array<{ - // Raw wire transfer identifier of the deposit. - wireTransferId: string; - // When was the wire transfer given to the bank. - timestampExecuted: TalerProtocolTimestamp; - // Total amount transfer for this wtid (including fees) - amountRaw: AmountString; - // Wire fee amount for this exchange - wireFee: AmountString; - }>; + trackingState: Array<DepositTransactionTrackingState>; } export interface TransactionByIdRequest { diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index f7bd3d120..4811d674f 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -73,7 +73,13 @@ import { codecForAbsoluteTime, codecForTimestamp, } from "./time.js"; -import { OrderShortInfo, TransactionType } from "./transactions-types.js"; +import { + OrderShortInfo, + TransactionMajorState, + TransactionMinorState, + TransactionState, + TransactionType, +} from "./transactions-types.js"; /** * Identifier for a transaction in the wallet. @@ -366,7 +372,7 @@ export const codecForAmountResponse = (): Codec<AmountResponse> => .property("rawAmount", codecForAmountString()) .build("AmountResponse"); -export interface Balance { +export interface WalletBalance { scopeInfo: ScopeInfo; available: AmountString; pendingIncoming: AmountString; @@ -458,11 +464,11 @@ export type ScopeInfoAuditor = { export type ScopeInfo = ScopeInfoGlobal | ScopeInfoExchange | ScopeInfoAuditor; export interface BalancesResponse { - balances: Balance[]; + balances: WalletBalance[]; } -export const codecForBalance = (): Codec<Balance> => - buildCodecForObject<Balance>() +export const codecForBalance = (): Codec<WalletBalance> => + buildCodecForObject<WalletBalance>() .property("scopeInfo", codecForAny()) // FIXME .property("available", codecForString()) .property("hasPendingTransactions", codecForBoolean()) @@ -1550,7 +1556,7 @@ export const codecForTestPayArgs = (): Codec<TestPayArgs> => export interface IntegrationTestArgs { exchangeBaseUrl: string; - bankAccessApiBaseUrl: string; + corebankApiBaseUrl: string; merchantBaseUrl: string; merchantAuthToken?: string; amountToWithdraw: string; @@ -1564,12 +1570,12 @@ export const codecForIntegrationTestArgs = (): Codec<IntegrationTestArgs> => .property("merchantAuthToken", codecOptional(codecForString())) .property("amountToSpend", codecForAmountString()) .property("amountToWithdraw", codecForAmountString()) - .property("bankAccessApiBaseUrl", codecForAmountString()) + .property("corebankApiBaseUrl", codecForAmountString()) .build("IntegrationTestArgs"); export interface IntegrationTestV2Args { exchangeBaseUrl: string; - bankAccessApiBaseUrl: string; + corebankApiBaseUrl: string; merchantBaseUrl: string; merchantAuthToken?: string; } @@ -1579,7 +1585,7 @@ export const codecForIntegrationTestV2Args = (): Codec<IntegrationTestV2Args> => .property("exchangeBaseUrl", codecForString()) .property("merchantBaseUrl", codecForString()) .property("merchantAuthToken", codecOptional(codecForString())) - .property("bankAccessApiBaseUrl", codecForAmountString()) + .property("corebankApiBaseUrl", codecForAmountString()) .build("IntegrationTestV2Args"); export interface AddExchangeRequest { @@ -1595,6 +1601,15 @@ export const codecForAddExchangeRequest = (): Codec<AddExchangeRequest> => .property("masterPub", codecOptional(codecForString())) .build("AddExchangeRequest"); +export interface UpdateExchangeEntryRequest { + exchangeBaseUrl: string; +} + +export const codecForUpdateExchangeEntryRequest = (): Codec<UpdateExchangeEntryRequest> => + buildCodecForObject<UpdateExchangeEntryRequest>() + .property("exchangeBaseUrl", codecForString()) + .build("UpdateExchangeEntryRequest"); + export interface ForceExchangeUpdateRequest { exchangeBaseUrl: string; } @@ -1846,9 +1861,9 @@ export interface CoreApiResponseError { export interface WithdrawTestBalanceRequest { amount: string; /** - * Bank access API base URL. + * Corebank API base URL. */ - bankAccessApiBaseUrl: string; + corebankApiBaseUrl: string; exchangeBaseUrl: string; forcedDenomSel?: ForcedDenomSel; } @@ -1921,7 +1936,7 @@ export const codecForWithdrawTestBalance = .property("amount", codecForString()) .property("exchangeBaseUrl", codecForString()) .property("forcedDenomSel", codecForAny()) - .property("bankAccessApiBaseUrl", codecForString()) + .property("corebankApiBaseUrl", codecForString()) .build("WithdrawTestBalanceRequest"); export interface SetCoinSuspendedRequest { @@ -2715,3 +2730,8 @@ export interface WalletContractData { maxDepositFee: AmountString; minimumAge?: number; } + +export interface TestingWaitTransactionRequest { + transactionId: string; + txState: TransactionState; +} |