From d50294f76e0aa357d690a933bb6d696a2f6aef1b Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 2 Nov 2022 17:42:14 +0100 Subject: wallet-core: DB FIXMEs (amount format) --- packages/taler-util/src/amounts.ts | 78 ++++++++++++++++++++++++--------- packages/taler-util/src/wallet-types.ts | 52 +++++++++++----------- 2 files changed, 83 insertions(+), 47 deletions(-) (limited to 'packages/taler-util/src') diff --git a/packages/taler-util/src/amounts.ts b/packages/taler-util/src/amounts.ts index c9a78356e..f59e325b0 100644 --- a/packages/taler-util/src/amounts.ts +++ b/packages/taler-util/src/amounts.ts @@ -103,10 +103,24 @@ export class Amounts { throw Error("not instantiable"); } + static currencyOf(amount: AmountLike) { + const amt = Amounts.parseOrThrow(amount); + return amt.currency; + } + + static zeroOfAmount(amount: AmountLike): AmountJson { + const amt = Amounts.parseOrThrow(amount); + return { + currency: amt.currency, + fraction: 0, + value: 0, + }; + } + /** * Get an amount that represents zero units of a currency. */ - static getZero(currency: string): AmountJson { + static zeroOfCurrency(currency: string): AmountJson { return { currency, fraction: 0, @@ -132,7 +146,7 @@ export class Amounts { static sumOrZero(currency: string, amounts: AmountLike[]): Result { if (amounts.length <= 0) { return { - amount: Amounts.getZero(currency), + amount: Amounts.zeroOfCurrency(currency), saturated: false, }; } @@ -147,9 +161,11 @@ export class Amounts { * * Throws when currencies don't match. */ - static add(first: AmountJson, ...rest: AmountJson[]): Result { - const currency = first.currency; - let value = first.value + Math.floor(first.fraction / amountFractionalBase); + static add(first: AmountLike, ...rest: AmountLike[]): Result { + const firstJ = Amounts.jsonifyAmount(first); + const currency = firstJ.currency; + let value = + firstJ.value + Math.floor(firstJ.fraction / amountFractionalBase); if (value > amountMaxValue) { return { amount: { @@ -160,17 +176,18 @@ export class Amounts { saturated: true, }; } - let fraction = first.fraction % amountFractionalBase; + let fraction = firstJ.fraction % amountFractionalBase; for (const x of rest) { - if (x.currency.toUpperCase() !== currency.toUpperCase()) { - throw Error(`Mismatched currency: ${x.currency} and ${currency}`); + const xJ = Amounts.jsonifyAmount(x); + if (xJ.currency.toUpperCase() !== currency.toUpperCase()) { + throw Error(`Mismatched currency: ${xJ.currency} and ${currency}`); } value = value + - x.value + - Math.floor((fraction + x.fraction) / amountFractionalBase); - fraction = Math.floor((fraction + x.fraction) % amountFractionalBase); + xJ.value + + Math.floor((fraction + xJ.fraction) / amountFractionalBase); + fraction = Math.floor((fraction + xJ.fraction) % amountFractionalBase); if (value > amountMaxValue) { return { amount: { @@ -322,12 +339,27 @@ export class Amounts { * Parse amount in standard string form (like 'EUR:20.5'), * throw if the input is not a valid amount. */ - static parseOrThrow(s: string): AmountJson { - const res = Amounts.parse(s); - if (!res) { - throw Error(`Can't parse amount: "${s}"`); + static parseOrThrow(s: AmountLike): AmountJson { + if (typeof s === "object") { + if (typeof s.currency !== "string") { + throw Error("invalid amount object"); + } + if (typeof s.value !== "number") { + throw Error("invalid amount object"); + } + if (typeof s.fraction !== "number") { + throw Error("invalid amount object"); + } + return { currency: s.currency, value: s.value, fraction: s.fraction }; + } else if (typeof s === "string") { + const res = Amounts.parse(s); + if (!res) { + throw Error(`Can't parse amount: "${s}"`); + } + return res; + } else { + throw Error("invalid amount (illegal type)"); } - return res; } /** @@ -371,10 +403,13 @@ export class Amounts { throw Error("amount can only be multiplied by a positive integer"); } if (n == 0) { - return { amount: Amounts.getZero(a.currency), saturated: false }; + return { + amount: Amounts.zeroOfCurrency(a.currency), + saturated: false, + }; } let x = a; - let acc = Amounts.getZero(a.currency); + let acc = Amounts.zeroOfCurrency(a.currency); while (n > 1) { if (n % 2 == 0) { n = n / 2; @@ -427,9 +462,10 @@ export class Amounts { return x1.currency.toUpperCase() === x2.currency.toUpperCase(); } - static stringifyValue(a: AmountJson, minFractional = 0): string { - const av = a.value + Math.floor(a.fraction / amountFractionalBase); - const af = a.fraction % amountFractionalBase; + static stringifyValue(a: AmountLike, minFractional = 0): string { + const aJ = Amounts.jsonifyAmount(a); + const av = aJ.value + Math.floor(aJ.fraction / amountFractionalBase); + const af = aJ.fraction % amountFractionalBase; let s = av.toString(); if (af) { diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index 5ff906faa..5d1c55b88 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -644,7 +644,7 @@ export enum RefreshReason { */ export interface CoinRefreshRequest { readonly coinPub: string; - readonly amount: AmountJson; + readonly amount: AmountString; } /** @@ -719,12 +719,12 @@ export interface WireFee { /** * Fee for wire transfers. */ - wireFee: AmountJson; + wireFee: AmountString; /** * Fees to close and refund a reserve. */ - closingFee: AmountJson; + closingFee: AmountString; /** * Start date of the fee. @@ -761,9 +761,9 @@ export interface ExchangeGlobalFees { startDate: TalerProtocolTimestamp; endDate: TalerProtocolTimestamp; - historyFee: AmountJson; - accountFee: AmountJson; - purseFee: AmountJson; + historyFee: AmountString; + accountFee: AmountString; + purseFee: AmountString; historyTimeout: TalerProtocolDuration; purseTimeout: TalerProtocolDuration; @@ -782,8 +782,8 @@ const codecForExchangeAccount = (): Codec => const codecForWireFee = (): Codec => buildCodecForObject() .property("sig", codecForString()) - .property("wireFee", codecForAmountJson()) - .property("closingFee", codecForAmountJson()) + .property("wireFee", codecForAmountString()) + .property("closingFee", codecForAmountString()) .property("startStamp", codecForTimestamp) .property("endStamp", codecForTimestamp) .build("codecForWireFee"); @@ -798,7 +798,7 @@ export interface DenominationInfo { /** * Value of one coin of the denomination. */ - value: AmountJson; + value: AmountString; /** * Hash of the denomination public key. @@ -811,22 +811,22 @@ export interface DenominationInfo { /** * Fee for withdrawing. */ - feeWithdraw: AmountJson; + feeWithdraw: AmountString; /** * Fee for depositing. */ - feeDeposit: AmountJson; + feeDeposit: AmountString; /** * Fee for refreshing. */ - feeRefresh: AmountJson; + feeRefresh: AmountString; /** * Fee for refunding. */ - feeRefund: AmountJson; + feeRefund: AmountString; /** * Validity start date of the denomination. @@ -858,21 +858,21 @@ export interface FeeDescription { group: string; from: AbsoluteTime; until: AbsoluteTime; - fee?: AmountJson; + fee?: AmountString; } export interface FeeDescriptionPair { group: string; from: AbsoluteTime; until: AbsoluteTime; - left?: AmountJson; - right?: AmountJson; + left?: AmountString; + right?: AmountString; } export interface TimePoint { id: string; group: string; - fee: AmountJson; + fee: AmountString; type: "start" | "end"; moment: AbsoluteTime; denom: T; @@ -955,8 +955,8 @@ export const codecForFeeDescriptionPair = (): Codec => .property("group", codecForString()) .property("from", codecForAbsoluteTime) .property("until", codecForAbsoluteTime) - .property("left", codecOptional(codecForAmountJson())) - .property("right", codecOptional(codecForAmountJson())) + .property("left", codecOptional(codecForAmountString())) + .property("right", codecOptional(codecForAmountString())) .build("FeeDescriptionPair"); export const codecForFeeDescription = (): Codec => @@ -964,7 +964,7 @@ export const codecForFeeDescription = (): Codec => .property("group", codecForString()) .property("from", codecForAbsoluteTime) .property("until", codecForAbsoluteTime) - .property("fee", codecOptional(codecForAmountJson())) + .property("fee", codecOptional(codecForAmountString())) .build("FeeDescription"); export const codecForFeesByOperations = (): Codec< @@ -1056,8 +1056,8 @@ export interface ManualWithdrawalDetails { * Selected denominations withn some extra info. */ export interface DenomSelectionState { - totalCoinValue: AmountJson; - totalWithdrawCost: AmountJson; + totalCoinValue: AmountString; + totalWithdrawCost: AmountString; selectedDenoms: { denomPubHash: string; count: number; @@ -1786,7 +1786,7 @@ export interface PayCoinSelection { /** * Amount requested by the merchant. */ - paymentAmount: AmountJson; + paymentAmount: AmountString; /** * Public keys of the coins that were selected. @@ -1796,17 +1796,17 @@ export interface PayCoinSelection { /** * Amount that each coin contributes. */ - coinContributions: AmountJson[]; + coinContributions: AmountString[]; /** * How much of the wire fees is the customer paying? */ - customerWireFees: AmountJson; + customerWireFees: AmountString; /** * How much of the deposit fees is the customer paying? */ - customerDepositFees: AmountJson; + customerDepositFees: AmountString; } export interface InitiatePeerPushPaymentRequest { -- cgit v1.2.3