summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/reward.ts
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-02-19 18:05:48 +0100
committerFlorian Dold <florian@dold.me>2024-02-19 18:05:48 +0100
commite951075d2ef52fa8e9e7489c62031777c3a7e66b (patch)
tree64208c09a9162f3a99adccf30edc36de1ef884ef /packages/taler-wallet-core/src/reward.ts
parente975740ac4e9ba4bc531226784d640a018c00833 (diff)
downloadwallet-core-e951075d2ef52fa8e9e7489c62031777c3a7e66b.tar.gz
wallet-core-e951075d2ef52fa8e9e7489c62031777c3a7e66b.tar.bz2
wallet-core-e951075d2ef52fa8e9e7489c62031777c3a7e66b.zip
wallet-core: flatten directory structure
Diffstat (limited to 'packages/taler-wallet-core/src/reward.ts')
-rw-r--r--packages/taler-wallet-core/src/reward.ts321
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");
+}