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:
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 {