wallet-core: handle more p2p abort cases nicely
This commit is contained in:
parent
6e7c88a620
commit
9fca44893a
@ -45,7 +45,7 @@ export interface HttpResponse {
|
||||
export const DEFAULT_REQUEST_TIMEOUT_MS = 60000;
|
||||
|
||||
export interface HttpRequestOptions {
|
||||
method?: "POST" | "PUT" | "GET";
|
||||
method?: "POST" | "PUT" | "GET" | "DELETE";
|
||||
headers?: { [name: string]: string };
|
||||
|
||||
/**
|
||||
|
@ -958,6 +958,7 @@ export enum TalerSignaturePurpose {
|
||||
WALLET_PURSE_MERGE = 1213,
|
||||
WALLET_ACCOUNT_MERGE = 1214,
|
||||
WALLET_PURSE_ECONTRACT = 1216,
|
||||
WALLET_PURSE_DELETE = 1220,
|
||||
EXCHANGE_CONFIRM_RECOUP = 1039,
|
||||
EXCHANGE_CONFIRM_RECOUP_REFRESH = 1041,
|
||||
ANASTASIS_POLICY_UPLOAD = 1400,
|
||||
|
@ -83,6 +83,7 @@ export enum TransactionMajorState {
|
||||
Dialog = "dialog",
|
||||
SuspendedAborting = "suspended-aborting",
|
||||
Failed = "failed",
|
||||
Expired = "expired",
|
||||
// Only used for the notification, never in the transaction history
|
||||
Deleted = "deleted",
|
||||
}
|
||||
|
@ -734,6 +734,7 @@ export enum RefreshReason {
|
||||
Refund = "refund",
|
||||
AbortPay = "abort-pay",
|
||||
AbortDeposit = "abort-deposit",
|
||||
AbortPeerPushDebit = "abort-peer-push-debit",
|
||||
Recoup = "recoup",
|
||||
BackupRestored = "backup-restored",
|
||||
Scheduled = "scheduled",
|
||||
|
@ -106,6 +106,8 @@ import {
|
||||
EncryptContractRequest,
|
||||
EncryptContractResponse,
|
||||
EncryptedContract,
|
||||
SignDeletePurseRequest,
|
||||
SignDeletePurseResponse,
|
||||
SignPurseMergeRequest,
|
||||
SignPurseMergeResponse,
|
||||
SignRefundRequest,
|
||||
@ -240,6 +242,8 @@ export interface TalerCryptoInterface {
|
||||
): Promise<SignReservePurseCreateResponse>;
|
||||
|
||||
signRefund(req: SignRefundRequest): Promise<SignRefundResponse>;
|
||||
|
||||
signDeletePurse(req: SignDeletePurseRequest): Promise<SignDeletePurseResponse>;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -419,6 +423,11 @@ export const nullCrypto: TalerCryptoInterface = {
|
||||
signRefund: function (req: SignRefundRequest): Promise<SignRefundResponse> {
|
||||
throw new Error("Function not implemented.");
|
||||
},
|
||||
signDeletePurse: function (
|
||||
req: SignDeletePurseRequest,
|
||||
): Promise<SignDeletePurseResponse> {
|
||||
throw new Error("Function not implemented.");
|
||||
},
|
||||
};
|
||||
|
||||
export type WithArg<X> = X extends (req: infer T) => infer R
|
||||
@ -1671,6 +1680,21 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
|
||||
sig: refundSigResp.sig,
|
||||
};
|
||||
},
|
||||
async signDeletePurse(
|
||||
tci: TalerCryptoInterfaceR,
|
||||
req: SignDeletePurseRequest,
|
||||
): Promise<SignDeletePurseResponse> {
|
||||
const deleteSigBlob = buildSigPS(
|
||||
TalerSignaturePurpose.WALLET_PURSE_DELETE,
|
||||
).build();
|
||||
const sigResp = await tci.eddsaSign(tci, {
|
||||
msg: encodeCrock(deleteSigBlob),
|
||||
priv: req.pursePriv,
|
||||
});
|
||||
return {
|
||||
sig: sigResp.sig,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function amountToBuffer(amount: AmountLike): Uint8Array {
|
||||
|
@ -268,7 +268,14 @@ export interface SignRefundResponse {
|
||||
sig: string;
|
||||
}
|
||||
|
||||
export interface SignRefundResponse {}
|
||||
|
||||
export interface SignDeletePurseRequest {
|
||||
pursePriv: string;
|
||||
}
|
||||
|
||||
export interface SignDeletePurseResponse {
|
||||
sig: EddsaSignatureString;
|
||||
}
|
||||
|
||||
export interface SignReservePurseCreateRequest {
|
||||
mergeTimestamp: TalerProtocolTimestamp;
|
||||
|
@ -1787,6 +1787,7 @@ export enum PeerPushPaymentInitiationStatus {
|
||||
Done = 50 /* DORMANT_START */,
|
||||
Aborted = 51,
|
||||
Failed = 52,
|
||||
Expired = 53,
|
||||
}
|
||||
|
||||
export interface PeerPushPaymentCoinSelection {
|
||||
@ -1844,6 +1845,8 @@ export interface PeerPushPaymentInitiationRecord {
|
||||
|
||||
timestampCreated: TalerPreciseTimestamp;
|
||||
|
||||
abortRefreshGroupId?: string;
|
||||
|
||||
/**
|
||||
* Status of the peer push payment initiation.
|
||||
*/
|
||||
|
@ -83,7 +83,6 @@ import {
|
||||
stopLongpolling,
|
||||
} from "./transactions.js";
|
||||
import {
|
||||
checkWithdrawalKycStatus,
|
||||
getExchangeWithdrawalInfo,
|
||||
internalCreateWithdrawalGroup,
|
||||
processWithdrawalGroup,
|
||||
@ -241,6 +240,62 @@ async function longpollKycStatus(
|
||||
};
|
||||
}
|
||||
|
||||
async function processPeerPullCreditAbortingDeletePurse(
|
||||
ws: InternalWalletState,
|
||||
peerPullIni: PeerPullPaymentInitiationRecord,
|
||||
): Promise<OperationAttemptResult> {
|
||||
const { pursePub, pursePriv } = peerPullIni;
|
||||
const transactionId = constructTransactionIdentifier({
|
||||
tag: TransactionType.PeerPushDebit,
|
||||
pursePub,
|
||||
});
|
||||
|
||||
const sigResp = await ws.cryptoApi.signDeletePurse({
|
||||
pursePriv,
|
||||
});
|
||||
const purseUrl = new URL(
|
||||
`purses/${pursePub}`,
|
||||
peerPullIni.exchangeBaseUrl,
|
||||
);
|
||||
const resp = await ws.http.fetch(purseUrl.href, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"taler-purse-signature": sigResp.sig,
|
||||
},
|
||||
});
|
||||
logger.info(`deleted purse with response status ${resp.status}`);
|
||||
|
||||
const transitionInfo = await ws.db
|
||||
.mktx((x) => [
|
||||
x.peerPullPaymentInitiations,
|
||||
x.refreshGroups,
|
||||
x.denominations,
|
||||
x.coinAvailability,
|
||||
x.coins,
|
||||
])
|
||||
.runReadWrite(async (tx) => {
|
||||
const ppiRec = await tx.peerPullPaymentInitiations.get(pursePub);
|
||||
if (!ppiRec) {
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
ppiRec.status !== PeerPullPaymentInitiationStatus.AbortingDeletePurse
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
const oldTxState = computePeerPullCreditTransactionState(ppiRec);
|
||||
ppiRec.status = PeerPullPaymentInitiationStatus.Aborted;
|
||||
const newTxState = computePeerPullCreditTransactionState(ppiRec);
|
||||
return {
|
||||
oldTxState,
|
||||
newTxState,
|
||||
};
|
||||
});
|
||||
notifyTransition(ws, transactionId, transitionInfo);
|
||||
|
||||
return OperationAttemptResult.pendingEmpty();
|
||||
}
|
||||
|
||||
export async function processPeerPullCredit(
|
||||
ws: InternalWalletState,
|
||||
pursePub: string,
|
||||
@ -320,6 +375,8 @@ export async function processPeerPullCredit(
|
||||
}
|
||||
case PeerPullPaymentInitiationStatus.PendingCreatePurse:
|
||||
break;
|
||||
case PeerPullPaymentInitiationStatus.AbortingDeletePurse:
|
||||
return await processPeerPullCreditAbortingDeletePurse(ws, pullIni);
|
||||
default:
|
||||
throw Error(`unknown PeerPullPaymentInitiationStatus ${pullIni.status}`);
|
||||
}
|
||||
|
@ -15,55 +15,152 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
ConfirmPeerPullDebitRequest,
|
||||
AcceptPeerPullPaymentResponse,
|
||||
Amounts,
|
||||
j2s,
|
||||
TalerError,
|
||||
TalerErrorCode,
|
||||
TransactionType,
|
||||
RefreshReason,
|
||||
ConfirmPeerPullDebitRequest,
|
||||
ExchangePurseDeposits,
|
||||
Logger,
|
||||
PeerContractTerms,
|
||||
PreparePeerPullDebitRequest,
|
||||
PreparePeerPullDebitResponse,
|
||||
RefreshReason,
|
||||
TalerError,
|
||||
TalerErrorCode,
|
||||
TalerPreciseTimestamp,
|
||||
TransactionAction,
|
||||
TransactionMajorState,
|
||||
TransactionMinorState,
|
||||
TransactionState,
|
||||
TransactionType,
|
||||
codecForAny,
|
||||
codecForExchangeGetContractResponse,
|
||||
codecForPeerContractTerms,
|
||||
decodeCrock,
|
||||
eddsaGetPublic,
|
||||
encodeCrock,
|
||||
getRandomBytes,
|
||||
j2s,
|
||||
parsePayPullUri,
|
||||
TransactionAction,
|
||||
TransactionMajorState,
|
||||
TransactionMinorState,
|
||||
TransactionState,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
|
||||
import {
|
||||
InternalWalletState,
|
||||
PeerPullDebitRecordStatus,
|
||||
PeerPullPaymentIncomingRecord,
|
||||
PendingTaskType,
|
||||
} from "../index.js";
|
||||
import { TaskIdentifiers, constructTaskIdentifier } from "../util/retries.js";
|
||||
import { spendCoins, runOperationWithErrorReporting } from "./common.js";
|
||||
import { assertUnreachable } from "../util/assertUnreachable.js";
|
||||
import {
|
||||
OperationAttemptResult,
|
||||
OperationAttemptResultType,
|
||||
TaskIdentifiers,
|
||||
constructTaskIdentifier,
|
||||
} from "../util/retries.js";
|
||||
import { runOperationWithErrorReporting, spendCoins } from "./common.js";
|
||||
import {
|
||||
codecForExchangePurseStatus,
|
||||
getTotalPeerPaymentCost,
|
||||
queryCoinInfosForSelection,
|
||||
selectPeerCoins,
|
||||
} from "./pay-peer-common.js";
|
||||
import { processPeerPullDebit } from "./pay-peer-push-credit.js";
|
||||
import {
|
||||
constructTransactionIdentifier,
|
||||
notifyTransition,
|
||||
stopLongpolling,
|
||||
} from "./transactions.js";
|
||||
import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
|
||||
import { assertUnreachable } from "../util/assertUnreachable.js";
|
||||
|
||||
const logger = new Logger("pay-peer-pull-debit.ts");
|
||||
|
||||
async function processPeerPullDebitPendingDeposit(
|
||||
ws: InternalWalletState,
|
||||
peerPullInc: PeerPullPaymentIncomingRecord,
|
||||
): Promise<OperationAttemptResult> {
|
||||
const peerPullPaymentIncomingId = peerPullInc.peerPullPaymentIncomingId;
|
||||
const pursePub = peerPullInc.pursePub;
|
||||
|
||||
const coinSel = peerPullInc.coinSel;
|
||||
if (!coinSel) {
|
||||
throw Error("invalid state, no coins selected");
|
||||
}
|
||||
|
||||
const coins = await queryCoinInfosForSelection(ws, coinSel);
|
||||
|
||||
const depositSigsResp = await ws.cryptoApi.signPurseDeposits({
|
||||
exchangeBaseUrl: peerPullInc.exchangeBaseUrl,
|
||||
pursePub: peerPullInc.pursePub,
|
||||
coins,
|
||||
});
|
||||
|
||||
const purseDepositUrl = new URL(
|
||||
`purses/${pursePub}/deposit`,
|
||||
peerPullInc.exchangeBaseUrl,
|
||||
);
|
||||
|
||||
const depositPayload: ExchangePurseDeposits = {
|
||||
deposits: depositSigsResp.deposits,
|
||||
};
|
||||
|
||||
if (logger.shouldLogTrace()) {
|
||||
logger.trace(`purse deposit payload: ${j2s(depositPayload)}`);
|
||||
}
|
||||
|
||||
const httpResp = await ws.http.postJson(purseDepositUrl.href, depositPayload);
|
||||
const resp = await readSuccessResponseJsonOrThrow(httpResp, codecForAny());
|
||||
logger.trace(`purse deposit response: ${j2s(resp)}`);
|
||||
|
||||
await ws.db
|
||||
.mktx((x) => [x.peerPullPaymentIncoming])
|
||||
.runReadWrite(async (tx) => {
|
||||
const pi = await tx.peerPullPaymentIncoming.get(
|
||||
peerPullPaymentIncomingId,
|
||||
);
|
||||
if (!pi) {
|
||||
throw Error("peer pull payment not found anymore");
|
||||
}
|
||||
if (pi.status === PeerPullDebitRecordStatus.PendingDeposit) {
|
||||
pi.status = PeerPullDebitRecordStatus.DonePaid;
|
||||
}
|
||||
await tx.peerPullPaymentIncoming.put(pi);
|
||||
});
|
||||
|
||||
return {
|
||||
type: OperationAttemptResultType.Finished,
|
||||
result: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async function processPeerPullDebitAbortingRefresh(
|
||||
ws: InternalWalletState,
|
||||
peerPullInc: PeerPullPaymentIncomingRecord,
|
||||
): Promise<OperationAttemptResult> {
|
||||
throw Error("not implemented");
|
||||
}
|
||||
|
||||
export async function processPeerPullDebit(
|
||||
ws: InternalWalletState,
|
||||
peerPullPaymentIncomingId: string,
|
||||
): Promise<OperationAttemptResult> {
|
||||
const peerPullInc = await ws.db
|
||||
.mktx((x) => [x.peerPullPaymentIncoming])
|
||||
.runReadOnly(async (tx) => {
|
||||
return tx.peerPullPaymentIncoming.get(peerPullPaymentIncomingId);
|
||||
});
|
||||
if (!peerPullInc) {
|
||||
throw Error("peer pull debit not found");
|
||||
}
|
||||
|
||||
switch (peerPullInc.status) {
|
||||
case PeerPullDebitRecordStatus.PendingDeposit:
|
||||
return await processPeerPullDebitPendingDeposit(ws, peerPullInc);
|
||||
case PeerPullDebitRecordStatus.AbortingRefresh:
|
||||
return await processPeerPullDebitAbortingRefresh(ws, peerPullInc);
|
||||
}
|
||||
return {
|
||||
type: OperationAttemptResultType.Finished,
|
||||
result: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
export async function confirmPeerPullDebit(
|
||||
ws: InternalWalletState,
|
||||
req: ConfirmPeerPullDebitRequest,
|
||||
|
@ -553,76 +553,6 @@ export async function confirmPeerPushCredit(
|
||||
};
|
||||
}
|
||||
|
||||
export async function processPeerPullDebit(
|
||||
ws: InternalWalletState,
|
||||
peerPullPaymentIncomingId: string,
|
||||
): Promise<OperationAttemptResult> {
|
||||
const peerPullInc = await ws.db
|
||||
.mktx((x) => [x.peerPullPaymentIncoming])
|
||||
.runReadOnly(async (tx) => {
|
||||
return tx.peerPullPaymentIncoming.get(peerPullPaymentIncomingId);
|
||||
});
|
||||
if (!peerPullInc) {
|
||||
throw Error("peer pull debit not found");
|
||||
}
|
||||
if (peerPullInc.status === PeerPullDebitRecordStatus.PendingDeposit) {
|
||||
const pursePub = peerPullInc.pursePub;
|
||||
|
||||
const coinSel = peerPullInc.coinSel;
|
||||
if (!coinSel) {
|
||||
throw Error("invalid state, no coins selected");
|
||||
}
|
||||
|
||||
const coins = await queryCoinInfosForSelection(ws, coinSel);
|
||||
|
||||
const depositSigsResp = await ws.cryptoApi.signPurseDeposits({
|
||||
exchangeBaseUrl: peerPullInc.exchangeBaseUrl,
|
||||
pursePub: peerPullInc.pursePub,
|
||||
coins,
|
||||
});
|
||||
|
||||
const purseDepositUrl = new URL(
|
||||
`purses/${pursePub}/deposit`,
|
||||
peerPullInc.exchangeBaseUrl,
|
||||
);
|
||||
|
||||
const depositPayload: ExchangePurseDeposits = {
|
||||
deposits: depositSigsResp.deposits,
|
||||
};
|
||||
|
||||
if (logger.shouldLogTrace()) {
|
||||
logger.trace(`purse deposit payload: ${j2s(depositPayload)}`);
|
||||
}
|
||||
|
||||
const httpResp = await ws.http.postJson(
|
||||
purseDepositUrl.href,
|
||||
depositPayload,
|
||||
);
|
||||
const resp = await readSuccessResponseJsonOrThrow(httpResp, codecForAny());
|
||||
logger.trace(`purse deposit response: ${j2s(resp)}`);
|
||||
}
|
||||
|
||||
await ws.db
|
||||
.mktx((x) => [x.peerPullPaymentIncoming])
|
||||
.runReadWrite(async (tx) => {
|
||||
const pi = await tx.peerPullPaymentIncoming.get(
|
||||
peerPullPaymentIncomingId,
|
||||
);
|
||||
if (!pi) {
|
||||
throw Error("peer pull payment not found anymore");
|
||||
}
|
||||
if (pi.status === PeerPullDebitRecordStatus.PendingDeposit) {
|
||||
pi.status = PeerPullDebitRecordStatus.DonePaid;
|
||||
}
|
||||
await tx.peerPullPaymentIncoming.put(pi);
|
||||
});
|
||||
|
||||
return {
|
||||
type: OperationAttemptResultType.Finished,
|
||||
result: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export async function suspendPeerPushCreditTransaction(
|
||||
ws: InternalWalletState,
|
||||
peerPushPaymentIncomingId: string,
|
||||
|
@ -18,6 +18,7 @@ import {
|
||||
Amounts,
|
||||
CheckPeerPushDebitRequest,
|
||||
CheckPeerPushDebitResponse,
|
||||
CoinRefreshRequest,
|
||||
ContractTermsUtil,
|
||||
HttpStatusCode,
|
||||
InitiatePeerPushDebitRequest,
|
||||
@ -27,13 +28,14 @@ import {
|
||||
TalerError,
|
||||
TalerErrorCode,
|
||||
TalerPreciseTimestamp,
|
||||
TalerUriAction,
|
||||
TransactionAction,
|
||||
TransactionMajorState,
|
||||
TransactionMinorState,
|
||||
TransactionState,
|
||||
TransactionType,
|
||||
constructPayPushUri,
|
||||
j2s,
|
||||
stringifyTalerUri,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { InternalWalletState } from "../internal-wallet-state.js";
|
||||
import {
|
||||
@ -46,6 +48,8 @@ import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
|
||||
import {
|
||||
PeerPushPaymentInitiationRecord,
|
||||
PeerPushPaymentInitiationStatus,
|
||||
RefreshOperationStatus,
|
||||
createRefreshGroup,
|
||||
} from "../index.js";
|
||||
import { PendingTaskType } from "../pending-types.js";
|
||||
import {
|
||||
@ -64,6 +68,7 @@ import {
|
||||
stopLongpolling,
|
||||
} from "./transactions.js";
|
||||
import { assertUnreachable } from "../util/assertUnreachable.js";
|
||||
import { checkLogicInvariant } from "../util/invariants.js";
|
||||
|
||||
const logger = new Logger("pay-peer-push-debit.ts");
|
||||
|
||||
@ -172,9 +177,89 @@ async function processPeerPushDebitCreateReserve(
|
||||
};
|
||||
}
|
||||
|
||||
async function transitionPeerPushDebitFromReadyToDone(
|
||||
async function processPeerPushDebitAbortingDeletePurse(
|
||||
ws: InternalWalletState,
|
||||
peerPushInitiation: PeerPushPaymentInitiationRecord,
|
||||
): Promise<OperationAttemptResult> {
|
||||
const { pursePub, pursePriv } = peerPushInitiation;
|
||||
const transactionId = constructTransactionIdentifier({
|
||||
tag: TransactionType.PeerPushDebit,
|
||||
pursePub,
|
||||
});
|
||||
|
||||
const sigResp = await ws.cryptoApi.signDeletePurse({
|
||||
pursePriv,
|
||||
});
|
||||
const purseUrl = new URL(
|
||||
`purses/${pursePub}`,
|
||||
peerPushInitiation.exchangeBaseUrl,
|
||||
);
|
||||
const resp = await ws.http.fetch(purseUrl.href, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"taler-purse-signature": sigResp.sig,
|
||||
},
|
||||
});
|
||||
logger.info(`deleted purse with response status ${resp.status}`);
|
||||
|
||||
const transitionInfo = await ws.db
|
||||
.mktx((x) => [
|
||||
x.peerPushPaymentInitiations,
|
||||
x.refreshGroups,
|
||||
x.denominations,
|
||||
x.coinAvailability,
|
||||
x.coins,
|
||||
])
|
||||
.runReadWrite(async (tx) => {
|
||||
const ppiRec = await tx.peerPushPaymentInitiations.get(pursePub);
|
||||
if (!ppiRec) {
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
ppiRec.status !== PeerPushPaymentInitiationStatus.AbortingDeletePurse
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
const currency = Amounts.currencyOf(ppiRec.amount);
|
||||
const oldTxState = computePeerPushDebitTransactionState(ppiRec);
|
||||
const coinPubs: CoinRefreshRequest[] = [];
|
||||
|
||||
for (let i = 0; i < ppiRec.coinSel.coinPubs.length; i++) {
|
||||
coinPubs.push({
|
||||
amount: ppiRec.coinSel.contributions[i],
|
||||
coinPub: ppiRec.coinSel.coinPubs[i],
|
||||
});
|
||||
}
|
||||
|
||||
const refresh = await createRefreshGroup(
|
||||
ws,
|
||||
tx,
|
||||
currency,
|
||||
coinPubs,
|
||||
RefreshReason.AbortPeerPushDebit,
|
||||
);
|
||||
ppiRec.status = PeerPushPaymentInitiationStatus.AbortingRefresh;
|
||||
ppiRec.abortRefreshGroupId = refresh.refreshGroupId;
|
||||
const newTxState = computePeerPushDebitTransactionState(ppiRec);
|
||||
return {
|
||||
oldTxState,
|
||||
newTxState,
|
||||
};
|
||||
});
|
||||
notifyTransition(ws, transactionId, transitionInfo);
|
||||
|
||||
return OperationAttemptResult.pendingEmpty();
|
||||
}
|
||||
|
||||
interface SimpleTransition {
|
||||
stFrom: PeerPushPaymentInitiationStatus;
|
||||
stTo: PeerPushPaymentInitiationStatus;
|
||||
}
|
||||
|
||||
async function transitionPeerPushDebitTransaction(
|
||||
ws: InternalWalletState,
|
||||
pursePub: string,
|
||||
transitionSpec: SimpleTransition,
|
||||
): Promise<void> {
|
||||
const transactionId = constructTransactionIdentifier({
|
||||
tag: TransactionType.PeerPushDebit,
|
||||
@ -187,11 +272,11 @@ async function transitionPeerPushDebitFromReadyToDone(
|
||||
if (!ppiRec) {
|
||||
return undefined;
|
||||
}
|
||||
if (ppiRec.status !== PeerPushPaymentInitiationStatus.PendingReady) {
|
||||
if (ppiRec.status !== transitionSpec.stFrom) {
|
||||
return undefined;
|
||||
}
|
||||
const oldTxState = computePeerPushDebitTransactionState(ppiRec);
|
||||
ppiRec.status = PeerPushPaymentInitiationStatus.Done;
|
||||
ppiRec.status = transitionSpec.stTo;
|
||||
const newTxState = computePeerPushDebitTransactionState(ppiRec);
|
||||
return {
|
||||
oldTxState,
|
||||
@ -201,6 +286,54 @@ async function transitionPeerPushDebitFromReadyToDone(
|
||||
notifyTransition(ws, transactionId, transitionInfo);
|
||||
}
|
||||
|
||||
async function processPeerPushDebitAbortingRefresh(
|
||||
ws: InternalWalletState,
|
||||
peerPushInitiation: PeerPushPaymentInitiationRecord,
|
||||
): Promise<OperationAttemptResult> {
|
||||
const pursePub = peerPushInitiation.pursePub;
|
||||
const abortRefreshGroupId = peerPushInitiation.abortRefreshGroupId;
|
||||
checkLogicInvariant(!!abortRefreshGroupId);
|
||||
const transactionId = constructTransactionIdentifier({
|
||||
tag: TransactionType.PeerPushDebit,
|
||||
pursePub: peerPushInitiation.pursePub,
|
||||
});
|
||||
const transitionInfo = await ws.db
|
||||
.mktx((x) => [x.refreshGroups, x.peerPushPaymentInitiations])
|
||||
.runReadWrite(async (tx) => {
|
||||
const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId);
|
||||
let newOpState: PeerPushPaymentInitiationStatus | undefined;
|
||||
if (!refreshGroup) {
|
||||
// Maybe it got manually deleted? Means that we should
|
||||
// just go into failed.
|
||||
logger.warn("no aborting refresh group found for deposit group");
|
||||
newOpState = PeerPushPaymentInitiationStatus.Failed;
|
||||
} else {
|
||||
if (refreshGroup.operationStatus === RefreshOperationStatus.Finished) {
|
||||
newOpState = PeerPushPaymentInitiationStatus.Aborted;
|
||||
} else if (
|
||||
refreshGroup.operationStatus === RefreshOperationStatus.Failed
|
||||
) {
|
||||
newOpState = PeerPushPaymentInitiationStatus.Failed;
|
||||
}
|
||||
}
|
||||
if (newOpState) {
|
||||
const newDg = await tx.peerPushPaymentInitiations.get(pursePub);
|
||||
if (!newDg) {
|
||||
return;
|
||||
}
|
||||
const oldTxState = computePeerPushDebitTransactionState(newDg);
|
||||
newDg.status = newOpState;
|
||||
const newTxState = computePeerPushDebitTransactionState(newDg);
|
||||
await tx.peerPushPaymentInitiations.put(newDg);
|
||||
return { oldTxState, newTxState };
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
notifyTransition(ws, transactionId, transitionInfo);
|
||||
// FIXME: Shouldn't this be finished in some cases?!
|
||||
return OperationAttemptResult.pendingEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the "pending(ready)" state of a peer-push-debit transaction.
|
||||
*/
|
||||
@ -214,7 +347,10 @@ async function processPeerPushDebitReady(
|
||||
pursePub,
|
||||
});
|
||||
runLongpollAsync(ws, retryTag, async (ct) => {
|
||||
const mergeUrl = new URL(`purses/${pursePub}/merge`);
|
||||
const mergeUrl = new URL(
|
||||
`purses/${pursePub}/merge`,
|
||||
peerPushInitiation.exchangeBaseUrl,
|
||||
);
|
||||
mergeUrl.searchParams.set("timeout_ms", "30000");
|
||||
const resp = await ws.http.fetch(mergeUrl.href, {
|
||||
// timeout: getReserveRequestTimeout(withdrawalGroup),
|
||||
@ -226,16 +362,30 @@ async function processPeerPushDebitReady(
|
||||
codecForExchangePurseStatus(),
|
||||
);
|
||||
if (purseStatus.deposit_timestamp) {
|
||||
await transitionPeerPushDebitFromReadyToDone(
|
||||
await transitionPeerPushDebitTransaction(
|
||||
ws,
|
||||
peerPushInitiation.pursePub,
|
||||
{
|
||||
stFrom: PeerPushPaymentInitiationStatus.PendingReady,
|
||||
stTo: PeerPushPaymentInitiationStatus.Done,
|
||||
},
|
||||
);
|
||||
return {
|
||||
ready: true,
|
||||
};
|
||||
}
|
||||
} else if (resp.status === HttpStatusCode.Gone) {
|
||||
// FIXME: transition the reserve into the expired state
|
||||
await transitionPeerPushDebitTransaction(
|
||||
ws,
|
||||
peerPushInitiation.pursePub,
|
||||
{
|
||||
stFrom: PeerPushPaymentInitiationStatus.PendingReady,
|
||||
stTo: PeerPushPaymentInitiationStatus.Expired,
|
||||
},
|
||||
);
|
||||
return {
|
||||
ready: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
ready: false,
|
||||
@ -280,6 +430,10 @@ export async function processPeerPushDebit(
|
||||
return processPeerPushDebitCreateReserve(ws, peerPushInitiation);
|
||||
case PeerPushPaymentInitiationStatus.PendingReady:
|
||||
return processPeerPushDebitReady(ws, peerPushInitiation);
|
||||
case PeerPushPaymentInitiationStatus.AbortingDeletePurse:
|
||||
return processPeerPushDebitAbortingDeletePurse(ws, peerPushInitiation);
|
||||
case PeerPushPaymentInitiationStatus.AbortingRefresh:
|
||||
return processPeerPushDebitAbortingRefresh(ws, peerPushInitiation);
|
||||
}
|
||||
|
||||
return {
|
||||
@ -396,7 +550,8 @@ export async function initiatePeerPushDebit(
|
||||
mergePriv: mergePair.priv,
|
||||
pursePub: pursePair.pub,
|
||||
exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl,
|
||||
talerUri: constructPayPushUri({
|
||||
talerUri: stringifyTalerUri({
|
||||
type: TalerUriAction.PayPush,
|
||||
exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl,
|
||||
contractPriv: contractKeyPair.priv,
|
||||
}),
|
||||
@ -431,6 +586,8 @@ export function computePeerPushDebitTransactionActions(
|
||||
return [TransactionAction.Suspend, TransactionAction.Abort];
|
||||
case PeerPushPaymentInitiationStatus.Done:
|
||||
return [TransactionAction.Delete];
|
||||
case PeerPushPaymentInitiationStatus.Expired:
|
||||
return [TransactionAction.Delete];
|
||||
case PeerPushPaymentInitiationStatus.Failed:
|
||||
return [TransactionAction.Delete];
|
||||
}
|
||||
@ -474,9 +631,9 @@ export async function abortPeerPushDebitTransaction(
|
||||
case PeerPushPaymentInitiationStatus.Done:
|
||||
case PeerPushPaymentInitiationStatus.AbortingDeletePurse:
|
||||
case PeerPushPaymentInitiationStatus.Aborted:
|
||||
// Do nothing
|
||||
break;
|
||||
case PeerPushPaymentInitiationStatus.Expired:
|
||||
case PeerPushPaymentInitiationStatus.Failed:
|
||||
// Do nothing
|
||||
break;
|
||||
default:
|
||||
assertUnreachable(pushDebitRec.status);
|
||||
@ -535,6 +692,7 @@ export async function failPeerPushDebitTransaction(
|
||||
case PeerPushPaymentInitiationStatus.Done:
|
||||
case PeerPushPaymentInitiationStatus.Aborted:
|
||||
case PeerPushPaymentInitiationStatus.Failed:
|
||||
case PeerPushPaymentInitiationStatus.Expired:
|
||||
// Do nothing
|
||||
break;
|
||||
default:
|
||||
@ -598,6 +756,7 @@ export async function suspendPeerPushDebitTransaction(
|
||||
case PeerPushPaymentInitiationStatus.Done:
|
||||
case PeerPushPaymentInitiationStatus.Aborted:
|
||||
case PeerPushPaymentInitiationStatus.Failed:
|
||||
case PeerPushPaymentInitiationStatus.Expired:
|
||||
// Do nothing
|
||||
break;
|
||||
default:
|
||||
@ -660,6 +819,7 @@ export async function resumePeerPushDebitTransaction(
|
||||
case PeerPushPaymentInitiationStatus.Done:
|
||||
case PeerPushPaymentInitiationStatus.Aborted:
|
||||
case PeerPushPaymentInitiationStatus.Failed:
|
||||
case PeerPushPaymentInitiationStatus.Expired:
|
||||
// Do nothing
|
||||
break;
|
||||
default:
|
||||
@ -681,7 +841,6 @@ export async function resumePeerPushDebitTransaction(
|
||||
notifyTransition(ws, transactionId, transitionInfo);
|
||||
}
|
||||
|
||||
|
||||
export function computePeerPushDebitTransactionState(
|
||||
ppiRecord: PeerPushPaymentInitiationRecord,
|
||||
): TransactionState {
|
||||
@ -738,5 +897,10 @@ export function computePeerPushDebitTransactionState(
|
||||
return {
|
||||
major: TransactionMajorState.Failed,
|
||||
};
|
||||
case PeerPushPaymentInitiationStatus.Expired:
|
||||
return {
|
||||
major: TransactionMajorState.Expired,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user