summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-05-02 10:04:58 +0200
committerFlorian Dold <florian@dold.me>2023-05-02 10:05:05 +0200
commitc4f5c83b8e8614ead5f48952ea8b60b5b3a3971c (patch)
treeae192089b516d65fd957a44708b781eb59002e56 /packages/taler-wallet-core/src
parent7c04fbd806998fc4fa2690ff8d462542375c4d78 (diff)
downloadwallet-core-c4f5c83b8e8614ead5f48952ea8b60b5b3a3971c.tar.gz
wallet-core-c4f5c83b8e8614ead5f48952ea8b60b5b3a3971c.tar.bz2
wallet-core-c4f5c83b8e8614ead5f48952ea8b60b5b3a3971c.zip
wallet-core: implement withdrawal tx transitions
Diffstat (limited to 'packages/taler-wallet-core/src')
-rw-r--r--packages/taler-wallet-core/src/db.ts15
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts309
2 files changed, 322 insertions, 2 deletions
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index cff874508..febc5f8fa 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -172,6 +172,16 @@ export enum WithdrawalGroupStatus {
AbortingBank = 14,
/**
+ * Exchange wants KYC info from the user.
+ */
+ Kyc = 16,
+
+ /**
+ * Exchange is doing AML checks.
+ */
+ Aml = 17,
+
+ /**
* The corresponding withdraw record has been created.
* No further processing is done, unless explicitly requested
* by the user.
@@ -187,7 +197,10 @@ export enum WithdrawalGroupStatus {
SuspendedWaitConfirmBank = 53,
SuspendedQueryingStatus = 54,
SuspendedReady = 55,
- SuspendedAbortingBank = 55,
+ SuspendedAbortingBank = 56,
+ SuspendedKyc = 57,
+ SuspendedAml = 58,
+ FailedAbortingBank = 59,
}
/**
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts
index 6d5644b06..3f3eb3784 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -112,6 +112,7 @@ import {
OperationAttemptResult,
OperationAttemptResultType,
TaskIdentifiers,
+ constructTaskIdentifier,
} from "../util/retries.js";
import {
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
@@ -128,13 +129,284 @@ import {
selectForcedWithdrawalDenominations,
selectWithdrawalDenominations,
} from "../util/coinSelection.js";
-import { isWithdrawableDenom } from "../index.js";
+import { PendingTaskType, isWithdrawableDenom } from "../index.js";
+import {
+ constructTransactionIdentifier,
+ stopLongpolling,
+} from "./transactions.js";
/**
* Logger for this file.
*/
const logger = new Logger("operations/withdraw.ts");
+export async function suspendWithdrawalTransaction(
+ ws: InternalWalletState,
+ withdrawalGroupId: string,
+) {
+ const taskId = constructTaskIdentifier({
+ tag: PendingTaskType.Withdraw,
+ withdrawalGroupId,
+ });
+ stopLongpolling(ws, taskId);
+ const stateUpdate = await ws.db
+ .mktx((x) => [x.withdrawalGroups])
+ .runReadWrite(async (tx) => {
+ const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
+ if (!wg) {
+ logger.warn(`withdrawal group ${withdrawalGroupId} not found`);
+ return;
+ }
+ let newStatus: WithdrawalGroupStatus | undefined = undefined;
+ switch (wg.status) {
+ case WithdrawalGroupStatus.Ready:
+ newStatus = WithdrawalGroupStatus.SuspendedReady;
+ break;
+ case WithdrawalGroupStatus.AbortingBank:
+ newStatus = WithdrawalGroupStatus.SuspendedAbortingBank;
+ break;
+ case WithdrawalGroupStatus.WaitConfirmBank:
+ newStatus = WithdrawalGroupStatus.SuspendedWaitConfirmBank;
+ break;
+ case WithdrawalGroupStatus.RegisteringBank:
+ newStatus = WithdrawalGroupStatus.SuspendedRegisteringBank;
+ break;
+ case WithdrawalGroupStatus.QueryingStatus:
+ newStatus = WithdrawalGroupStatus.QueryingStatus;
+ break;
+ case WithdrawalGroupStatus.Kyc:
+ newStatus = WithdrawalGroupStatus.SuspendedKyc;
+ break;
+ case WithdrawalGroupStatus.Aml:
+ newStatus = WithdrawalGroupStatus.SuspendedAml;
+ break;
+ default:
+ logger.warn(
+ `Unsupported 'suspend' on withdrawal transaction in status ${wg.status}`,
+ );
+ }
+ if (newStatus != null) {
+ const oldTxState = computeWithdrawalTransactionStatus(wg);
+ wg.status = newStatus;
+ const newTxState = computeWithdrawalTransactionStatus(wg);
+ await tx.withdrawalGroups.put(wg);
+ return {
+ oldTxState,
+ newTxState,
+ };
+ }
+ return undefined;
+ });
+
+ if (stateUpdate) {
+ ws.notify({
+ type: NotificationType.TransactionStateTransition,
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.Withdrawal,
+ withdrawalGroupId,
+ }),
+ oldTxState: stateUpdate.oldTxState,
+ newTxState: stateUpdate.newTxState,
+ });
+ }
+}
+
+export async function resumeWithdrawalTransaction(
+ ws: InternalWalletState,
+ withdrawalGroupId: string,
+) {
+ const stateUpdate = await ws.db
+ .mktx((x) => [x.withdrawalGroups])
+ .runReadWrite(async (tx) => {
+ const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
+ if (!wg) {
+ logger.warn(`withdrawal group ${withdrawalGroupId} not found`);
+ return;
+ }
+ let newStatus: WithdrawalGroupStatus | undefined = undefined;
+ switch (wg.status) {
+ case WithdrawalGroupStatus.SuspendedReady:
+ newStatus = WithdrawalGroupStatus.Ready;
+ break;
+ case WithdrawalGroupStatus.SuspendedAbortingBank:
+ newStatus = WithdrawalGroupStatus.AbortingBank;
+ break;
+ case WithdrawalGroupStatus.SuspendedWaitConfirmBank:
+ newStatus = WithdrawalGroupStatus.WaitConfirmBank;
+ break;
+ case WithdrawalGroupStatus.SuspendedQueryingStatus:
+ newStatus = WithdrawalGroupStatus.QueryingStatus;
+ break;
+ case WithdrawalGroupStatus.SuspendedRegisteringBank:
+ newStatus = WithdrawalGroupStatus.RegisteringBank;
+ break;
+ case WithdrawalGroupStatus.SuspendedAml:
+ newStatus = WithdrawalGroupStatus.Aml;
+ break;
+ case WithdrawalGroupStatus.SuspendedKyc:
+ newStatus = WithdrawalGroupStatus.Kyc;
+ break;
+ default:
+ logger.warn(
+ `Unsupported 'resume' on withdrawal transaction in status ${wg.status}`,
+ );
+ }
+ if (newStatus != null) {
+ const oldTxState = computeWithdrawalTransactionStatus(wg);
+ wg.status = newStatus;
+ const newTxState = computeWithdrawalTransactionStatus(wg);
+ await tx.withdrawalGroups.put(wg);
+ return {
+ oldTxState,
+ newTxState,
+ };
+ }
+ return undefined;
+ });
+
+ if (stateUpdate) {
+ ws.notify({
+ type: NotificationType.TransactionStateTransition,
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.Withdrawal,
+ withdrawalGroupId,
+ }),
+ oldTxState: stateUpdate.oldTxState,
+ newTxState: stateUpdate.newTxState,
+ });
+ }
+}
+
+export async function abortWithdrawalTransaction(
+ ws: InternalWalletState,
+ withdrawalGroupId: string,
+) {
+ const taskId = constructTaskIdentifier({
+ tag: PendingTaskType.Withdraw,
+ withdrawalGroupId,
+ });
+ stopLongpolling(ws, taskId);
+ const stateUpdate = await ws.db
+ .mktx((x) => [x.withdrawalGroups])
+ .runReadWrite(async (tx) => {
+ const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
+ if (!wg) {
+ logger.warn(`withdrawal group ${withdrawalGroupId} not found`);
+ return;
+ }
+ let newStatus: WithdrawalGroupStatus | undefined = undefined;
+ switch (wg.status) {
+ case WithdrawalGroupStatus.WaitConfirmBank:
+ case WithdrawalGroupStatus.RegisteringBank:
+ case WithdrawalGroupStatus.AbortingBank:
+ newStatus = WithdrawalGroupStatus.AbortingBank;
+ break;
+ case WithdrawalGroupStatus.Aml:
+ newStatus = WithdrawalGroupStatus.SuspendedAml;
+ break;
+ case WithdrawalGroupStatus.Kyc:
+ newStatus = WithdrawalGroupStatus.SuspendedKyc;
+ break;
+ case WithdrawalGroupStatus.QueryingStatus:
+ newStatus = WithdrawalGroupStatus.SuspendedQueryingStatus;
+ break;
+ case WithdrawalGroupStatus.Ready:
+ newStatus = WithdrawalGroupStatus.SuspendedReady;
+ break;
+ case WithdrawalGroupStatus.SuspendedAbortingBank:
+ case WithdrawalGroupStatus.SuspendedQueryingStatus:
+ case WithdrawalGroupStatus.SuspendedAml:
+ case WithdrawalGroupStatus.SuspendedKyc:
+ case WithdrawalGroupStatus.SuspendedReady:
+ // No transition needed
+ break;
+ case WithdrawalGroupStatus.SuspendedRegisteringBank:
+ case WithdrawalGroupStatus.SuspendedWaitConfirmBank:
+ case WithdrawalGroupStatus.Finished:
+ case WithdrawalGroupStatus.BankAborted:
+ // Not allowed
+ break;
+ }
+ if (newStatus != null) {
+ const oldTxState = computeWithdrawalTransactionStatus(wg);
+ wg.status = newStatus;
+ const newTxState = computeWithdrawalTransactionStatus(wg);
+ await tx.withdrawalGroups.put(wg);
+ return {
+ oldTxState,
+ newTxState,
+ };
+ }
+ return undefined;
+ });
+
+ if (stateUpdate) {
+ ws.notify({
+ type: NotificationType.TransactionStateTransition,
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.Withdrawal,
+ withdrawalGroupId,
+ }),
+ oldTxState: stateUpdate.oldTxState,
+ newTxState: stateUpdate.newTxState,
+ });
+ }
+}
+
+// Called "cancel" in the spec right now,
+// from suspended-aborting.
+export async function cancelAbortingWithdrawalTransaction(
+ ws: InternalWalletState,
+ withdrawalGroupId: string,
+) {
+ const taskId = constructTaskIdentifier({
+ tag: PendingTaskType.Withdraw,
+ withdrawalGroupId,
+ });
+ stopLongpolling(ws, taskId);
+ const stateUpdate = await ws.db
+ .mktx((x) => [x.withdrawalGroups])
+ .runReadWrite(async (tx) => {
+ const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
+ if (!wg) {
+ logger.warn(`withdrawal group ${withdrawalGroupId} not found`);
+ return;
+ }
+ let newStatus: WithdrawalGroupStatus | undefined = undefined;
+ switch (wg.status) {
+ case WithdrawalGroupStatus.AbortingBank:
+ newStatus = WithdrawalGroupStatus.FailedAbortingBank;
+ break;
+ default:
+ break;
+ }
+ if (newStatus != null) {
+ const oldTxState = computeWithdrawalTransactionStatus(wg);
+ wg.status = newStatus;
+ const newTxState = computeWithdrawalTransactionStatus(wg);
+ await tx.withdrawalGroups.put(wg);
+ return {
+ oldTxState,
+ newTxState,
+ };
+ }
+ return undefined;
+ });
+
+ if (stateUpdate) {
+ ws.notify({
+ type: NotificationType.TransactionStateTransition,
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.Withdrawal,
+ withdrawalGroupId,
+ }),
+ oldTxState: stateUpdate.oldTxState,
+ newTxState: stateUpdate.newTxState,
+ });
+ }
+}
+
+
export function computeWithdrawalTransactionStatus(
wgRecord: WithdrawalGroupRecord,
): TransactionState {
@@ -192,6 +464,41 @@ export function computeWithdrawalTransactionStatus(
major: TransactionMajorState.Suspended,
minor: TransactionMinorState.BankConfirmTransfer,
};
+ case WithdrawalGroupStatus.SuspendedReady: {
+ return {
+ major: TransactionMajorState.Suspended,
+ minor: TransactionMinorState.WithdrawCoins,
+ };
+ }
+ case WithdrawalGroupStatus.Aml: {
+ return {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.AmlRequired,
+ };
+ }
+ case WithdrawalGroupStatus.Kyc: {
+ return {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.KycRequired,
+ };
+ }
+ case WithdrawalGroupStatus.SuspendedAml: {
+ return {
+ major: TransactionMajorState.Suspended,
+ minor: TransactionMinorState.AmlRequired,
+ };
+ }
+ case WithdrawalGroupStatus.SuspendedKyc: {
+ return {
+ major: TransactionMajorState.Suspended,
+ minor: TransactionMinorState.KycRequired,
+ };
+ }
+ case WithdrawalGroupStatus.FailedAbortingBank:
+ return {
+ major: TransactionMajorState.Failed,
+ minor: TransactionMinorState.AbortingBank,
+ };
}
}