aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-util/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-util/src')
-rw-r--r--packages/taler-util/src/MerchantApiClient.ts2
-rw-r--r--packages/taler-util/src/amounts.ts5
-rw-r--r--packages/taler-util/src/bank-api-client.ts173
-rw-r--r--packages/taler-util/src/errors.ts2
-rw-r--r--packages/taler-util/src/payto.ts11
-rw-r--r--packages/taler-util/src/taler-types.ts13
-rw-r--r--packages/taler-util/src/talerconfig.ts6
-rw-r--r--packages/taler-util/src/time.ts6
-rw-r--r--packages/taler-util/src/transactions-types.ts24
-rw-r--r--packages/taler-util/src/wallet-types.ts44
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;
+}