/*
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 } from "./wallet.js";
const logger = new Logger("operations/tip.ts");
export class RewardTransactionContext implements TransactionContext {
public transactionId: TransactionIdStr;
public taskId: TaskIdStr;
constructor(
public ws: InternalWalletState,
public walletRewardId: string,
) {
this.transactionId = constructTransactionIdentifier({
tag: TransactionType.Reward,
walletRewardId,
});
this.taskId = constructTaskIdentifier({
tag: PendingTaskType.RewardPickup,
walletRewardId,
});
}
async deleteTransaction(): Promise {
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 {
const { 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.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 {
const { 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.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 {
const { 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 { 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");
}