taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 18a27cc4cf1b9582fe9ef5f46bcec0a7c2482e0f
parent f9170accd7225ef9525f493233d6ff9376f9091f
Author: Florian Dold <florian@dold.me>
Date:   Wed, 22 Jul 2026 11:51:13 +0200

wallet-core: posConfirmationViaNfc in transaction details

Diffstat:
Mpackages/taler-util/src/taleruri.ts | 7+++++++
Mpackages/taler-wallet-core/src/db-common.ts | 5+++++
Mpackages/taler-wallet-core/src/db-sqlite-schema.ts | 1+
Mpackages/taler-wallet-core/src/dbtx-sqlite.ts | 11++++++++---
Mpackages/taler-wallet-core/src/pay-merchant.ts | 21++++++++++++++++++---
5 files changed, 39 insertions(+), 6 deletions(-)

diff --git a/packages/taler-util/src/taleruri.ts b/packages/taler-util/src/taleruri.ts @@ -613,6 +613,11 @@ function parsePay( // get session const sessionId = decodeUriSegment(cs[cs.length - 1]); + let nfc: boolean | undefined; + if ("nfc" in params) { + nfc = params["nfc"] === "1"; + } + return Result.of<TalerUri>({ type: TalerUriAction.Pay, merchantBaseUrl: merchant ?? (cs[0] as HostPortPath), @@ -620,6 +625,7 @@ function parsePay( sessionId, claimToken: params["c"], noncePriv: params["n"], + nfc, }); } @@ -1064,6 +1070,7 @@ export interface TalerPayUriResult { * "continue on mobile" payment flow. */ noncePriv?: string; + nfc?: boolean; } export type TemplateParams = { diff --git a/packages/taler-wallet-core/src/db-common.ts b/packages/taler-wallet-core/src/db-common.ts @@ -1050,6 +1050,11 @@ export interface WalletPurchase { * did not picked up yet */ refundAmountAwaiting: AmountString | undefined; + + /** + * Taler URI that started this purchase, if available. + */ + talerUri?: string; } /** diff --git a/packages/taler-wallet-core/src/db-sqlite-schema.ts b/packages/taler-wallet-core/src/db-sqlite-schema.ts @@ -616,6 +616,7 @@ CREATE TABLE IF NOT EXISTS purchases ( last_session_id TEXT, auto_refund_deadline INTEGER, refund_amount_awaiting TEXT + taler_uri TEXT ); CREATE INDEX IF NOT EXISTS purchases_by_status ON purchases (purchase_status); diff --git a/packages/taler-wallet-core/src/dbtx-sqlite.ts b/packages/taler-wallet-core/src/dbtx-sqlite.ts @@ -3572,6 +3572,9 @@ export class SqliteWalletTransaction implements WalletDbTransaction { ...(row.timestamp_expired != null ? { timestampExpired: dbTimestamp(row.timestamp_expired) } : undefined), + ...(row.taler_uri != null + ? { talerUri: str(row.taler_uri) } + : undefined), }; } @@ -3635,12 +3638,12 @@ export class SqliteWalletTransaction implements WalletDbTransaction { donau_tax_id_hash, donau_tax_id_salt, donau_tax_id, donau_year, shared, created_from_shared, timestamp, timestamp_accept, timestamp_last_refund_status, timestamp_expired, last_session_id, - auto_refund_deadline, refund_amount_awaiting + auto_refund_deadline, refund_amount_awaiting, taler_uri, ) VALUES ( $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 + $cfs, $ts, $tsa, $tslrs, $tse, $lsid, $ard, $raa, $turi ) ON CONFLICT(proposal_id) DO UPDATE SET order_id = excluded.order_id, @@ -3680,7 +3683,8 @@ export class SqliteWalletTransaction implements WalletDbTransaction { timestamp_expired = excluded.timestamp_expired, last_session_id = excluded.last_session_id, auto_refund_deadline = excluded.auto_refund_deadline, - refund_amount_awaiting = excluded.refund_amount_awaiting`, + refund_amount_awaiting = excluded.refund_amount_awaiting, + taler_uri = excluded.taler_uri`, { id: rec.proposalId, oid: rec.orderId, @@ -3722,6 +3726,7 @@ export class SqliteWalletTransaction implements WalletDbTransaction { lsid: rec.lastSessionId ?? null, ard: rec.autoRefundDeadline ?? null, raa: rec.refundAmountAwaiting ?? null, + turi: rec.talerUri ?? null, }, ); // Replace the exchange rows wholesale: an update that drops an exchange diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts @@ -96,6 +96,7 @@ import { TalerErrorCode, TalerErrorDetail, TalerPreciseTimestamp, + TalerUri, TalerUriAction, TalerUris, TokenUseSig, @@ -150,9 +151,9 @@ import { timestampPreciseToDb, timestampProtocolFromDb, timestampProtocolToDb, - WalletDenomination, WalletCoin, WalletCoinSelection, + WalletDenomination, WalletDonationPlanchet, WalletPurchase, WalletRefundGroup, @@ -161,6 +162,7 @@ import { WalletToken, } from "./db-common.js"; import { RefundReason } from "./db-indexeddb.js"; +import { WalletDbTransaction } from "./dbtx.js"; import { acceptDonauBlindSigs, generateDonauPlanchets } from "./donau.js"; import { getExchangeScopeInfoOrUndefined, @@ -190,7 +192,6 @@ import { WalletExecutionContext, walletMerchantClient, } from "./wallet.js"; -import { WalletDbTransaction } from "./dbtx.js"; /** * Logger. @@ -260,6 +261,13 @@ export class PayMerchantTransactionContext implements TransactionContext { const payOpId = TaskIdentifiers.forPay(purchaseRec); const payRetryRec = await tx.getOperationRetry(payOpId); const unk = "UNKNOWN:0"; + let nfc: boolean | undefined; + if (purchaseRec.talerUri) { + const uri = Result.orUndefined(TalerUris.parse(purchaseRec.talerUri)); + if (uri?.type === TalerUriAction.Pay) { + nfc = uri.nfc; + } + } if (!purchaseRec.download) { return { type: TransactionType.Payment, @@ -288,6 +296,7 @@ export class PayMerchantTransactionContext implements TransactionContext { contractTerms: undefined, refundQueryActive: false, choiceIndex: purchaseRec.choiceIndex, + posConfirmationViaNfc: nfc, }; } @@ -405,6 +414,7 @@ export class PayMerchantTransactionContext implements TransactionContext { purchaseRec.purchaseStatus === PurchaseStatus.PendingQueryingRefund, ...(payRetryRec?.lastError ? { error: payRetryRec.lastError } : {}), choiceIndex: purchaseRec.choiceIndex, + posConfirmationViaNfc: nfc, }; } @@ -1350,6 +1360,7 @@ async function createOrReusePurchase( sessionId: string | undefined, claimToken: string | undefined, noncePriv: string | undefined, + talerUri: string | undefined, ): Promise<{ proposalId: string; transactionId: TransactionIdStr; @@ -1488,6 +1499,7 @@ async function createOrReusePurchase( posConfirmation: undefined, shared: false, createdFromShared, + talerUri, }; const ctx = new PayMerchantTransactionContext(wex, proposalId); @@ -1772,7 +1784,8 @@ async function handleInsufficientFunds( if (exchangeReply == null) { await ctx.failTransaction(proposal.purchaseStatus, { code: TalerErrorCode.WALLET_TRANSACTION_PROTOCOL_VIOLATION, - message: "merchant claimed insufficient funds without an exchange reply", + message: + "merchant claimed insufficient funds without an exchange reply", }); return TaskRunResult.progress(); } @@ -2186,6 +2199,7 @@ export async function preparePayForUri( uriResult.sessionId, uriResult.claimToken, uriResult.noncePriv, + talerPayUri, ); await waitProposalDownloaded(wex, proposalRes.proposalId); @@ -2222,6 +2236,7 @@ export async function preparePayForUriV2( uriResult.sessionId, uriResult.claimToken, uriResult.noncePriv, + talerPayUri, ); return {