/* This file is part of GNU Taler (C) 2019 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ /** * Imports. */ import { AcceptTipResponse, Logger, PrepareTipResult, TransactionAction, TransactionIdStr, TransactionMajorState, TransactionMinorState, TransactionState, TransactionType, assertUnreachable, } from "@gnu-taler/taler-util"; import { PendingTaskType, TaskIdStr, TaskRunResult, TombstoneTag, TransactionContext, constructTaskIdentifier, } from "./common.js"; import { RewardRecord, RewardRecordStatus } from "./db.js"; import { constructTransactionIdentifier, notifyTransition, } from "./transactions.js"; import { InternalWalletState, WalletExecutionContext } from "./wallet.js"; const logger = new Logger("operations/tip.ts"); export class RewardTransactionContext implements TransactionContext { public transactionId: TransactionIdStr; public taskId: TaskIdStr; constructor( public wex: WalletExecutionContext, public walletRewardId: string, ) { this.transactionId = constructTransactionIdentifier({ tag: TransactionType.Reward, walletRewardId, }); this.taskId = constructTaskIdentifier({ tag: PendingTaskType.RewardPickup, walletRewardId, }); } async deleteTransaction(): Promise { const { wex, walletRewardId } = this; await wex.db.runReadWriteTx(["rewards", "tombstones"], async (tx) => { const tipRecord = await tx.rewards.get(walletRewardId); if (tipRecord) { await tx.rewards.delete(walletRewardId); await tx.tombstones.put({ id: TombstoneTag.DeleteReward + ":" + walletRewardId, }); } }); } async suspendTransaction(): Promise { const { wex, walletRewardId, transactionId, taskId } = this; const transitionInfo = await wex.db.runReadWriteTx( ["rewards"], async (tx) => { const tipRec = await tx.rewards.get(walletRewardId); if (!tipRec) { logger.warn(`transaction tip ${walletRewardId} not found`); return; } let newStatus: RewardRecordStatus | undefined = undefined; switch (tipRec.status) { case RewardRecordStatus.Done: case RewardRecordStatus.SuspendedPickup: case RewardRecordStatus.Aborted: case RewardRecordStatus.DialogAccept: case RewardRecordStatus.Failed: break; case RewardRecordStatus.PendingPickup: newStatus = RewardRecordStatus.SuspendedPickup; break; default: assertUnreachable(tipRec.status); } if (newStatus != null) { const oldTxState = computeRewardTransactionStatus(tipRec); tipRec.status = newStatus; const newTxState = computeRewardTransactionStatus(tipRec); await tx.rewards.put(tipRec); return { oldTxState, newTxState, }; } return undefined; }, ); notifyTransition(wex, transactionId, transitionInfo); } async abortTransaction(): Promise { const { wex, walletRewardId, transactionId } = this; const transitionInfo = await wex.db.runReadWriteTx( ["rewards"], async (tx) => { const tipRec = await tx.rewards.get(walletRewardId); if (!tipRec) { logger.warn(`transaction tip ${walletRewardId} not found`); return; } let newStatus: RewardRecordStatus | undefined = undefined; switch (tipRec.status) { case RewardRecordStatus.Done: case RewardRecordStatus.Aborted: case RewardRecordStatus.PendingPickup: case RewardRecordStatus.DialogAccept: case RewardRecordStatus.Failed: break; case RewardRecordStatus.SuspendedPickup: newStatus = RewardRecordStatus.Aborted; break; default: assertUnreachable(tipRec.status); } if (newStatus != null) { const oldTxState = computeRewardTransactionStatus(tipRec); tipRec.status = newStatus; const newTxState = computeRewardTransactionStatus(tipRec); await tx.rewards.put(tipRec); return { oldTxState, newTxState, }; } return undefined; }, ); notifyTransition(wex, transactionId, transitionInfo); } async resumeTransaction(): Promise { const { wex: ws, walletRewardId, transactionId, taskId: retryTag } = this; const transitionInfo = await ws.db.runReadWriteTx( ["rewards"], async (tx) => { const rewardRec = await tx.rewards.get(walletRewardId); if (!rewardRec) { logger.warn(`transaction reward ${walletRewardId} not found`); return; } let newStatus: RewardRecordStatus | undefined = undefined; switch (rewardRec.status) { case RewardRecordStatus.Done: case RewardRecordStatus.PendingPickup: case RewardRecordStatus.Aborted: case RewardRecordStatus.DialogAccept: case RewardRecordStatus.Failed: break; case RewardRecordStatus.SuspendedPickup: newStatus = RewardRecordStatus.PendingPickup; break; default: assertUnreachable(rewardRec.status); } if (newStatus != null) { const oldTxState = computeRewardTransactionStatus(rewardRec); rewardRec.status = newStatus; const newTxState = computeRewardTransactionStatus(rewardRec); await tx.rewards.put(rewardRec); return { oldTxState, newTxState, }; } return undefined; }, ); notifyTransition(ws, transactionId, transitionInfo); } async failTransaction(): Promise { const { wex: ws, walletRewardId, transactionId, taskId: retryTag } = this; const transitionInfo = await ws.db.runReadWriteTx( ["rewards"], async (tx) => { const tipRec = await tx.rewards.get(walletRewardId); if (!tipRec) { logger.warn(`transaction tip ${walletRewardId} not found`); return; } let newStatus: RewardRecordStatus | undefined = undefined; switch (tipRec.status) { case RewardRecordStatus.Done: case RewardRecordStatus.Aborted: case RewardRecordStatus.Failed: break; case RewardRecordStatus.PendingPickup: case RewardRecordStatus.DialogAccept: case RewardRecordStatus.SuspendedPickup: newStatus = RewardRecordStatus.Failed; break; default: assertUnreachable(tipRec.status); } if (newStatus != null) { const oldTxState = computeRewardTransactionStatus(tipRec); tipRec.status = newStatus; const newTxState = computeRewardTransactionStatus(tipRec); await tx.rewards.put(tipRec); return { oldTxState, newTxState, }; } return undefined; }, ); notifyTransition(ws, transactionId, transitionInfo); } } /** * Get the (DD37-style) transaction status based on the * database record of a reward. */ export function computeRewardTransactionStatus( tipRecord: RewardRecord, ): TransactionState { switch (tipRecord.status) { case RewardRecordStatus.Done: return { major: TransactionMajorState.Done, }; case RewardRecordStatus.Aborted: return { major: TransactionMajorState.Aborted, }; case RewardRecordStatus.PendingPickup: return { major: TransactionMajorState.Pending, minor: TransactionMinorState.Pickup, }; case RewardRecordStatus.DialogAccept: return { major: TransactionMajorState.Dialog, minor: TransactionMinorState.Proposed, }; case RewardRecordStatus.SuspendedPickup: return { major: TransactionMajorState.Pending, minor: TransactionMinorState.Pickup, }; case RewardRecordStatus.Failed: return { major: TransactionMajorState.Failed, }; default: assertUnreachable(tipRecord.status); } } export function computeTipTransactionActions( tipRecord: RewardRecord, ): TransactionAction[] { switch (tipRecord.status) { case RewardRecordStatus.Done: return [TransactionAction.Delete]; case RewardRecordStatus.Failed: return [TransactionAction.Delete]; case RewardRecordStatus.Aborted: return [TransactionAction.Delete]; case RewardRecordStatus.PendingPickup: return [TransactionAction.Suspend, TransactionAction.Fail]; case RewardRecordStatus.SuspendedPickup: return [TransactionAction.Resume, TransactionAction.Fail]; case RewardRecordStatus.DialogAccept: return [TransactionAction.Abort]; default: assertUnreachable(tipRecord.status); } } export async function prepareReward( ws: InternalWalletState, talerTipUri: string, ): Promise { throw Error("the rewards feature is not supported anymore"); } export async function processTip( ws: InternalWalletState, walletTipId: string, ): Promise { return TaskRunResult.finished(); } export async function acceptTip( ws: InternalWalletState, transactionId: TransactionIdStr, ): Promise { throw Error("the rewards feature is not supported anymore"); }