diff options
Diffstat (limited to 'packages/taler-wallet-core/src/reward.ts')
-rw-r--r-- | packages/taler-wallet-core/src/reward.ts | 321 |
1 files changed, 321 insertions, 0 deletions
diff --git a/packages/taler-wallet-core/src/reward.ts b/packages/taler-wallet-core/src/reward.ts new file mode 100644 index 000000000..da43e65b0 --- /dev/null +++ b/packages/taler-wallet-core/src/reward.ts @@ -0,0 +1,321 @@ +/* + 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 <http://www.gnu.org/licenses/> + */ + +/** + * Imports. + */ +import { + AcceptTipResponse, + Logger, + PrepareTipResult, + TransactionAction, + TransactionIdStr, + TransactionMajorState, + TransactionMinorState, + TransactionState, + TransactionType, +} from "@gnu-taler/taler-util"; +import { RewardRecord, RewardRecordStatus } from "./db.js"; +import { InternalWalletState } from "./internal-wallet-state.js"; +import { PendingTaskType } from "./pending-types.js"; +import { assertUnreachable } from "./util/assertUnreachable.js"; +import { + TaskRunResult, + TombstoneTag, + TransactionContext, + constructTaskIdentifier, +} from "./common.js"; +import { + constructTransactionIdentifier, + notifyTransition, +} from "./transactions.js"; + +const logger = new Logger("operations/tip.ts"); + +export class RewardTransactionContext implements TransactionContext { + public transactionId: string; + public retryTag: string; + + constructor( + public ws: InternalWalletState, + public walletRewardId: string, + ) { + this.transactionId = constructTransactionIdentifier({ + tag: TransactionType.Reward, + walletRewardId, + }); + this.retryTag = constructTaskIdentifier({ + tag: PendingTaskType.RewardPickup, + walletRewardId, + }); + } + + async deleteTransaction(): Promise<void> { + const { ws, walletRewardId } = this; + await ws.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<void> { + const { ws, walletRewardId, transactionId, 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.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(ws, transactionId, transitionInfo); + } + + async abortTransaction(): Promise<void> { + const { ws, walletRewardId, transactionId, 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.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(ws, transactionId, transitionInfo); + } + + async resumeTransaction(): Promise<void> { + const { ws, walletRewardId, transactionId, 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<void> { + const { ws, walletRewardId, transactionId, 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<PrepareTipResult> { + throw Error("the rewards feature is not supported anymore"); +} + +export async function processTip( + ws: InternalWalletState, + walletTipId: string, +): Promise<TaskRunResult> { + return TaskRunResult.finished(); +} + +export async function acceptTip( + ws: InternalWalletState, + transactionId: TransactionIdStr, +): Promise<AcceptTipResponse> { + throw Error("the rewards feature is not supported anymore"); +} |