wallet-core: try to abort withdrawals wallet-side with the bank
This commit is contained in:
parent
f9c33136b4
commit
002ab0dab7
@ -210,6 +210,8 @@ export enum WithdrawalGroupStatus {
|
||||
* wired or not.
|
||||
*/
|
||||
AbortedExchange = 60,
|
||||
|
||||
AbortedBank = 61,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user