diff options
| author | Florian Dold <florian@dold.me> | 2023-06-06 17:07:09 +0200 | 
|---|---|---|
| committer | Florian Dold <florian@dold.me> | 2023-06-06 17:07:09 +0200 | 
| commit | 002ab0dab7b83c5999b0f82c430e716c718251e6 (patch) | |
| tree | 324f81160453ae9ea5a0bc609be0ec3f0725b772 /packages | |
| parent | f9c33136b4885827a18d0565c35d5854b44bb5ca (diff) | |
wallet-core: try to abort withdrawals wallet-side with the bank
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/taler-wallet-core/src/db.ts | 2 | ||||
| -rw-r--r-- | packages/taler-wallet-core/src/operations/withdraw.ts | 216 | 
2 files changed, 147 insertions, 71 deletions
| diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 195760831..9905fa370 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -210,6 +210,8 @@ export enum WithdrawalGroupStatus {     * wired or not.     */    AbortedExchange = 60, + +  AbortedBank = 61,  }  /** diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index 7db6dcd2a..61cab6fbb 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -316,6 +316,7 @@ export async function abortWithdrawalTransaction(          case WithdrawalGroupStatus.Finished:          case WithdrawalGroupStatus.FailedBankAborted:          case WithdrawalGroupStatus.AbortedExchange: +        case WithdrawalGroupStatus.AbortedBank:          case WithdrawalGroupStatus.FailedAbortingBank:            // Not allowed            throw Error("abort not allowed in current state"); @@ -481,6 +482,12 @@ export function computeWithdrawalTransactionStatus(          major: TransactionMajorState.Aborted,          minor: TransactionMinorState.Exchange,        }; + +    case WithdrawalGroupStatus.AbortedBank: +      return { +        major: TransactionMajorState.Aborted, +        minor: TransactionMinorState.Bank, +      };    }  } @@ -507,7 +514,7 @@ export function computeWithdrawalTransactionActions(      case WithdrawalGroupStatus.SuspendedQueryingStatus:        return [TransactionAction.Resume, TransactionAction.Abort];      case WithdrawalGroupStatus.SuspendedRegisteringBank: -      return [TransactionAction.Resume, TransactionAction.Abort] +      return [TransactionAction.Resume, TransactionAction.Abort];      case WithdrawalGroupStatus.SuspendedWaitConfirmBank:        return [TransactionAction.Resume, TransactionAction.Abort];      case WithdrawalGroupStatus.SuspendedReady: @@ -519,11 +526,13 @@ export function computeWithdrawalTransactionActions(      case WithdrawalGroupStatus.SuspendedAml:        return [TransactionAction.Resume, TransactionAction.Abort];      case WithdrawalGroupStatus.SuspendedKyc: -      return [TransactionAction.Resume, TransactionAction.Abort] +      return [TransactionAction.Resume, TransactionAction.Abort];      case WithdrawalGroupStatus.FailedAbortingBank:        return [TransactionAction.Delete];      case WithdrawalGroupStatus.AbortedExchange:        return [TransactionAction.Delete]; +    case WithdrawalGroupStatus.AbortedBank: +      return [TransactionAction.Delete];    }  } @@ -1270,87 +1279,61 @@ export interface WithdrawalGroupContext {    wgRecord: WithdrawalGroupRecord;  } -export async function processWithdrawalGroup( +async function processWithdrawalGroupAbortingBank(    ws: InternalWalletState, -  withdrawalGroupId: string, +  withdrawalGroup: WithdrawalGroupRecord,  ): Promise<OperationAttemptResult> { -  logger.trace("processing withdrawal group", withdrawalGroupId); -  const withdrawalGroup = await ws.db -    .mktx((x) => [x.withdrawalGroups]) -    .runReadOnly(async (tx) => { -      return tx.withdrawalGroups.get(withdrawalGroupId); -    }); - -  if (!withdrawalGroup) { -    throw Error(`withdrawal group ${withdrawalGroupId} not found`); -  } - -  const retryTag = TaskIdentifiers.forWithdrawal(withdrawalGroup); +  const { withdrawalGroupId } = withdrawalGroup;    const transactionId = constructTransactionIdentifier({      tag: TransactionType.Withdrawal,      withdrawalGroupId,    }); -  // We're already running! -  if (ws.activeLongpoll[retryTag]) { -    logger.info("withdrawal group already in long-polling, returning!"); -    return { -      type: OperationAttemptResultType.Longpoll, -    }; +  const wgInfo = withdrawalGroup.wgInfo; +  if (wgInfo.withdrawalType != WithdrawalRecordType.BankIntegrated) { +    throw Error("invalid state (aborting(bank) without bank info");    } +  const abortUrl = getBankAbortUrl(wgInfo.bankInfo.talerWithdrawUri); +  logger.info(`aborting withdrawal at ${abortUrl}`); +  const abortResp = await ws.http.fetch(abortUrl, { +    method: "POST", +    body: {}, +  }); +  logger.info(`abort response status: ${abortResp.status}`); -  switch (withdrawalGroup.status) { -    case WithdrawalGroupStatus.PendingRegisteringBank: -      await processReserveBankStatus(ws, withdrawalGroupId); -      // FIXME: This will get called by the main task loop, why call it here?! -      return await processWithdrawalGroup(ws, withdrawalGroupId); -    case WithdrawalGroupStatus.PendingQueryingStatus: { -      runLongpollAsync(ws, retryTag, (ct) => { -        return queryReserve(ws, withdrawalGroupId, ct); -      }); -      logger.trace( -        "returning early from withdrawal for long-polling in background", -      ); -      return { -        type: OperationAttemptResultType.Longpoll, -      }; -    } -    case WithdrawalGroupStatus.PendingWaitConfirmBank: { -      const res = await processReserveBankStatus(ws, withdrawalGroupId); -      switch (res.status) { -        case BankStatusResultCode.Aborted: -        case BankStatusResultCode.Done: -          return { -            type: OperationAttemptResultType.Finished, -            result: undefined, -          }; -        case BankStatusResultCode.Waiting: { -          return { -            type: OperationAttemptResultType.Pending, -            result: undefined, -          }; -        } +  const transitionInfo = await ws.db +    .mktx((x) => [x.withdrawalGroups]) +    .runReadWrite(async (tx) => { +      const wg = await tx.withdrawalGroups.get(withdrawalGroupId); +      if (!wg) { +        return undefined;        } -      break; -    } -    case WithdrawalGroupStatus.FailedBankAborted: { -      // FIXME +      const txStatusOld = computeWithdrawalTransactionStatus(wg); +      wg.status = WithdrawalGroupStatus.AbortedBank; +      wg.timestampFinish = TalerPreciseTimestamp.now(); +      const txStatusNew = computeWithdrawalTransactionStatus(wg); +      await tx.withdrawalGroups.put(wg);        return { -        type: OperationAttemptResultType.Pending, -        result: undefined, +        oldTxState: txStatusOld, +        newTxState: txStatusNew,        }; -    } -    case WithdrawalGroupStatus.Finished: -      // We can try to withdraw, nothing needs to be done with the reserve. -      break; -    case WithdrawalGroupStatus.PendingReady: -      // Continue with the actual withdrawal! -      break; -    default: -      throw new InvariantViolatedError( -        `unknown reserve record status: ${withdrawalGroup.status}`, -      ); -  } +    }); +  notifyTransition(ws, transactionId, transitionInfo); +  return { +    type: OperationAttemptResultType.Finished, +    result: undefined, +  }; +} + +async function processWithdrawalGroupPendingReady( +  ws: InternalWalletState, +  withdrawalGroup: WithdrawalGroupRecord, +): Promise<OperationAttemptResult> { +  const { withdrawalGroupId } = withdrawalGroup; +  const transactionId = constructTransactionIdentifier({ +    tag: TransactionType.Withdrawal, +    withdrawalGroupId, +  });    await ws.exchangeOps.updateExchangeFromUrl(      ws, @@ -1544,6 +1527,85 @@ export async function processWithdrawalGroup(    };  } +export async function processWithdrawalGroup( +  ws: InternalWalletState, +  withdrawalGroupId: string, +): Promise<OperationAttemptResult> { +  logger.trace("processing withdrawal group", withdrawalGroupId); +  const withdrawalGroup = await ws.db +    .mktx((x) => [x.withdrawalGroups]) +    .runReadOnly(async (tx) => { +      return tx.withdrawalGroups.get(withdrawalGroupId); +    }); + +  if (!withdrawalGroup) { +    throw Error(`withdrawal group ${withdrawalGroupId} not found`); +  } + +  const retryTag = TaskIdentifiers.forWithdrawal(withdrawalGroup); + +  // We're already running! +  if (ws.activeLongpoll[retryTag]) { +    logger.info("withdrawal group already in long-polling, returning!"); +    return { +      type: OperationAttemptResultType.Longpoll, +    }; +  } + +  switch (withdrawalGroup.status) { +    case WithdrawalGroupStatus.PendingRegisteringBank: +      await processReserveBankStatus(ws, withdrawalGroupId); +      // FIXME: This will get called by the main task loop, why call it here?! +      return await processWithdrawalGroup(ws, withdrawalGroupId); +    case WithdrawalGroupStatus.PendingQueryingStatus: { +      runLongpollAsync(ws, retryTag, (ct) => { +        return queryReserve(ws, withdrawalGroupId, ct); +      }); +      logger.trace( +        "returning early from withdrawal for long-polling in background", +      ); +      return { +        type: OperationAttemptResultType.Longpoll, +      }; +    } +    case WithdrawalGroupStatus.PendingWaitConfirmBank: { +      const res = await processReserveBankStatus(ws, withdrawalGroupId); +      switch (res.status) { +        case BankStatusResultCode.Aborted: +        case BankStatusResultCode.Done: +          return { +            type: OperationAttemptResultType.Finished, +            result: undefined, +          }; +        case BankStatusResultCode.Waiting: { +          return { +            type: OperationAttemptResultType.Pending, +            result: undefined, +          }; +        } +      } +      break; +    } +    case WithdrawalGroupStatus.Finished: +    case WithdrawalGroupStatus.FailedBankAborted: { +      // FIXME +      return { +        type: OperationAttemptResultType.Pending, +        result: undefined, +      }; +    } +    case WithdrawalGroupStatus.PendingReady: +      // Continue with the actual withdrawal! +      return await processWithdrawalGroupPendingReady(ws, withdrawalGroup); +    case WithdrawalGroupStatus.AbortingBank: +      return await processWithdrawalGroupAbortingBank(ws, withdrawalGroup); +    default: +      throw new InvariantViolatedError( +        `unknown withdrawal group status: ${withdrawalGroup.status}`, +      ); +  } +} +  export async function checkWithdrawalKycStatus(    ws: InternalWalletState,    exchangeUrl: string, @@ -1890,6 +1952,18 @@ export function getBankStatusUrl(talerWithdrawUri: string): string {    return url.href;  } +export function getBankAbortUrl(talerWithdrawUri: string): string { +  const uriResult = parseWithdrawUri(talerWithdrawUri); +  if (!uriResult) { +    throw Error(`can't parse withdrawal URL ${talerWithdrawUri}`); +  } +  const url = new URL( +    `withdrawal-operation/${uriResult.withdrawalOperationId}/abort`, +    uriResult.bankIntegrationApiBaseUrl, +  ); +  return url.href; +} +  async function registerReserveWithBank(    ws: InternalWalletState,    withdrawalGroupId: string, | 
