commit 05eba69261fdc8c3a914fe78ed1a8a05b377c6a2
parent 0a4392deabb7c8e90f879e18b8ca82844438f077
Author: Florian Dold <dold@taler.net>
Date: Sat, 5 Sep 2026 13:44:42 +0200
wallet-core: persist transaction amount accounting
Store confirmed amounts, consumed inputs and settled refresh outputs in
operation records. Preserve these fields in SQLite, IndexedDB and database
conversion, with schema migrations and round-trip coverage.
Issue: https://bugs.taler.net/n/7903
Diffstat:
6 files changed, 213 insertions(+), 20 deletions(-)
diff --git a/packages/taler-wallet-core/src/db/indexeddb/schema.ts b/packages/taler-wallet-core/src/db/indexeddb/schema.ts
@@ -76,6 +76,7 @@ import {
WalletToken,
WalletTombstone,
WalletTransactionMeta,
+ WalletTransactionAmounts,
WalletWithdrawalGroup,
} from "../records.js";
export {
@@ -167,7 +168,7 @@ export const TALER_WALLET_DB_GENERATION_PREFIX = `${TALER_WALLET_MAIN_DB_NAME}-g
* backwards-compatible way or object stores and indices
* are added.
*/
-export const WALLET_DB_MINOR_VERSION = 34;
+export const WALLET_DB_MINOR_VERSION = 35;
export interface BankWithdrawUriRecord {
/**
@@ -190,6 +191,7 @@ export interface DbPeerPushPaymentCoinSelection {
* Record for a push P2P payment that this wallet initiated.
*/
export interface PeerPushDebitRecord {
+ transactionAmounts?: WalletTransactionAmounts;
/**
* What exchange are funds coming from?
*/
@@ -263,6 +265,7 @@ export interface PeerPushDebitRecord {
}
export interface PeerPullCreditRecord {
+ transactionAmounts?: WalletTransactionAmounts;
/**
* What exchange are we using for the payment request?
*/
@@ -334,6 +337,7 @@ export interface PeerPullCreditRecord {
* Unique: (exchangeBaseUrl, pursePub)
*/
export interface PeerPushCreditRecord {
+ transactionAmounts?: WalletTransactionAmounts;
peerPushCreditId: string;
exchangeBaseUrl: string;
@@ -409,6 +413,7 @@ export interface PeerPullPaymentCoinSelection {
* AKA PeerPullDebit.
*/
export interface PeerPullPaymentIncomingRecord {
+ transactionAmounts?: WalletTransactionAmounts;
peerPullDebitId: string;
pursePub: string;
diff --git a/packages/taler-wallet-core/src/db/indexeddb/transaction.ts b/packages/taler-wallet-core/src/db/indexeddb/transaction.ts
@@ -1631,6 +1631,9 @@ export class IdbWalletTransaction implements WalletDbTransaction {
return undefined;
}
return {
+ ...(r.transactionAmounts === undefined
+ ? {}
+ : { transactionAmounts: r.transactionAmounts }),
exchangeBaseUrl: r.exchangeBaseUrl,
amount: r.amount,
estimatedAmountEffective: r.estimatedAmountEffective,
@@ -1662,6 +1665,9 @@ export class IdbWalletTransaction implements WalletDbTransaction {
async upsertPeerPullCredit(rec: WalletPeerPullCredit): Promise<void> {
const tx = this.tx;
await tx.peerPullCredit.put({
+ ...(rec.transactionAmounts === undefined
+ ? {}
+ : { transactionAmounts: rec.transactionAmounts }),
exchangeBaseUrl: rec.exchangeBaseUrl,
amount: rec.amount,
estimatedAmountEffective: rec.estimatedAmountEffective,
@@ -1704,6 +1710,9 @@ export class IdbWalletTransaction implements WalletDbTransaction {
return undefined;
}
return {
+ ...(r.transactionAmounts === undefined
+ ? {}
+ : { transactionAmounts: r.transactionAmounts }),
exchangeBaseUrl: r.exchangeBaseUrl,
restrictScope: r.restrictScope,
amount: r.amount,
@@ -1729,6 +1738,9 @@ export class IdbWalletTransaction implements WalletDbTransaction {
async upsertPeerPushDebit(rec: WalletPeerPushDebit): Promise<void> {
const tx = this.tx;
await tx.peerPushDebit.put({
+ ...(rec.transactionAmounts === undefined
+ ? {}
+ : { transactionAmounts: rec.transactionAmounts }),
exchangeBaseUrl: rec.exchangeBaseUrl,
restrictScope: rec.restrictScope,
amount: rec.amount,
@@ -1765,6 +1777,9 @@ export class IdbWalletTransaction implements WalletDbTransaction {
return undefined;
}
return {
+ ...(r.transactionAmounts === undefined
+ ? {}
+ : { transactionAmounts: r.transactionAmounts }),
peerPushCreditId: r.peerPushCreditId,
exchangeBaseUrl: r.exchangeBaseUrl,
pursePub: r.pursePub,
@@ -1792,6 +1807,9 @@ export class IdbWalletTransaction implements WalletDbTransaction {
async upsertPeerPushCredit(rec: WalletPeerPushCredit): Promise<void> {
const tx = this.tx;
await tx.peerPushCredit.put({
+ ...(rec.transactionAmounts === undefined
+ ? {}
+ : { transactionAmounts: rec.transactionAmounts }),
peerPushCreditId: rec.peerPushCreditId,
exchangeBaseUrl: rec.exchangeBaseUrl,
pursePub: rec.pursePub,
@@ -1845,6 +1863,9 @@ export class IdbWalletTransaction implements WalletDbTransaction {
return undefined;
}
return {
+ ...(r.transactionAmounts === undefined
+ ? {}
+ : { transactionAmounts: r.transactionAmounts }),
peerPullDebitId: r.peerPullDebitId,
pursePub: r.pursePub,
exchangeBaseUrl: r.exchangeBaseUrl,
@@ -1865,6 +1886,9 @@ export class IdbWalletTransaction implements WalletDbTransaction {
async upsertPeerPullDebit(rec: WalletPeerPullDebit): Promise<void> {
const tx = this.tx;
await tx.peerPullDebit.put({
+ ...(rec.transactionAmounts === undefined
+ ? {}
+ : { transactionAmounts: rec.transactionAmounts }),
peerPullDebitId: rec.peerPullDebitId,
pursePub: rec.pursePub,
exchangeBaseUrl: rec.exchangeBaseUrl,
diff --git a/packages/taler-wallet-core/src/db/records.ts b/packages/taler-wallet-core/src/db/records.ts
@@ -69,6 +69,20 @@ import {
export * from "./timestamps.js";
+/** Durable accounting evidence, independent of the transaction metadata view. */
+export interface WalletTransactionAmounts {
+ parentTransactionId?: string;
+ isAbortRecovery?: boolean;
+ initial?: AmountString;
+ final?: AmountString;
+ /** Face value of each consumed coin, counted once across reselections. */
+ inputs?: Record<string, AmountString>;
+ missingInputs?: string[];
+ unsettledRefreshIds?: string[];
+ /** Settled outputs retained when a supporting refresh is deleted. */
+ refreshOutputs?: Record<string, AmountString>;
+}
+
/**
* Metadata for a transaction.
* This object store is effectively a materialzed view of transactions gathered
@@ -196,6 +210,7 @@ export interface WalletDepositInfoPerExchange {
}
export interface WalletDepositGroup {
+ transactionAmounts?: WalletTransactionAmounts;
depositGroupId: string;
currency: string;
@@ -383,6 +398,7 @@ export interface WalletRefreshGroupPerExchangeInfo {
* currency.
*/
export interface WalletRefreshGroup {
+ transactionAmounts?: WalletTransactionAmounts;
operationStatus: RefreshOperationStatus;
/**
@@ -605,6 +621,7 @@ export type WgInfo =
* the coin selection we want to withdraw.
*/
export interface WalletWithdrawalGroup {
+ transactionAmounts?: WalletTransactionAmounts;
/**
* Unique identifier for the withdrawal group.
*/
@@ -877,6 +894,7 @@ export interface WalletPurchasePayInfo {
* Operation status: {@link purchaseStatus}
*/
export interface WalletPurchase {
+ transactionAmounts?: WalletTransactionAmounts;
/**
* Proposal ID for this purchase. Uniquely identifies the
* purchase and the proposal.
@@ -1044,6 +1062,7 @@ export interface WalletPurchase {
* Metadata about a group of refunds with the merchant.
*/
export interface WalletRefundGroup {
+ transactionAmounts?: WalletTransactionAmounts;
status: RefundGroupStatus;
/**
@@ -2416,6 +2435,7 @@ export interface PeerPullPaymentCoinSelection {
* Record for a push P2P payment that this wallet initiated.
*/
export interface WalletPeerPushDebit {
+ transactionAmounts?: WalletTransactionAmounts;
/**
* What exchange are funds coming from?
*/
@@ -2495,6 +2515,7 @@ export interface WalletPeerPushDebit {
* Record for a pull P2P payment that this wallet initiated.
*/
export interface WalletPeerPullCredit {
+ transactionAmounts?: WalletTransactionAmounts;
/**
* What exchange are we using for the payment request?
*/
@@ -2569,6 +2590,7 @@ export interface WalletPeerPullCredit {
* Record for a push P2P payment that this wallet was offered.
*/
export interface WalletPeerPushCredit {
+ transactionAmounts?: WalletTransactionAmounts;
peerPushCreditId: string;
exchangeBaseUrl: string;
@@ -2635,6 +2657,7 @@ export interface WalletPeerPushCredit {
* AKA PeerPullDebit. Record for a pull P2P payment that this wallet was offered.
*/
export interface WalletPeerPullDebit {
+ transactionAmounts?: WalletTransactionAmounts;
peerPullDebitId: string;
pursePub: string;
diff --git a/packages/taler-wallet-core/src/db/sqlite/schema.ts b/packages/taler-wallet-core/src/db/sqlite/schema.ts
@@ -85,7 +85,7 @@
*
* Bump this when adding a migration to {@link schemaMigrations}.
*/
-export const SQLITE_SCHEMA_VERSION = 20;
+export const SQLITE_SCHEMA_VERSION = 21;
/**
* Tables of the IndexedDB emulation, children before parents.
@@ -1463,6 +1463,21 @@ export const schemaMigrations: SchemaMigration[] = [
"ALTER TABLE recoup_groups ADD COLUMN successful_coin_pubs TEXT",
],
},
+ {
+ version: 21,
+ name: "transaction-final-amounts",
+ statements: [
+ "ALTER TABLE withdrawal_groups ADD COLUMN transaction_amounts TEXT",
+ "ALTER TABLE purchases ADD COLUMN transaction_amounts TEXT",
+ "ALTER TABLE deposit_groups ADD COLUMN transaction_amounts TEXT",
+ "ALTER TABLE refresh_groups ADD COLUMN transaction_amounts TEXT",
+ "ALTER TABLE peer_push_debit ADD COLUMN transaction_amounts TEXT",
+ "ALTER TABLE peer_push_credit ADD COLUMN transaction_amounts TEXT",
+ "ALTER TABLE peer_pull_debit ADD COLUMN transaction_amounts TEXT",
+ "ALTER TABLE peer_pull_credit ADD COLUMN transaction_amounts TEXT",
+ "ALTER TABLE refund_groups ADD COLUMN transaction_amounts TEXT",
+ ],
+ },
];
/** Native tables that contain wallet records (not schema bookkeeping). */
diff --git a/packages/taler-wallet-core/src/db/sqlite/transaction.ts b/packages/taler-wallet-core/src/db/sqlite/transaction.ts
@@ -991,11 +991,12 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
async upsertRefundGroup(rec: WalletRefundGroup): Promise<void> {
await this.run(
- `INSERT INTO refund_groups (
+ `INSERT INTO refund_groups (transaction_amounts,
refund_group_id, proposal_id, status, timestamp_created,
amount_raw, amount_effective, refresh_group_id
- ) VALUES ($id, $proposal_id, $status, $ts, $raw, $eff, $refresh_group_id)
+ ) VALUES ($transactionAmounts, $id, $proposal_id, $status, $ts, $raw, $eff, $refresh_group_id)
ON CONFLICT(refund_group_id) DO UPDATE SET
+ transaction_amounts = excluded.transaction_amounts,
proposal_id = excluded.proposal_id,
status = excluded.status,
timestamp_created = excluded.timestamp_created,
@@ -1003,6 +1004,10 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
amount_effective = excluded.amount_effective,
refresh_group_id = excluded.refresh_group_id`,
{
+ transactionAmounts:
+ rec.transactionAmounts === undefined
+ ? null
+ : jsonToDb(rec.transactionAmounts),
id: rec.refundGroupId,
proposal_id: rec.proposalId,
status: rec.status,
@@ -1035,6 +1040,9 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
// does not exist on WalletRefundGroup survive review.
private rowToRefundGroup(row: ResultRow): WalletRefundGroup {
return {
+ ...(row.transaction_amounts == null
+ ? {}
+ : { transactionAmounts: dbToJson(row.transaction_amounts) }),
refundGroupId: str(row.refund_group_id),
proposalId: str(row.proposal_id),
status: num(row.status),
@@ -2352,6 +2360,9 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
private rowToWithdrawalGroup(row: ResultRow): WalletWithdrawalGroup {
return {
+ ...(row.transaction_amounts == null
+ ? {}
+ : { transactionAmounts: dbToJson(row.transaction_amounts) }),
withdrawalGroupId: str(row.withdrawal_group_id),
...(dbToBool(row.legacy) ? { legacy: true } : undefined),
wgInfo: this.rowToWgInfo(row),
@@ -2437,7 +2448,7 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
async upsertWithdrawalGroup(rec: WalletWithdrawalGroup): Promise<void> {
const wg = this.wgInfoToCols(rec.wgInfo);
await this.run(
- `INSERT INTO withdrawal_groups (
+ `INSERT INTO withdrawal_groups (transaction_amounts,
withdrawal_group_id, withdrawal_type, taler_withdraw_uri,
contract_priv, bank_info, exchange_credit_accounts,
is_foreign_account, kyc_payto_hash, kyc_access_token,
@@ -2448,13 +2459,14 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
instructed_amount, reserve_balance_amount, raw_withdrawal_amount,
effective_withdrawal_amount, denoms_sel, abort_reason, fail_reason,
legacy
- ) VALUES (
+ ) VALUES ($transactionAmounts,
$id, $wtype, $uri, $cpriv, $binfo, $eca, $ifa, $kph, $kat, $klcs,
$klcc, $klrg, $klar, $kld, $kwd, $seed, $rpub, $rpriv, $url,
$tstart, $tfinish, $status, $age, $ia, $rba, $rwa, $ewa, $ds,
$abort, $fail, $legacy
)
ON CONFLICT(withdrawal_group_id) DO UPDATE SET
+ transaction_amounts = excluded.transaction_amounts,
withdrawal_type = excluded.withdrawal_type,
taler_withdraw_uri = excluded.taler_withdraw_uri,
contract_priv = excluded.contract_priv,
@@ -2486,6 +2498,10 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
fail_reason = excluded.fail_reason,
legacy = excluded.legacy`,
{
+ transactionAmounts:
+ rec.transactionAmounts === undefined
+ ? null
+ : jsonToDb(rec.transactionAmounts),
id: rec.withdrawalGroupId,
...wg,
ifa: boolToDb(rec.isForeignAccount),
@@ -2970,6 +2986,9 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
private rowToPeerPushDebit(row: ResultRow): WalletPeerPushDebit {
return {
+ ...(row.transaction_amounts == null
+ ? {}
+ : { transactionAmounts: dbToJson(row.transaction_amounts) }),
pursePub: dbToCrock(row.purse_pub),
exchangeBaseUrl: str(row.exchange_base_url),
amount: dbAmount(row.amount),
@@ -3014,18 +3033,19 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
async upsertPeerPushDebit(rec: WalletPeerPushDebit): Promise<void> {
await this.run(
- `INSERT INTO peer_push_debit (
+ `INSERT INTO peer_push_debit (transaction_amounts,
purse_pub, exchange_base_url, restrict_scope, amount, total_cost,
coin_sel, contract_terms_hash, purse_priv, merge_pub, merge_priv,
contract_priv, contract_pub, contract_enc_nonce, purse_expiration,
timestamp_created, abort_refresh_group_id, abort_reason,
fail_reason, status
- ) VALUES (
+ ) VALUES ($transactionAmounts,
$pub, $url, $scope, $amt, $cost, $csel, $cth, $ppriv, $mpub,
$mpriv, $cpriv, $cpub, $nonce, $exp, $created, $argi, $abort,
$fail, $status
)
ON CONFLICT(purse_pub) DO UPDATE SET
+ transaction_amounts = excluded.transaction_amounts,
exchange_base_url = excluded.exchange_base_url,
restrict_scope = excluded.restrict_scope,
amount = excluded.amount,
@@ -3045,6 +3065,10 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
fail_reason = excluded.fail_reason,
status = excluded.status`,
{
+ transactionAmounts:
+ rec.transactionAmounts === undefined
+ ? null
+ : jsonToDb(rec.transactionAmounts),
pub: crockToDb(rec.pursePub),
url: rec.exchangeBaseUrl,
scope:
@@ -3096,6 +3120,9 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
private rowToPeerPushCredit(row: ResultRow): WalletPeerPushCredit {
return {
+ ...(row.transaction_amounts == null
+ ? {}
+ : { transactionAmounts: dbToJson(row.transaction_amounts) }),
peerPushCreditId: str(row.peer_push_credit_id),
exchangeBaseUrl: str(row.exchange_base_url),
pursePub: dbToCrock(row.purse_pub),
@@ -3153,19 +3180,20 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
async upsertPeerPushCredit(rec: WalletPeerPushCredit): Promise<void> {
await this.run(
- `INSERT INTO peer_push_credit (
+ `INSERT INTO peer_push_credit (transaction_amounts,
peer_push_credit_id, exchange_base_url, purse_pub, merge_priv,
contract_priv, timestamp, estimated_amount_effective,
contract_terms_hash, status, abort_reason, fail_reason,
withdrawal_group_id, merge_reserve_row_id, currency, kyc_payto_hash, kyc_access_token,
kyc_last_check_status, kyc_last_check_code, kyc_last_rule_gen,
kyc_last_aml_review, kyc_last_deny
- ) VALUES (
+ ) VALUES ($transactionAmounts,
$id, $url, $ppub, $mpriv, $cpriv, $ts, $eae, $cth, $status,
$abort, $fail, $wgid, $mrri, $cur, $kph, $kat, $klcs, $klcc, $klrg,
$klar, $kld
)
ON CONFLICT(peer_push_credit_id) DO UPDATE SET
+ transaction_amounts = excluded.transaction_amounts,
exchange_base_url = excluded.exchange_base_url,
purse_pub = excluded.purse_pub,
merge_priv = excluded.merge_priv,
@@ -3187,6 +3215,10 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
kyc_last_aml_review = excluded.kyc_last_aml_review,
kyc_last_deny = excluded.kyc_last_deny`,
{
+ transactionAmounts:
+ rec.transactionAmounts === undefined
+ ? null
+ : jsonToDb(rec.transactionAmounts),
id: rec.peerPushCreditId,
url: rec.exchangeBaseUrl,
ppub: crockToDb(rec.pursePub),
@@ -3252,6 +3284,9 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
private rowToPeerPullDebit(row: ResultRow): WalletPeerPullDebit {
return {
+ ...(row.transaction_amounts == null
+ ? {}
+ : { transactionAmounts: dbToJson(row.transaction_amounts) }),
peerPullDebitId: str(row.peer_pull_debit_id),
pursePub: dbToCrock(row.purse_pub),
exchangeBaseUrl: str(row.exchange_base_url),
@@ -3291,16 +3326,17 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
async upsertPeerPullDebit(rec: WalletPeerPullDebit): Promise<void> {
await this.run(
- `INSERT INTO peer_pull_debit (
+ `INSERT INTO peer_pull_debit (transaction_amounts,
peer_pull_debit_id, purse_pub, exchange_base_url, amount,
contract_terms_hash, timestamp_created, contract_priv, status,
total_cost_estimated, abort_refresh_group_id, abort_reason,
fail_reason, coin_sel, cleanup_final_status
- ) VALUES (
+ ) VALUES ($transactionAmounts,
$id, $ppub, $url, $amt, $cth, $created, $cpriv, $status, $tce,
$argi, $abort, $fail, $csel, $cfs
)
ON CONFLICT(peer_pull_debit_id) DO UPDATE SET
+ transaction_amounts = excluded.transaction_amounts,
purse_pub = excluded.purse_pub,
exchange_base_url = excluded.exchange_base_url,
amount = excluded.amount,
@@ -3315,6 +3351,10 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
coin_sel = excluded.coin_sel,
cleanup_final_status = excluded.cleanup_final_status`,
{
+ transactionAmounts:
+ rec.transactionAmounts === undefined
+ ? null
+ : jsonToDb(rec.transactionAmounts),
id: rec.peerPullDebitId,
ppub: crockToDb(rec.pursePub),
url: rec.exchangeBaseUrl,
@@ -3361,6 +3401,9 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
private rowToPeerPullCredit(row: ResultRow): WalletPeerPullCredit {
return {
+ ...(row.transaction_amounts == null
+ ? {}
+ : { transactionAmounts: dbToJson(row.transaction_amounts) }),
pursePub: dbToCrock(row.purse_pub),
exchangeBaseUrl: str(row.exchange_base_url),
amount: dbAmount(row.amount),
@@ -3421,7 +3464,7 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
async upsertPeerPullCredit(rec: WalletPeerPullCredit): Promise<void> {
await this.run(
- `INSERT INTO peer_pull_credit (
+ `INSERT INTO peer_pull_credit (transaction_amounts,
purse_pub, exchange_base_url, amount, estimated_amount_effective,
purse_priv, contract_terms_hash, merge_pub, merge_priv,
contract_pub, contract_priv, contract_enc_nonce, merge_timestamp,
@@ -3429,12 +3472,13 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
kyc_last_check_status, kyc_last_check_code, kyc_last_rule_gen,
kyc_last_aml_review, kyc_last_deny, abort_reason, fail_reason,
withdrawal_group_id, purse_create_proof
- ) VALUES (
+ ) VALUES ($transactionAmounts,
$pub, $url, $amt, $eae, $ppriv, $cth, $mpub, $mpriv, $cpub,
$cpriv, $nonce, $mts, $mrri, $status, $kph, $kat, $klcs, $klcc,
$klrg, $klar, $kld, $abort, $fail, $wgid, $pcp
)
ON CONFLICT(purse_pub) DO UPDATE SET
+ transaction_amounts = excluded.transaction_amounts,
exchange_base_url = excluded.exchange_base_url,
amount = excluded.amount,
estimated_amount_effective = excluded.estimated_amount_effective,
@@ -3460,6 +3504,10 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
withdrawal_group_id = excluded.withdrawal_group_id,
purse_create_proof = excluded.purse_create_proof`,
{
+ transactionAmounts:
+ rec.transactionAmounts === undefined
+ ? null
+ : jsonToDb(rec.transactionAmounts),
pub: crockToDb(rec.pursePub),
url: rec.exchangeBaseUrl,
amt: rec.amount,
@@ -3507,6 +3555,9 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
private rowToDepositGroup(row: ResultRow): WalletDepositGroup {
return {
+ ...(row.transaction_amounts == null
+ ? {}
+ : { transactionAmounts: dbToJson(row.transaction_amounts) }),
depositGroupId: str(row.deposit_group_id),
currency: str(row.currency),
amount: dbAmount(row.amount),
@@ -3579,7 +3630,7 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
async upsertDepositGroup(rec: WalletDepositGroup): Promise<void> {
await this.run(
- `INSERT INTO deposit_groups (
+ `INSERT INTO deposit_groups (transaction_amounts,
deposit_group_id, currency, amount, wire_transfer_deadline,
merchant_pub, merchant_priv, nonce_priv, nonce_pub, wire,
contract_terms_hash, pay_coin_selection, pay_coin_selection_uid,
@@ -3589,13 +3640,14 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
info_per_exchange, abort_refresh_group_id, abort_reason,
fail_reason, kyc_info, kyc_auth_transfer_options,
kyc_auth_transfer_expiry, tracking_state
- ) VALUES (
+ ) VALUES ($transactionAmounts,
$id, $cur, $amt, $wtd, $mpub, $mpriv, $npriv, $npub, $wire, $cth,
$pcs, $pcsu, $tpc, $ceda, $created, $finished, $lastAttempt,
$status, $spc, $ipe, $argi, $abort, $fail, $kyc, $kato, $kate,
$tracking
)
ON CONFLICT(deposit_group_id) DO UPDATE SET
+ transaction_amounts = excluded.transaction_amounts,
currency = excluded.currency,
amount = excluded.amount,
wire_transfer_deadline = excluded.wire_transfer_deadline,
@@ -3625,6 +3677,10 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
kyc_auth_transfer_expiry = excluded.kyc_auth_transfer_expiry,
tracking_state = excluded.tracking_state`,
{
+ transactionAmounts:
+ rec.transactionAmounts === undefined
+ ? null
+ : jsonToDb(rec.transactionAmounts),
id: rec.depositGroupId,
cur: rec.currency,
amt: rec.amount,
@@ -3698,6 +3754,9 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
private rowToRefreshGroup(row: ResultRow): WalletRefreshGroup {
return {
+ ...(row.transaction_amounts == null
+ ? {}
+ : { transactionAmounts: dbToJson(row.transaction_amounts) }),
refreshGroupId: str(row.refresh_group_id),
operationStatus: num(row.operation_status),
currency: str(row.currency),
@@ -3736,16 +3795,17 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
async upsertRefreshGroup(rec: WalletRefreshGroup): Promise<void> {
await this.run(
- `INSERT INTO refresh_groups (
+ `INSERT INTO refresh_groups (transaction_amounts,
refresh_group_id, operation_status, currency, reason,
originating_transaction_id, old_coin_pubs, input_per_coin,
expected_output_per_coin, info_per_exchange, status_per_coin,
refund_requests, timestamp_created, fail_reason, timestamp_finished
- ) VALUES (
+ ) VALUES ($transactionAmounts,
$id, $status, $cur, $reason, $otid, $ocp, $ipc, $eopc, $ipe,
$spc, $rr, $created, $fail, $finished
)
ON CONFLICT(refresh_group_id) DO UPDATE SET
+ transaction_amounts = excluded.transaction_amounts,
operation_status = excluded.operation_status,
currency = excluded.currency,
reason = excluded.reason,
@@ -3760,6 +3820,10 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
fail_reason = excluded.fail_reason,
timestamp_finished = excluded.timestamp_finished`,
{
+ transactionAmounts:
+ rec.transactionAmounts === undefined
+ ? null
+ : jsonToDb(rec.transactionAmounts),
id: rec.refreshGroupId,
status: rec.operationStatus,
cur: rec.currency,
@@ -3907,6 +3971,9 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
download.fulfillmentUrl = str(row.download_fulfillment_url);
}
return {
+ ...(row.transaction_amounts == null
+ ? {}
+ : { transactionAmounts: dbToJson(row.transaction_amounts) }),
proposalId: str(row.proposal_id),
orderId: str(row.order_id),
merchantBaseUrl: str(row.merchant_base_url),
@@ -4062,7 +4129,7 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
downloadJson = jsonToDb(rest);
}
await this.run(
- `INSERT INTO purchases (
+ `INSERT INTO purchases (transaction_amounts,
proposal_id, order_id, merchant_base_url, claim_token,
download_session_id, repurchase_proposal_id, purchase_status,
abort_refresh_group_id, abort_reason, fail_reason, nonce_priv,
@@ -4074,13 +4141,14 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
shared, created_from_shared, timestamp, timestamp_accept,
timestamp_last_refund_status, timestamp_expired, last_session_id,
auto_refund_deadline, refund_amount_awaiting, taler_uri
- ) VALUES (
+ ) VALUES ($transactionAmounts,
$id, $oid, $url, $ct, $dsid, $rpid, $status, $argi, $abort, $fail,
$npriv, $npub, $ci, $seed, $dl, $ffu, $pi, $prcp, $tfsp, $mps,
$posc, $doi, $dbu, $damt, $dtih, $dtis, $dti, $dy, $shared,
$cfs, $ts, $tsa, $tslrs, $tse, $lsid, $ard, $raa, $turi
)
ON CONFLICT(proposal_id) DO UPDATE SET
+ transaction_amounts = excluded.transaction_amounts,
order_id = excluded.order_id,
merchant_base_url = excluded.merchant_base_url,
claim_token = excluded.claim_token,
@@ -4121,6 +4189,10 @@ export class SqliteWalletTransaction implements WalletDbTransaction {
refund_amount_awaiting = excluded.refund_amount_awaiting,
taler_uri = excluded.taler_uri`,
{
+ transactionAmounts:
+ rec.transactionAmounts === undefined
+ ? null
+ : jsonToDb(rec.transactionAmounts),
id: rec.proposalId,
oid: rec.orderId,
url: rec.merchantBaseUrl,
diff --git a/packages/taler-wallet-core/src/db/testing/conformance-cases.ts b/packages/taler-wallet-core/src/db/testing/conformance-cases.ts
@@ -382,6 +382,12 @@ function makeBankInfo(talerWithdrawUri: string): ReserveBankInfo {
function makeWithdrawalGroup(withdrawalGroupId: string): WalletWithdrawalGroup {
const wg: WalletWithdrawalGroup = {
+ transactionAmounts: {
+ initial: amt("TESTKUDOS:5.2"),
+ final: amt("TESTKUDOS:0.3"),
+ inputs: { coin: amt("TESTKUDOS:10") },
+ refreshOutputs: { recovery: amt("TESTKUDOS:9.7") },
+ },
withdrawalGroupId,
wgInfo: { withdrawalType: WithdrawalRecordType.BankManual },
secretSeed: ck(`seed-${withdrawalGroupId}`),
@@ -550,6 +556,12 @@ function makeSlate(
function makeDepositGroup(depositGroupId: string): WalletDepositGroup {
const dg: WalletDepositGroup = {
+ transactionAmounts: {
+ initial: amt("TESTKUDOS:5.2"),
+ final: amt("TESTKUDOS:0.3"),
+ inputs: { coin: amt("TESTKUDOS:10") },
+ refreshOutputs: { recovery: amt("TESTKUDOS:9.7") },
+ },
depositGroupId,
currency: "TESTKUDOS",
amount: amt("TESTKUDOS:5"),
@@ -572,6 +584,12 @@ function makeDepositGroup(depositGroupId: string): WalletDepositGroup {
function makeRefreshGroup(refreshGroupId: string): WalletRefreshGroup {
const rg: WalletRefreshGroup = {
+ transactionAmounts: {
+ initial: amt("TESTKUDOS:5.2"),
+ final: amt("TESTKUDOS:0.3"),
+ inputs: { coin: amt("TESTKUDOS:10") },
+ refreshOutputs: { recovery: amt("TESTKUDOS:9.7") },
+ },
refreshGroupId,
operationStatus: RefreshOperationStatus.Pending,
currency: "TESTKUDOS",
@@ -619,6 +637,12 @@ function makeRecoupGroup(
function makePeerPushDebit(pursePub: string): WalletPeerPushDebit {
const rec: WalletPeerPushDebit = {
+ transactionAmounts: {
+ initial: amt("TESTKUDOS:5.2"),
+ final: amt("TESTKUDOS:0.3"),
+ inputs: { coin: amt("TESTKUDOS:10") },
+ refreshOutputs: { recovery: amt("TESTKUDOS:9.7") },
+ },
pursePub: ck(pursePub),
exchangeBaseUrl: "https://exchange.test/",
amount: amt("TESTKUDOS:3"),
@@ -639,6 +663,12 @@ function makePeerPushDebit(pursePub: string): WalletPeerPushDebit {
function makePeerPushCredit(peerPushCreditId: string): WalletPeerPushCredit {
const rec: WalletPeerPushCredit = {
+ transactionAmounts: {
+ initial: amt("TESTKUDOS:5.2"),
+ final: amt("TESTKUDOS:0.3"),
+ inputs: { coin: amt("TESTKUDOS:10") },
+ refreshOutputs: { recovery: amt("TESTKUDOS:9.7") },
+ },
peerPushCreditId,
exchangeBaseUrl: "https://exchange.test/",
pursePub: ck(`purse-${peerPushCreditId}`),
@@ -656,6 +686,12 @@ function makePeerPushCredit(peerPushCreditId: string): WalletPeerPushCredit {
function makePeerPullDebit(peerPullDebitId: string): WalletPeerPullDebit {
const rec: WalletPeerPullDebit = {
+ transactionAmounts: {
+ initial: amt("TESTKUDOS:5.2"),
+ final: amt("TESTKUDOS:0.3"),
+ inputs: { coin: amt("TESTKUDOS:10") },
+ refreshOutputs: { recovery: amt("TESTKUDOS:9.7") },
+ },
peerPullDebitId,
pursePub: ck(`purse-${peerPullDebitId}`),
exchangeBaseUrl: "https://exchange.test/",
@@ -671,6 +707,12 @@ function makePeerPullDebit(peerPullDebitId: string): WalletPeerPullDebit {
function makePeerPullCredit(pursePub: string): WalletPeerPullCredit {
const rec: WalletPeerPullCredit = {
+ transactionAmounts: {
+ initial: amt("TESTKUDOS:5.2"),
+ final: amt("TESTKUDOS:0.3"),
+ inputs: { coin: amt("TESTKUDOS:10") },
+ refreshOutputs: { recovery: amt("TESTKUDOS:9.7") },
+ },
pursePub: ck(pursePub),
exchangeBaseUrl: "https://exchange.test/",
amount: amt("TESTKUDOS:6"),
@@ -709,6 +751,12 @@ function makeRefundGroup(
proposalId = "prop-1",
): WalletRefundGroup {
const grp: WalletRefundGroup = {
+ transactionAmounts: {
+ initial: amt("TESTKUDOS:5.2"),
+ final: amt("TESTKUDOS:0.3"),
+ inputs: { coin: amt("TESTKUDOS:10") },
+ refreshOutputs: { recovery: amt("TESTKUDOS:9.7") },
+ },
refundGroupId,
proposalId,
status: RefundGroupStatus.Done,
@@ -3528,6 +3576,12 @@ export const conformanceCases: ConformanceCase[] = [
async run(t, runner) {
const p = makePurchase("prop-rt");
p.exchanges = ["https://ex-a/", "https://ex-b/"];
+ p.transactionAmounts = {
+ initial: amt("TESTKUDOS:5.2"),
+ final: amt("TESTKUDOS:0.3"),
+ inputs: { coin: amt("TESTKUDOS:10") },
+ refreshOutputs: { recovery: amt("TESTKUDOS:9.7") },
+ };
await runner.runReadWriteTx((tx) => tx.upsertPurchase(p));
const got = await runner.runReadWriteTx((tx) =>
tx.getPurchase("prop-rt"),