diff options
author | Florian Dold <florian@dold.me> | 2023-05-02 10:04:58 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2023-05-02 10:05:05 +0200 |
commit | c4f5c83b8e8614ead5f48952ea8b60b5b3a3971c (patch) | |
tree | ae192089b516d65fd957a44708b781eb59002e56 /packages | |
parent | 7c04fbd806998fc4fa2690ff8d462542375c4d78 (diff) | |
download | wallet-core-c4f5c83b8e8614ead5f48952ea8b60b5b3a3971c.tar.xz |
wallet-core: implement withdrawal tx transitions
Diffstat (limited to 'packages')
-rw-r--r-- | packages/taler-util/src/transactions-types.ts | 4 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/db.ts | 15 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/withdraw.ts | 309 |
3 files changed, 325 insertions, 3 deletions
diff --git a/packages/taler-util/src/transactions-types.ts b/packages/taler-util/src/transactions-types.ts index a5fc6c070..5c5a6a913 100644 --- a/packages/taler-util/src/transactions-types.ts +++ b/packages/taler-util/src/transactions-types.ts @@ -101,7 +101,8 @@ export enum TransactionMinorState { // Placeholder until D37 is fully implemented Unknown = "unknown", Deposit = "deposit", - KycRequired = "kyc-required", + KycRequired = "kyc", + AmlRequired = "aml", Track = "track", Refresh = "refresh", Pickup = "pickup", @@ -111,6 +112,7 @@ export enum TransactionMinorState { BankConfirmTransfer = "bank-confirm-transfer", WithdrawCoins = "withdraw-coins", ExchangeWaitReserve = "exchange-wait-reserve", + AbortingBank = "aborting-bank", } export interface TransactionsResponse { 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, + }; } } |