wallet-core: towards DD37 for deposits
This commit is contained in:
parent
8eee38d559
commit
2823b1cdf4
@ -60,10 +60,4 @@ export async function runDepositTest(t: GlobalTestState) {
|
||||
// The raw amount is what ends up on the bank account, which includes
|
||||
// deposit and wire fees.
|
||||
t.assertDeepEqual(transactions.transactions[1].amountRaw, "TESTKUDOS:9.79");
|
||||
|
||||
const trackResult = wallet.client.call(WalletApiOperation.TrackDepositGroup, {
|
||||
depositGroupId,
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(trackResult, undefined, 2));
|
||||
}
|
||||
|
@ -104,6 +104,8 @@ export interface HttpRequestLibrary {
|
||||
* Make an HTTP GET request.
|
||||
*
|
||||
* FIXME: Get rid of this, we want the API surface to be minimal.
|
||||
*
|
||||
* @deprecated use fetch instead
|
||||
*/
|
||||
get(url: string, opt?: HttpRequestOptions): Promise<HttpResponse>;
|
||||
|
||||
@ -111,6 +113,8 @@ export interface HttpRequestLibrary {
|
||||
* Make an HTTP POST request with a JSON body.
|
||||
*
|
||||
* FIXME: Get rid of this, we want the API surface to be minimal.
|
||||
*
|
||||
* @deprecated use fetch instead
|
||||
*/
|
||||
postJson(
|
||||
url: string,
|
||||
|
@ -59,6 +59,11 @@ export enum ExtendedStatus {
|
||||
KycRequired = "kyc-required",
|
||||
}
|
||||
|
||||
export interface TransactionStateInfo {
|
||||
txState: TransactionState;
|
||||
txSubstate: TransactionSubstate;
|
||||
}
|
||||
|
||||
export interface TransactionsRequest {
|
||||
/**
|
||||
* return only transactions in the given currency
|
||||
@ -76,6 +81,32 @@ export interface TransactionsRequest {
|
||||
includeRefreshes?: boolean;
|
||||
}
|
||||
|
||||
export enum TransactionState {
|
||||
// No state, only used when reporting transitions into the initial state
|
||||
None = "none",
|
||||
Pending = "pending",
|
||||
Done = "done",
|
||||
Aborting = "aborting",
|
||||
Aborted = "aborted",
|
||||
Suspended = "suspended",
|
||||
Failed = "failed",
|
||||
// Only used for the notification, never in the transaction history
|
||||
Deleted = "deleted",
|
||||
// Placeholder until D37 is fully implemented
|
||||
Unknown = "unknown",
|
||||
}
|
||||
|
||||
export enum TransactionSubstate {
|
||||
// Placeholder until D37 is fully implemented
|
||||
Unknown = "unknown",
|
||||
// No substate
|
||||
None = "none",
|
||||
DepositPendingInitial = "initial",
|
||||
DepositKycRequired = "kyc-required",
|
||||
DepositPendingTrack = "track",
|
||||
DepositAbortingRefresh = "refresh",
|
||||
}
|
||||
|
||||
export interface TransactionsResponse {
|
||||
// a list of past and pending transactions sorted by pending, timestamp and transactionId.
|
||||
// In case two events are both pending and have the same timestamp,
|
||||
@ -95,6 +126,13 @@ export interface TransactionCommon {
|
||||
// main timestamp of the transaction
|
||||
timestamp: TalerProtocolTimestamp;
|
||||
|
||||
txState: TransactionState;
|
||||
|
||||
txSubstate: TransactionSubstate;
|
||||
|
||||
/**
|
||||
* @deprecated in favor of statusMajor and statusMinor
|
||||
*/
|
||||
extendedStatus: ExtendedStatus;
|
||||
|
||||
/**
|
||||
|
@ -1228,8 +1228,7 @@ export const codecForTestPayArgs = (): Codec<TestPayArgs> =>
|
||||
|
||||
export interface IntegrationTestArgs {
|
||||
exchangeBaseUrl: string;
|
||||
bankBaseUrl: string;
|
||||
bankAccessApiBaseUrl?: string;
|
||||
bankAccessApiBaseUrl: string;
|
||||
merchantBaseUrl: string;
|
||||
merchantAuthToken?: string;
|
||||
amountToWithdraw: string;
|
||||
@ -1239,12 +1238,11 @@ export interface IntegrationTestArgs {
|
||||
export const codecForIntegrationTestArgs = (): Codec<IntegrationTestArgs> =>
|
||||
buildCodecForObject<IntegrationTestArgs>()
|
||||
.property("exchangeBaseUrl", codecForString())
|
||||
.property("bankBaseUrl", codecForString())
|
||||
.property("merchantBaseUrl", codecForString())
|
||||
.property("merchantAuthToken", codecOptional(codecForString()))
|
||||
.property("amountToSpend", codecForAmountString())
|
||||
.property("amountToWithdraw", codecForAmountString())
|
||||
.property("bankAccessApiBaseUrl", codecOptional(codecForAmountString()))
|
||||
.property("bankAccessApiBaseUrl", codecForAmountString())
|
||||
.build("IntegrationTestArgs");
|
||||
|
||||
export interface IntegrationTestV2Args {
|
||||
@ -1499,11 +1497,10 @@ export interface CoreApiResponseError {
|
||||
|
||||
export interface WithdrawTestBalanceRequest {
|
||||
amount: string;
|
||||
bankBaseUrl: string;
|
||||
/**
|
||||
* Bank access API base URL. Defaults to the bankBaseUrl.
|
||||
* Bank access API base URL.
|
||||
*/
|
||||
bankAccessApiBaseUrl?: string;
|
||||
bankAccessApiBaseUrl: string;
|
||||
exchangeBaseUrl: string;
|
||||
forcedDenomSel?: ForcedDenomSel;
|
||||
}
|
||||
@ -1580,10 +1577,9 @@ export const codecForWithdrawTestBalance =
|
||||
(): Codec<WithdrawTestBalanceRequest> =>
|
||||
buildCodecForObject<WithdrawTestBalanceRequest>()
|
||||
.property("amount", codecForString())
|
||||
.property("bankBaseUrl", codecForString())
|
||||
.property("exchangeBaseUrl", codecForString())
|
||||
.property("forcedDenomSel", codecForAny())
|
||||
.property("bankAccessApiBaseUrl", codecOptional(codecForString()))
|
||||
.property("bankAccessApiBaseUrl", codecForString())
|
||||
.build("WithdrawTestBalanceRequest");
|
||||
|
||||
export interface ApplyRefundResponse {
|
||||
@ -1702,11 +1698,6 @@ export const codecForAbortTransaction = (): Codec<AbortTransactionRequest> =>
|
||||
.property("forceImmediateAbort", codecOptional(codecForBoolean()))
|
||||
.build("AbortTransactionRequest");
|
||||
|
||||
export interface GetFeeForDepositRequest {
|
||||
depositPaytoUri: string;
|
||||
amount: AmountString;
|
||||
}
|
||||
|
||||
export interface DepositGroupFees {
|
||||
coin: AmountString;
|
||||
wire: AmountString;
|
||||
@ -1718,12 +1709,6 @@ export interface CreateDepositGroupRequest {
|
||||
amount: AmountString;
|
||||
}
|
||||
|
||||
export const codecForGetFeeForDeposit = (): Codec<GetFeeForDepositRequest> =>
|
||||
buildCodecForObject<GetFeeForDepositRequest>()
|
||||
.property("amount", codecForAmountString())
|
||||
.property("depositPaytoUri", codecForString())
|
||||
.build("GetFeeForDepositRequest");
|
||||
|
||||
export interface PrepareDepositRequest {
|
||||
depositPaytoUri: string;
|
||||
amount: AmountString;
|
||||
@ -1751,20 +1736,6 @@ export interface CreateDepositGroupResponse {
|
||||
transactionId: string;
|
||||
}
|
||||
|
||||
export interface TrackDepositGroupRequest {
|
||||
depositGroupId: string;
|
||||
}
|
||||
|
||||
export interface TrackDepositGroupResponse {
|
||||
responses: TrackTransaction[];
|
||||
}
|
||||
|
||||
export const codecForTrackDepositGroupRequest =
|
||||
(): Codec<TrackDepositGroupRequest> =>
|
||||
buildCodecForObject<TrackDepositGroupRequest>()
|
||||
.property("depositGroupId", codecForAmountString())
|
||||
.build("TrackDepositGroupRequest");
|
||||
|
||||
export interface WithdrawUriInfoResponse {
|
||||
amount: AmountString;
|
||||
defaultExchangeBaseUrl?: string;
|
||||
|
@ -37,7 +37,6 @@ import {
|
||||
setDangerousTimetravel,
|
||||
setGlobalLogLevelFromString,
|
||||
summarizeTalerErrorDetail,
|
||||
TalerProtocolTimestamp,
|
||||
TalerUriType,
|
||||
WalletNotification,
|
||||
} from "@gnu-taler/taler-util";
|
||||
@ -928,22 +927,6 @@ depositCli
|
||||
});
|
||||
});
|
||||
|
||||
// FIXME: should probably be removed
|
||||
depositCli
|
||||
.subcommand("trackDepositArgs", "track")
|
||||
.requiredArgument("depositGroupId", clk.STRING)
|
||||
.action(async (args) => {
|
||||
await withWallet(args, async (wallet) => {
|
||||
const resp = await wallet.client.call(
|
||||
WalletApiOperation.TrackDepositGroup,
|
||||
{
|
||||
depositGroupId: args.trackDepositArgs.depositGroupId,
|
||||
},
|
||||
);
|
||||
console.log(JSON.stringify(resp, undefined, 2));
|
||||
});
|
||||
});
|
||||
|
||||
const peerCli = walletCli.subcommand("peerArgs", "p2p", {
|
||||
help: "Subcommands for peer-to-peer payments.",
|
||||
});
|
||||
@ -1244,7 +1227,6 @@ advancedCli
|
||||
await wallet.client.call(WalletApiOperation.RunIntegrationTest, {
|
||||
amountToSpend: "TESTKUDOS:1",
|
||||
amountToWithdraw: "TESTKUDOS:3",
|
||||
bankBaseUrl: "http://localhost:8082/",
|
||||
bankAccessApiBaseUrl: "http://localhost:8082/taler-bank-access/",
|
||||
exchangeBaseUrl: "http://localhost:8081/",
|
||||
merchantBaseUrl: "http://localhost:8083/",
|
||||
@ -1502,6 +1484,17 @@ testCli
|
||||
});
|
||||
});
|
||||
|
||||
testCli.subcommand("withdrawKudos", "withdraw-kudos").action(async (args) => {
|
||||
await withWallet(args, async (wallet) => {
|
||||
await wallet.client.call(WalletApiOperation.WithdrawTestBalance, {
|
||||
amount: "KUDOS:50",
|
||||
bankAccessApiBaseUrl:
|
||||
"https://bank.demo.taler.net/demobanks/default/access-api/",
|
||||
exchangeBaseUrl: "https://exchange.demo.taler.net/",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class PerfTimer {
|
||||
tStarted: bigint | undefined;
|
||||
tSum = BigInt(0);
|
||||
|
@ -858,6 +858,13 @@ export enum RefreshOperationStatus {
|
||||
FinishedWithError = 51 /* DORMANT_START + 1 */,
|
||||
}
|
||||
|
||||
export enum DepositGroupOperationStatus {
|
||||
Finished = 50 /* DORMANT_START */,
|
||||
Failed = 51 /* DORMANT_START + 1 */,
|
||||
Pending = 10 /* ACTIVE_START */,
|
||||
AbortingWithRefresh = 11 /* ACTIVE_START + 1 */,
|
||||
}
|
||||
|
||||
export enum TransactionStatus {
|
||||
Unknown = 10,
|
||||
Accepted = 20,
|
||||
@ -1662,6 +1669,11 @@ export interface DepositGroupRecord {
|
||||
|
||||
totalPayCost: AmountString;
|
||||
|
||||
/**
|
||||
* The counterparty effective deposit amount.
|
||||
*
|
||||
* FIXME: If possible, rename to counterpartyEffectiveDepositAmount.
|
||||
*/
|
||||
effectiveDepositAmount: AmountString;
|
||||
|
||||
depositedPerCoin: boolean[];
|
||||
|
@ -363,6 +363,8 @@ export enum TombstoneTag {
|
||||
|
||||
/**
|
||||
* Create an event ID from the type and the primary key for the event.
|
||||
*
|
||||
* @deprecated use constructTransactionIdentifier instead
|
||||
*/
|
||||
export function makeTransactionId(
|
||||
type: TransactionType,
|
||||
|
@ -33,7 +33,6 @@ import {
|
||||
durationFromSpec,
|
||||
encodeCrock,
|
||||
ExchangeDepositRequest,
|
||||
GetFeeForDepositRequest,
|
||||
getRandomBytes,
|
||||
hashTruncate32,
|
||||
hashWire,
|
||||
@ -41,7 +40,6 @@ import {
|
||||
j2s,
|
||||
Logger,
|
||||
MerchantContractTerms,
|
||||
NotificationType,
|
||||
parsePaytoUri,
|
||||
PayCoinSelection,
|
||||
PrepareDepositRequest,
|
||||
@ -50,18 +48,16 @@ import {
|
||||
stringToBytes,
|
||||
TalerErrorCode,
|
||||
TalerProtocolTimestamp,
|
||||
TrackDepositGroupRequest,
|
||||
TrackDepositGroupResponse,
|
||||
TrackTransaction,
|
||||
TrackTransactionWired,
|
||||
TransactionState,
|
||||
TransactionStateInfo,
|
||||
TransactionSubstate,
|
||||
TransactionType,
|
||||
URL,
|
||||
WireFee,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import {
|
||||
DenominationRecord,
|
||||
DepositGroupRecord,
|
||||
ExchangeDetailsRecord,
|
||||
OperationStatus,
|
||||
TransactionStatus,
|
||||
} from "../db.js";
|
||||
@ -77,7 +73,6 @@ import {
|
||||
generateDepositPermissions,
|
||||
getTotalPaymentCost,
|
||||
} from "./pay-merchant.js";
|
||||
import { getTotalRefreshCost } from "./refresh.js";
|
||||
import { selectPayCoinsNew } from "../util/coinSelection.js";
|
||||
|
||||
/**
|
||||
@ -85,7 +80,81 @@ import { selectPayCoinsNew } from "../util/coinSelection.js";
|
||||
*/
|
||||
const logger = new Logger("deposits.ts");
|
||||
|
||||
export async function checkDepositKycStatus(
|
||||
export async function computeDepositTransactionStatus(
|
||||
ws: InternalWalletState,
|
||||
dg: DepositGroupRecord,
|
||||
): Promise<TransactionStateInfo> {
|
||||
switch (dg.operationStatus) {
|
||||
case OperationStatus.Finished: {
|
||||
return {
|
||||
txState: TransactionState.Done,
|
||||
txSubstate: TransactionSubstate.None,
|
||||
};
|
||||
}
|
||||
case OperationStatus.Pending: {
|
||||
const numTotal = dg.payCoinSelection.coinPubs.length;
|
||||
let numDeposited = 0;
|
||||
let numKycRequired = 0;
|
||||
let numWired = 0;
|
||||
for (let i = 0; i < numTotal; i++) {
|
||||
if (dg.depositedPerCoin[i]) {
|
||||
numDeposited++;
|
||||
}
|
||||
switch (dg.transactionPerCoin[i]) {
|
||||
case TransactionStatus.KycRequired:
|
||||
numKycRequired++;
|
||||
break;
|
||||
case TransactionStatus.Wired:
|
||||
numWired++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (numKycRequired > 0) {
|
||||
return {
|
||||
txState: TransactionState.Pending,
|
||||
txSubstate: TransactionSubstate.DepositKycRequired,
|
||||
};
|
||||
}
|
||||
|
||||
if (numDeposited == numTotal) {
|
||||
return {
|
||||
txState: TransactionState.Pending,
|
||||
txSubstate: TransactionSubstate.DepositPendingTrack,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
txState: TransactionState.Pending,
|
||||
txSubstate: TransactionSubstate.DepositPendingInitial,
|
||||
};
|
||||
}
|
||||
default:
|
||||
throw Error("unexpected deposit group state");
|
||||
}
|
||||
}
|
||||
|
||||
export async function suspendDepositGroup(
|
||||
ws: InternalWalletState,
|
||||
depositGroupId: string,
|
||||
): Promise<void> {
|
||||
throw Error("not implemented");
|
||||
}
|
||||
|
||||
export async function abortDepositGroup(
|
||||
ws: InternalWalletState,
|
||||
depositGroupId: string,
|
||||
): Promise<void> {
|
||||
throw Error("not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check KYC status with the exchange, throw an appropriate exception when KYC is required.
|
||||
*
|
||||
* FIXME: Why does this throw an exception when KYC is required?
|
||||
* Should we not return some proper result record here?
|
||||
*/
|
||||
async function checkDepositKycStatus(
|
||||
ws: InternalWalletState,
|
||||
exchangeUrl: string,
|
||||
kycInfo: KycPendingInfo,
|
||||
@ -119,7 +188,7 @@ export async function checkDepositKycStatus(
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {processDepositGroup}
|
||||
* Process a deposit group that is not in its final state yet.
|
||||
*/
|
||||
export async function processDepositGroup(
|
||||
ws: InternalWalletState,
|
||||
@ -181,7 +250,9 @@ export async function processDepositGroup(
|
||||
options.cancellationToken?.throwIfCancelled();
|
||||
const url = new URL(`coins/${perm.coin_pub}/deposit`, perm.exchange_url);
|
||||
logger.info(`depositing to ${url}`);
|
||||
const httpResp = await ws.http.postJson(url.href, requestBody, {
|
||||
const httpResp = await ws.http.fetch(url.href, {
|
||||
method: "POST",
|
||||
body: requestBody,
|
||||
cancellationToken: options.cancellationToken,
|
||||
});
|
||||
await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess());
|
||||
@ -198,7 +269,6 @@ export async function processDepositGroup(
|
||||
}
|
||||
| undefined;
|
||||
|
||||
let signature: string | undefined;
|
||||
if (depositGroup.transactionPerCoin[i] !== TransactionStatus.Wired) {
|
||||
const track = await trackDepositPermission(ws, depositGroup, perm);
|
||||
|
||||
@ -347,39 +417,6 @@ async function getExchangeWireFee(
|
||||
return fee;
|
||||
}
|
||||
|
||||
export async function trackDepositGroup(
|
||||
ws: InternalWalletState,
|
||||
req: TrackDepositGroupRequest,
|
||||
): Promise<TrackDepositGroupResponse> {
|
||||
const responses: TrackTransaction[] = [];
|
||||
const depositGroup = await ws.db
|
||||
.mktx((x) => [x.depositGroups])
|
||||
.runReadOnly(async (tx) => {
|
||||
return tx.depositGroups.get(req.depositGroupId);
|
||||
});
|
||||
if (!depositGroup) {
|
||||
throw Error("deposit group not found");
|
||||
}
|
||||
const contractData = extractContractData(
|
||||
depositGroup.contractTermsRaw,
|
||||
depositGroup.contractTermsHash,
|
||||
"",
|
||||
);
|
||||
|
||||
const depositPermissions = await generateDepositPermissions(
|
||||
ws,
|
||||
depositGroup.payCoinSelection,
|
||||
contractData,
|
||||
);
|
||||
|
||||
for (const dp of depositPermissions) {
|
||||
const track = await trackDepositPermission(ws, depositGroup, dp);
|
||||
responses.push(track);
|
||||
}
|
||||
|
||||
return { responses };
|
||||
}
|
||||
|
||||
async function trackDepositPermission(
|
||||
ws: InternalWalletState,
|
||||
depositGroup: DepositGroupRecord,
|
||||
@ -423,66 +460,10 @@ async function trackDepositPermission(
|
||||
}
|
||||
}
|
||||
|
||||
export async function getFeeForDeposit(
|
||||
ws: InternalWalletState,
|
||||
req: GetFeeForDepositRequest,
|
||||
): Promise<DepositGroupFees> {
|
||||
const p = parsePaytoUri(req.depositPaytoUri);
|
||||
if (!p) {
|
||||
throw Error("invalid payto URI");
|
||||
}
|
||||
|
||||
const amount = Amounts.parseOrThrow(req.amount);
|
||||
|
||||
const exchangeInfos: { url: string; master_pub: string }[] = [];
|
||||
|
||||
await ws.db
|
||||
.mktx((x) => [x.exchanges, x.exchangeDetails])
|
||||
.runReadOnly(async (tx) => {
|
||||
const allExchanges = await tx.exchanges.iter().toArray();
|
||||
for (const e of allExchanges) {
|
||||
const details = await getExchangeDetails(tx, e.baseUrl);
|
||||
if (!details || amount.currency !== details.currency) {
|
||||
continue;
|
||||
}
|
||||
exchangeInfos.push({
|
||||
master_pub: details.masterPublicKey,
|
||||
url: e.baseUrl,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const payCoinSel = await selectPayCoinsNew(ws, {
|
||||
auditors: [],
|
||||
exchanges: Object.values(exchangeInfos).map((v) => ({
|
||||
exchangeBaseUrl: v.url,
|
||||
exchangePub: v.master_pub,
|
||||
})),
|
||||
wireMethod: p.targetType,
|
||||
contractTermsAmount: Amounts.parseOrThrow(req.amount),
|
||||
depositFeeLimit: Amounts.parseOrThrow(req.amount),
|
||||
wireFeeAmortization: 1,
|
||||
wireFeeLimit: Amounts.parseOrThrow(req.amount),
|
||||
prevPayCoins: [],
|
||||
});
|
||||
|
||||
if (payCoinSel.type !== "success") {
|
||||
throw TalerError.fromDetail(
|
||||
TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE,
|
||||
{
|
||||
insufficientBalanceDetails: payCoinSel.insufficientBalanceDetails,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return await getTotalFeesForDepositAmount(
|
||||
ws,
|
||||
p.targetType,
|
||||
amount,
|
||||
payCoinSel.coinSel,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* FIXME: This should be renamed to checkDepositGroup,
|
||||
* as it doesn't prepare anything
|
||||
*/
|
||||
export async function prepareDepositGroup(
|
||||
ws: InternalWalletState,
|
||||
req: PrepareDepositRequest,
|
||||
@ -569,7 +550,7 @@ export async function prepareDepositGroup(
|
||||
|
||||
const totalDepositCost = await getTotalPaymentCost(ws, payCoinSel.coinSel);
|
||||
|
||||
const effectiveDepositAmount = await getEffectiveDepositAmount(
|
||||
const effectiveDepositAmount = await getCounterpartyEffectiveDepositAmount(
|
||||
ws,
|
||||
p.targetType,
|
||||
payCoinSel.coinSel,
|
||||
@ -674,11 +655,12 @@ export async function createDepositGroup(
|
||||
|
||||
const depositGroupId = encodeCrock(getRandomBytes(32));
|
||||
|
||||
const effectiveDepositAmount = await getEffectiveDepositAmount(
|
||||
ws,
|
||||
p.targetType,
|
||||
payCoinSel.coinSel,
|
||||
);
|
||||
const countarpartyEffectiveDepositAmount =
|
||||
await getCounterpartyEffectiveDepositAmount(
|
||||
ws,
|
||||
p.targetType,
|
||||
payCoinSel.coinSel,
|
||||
);
|
||||
|
||||
const depositGroup: DepositGroupRecord = {
|
||||
contractTermsHash,
|
||||
@ -697,7 +679,9 @@ export async function createDepositGroup(
|
||||
merchantPriv: merchantPair.priv,
|
||||
merchantPub: merchantPair.pub,
|
||||
totalPayCost: Amounts.stringify(totalDepositCost),
|
||||
effectiveDepositAmount: Amounts.stringify(effectiveDepositAmount),
|
||||
effectiveDepositAmount: Amounts.stringify(
|
||||
countarpartyEffectiveDepositAmount,
|
||||
),
|
||||
wire: {
|
||||
payto_uri: req.depositPaytoUri,
|
||||
salt: wireSalt,
|
||||
@ -733,10 +717,10 @@ export async function createDepositGroup(
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the amount that will be deposited on the merchant's bank
|
||||
* account, not considering aggregation.
|
||||
* Get the amount that will be deposited on the users bank
|
||||
* account after depositing, not considering aggregation.
|
||||
*/
|
||||
export async function getEffectiveDepositAmount(
|
||||
export async function getCounterpartyEffectiveDepositAmount(
|
||||
ws: InternalWalletState,
|
||||
wireType: string,
|
||||
pcs: PayCoinSelection,
|
||||
@ -790,83 +774,3 @@ export async function getEffectiveDepositAmount(
|
||||
});
|
||||
return Amounts.sub(Amounts.sum(amt).amount, Amounts.sum(fees).amount).amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fee amount that will be charged when trying to deposit the
|
||||
* specified amount using the selected coins and the wire method.
|
||||
*/
|
||||
export async function getTotalFeesForDepositAmount(
|
||||
ws: InternalWalletState,
|
||||
wireType: string,
|
||||
total: AmountJson,
|
||||
pcs: PayCoinSelection,
|
||||
): Promise<DepositGroupFees> {
|
||||
const wireFee: AmountJson[] = [];
|
||||
const coinFee: AmountJson[] = [];
|
||||
const refreshFee: AmountJson[] = [];
|
||||
const exchangeSet: Set<string> = new Set();
|
||||
|
||||
await ws.db
|
||||
.mktx((x) => [x.coins, x.denominations, x.exchanges, x.exchangeDetails])
|
||||
.runReadOnly(async (tx) => {
|
||||
for (let i = 0; i < pcs.coinPubs.length; i++) {
|
||||
const coin = await tx.coins.get(pcs.coinPubs[i]);
|
||||
if (!coin) {
|
||||
throw Error("can't calculate deposit amount, coin not found");
|
||||
}
|
||||
const denom = await ws.getDenomInfo(
|
||||
ws,
|
||||
tx,
|
||||
coin.exchangeBaseUrl,
|
||||
coin.denomPubHash,
|
||||
);
|
||||
if (!denom) {
|
||||
throw Error("can't find denomination to calculate deposit amount");
|
||||
}
|
||||
coinFee.push(Amounts.parseOrThrow(denom.feeDeposit));
|
||||
exchangeSet.add(coin.exchangeBaseUrl);
|
||||
|
||||
const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
|
||||
.iter(coin.exchangeBaseUrl)
|
||||
.filter((x) =>
|
||||
Amounts.isSameCurrency(
|
||||
DenominationRecord.getValue(x),
|
||||
pcs.coinContributions[i],
|
||||
),
|
||||
);
|
||||
const amountLeft = Amounts.sub(
|
||||
denom.value,
|
||||
pcs.coinContributions[i],
|
||||
).amount;
|
||||
const refreshCost = getTotalRefreshCost(allDenoms, denom, amountLeft);
|
||||
refreshFee.push(refreshCost);
|
||||
}
|
||||
|
||||
for (const exchangeUrl of exchangeSet.values()) {
|
||||
const exchangeDetails = await getExchangeDetails(tx, exchangeUrl);
|
||||
if (!exchangeDetails) {
|
||||
continue;
|
||||
}
|
||||
const fee = exchangeDetails.wireInfo.feesForType[wireType]?.find(
|
||||
(x) => {
|
||||
return AbsoluteTime.isBetween(
|
||||
AbsoluteTime.now(),
|
||||
AbsoluteTime.fromTimestamp(x.startStamp),
|
||||
AbsoluteTime.fromTimestamp(x.endStamp),
|
||||
);
|
||||
},
|
||||
)?.wireFee;
|
||||
if (fee) {
|
||||
wireFee.push(Amounts.parseOrThrow(fee));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
coin: Amounts.stringify(Amounts.sumOrZero(total.currency, coinFee).amount),
|
||||
wire: Amounts.stringify(Amounts.sumOrZero(total.currency, wireFee).amount),
|
||||
refresh: Amounts.stringify(
|
||||
Amounts.sumOrZero(total.currency, refreshFee).amount,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ export async function withdrawTestBalance(
|
||||
): Promise<void> {
|
||||
const amount = req.amount;
|
||||
const exchangeBaseUrl = req.exchangeBaseUrl;
|
||||
const bankAccessApiBaseUrl = req.bankAccessApiBaseUrl ?? req.bankBaseUrl;
|
||||
const bankAccessApiBaseUrl = req.bankAccessApiBaseUrl;
|
||||
|
||||
logger.trace(
|
||||
`Registered bank user, bank access base url ${bankAccessApiBaseUrl}`,
|
||||
@ -364,8 +364,7 @@ export async function runIntegrationTest(
|
||||
logger.info("withdrawing test balance");
|
||||
await withdrawTestBalance(ws, {
|
||||
amount: args.amountToWithdraw,
|
||||
bankBaseUrl: args.bankBaseUrl,
|
||||
bankAccessApiBaseUrl: args.bankAccessApiBaseUrl ?? args.bankBaseUrl,
|
||||
bankAccessApiBaseUrl: args.bankAccessApiBaseUrl,
|
||||
exchangeBaseUrl: args.exchangeBaseUrl,
|
||||
});
|
||||
await ws.runUntilDone();
|
||||
@ -393,8 +392,7 @@ export async function runIntegrationTest(
|
||||
|
||||
await withdrawTestBalance(ws, {
|
||||
amount: Amounts.stringify(withdrawAmountTwo),
|
||||
bankBaseUrl: args.bankBaseUrl,
|
||||
bankAccessApiBaseUrl: args.bankAccessApiBaseUrl ?? args.bankBaseUrl,
|
||||
bankAccessApiBaseUrl: args.bankAccessApiBaseUrl,
|
||||
exchangeBaseUrl: args.exchangeBaseUrl,
|
||||
});
|
||||
|
||||
@ -457,7 +455,6 @@ export async function runIntegrationTest2(
|
||||
logger.info("withdrawing test balance");
|
||||
await withdrawTestBalance(ws, {
|
||||
amount: Amounts.stringify(amountToWithdraw),
|
||||
bankBaseUrl: args.bankAccessApiBaseUrl /* FIXME: not necessary */,
|
||||
bankAccessApiBaseUrl: args.bankAccessApiBaseUrl,
|
||||
exchangeBaseUrl: args.exchangeBaseUrl,
|
||||
});
|
||||
@ -491,7 +488,6 @@ export async function runIntegrationTest2(
|
||||
|
||||
await withdrawTestBalance(ws, {
|
||||
amount: Amounts.stringify(withdrawAmountTwo),
|
||||
bankBaseUrl: args.bankAccessApiBaseUrl /* FIXME: not necessary */,
|
||||
bankAccessApiBaseUrl: args.bankAccessApiBaseUrl,
|
||||
exchangeBaseUrl: args.exchangeBaseUrl,
|
||||
});
|
||||
|
@ -36,6 +36,8 @@ import {
|
||||
TransactionByIdRequest,
|
||||
TransactionsRequest,
|
||||
TransactionsResponse,
|
||||
TransactionState,
|
||||
TransactionSubstate,
|
||||
TransactionType,
|
||||
WithdrawalType,
|
||||
} from "@gnu-taler/taler-util";
|
||||
@ -429,6 +431,8 @@ function buildTransactionForPushPaymentDebit(
|
||||
): Transaction {
|
||||
return {
|
||||
type: TransactionType.PeerPushDebit,
|
||||
txState: TransactionState.Unknown,
|
||||
txSubstate: TransactionSubstate.Unknown,
|
||||
amountEffective: pi.totalCost,
|
||||
amountRaw: pi.amount,
|
||||
exchangeBaseUrl: pi.exchangeBaseUrl,
|
||||
@ -461,6 +465,8 @@ function buildTransactionForPullPaymentDebit(
|
||||
): Transaction {
|
||||
return {
|
||||
type: TransactionType.PeerPullDebit,
|
||||
txState: TransactionState.Unknown,
|
||||
txSubstate: TransactionSubstate.Unknown,
|
||||
amountEffective: pi.coinSel?.totalCost
|
||||
? pi.coinSel?.totalCost
|
||||
: Amounts.stringify(pi.contractTerms.amount),
|
||||
@ -510,6 +516,8 @@ function buildTransactionForPeerPullCredit(
|
||||
});
|
||||
return {
|
||||
type: TransactionType.PeerPullCredit,
|
||||
txState: TransactionState.Unknown,
|
||||
txSubstate: TransactionSubstate.Unknown,
|
||||
amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
|
||||
amountRaw: Amounts.stringify(wsr.instructedAmount),
|
||||
exchangeBaseUrl: wsr.exchangeBaseUrl,
|
||||
@ -543,6 +551,8 @@ function buildTransactionForPeerPullCredit(
|
||||
|
||||
return {
|
||||
type: TransactionType.PeerPullCredit,
|
||||
txState: TransactionState.Unknown,
|
||||
txSubstate: TransactionSubstate.Unknown,
|
||||
amountEffective: Amounts.stringify(pullCredit.estimatedAmountEffective),
|
||||
amountRaw: Amounts.stringify(peerContractTerms.amount),
|
||||
exchangeBaseUrl: pullCredit.exchangeBaseUrl,
|
||||
@ -580,6 +590,8 @@ function buildTransactionForPeerPushCredit(
|
||||
|
||||
return {
|
||||
type: TransactionType.PeerPushCredit,
|
||||
txState: TransactionState.Unknown,
|
||||
txSubstate: TransactionSubstate.Unknown,
|
||||
amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
|
||||
amountRaw: Amounts.stringify(wsr.instructedAmount),
|
||||
exchangeBaseUrl: wsr.exchangeBaseUrl,
|
||||
@ -603,6 +615,8 @@ function buildTransactionForPeerPushCredit(
|
||||
|
||||
return {
|
||||
type: TransactionType.PeerPushCredit,
|
||||
txState: TransactionState.Unknown,
|
||||
txSubstate: TransactionSubstate.Unknown,
|
||||
// FIXME: This is wrong, needs to consider fees!
|
||||
amountEffective: Amounts.stringify(peerContractTerms.amount),
|
||||
amountRaw: Amounts.stringify(peerContractTerms.amount),
|
||||
@ -632,6 +646,8 @@ function buildTransactionForBankIntegratedWithdraw(
|
||||
|
||||
return {
|
||||
type: TransactionType.Withdrawal,
|
||||
txState: TransactionState.Unknown,
|
||||
txSubstate: TransactionSubstate.Unknown,
|
||||
amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
|
||||
amountRaw: Amounts.stringify(wsr.instructedAmount),
|
||||
withdrawalDetails: {
|
||||
@ -677,6 +693,8 @@ function buildTransactionForManualWithdraw(
|
||||
|
||||
return {
|
||||
type: TransactionType.Withdrawal,
|
||||
txState: TransactionState.Unknown,
|
||||
txSubstate: TransactionSubstate.Unknown,
|
||||
amountEffective: Amounts.stringify(
|
||||
withdrawalGroup.denomsSel.totalCoinValue,
|
||||
),
|
||||
@ -727,6 +745,8 @@ function buildTransactionForRefresh(
|
||||
).amount;
|
||||
return {
|
||||
type: TransactionType.Refresh,
|
||||
txState: TransactionState.Unknown,
|
||||
txSubstate: TransactionSubstate.Unknown,
|
||||
refreshReason: refreshGroupRecord.reason,
|
||||
amountEffective: Amounts.stringify(
|
||||
Amounts.zeroOfCurrency(refreshGroupRecord.currency),
|
||||
@ -768,6 +788,8 @@ function buildTransactionForDeposit(
|
||||
|
||||
return {
|
||||
type: TransactionType.Deposit,
|
||||
txState: TransactionState.Unknown,
|
||||
txSubstate: TransactionSubstate.Unknown,
|
||||
amountRaw: Amounts.stringify(dg.effectiveDepositAmount),
|
||||
amountEffective: Amounts.stringify(dg.totalPayCost),
|
||||
extendedStatus: dg.timestampFinished
|
||||
@ -804,6 +826,8 @@ function buildTransactionForTip(
|
||||
|
||||
return {
|
||||
type: TransactionType.Tip,
|
||||
txState: TransactionState.Unknown,
|
||||
txSubstate: TransactionSubstate.Unknown,
|
||||
amountEffective: Amounts.stringify(tipRecord.tipAmountEffective),
|
||||
amountRaw: Amounts.stringify(tipRecord.tipAmountRaw),
|
||||
extendedStatus: tipRecord.pickedUpTimestamp
|
||||
@ -899,6 +923,8 @@ async function buildTransactionForRefund(
|
||||
|
||||
return {
|
||||
type: TransactionType.Refund,
|
||||
txState: TransactionState.Unknown,
|
||||
txSubstate: TransactionSubstate.Unknown,
|
||||
info,
|
||||
refundedTransactionId: makeTransactionId(
|
||||
TransactionType.Payment,
|
||||
@ -1001,6 +1027,8 @@ async function buildTransactionForPurchase(
|
||||
|
||||
return {
|
||||
type: TransactionType.Payment,
|
||||
txState: TransactionState.Unknown,
|
||||
txSubstate: TransactionSubstate.Unknown,
|
||||
amountRaw: Amounts.stringify(contractData.amount),
|
||||
amountEffective: Amounts.stringify(purchaseRecord.payInfo.totalPayCost),
|
||||
totalRefundRaw: Amounts.stringify(totalRefund.raw),
|
||||
|
@ -61,7 +61,6 @@ import {
|
||||
GetContractTermsDetailsRequest,
|
||||
GetExchangeTosRequest,
|
||||
GetExchangeTosResult,
|
||||
GetFeeForDepositRequest,
|
||||
GetWithdrawalDetailsForAmountRequest,
|
||||
GetWithdrawalDetailsForUriRequest,
|
||||
InitiatePeerPullCreditRequest,
|
||||
@ -94,8 +93,6 @@ import {
|
||||
SetWalletDeviceIdRequest,
|
||||
TestPayArgs,
|
||||
TestPayResult,
|
||||
TrackDepositGroupRequest,
|
||||
TrackDepositGroupResponse,
|
||||
Transaction,
|
||||
TransactionByIdRequest,
|
||||
TransactionsRequest,
|
||||
@ -175,8 +172,6 @@ export enum WalletApiOperation {
|
||||
ExportBackupRecovery = "exportBackupRecovery",
|
||||
ImportBackupRecovery = "importBackupRecovery",
|
||||
GetBackupInfo = "getBackupInfo",
|
||||
TrackDepositGroup = "trackDepositGroup",
|
||||
GetFeeForDeposit = "getFeeForDeposit",
|
||||
PrepareDeposit = "prepareDeposit",
|
||||
GetVersion = "getVersion",
|
||||
DeleteTransaction = "deleteTransaction",
|
||||
@ -512,21 +507,6 @@ export type CreateDepositGroupOp = {
|
||||
response: CreateDepositGroupResponse;
|
||||
};
|
||||
|
||||
/**
|
||||
* Track the status of a deposit group by querying the exchange.
|
||||
*/
|
||||
export type TrackDepositGroupOp = {
|
||||
op: WalletApiOperation.TrackDepositGroup;
|
||||
request: TrackDepositGroupRequest;
|
||||
response: TrackDepositGroupResponse;
|
||||
};
|
||||
|
||||
export type GetFeeForDepositOp = {
|
||||
op: WalletApiOperation.GetFeeForDeposit;
|
||||
request: GetFeeForDepositRequest;
|
||||
response: DepositGroupFees;
|
||||
};
|
||||
|
||||
export type PrepareDepositOp = {
|
||||
op: WalletApiOperation.PrepareDeposit;
|
||||
request: PrepareDepositRequest;
|
||||
@ -928,8 +908,6 @@ export type WalletOperations = {
|
||||
[WalletApiOperation.SetExchangeTosAccepted]: SetExchangeTosAcceptedOp;
|
||||
[WalletApiOperation.GetExchangeTos]: GetExchangeTosOp;
|
||||
[WalletApiOperation.GetExchangeDetailedInfo]: GetExchangeDetailedInfoOp;
|
||||
[WalletApiOperation.TrackDepositGroup]: TrackDepositGroupOp;
|
||||
[WalletApiOperation.GetFeeForDeposit]: GetFeeForDepositOp;
|
||||
[WalletApiOperation.PrepareDeposit]: PrepareDepositOp;
|
||||
[WalletApiOperation.CreateDepositGroup]: CreateDepositGroupOp;
|
||||
[WalletApiOperation.SetWalletDeviceId]: SetWalletDeviceIdOp;
|
||||
|
@ -48,7 +48,6 @@ import {
|
||||
codecForGetBalanceDetailRequest,
|
||||
codecForGetContractTermsDetails,
|
||||
codecForGetExchangeTosRequest,
|
||||
codecForGetFeeForDeposit,
|
||||
codecForGetWithdrawalDetailsForAmountRequest,
|
||||
codecForGetWithdrawalDetailsForUri,
|
||||
codecForImportDbRequest,
|
||||
@ -69,7 +68,6 @@ import {
|
||||
codecForSetDevModeRequest,
|
||||
codecForSetWalletDeviceIdRequest,
|
||||
codecForTestPayArgs,
|
||||
codecForTrackDepositGroupRequest,
|
||||
codecForTransactionByIdRequest,
|
||||
codecForTransactionsRequest,
|
||||
codecForUserAttentionByIdRequest,
|
||||
@ -174,10 +172,8 @@ import {
|
||||
} from "./operations/common.js";
|
||||
import {
|
||||
createDepositGroup,
|
||||
getFeeForDeposit,
|
||||
prepareDepositGroup,
|
||||
processDepositGroup,
|
||||
trackDepositGroup,
|
||||
} from "./operations/deposits.js";
|
||||
import {
|
||||
acceptExchangeTermsOfService,
|
||||
@ -1030,8 +1026,8 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
|
||||
case WalletApiOperation.WithdrawTestkudos: {
|
||||
await withdrawTestBalance(ws, {
|
||||
amount: "TESTKUDOS:10",
|
||||
bankBaseUrl: "https://bank.test.taler.net/",
|
||||
bankAccessApiBaseUrl: "https://bank.test.taler.net/",
|
||||
bankAccessApiBaseUrl:
|
||||
"https://bank.test.taler.net/demobanks/default/access-api/",
|
||||
exchangeBaseUrl: "https://exchange.test.taler.net/",
|
||||
});
|
||||
return {
|
||||
@ -1348,10 +1344,6 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
|
||||
const resp = await getBackupInfo(ws);
|
||||
return resp;
|
||||
}
|
||||
case WalletApiOperation.GetFeeForDeposit: {
|
||||
const req = codecForGetFeeForDeposit().decode(payload);
|
||||
return await getFeeForDeposit(ws, req);
|
||||
}
|
||||
case WalletApiOperation.PrepareDeposit: {
|
||||
const req = codecForPrepareDepositRequest().decode(payload);
|
||||
return await prepareDepositGroup(ws, req);
|
||||
@ -1360,10 +1352,6 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
|
||||
const req = codecForCreateDepositGroupRequest().decode(payload);
|
||||
return await createDepositGroup(ws, req);
|
||||
}
|
||||
case WalletApiOperation.TrackDepositGroup: {
|
||||
const req = codecForTrackDepositGroupRequest().decode(payload);
|
||||
return trackDepositGroup(ws, req);
|
||||
}
|
||||
case WalletApiOperation.DeleteTransaction: {
|
||||
const req = codecForDeleteTransactionRequest().decode(payload);
|
||||
await deleteTransaction(ws, req.transactionId);
|
||||
|
Loading…
Reference in New Issue
Block a user