summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/taler-util/src/transactions-types.ts4
-rw-r--r--packages/taler-wallet-core/src/db.ts15
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts309
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,
+ };
}
}