aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations/pay-peer.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core/src/operations/pay-peer.ts')
-rw-r--r--packages/taler-wallet-core/src/operations/pay-peer.ts672
1 files changed, 587 insertions, 85 deletions
diff --git a/packages/taler-wallet-core/src/operations/pay-peer.ts b/packages/taler-wallet-core/src/operations/pay-peer.ts
index 33659afe0..31e395cab 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer.ts
@@ -75,20 +75,21 @@ import {
NotificationType,
HttpStatusCode,
codecForWalletKycUuid,
- WalletKycUuid,
+ TransactionState,
+ TransactionMajorState,
+ TransactionMinorState,
} from "@gnu-taler/taler-util";
import { SpendCoinDetails } from "../crypto/cryptoImplementation.js";
import {
DenominationRecord,
- KycPendingInfo,
- KycUserType,
- OperationStatus,
+ PeerPullPaymentIncomingRecord,
PeerPullPaymentIncomingStatus,
PeerPullPaymentInitiationRecord,
PeerPullPaymentInitiationStatus,
PeerPushPaymentCoinSelection,
PeerPushPaymentIncomingRecord,
PeerPushPaymentIncomingStatus,
+ PeerPushPaymentInitiationRecord,
PeerPushPaymentInitiationStatus,
ReserveRecord,
WithdrawalGroupStatus,
@@ -98,7 +99,6 @@ import { TalerError } from "@gnu-taler/taler-util";
import { InternalWalletState } from "../internal-wallet-state.js";
import {
LongpollResult,
- makeTransactionId,
resetOperationTimeout,
runLongpollAsync,
runOperationWithErrorReporting,
@@ -128,8 +128,10 @@ import {
import { PendingTaskType } from "../pending-types.js";
import {
constructTransactionIdentifier,
+ notifyTransition,
stopLongpolling,
} from "./transactions.js";
+import { assertUnreachable } from "../util/assertUnreachable.js";
const logger = new Logger("operations/peer-to-peer.ts");
@@ -451,19 +453,11 @@ export async function checkPeerPushDebit(
};
}
-export async function processPeerPushInitiation(
+async function processPeerPushDebitCreateReserve(
ws: InternalWalletState,
- pursePub: string,
+ peerPushInitiation: PeerPushPaymentInitiationRecord,
): Promise<OperationAttemptResult> {
- const peerPushInitiation = await ws.db
- .mktx((x) => [x.peerPushPaymentInitiations])
- .runReadOnly(async (tx) => {
- return tx.peerPushPaymentInitiations.get(pursePub);
- });
- if (!peerPushInitiation) {
- throw Error("peer push payment not found");
- }
-
+ const pursePub = peerPushInitiation.pursePub;
const purseExpiration = peerPushInitiation.purseExpiration;
const hContractTerms = peerPushInitiation.contractTermsHash;
@@ -501,22 +495,25 @@ export async function processPeerPushInitiation(
peerPushInitiation.exchangeBaseUrl,
);
- const httpResp = await ws.http.postJson(createPurseUrl.href, {
- amount: peerPushInitiation.amount,
- merge_pub: peerPushInitiation.mergePub,
- purse_sig: purseSigResp.sig,
- h_contract_terms: hContractTerms,
- purse_expiration: purseExpiration,
- deposits: depositSigsResp.deposits,
- min_age: 0,
- econtract: econtractResp.econtract,
+ const httpResp = await ws.http.fetch(createPurseUrl.href, {
+ method: "POST",
+ body: {
+ amount: peerPushInitiation.amount,
+ merge_pub: peerPushInitiation.mergePub,
+ purse_sig: purseSigResp.sig,
+ h_contract_terms: hContractTerms,
+ purse_expiration: purseExpiration,
+ deposits: depositSigsResp.deposits,
+ min_age: 0,
+ econtract: econtractResp.econtract,
+ },
});
const resp = await httpResp.json();
logger.info(`resp: ${j2s(resp)}`);
- if (httpResp.status !== 200) {
+ if (httpResp.status !== HttpStatusCode.Ok) {
throw Error("got error response from exchange");
}
@@ -527,7 +524,7 @@ export async function processPeerPushInitiation(
if (!ppi) {
return;
}
- ppi.status = PeerPushPaymentInitiationStatus.PurseCreated;
+ ppi.status = PeerPushPaymentInitiationStatus.Done;
await tx.peerPushPaymentInitiations.put(ppi);
});
@@ -537,6 +534,122 @@ export async function processPeerPushInitiation(
};
}
+async function transitionPeerPushDebitFromReadyToDone(
+ ws: InternalWalletState,
+ pursePub: string,
+): Promise<void> {
+ const transactionId = constructTransactionIdentifier({
+ tag: TransactionType.PeerPushDebit,
+ pursePub,
+ });
+ const transitionInfo = await ws.db
+ .mktx((x) => [x.peerPushPaymentInitiations])
+ .runReadWrite(async (tx) => {
+ const ppiRec = await tx.peerPushPaymentInitiations.get(pursePub);
+ if (!ppiRec) {
+ return undefined;
+ }
+ if (ppiRec.status !== PeerPushPaymentInitiationStatus.PendingReady) {
+ return undefined;
+ }
+ const oldTxState = computePeerPushDebitTransactionState(ppiRec);
+ ppiRec.status = PeerPushPaymentInitiationStatus.Done;
+ const newTxState = computePeerPushDebitTransactionState(ppiRec);
+ return {
+ oldTxState,
+ newTxState,
+ };
+ });
+ notifyTransition(ws, transactionId, transitionInfo);
+}
+
+/**
+ * Process the "pending(ready)" state of a peer-push-debit transaction.
+ */
+async function processPeerPushDebitReady(
+ ws: InternalWalletState,
+ peerPushInitiation: PeerPushPaymentInitiationRecord,
+): Promise<OperationAttemptResult> {
+ const pursePub = peerPushInitiation.pursePub;
+ const retryTag = constructTaskIdentifier({
+ tag: PendingTaskType.PeerPushDebit,
+ pursePub,
+ });
+ runLongpollAsync(ws, retryTag, async (ct) => {
+ const mergeUrl = new URL(`purses/${pursePub}/merge`);
+ mergeUrl.searchParams.set("timeout_ms", "30000");
+ const resp = await ws.http.fetch(mergeUrl.href, {
+ // timeout: getReserveRequestTimeout(withdrawalGroup),
+ cancellationToken: ct,
+ });
+ if (resp.status === HttpStatusCode.Ok) {
+ const purseStatus = await readSuccessResponseJsonOrThrow(
+ resp,
+ codecForExchangePurseStatus(),
+ );
+ if (purseStatus.deposit_timestamp) {
+ await transitionPeerPushDebitFromReadyToDone(
+ ws,
+ peerPushInitiation.pursePub,
+ );
+ return {
+ ready: true,
+ };
+ }
+ } else if (resp.status === HttpStatusCode.Gone) {
+ // FIXME: transition the reserve into the expired state
+ }
+ return {
+ ready: false,
+ };
+ });
+ logger.trace(
+ "returning early from withdrawal for long-polling in background",
+ );
+ return {
+ type: OperationAttemptResultType.Longpoll,
+ };
+}
+
+export async function processPeerPushDebit(
+ ws: InternalWalletState,
+ pursePub: string,
+): Promise<OperationAttemptResult> {
+ const peerPushInitiation = await ws.db
+ .mktx((x) => [x.peerPushPaymentInitiations])
+ .runReadOnly(async (tx) => {
+ return tx.peerPushPaymentInitiations.get(pursePub);
+ });
+ if (!peerPushInitiation) {
+ throw Error("peer push payment not found");
+ }
+
+ const retryTag = constructTaskIdentifier({
+ tag: PendingTaskType.PeerPushDebit,
+ pursePub,
+ });
+
+ // We're already running!
+ if (ws.activeLongpoll[retryTag]) {
+ logger.info("peer-push-debit task already in long-polling, returning!");
+ return {
+ type: OperationAttemptResultType.Longpoll,
+ };
+ }
+
+ switch (peerPushInitiation.status) {
+ case PeerPushPaymentInitiationStatus.PendingCreatePurse:
+ return processPeerPushDebitCreateReserve(ws, peerPushInitiation);
+ case PeerPushPaymentInitiationStatus.PendingReady:
+ return processPeerPushDebitReady(ws, peerPushInitiation);
+ }
+
+ return {
+ type: OperationAttemptResultType.Finished,
+ result: undefined,
+ };
+}
+
/**
* Initiate sending a peer-to-peer push payment.
*/
@@ -612,7 +725,7 @@ export async function initiatePeerPushPayment(
pursePriv: pursePair.priv,
pursePub: pursePair.pub,
timestampCreated: TalerProtocolTimestamp.now(),
- status: PeerPushPaymentInitiationStatus.Initiated,
+ status: PeerPushPaymentInitiationStatus.PendingCreatePurse,
contractTerms: contractTerms,
coinSel: {
coinPubs: sel.coins.map((x) => x.coinPub),
@@ -628,12 +741,12 @@ export async function initiatePeerPushPayment(
});
const taskId = constructTaskIdentifier({
- tag: PendingTaskType.PeerPushInitiation,
+ tag: PendingTaskType.PeerPushDebit,
pursePub: pursePair.pub,
});
await runOperationWithErrorReporting(ws, taskId, async () => {
- return await processPeerPushInitiation(ws, pursePair.pub);
+ return await processPeerPushDebit(ws, pursePair.pub);
});
return {
@@ -645,22 +758,24 @@ export async function initiatePeerPushPayment(
exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl,
contractPriv: contractKeyPair.priv,
}),
- transactionId: makeTransactionId(
- TransactionType.PeerPushDebit,
- pursePair.pub,
- ),
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.PeerPushDebit,
+ pursePub: pursePair.pub,
+ }),
};
}
interface ExchangePurseStatus {
balance: AmountString;
deposit_timestamp?: TalerProtocolTimestamp;
+ merge_timestamp?: TalerProtocolTimestamp;
}
export const codecForExchangePurseStatus = (): Codec<ExchangePurseStatus> =>
buildCodecForObject<ExchangePurseStatus>()
.property("balance", codecForAmountString())
.property("deposit_timestamp", codecOptional(codecForTimestamp))
+ .property("merge_timestamp", codecOptional(codecForTimestamp))
.build("ExchangePurseStatus");
export async function preparePeerPushCredit(
@@ -879,13 +994,13 @@ export async function processPeerPushCredit(
const amount = Amounts.parseOrThrow(contractTerms.amount);
if (
- peerInc.status === PeerPushPaymentIncomingStatus.KycRequired &&
+ peerInc.status === PeerPushPaymentIncomingStatus.MergeKycRequired &&
peerInc.kycInfo
) {
- const txId = makeTransactionId(
- TransactionType.PeerPushCredit,
- peerInc.peerPushPaymentIncomingId,
- );
+ const txId = constructTransactionIdentifier({
+ tag: TransactionType.PeerPushCredit,
+ peerPushPaymentIncomingId: peerInc.peerPushPaymentIncomingId,
+ });
await checkWithdrawalKycStatus(
ws,
peerInc.exchangeBaseUrl,
@@ -951,7 +1066,7 @@ export async function processPeerPushCredit(
paytoHash: kycPending.h_payto,
requirementRow: kycPending.requirement_row,
};
- peerInc.status = PeerPushPaymentIncomingStatus.KycRequired;
+ peerInc.status = PeerPushPaymentIncomingStatus.MergeKycRequired;
await tx.peerPushPaymentIncoming.put(peerInc);
});
return {
@@ -975,7 +1090,7 @@ export async function processPeerPushCredit(
},
forcedWithdrawalGroupId: peerInc.withdrawalGroupId,
exchangeBaseUrl: peerInc.exchangeBaseUrl,
- reserveStatus: WithdrawalGroupStatus.QueryingStatus,
+ reserveStatus: WithdrawalGroupStatus.PendingQueryingStatus,
reserveKeyPair: {
priv: mergeReserveInfo.reservePriv,
pub: mergeReserveInfo.reservePub,
@@ -993,9 +1108,9 @@ export async function processPeerPushCredit(
}
if (
peerInc.status === PeerPushPaymentIncomingStatus.Accepted ||
- peerInc.status === PeerPushPaymentIncomingStatus.KycRequired
+ peerInc.status === PeerPushPaymentIncomingStatus.MergeKycRequired
) {
- peerInc.status = PeerPushPaymentIncomingStatus.WithdrawalCreated;
+ peerInc.status = PeerPushPaymentIncomingStatus.Done;
}
await tx.peerPushPaymentIncoming.put(peerInc);
});
@@ -1011,7 +1126,7 @@ export async function confirmPeerPushCredit(
req: ConfirmPeerPushCreditRequest,
): Promise<AcceptPeerPushPaymentResponse> {
let peerInc: PeerPushPaymentIncomingRecord | undefined;
- let contractTerms: PeerContractTerms | undefined;
+
await ws.db
.mktx((x) => [x.contractTerms, x.peerPushPaymentIncoming])
.runReadWrite(async (tx) => {
@@ -1021,10 +1136,6 @@ export async function confirmPeerPushCredit(
if (!peerInc) {
return;
}
- const ctRec = await tx.contractTerms.get(peerInc.contractTermsHash);
- if (ctRec) {
- contractTerms = ctRec.contractTermsRaw;
- }
if (peerInc.status === PeerPushPaymentIncomingStatus.Proposed) {
peerInc.status = PeerPushPaymentIncomingStatus.Accepted;
}
@@ -1037,21 +1148,15 @@ export async function confirmPeerPushCredit(
);
}
- checkDbInvariant(!!contractTerms);
-
- await updateExchangeFromUrl(ws, peerInc.exchangeBaseUrl);
-
- const retryTag = TaskIdentifiers.forPeerPushCredit(peerInc);
+ ws.workAvailable.trigger();
- await runOperationWithErrorReporting(ws, retryTag, () =>
- processPeerPushCredit(ws, req.peerPushPaymentIncomingId),
- );
+ const transactionId = constructTransactionIdentifier({
+ tag: TransactionType.PeerPushCredit,
+ peerPushPaymentIncomingId: req.peerPushPaymentIncomingId,
+ });
return {
- transactionId: makeTransactionId(
- TransactionType.PeerPushCredit,
- req.peerPushPaymentIncomingId,
- ),
+ transactionId,
};
}
@@ -1209,11 +1314,13 @@ export async function confirmPeerPullDebit(
},
);
+ const transactionId = constructTransactionIdentifier({
+ tag: TransactionType.PeerPullDebit,
+ peerPullPaymentIncomingId: req.peerPullPaymentIncomingId,
+ });
+
return {
- transactionId: makeTransactionId(
- TransactionType.PeerPullDebit,
- req.peerPullPaymentIncomingId,
- ),
+ transactionId,
};
}
@@ -1395,7 +1502,7 @@ export async function queryPurseForPeerPullCredit(
},
forcedWithdrawalGroupId: pullIni.withdrawalGroupId,
exchangeBaseUrl: pullIni.exchangeBaseUrl,
- reserveStatus: WithdrawalGroupStatus.QueryingStatus,
+ reserveStatus: WithdrawalGroupStatus.PendingQueryingStatus,
reserveKeyPair: {
priv: reserve.reservePriv,
pub: reserve.reservePub,
@@ -1410,8 +1517,8 @@ export async function queryPurseForPeerPullCredit(
logger.warn("peerPullPaymentInitiation not found anymore");
return;
}
- if (finPi.status === PeerPullPaymentInitiationStatus.PurseCreated) {
- finPi.status = PeerPullPaymentInitiationStatus.PurseDeposited;
+ if (finPi.status === PeerPullPaymentInitiationStatus.PendingReady) {
+ finPi.status = PeerPullPaymentInitiationStatus.DonePurseDeposited;
}
await tx.peerPullPaymentInitiations.put(finPi);
});
@@ -1434,7 +1541,7 @@ export async function processPeerPullCredit(
}
const retryTag = constructTaskIdentifier({
- tag: PendingTaskType.PeerPullInitiation,
+ tag: PendingTaskType.PeerPullCredit,
pursePub,
});
@@ -1449,7 +1556,7 @@ export async function processPeerPullCredit(
logger.trace(`processing ${retryTag}, status=${pullIni.status}`);
switch (pullIni.status) {
- case PeerPullPaymentInitiationStatus.PurseDeposited: {
+ case PeerPullPaymentInitiationStatus.DonePurseDeposited: {
// We implement this case so that the "retry" action on a peer-pull-credit transaction
// also retries the withdrawal task.
@@ -1475,7 +1582,7 @@ export async function processPeerPullCredit(
result: undefined,
};
}
- case PeerPullPaymentInitiationStatus.PurseCreated:
+ case PeerPullPaymentInitiationStatus.PendingReady:
runLongpollAsync(ws, retryTag, async (cancellationToken) =>
queryPurseForPeerPullCredit(ws, pullIni, cancellationToken),
);
@@ -1485,23 +1592,23 @@ export async function processPeerPullCredit(
return {
type: OperationAttemptResultType.Longpoll,
};
- case PeerPullPaymentInitiationStatus.KycRequired: {
+ case PeerPullPaymentInitiationStatus.PendingMergeKycRequired: {
+ const transactionId = constructTransactionIdentifier({
+ tag: TransactionType.PeerPullCredit,
+ pursePub: pullIni.pursePub,
+ });
if (pullIni.kycInfo) {
- const txId = makeTransactionId(
- TransactionType.PeerPullCredit,
- pullIni.pursePub,
- );
await checkWithdrawalKycStatus(
ws,
pullIni.exchangeBaseUrl,
- txId,
+ transactionId,
pullIni.kycInfo,
"individual",
);
}
break;
}
- case PeerPullPaymentInitiationStatus.Initial:
+ case PeerPullPaymentInitiationStatus.PendingCreatePurse:
break;
default:
throw Error(`unknown PeerPullPaymentInitiationStatus ${pullIni.status}`);
@@ -1590,7 +1697,8 @@ export async function processPeerPullCredit(
paytoHash: kycPending.h_payto,
requirementRow: kycPending.requirement_row,
};
- peerIni.status = PeerPullPaymentInitiationStatus.KycRequired;
+ peerIni.status =
+ PeerPullPaymentInitiationStatus.PendingMergeKycRequired;
await tx.peerPullPaymentInitiations.put(peerIni);
});
return {
@@ -1610,7 +1718,7 @@ export async function processPeerPullCredit(
if (!pi2) {
return;
}
- pi2.status = PeerPullPaymentInitiationStatus.PurseCreated;
+ pi2.status = PeerPullPaymentInitiationStatus.PendingReady;
await tx.peerPullPaymentInitiations.put(pi2);
});
@@ -1776,7 +1884,7 @@ export async function initiatePeerPullPayment(
pursePub: pursePair.pub,
mergePriv: mergePair.priv,
mergePub: mergePair.pub,
- status: PeerPullPaymentInitiationStatus.Initial,
+ status: PeerPullPaymentInitiationStatus.PendingCreatePurse,
contractTerms: contractTerms,
mergeTimestamp,
mergeReserveRowId: mergeReserveRowId,
@@ -1796,7 +1904,7 @@ export async function initiatePeerPullPayment(
// check this asynchronously from the transaction status?
const taskId = constructTaskIdentifier({
- tag: PendingTaskType.PeerPullInitiation,
+ tag: PendingTaskType.PeerPullCredit,
pursePub: pursePair.pub,
});
@@ -1804,14 +1912,408 @@ export async function initiatePeerPullPayment(
return processPeerPullCredit(ws, pursePair.pub);
});
+ const transactionId = constructTransactionIdentifier({
+ tag: TransactionType.PeerPullCredit,
+ pursePub: pursePair.pub,
+ });
+
return {
talerUri: constructPayPullUri({
exchangeBaseUrl: exchangeBaseUrl,
contractPriv: contractKeyPair.priv,
}),
- transactionId: makeTransactionId(
- TransactionType.PeerPullCredit,
- pursePair.pub,
- ),
+ transactionId,
};
}
+
+export function computePeerPushDebitTransactionState(
+ ppiRecord: PeerPushPaymentInitiationRecord,
+): TransactionState {
+ switch (ppiRecord.status) {
+ case PeerPushPaymentInitiationStatus.PendingCreatePurse:
+ return {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.CreatePurse,
+ };
+ case PeerPushPaymentInitiationStatus.PendingReady:
+ return {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.Ready,
+ };
+ case PeerPushPaymentInitiationStatus.Aborted:
+ return {
+ major: TransactionMajorState.Aborted,
+ };
+ case PeerPushPaymentInitiationStatus.AbortingDeletePurse:
+ return {
+ major: TransactionMajorState.Aborting,
+ minor: TransactionMinorState.DeletePurse,
+ };
+ case PeerPushPaymentInitiationStatus.AbortingRefresh:
+ return {
+ major: TransactionMajorState.Aborting,
+ minor: TransactionMinorState.Refresh,
+ };
+ case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse:
+ return {
+ major: TransactionMajorState.SuspendedAborting,
+ minor: TransactionMinorState.DeletePurse,
+ };
+ case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh:
+ return {
+ major: TransactionMajorState.SuspendedAborting,
+ minor: TransactionMinorState.Refresh,
+ };
+ case PeerPushPaymentInitiationStatus.SuspendedCreatePurse:
+ return {
+ major: TransactionMajorState.Suspended,
+ minor: TransactionMinorState.CreatePurse,
+ };
+ case PeerPushPaymentInitiationStatus.SuspendedReady:
+ return {
+ major: TransactionMajorState.Suspended,
+ minor: TransactionMinorState.Ready,
+ };
+ case PeerPushPaymentInitiationStatus.Done:
+ return {
+ major: TransactionMajorState.Done,
+ };
+ }
+}
+
+export async function abortPeerPushDebitTransaction(
+ ws: InternalWalletState,
+ pursePub: string,
+) {
+ const taskId = constructTaskIdentifier({
+ tag: PendingTaskType.PeerPushDebit,
+ pursePub,
+ });
+ const transactionId = constructTransactionIdentifier({
+ tag: TransactionType.PeerPushDebit,
+ pursePub,
+ });
+ stopLongpolling(ws, taskId);
+ const transitionInfo = await ws.db
+ .mktx((x) => [x.peerPushPaymentInitiations])
+ .runReadWrite(async (tx) => {
+ const pushDebitRec = await tx.peerPushPaymentInitiations.get(pursePub);
+ if (!pushDebitRec) {
+ logger.warn(`peer push debit ${pursePub} not found`);
+ return;
+ }
+ let newStatus: PeerPushPaymentInitiationStatus | undefined = undefined;
+ switch (pushDebitRec.status) {
+ case PeerPushPaymentInitiationStatus.PendingReady:
+ case PeerPushPaymentInitiationStatus.SuspendedReady:
+ newStatus = PeerPushPaymentInitiationStatus.AbortingDeletePurse;
+ break;
+ case PeerPushPaymentInitiationStatus.SuspendedCreatePurse:
+ case PeerPushPaymentInitiationStatus.PendingCreatePurse:
+ // Network request might already be in-flight!
+ newStatus = PeerPushPaymentInitiationStatus.AbortingDeletePurse;
+ break;
+ case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh:
+ case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse:
+ case PeerPushPaymentInitiationStatus.AbortingRefresh:
+ case PeerPushPaymentInitiationStatus.Done:
+ case PeerPushPaymentInitiationStatus.AbortingDeletePurse:
+ case PeerPushPaymentInitiationStatus.Aborted:
+ // Do nothing
+ break;
+ default:
+ assertUnreachable(pushDebitRec.status);
+ }
+ if (newStatus != null) {
+ const oldTxState = computePeerPushDebitTransactionState(pushDebitRec);
+ pushDebitRec.status = newStatus;
+ const newTxState = computePeerPushDebitTransactionState(pushDebitRec);
+ await tx.peerPushPaymentInitiations.put(pushDebitRec);
+ return {
+ oldTxState,
+ newTxState,
+ };
+ }
+ return undefined;
+ });
+ notifyTransition(ws, transactionId, transitionInfo);
+}
+
+export async function cancelAbortingPeerPushDebitTransaction(
+ ws: InternalWalletState,
+ pursePub: string,
+) {
+ const taskId = constructTaskIdentifier({
+ tag: PendingTaskType.PeerPushDebit,
+ pursePub,
+ });
+ const transactionId = constructTransactionIdentifier({
+ tag: TransactionType.PeerPushDebit,
+ pursePub,
+ });
+ stopLongpolling(ws, taskId);
+ const transitionInfo = await ws.db
+ .mktx((x) => [x.peerPushPaymentInitiations])
+ .runReadWrite(async (tx) => {
+ const pushDebitRec = await tx.peerPushPaymentInitiations.get(pursePub);
+ if (!pushDebitRec) {
+ logger.warn(`peer push debit ${pursePub} not found`);
+ return;
+ }
+ let newStatus: PeerPushPaymentInitiationStatus | undefined = undefined;
+ switch (pushDebitRec.status) {
+ case PeerPushPaymentInitiationStatus.AbortingRefresh:
+ case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh:
+ // FIXME: We also need to abort the refresh group!
+ newStatus = PeerPushPaymentInitiationStatus.Aborted;
+ break;
+ case PeerPushPaymentInitiationStatus.AbortingDeletePurse:
+ case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse:
+ newStatus = PeerPushPaymentInitiationStatus.Aborted;
+ break;
+ case PeerPushPaymentInitiationStatus.PendingReady:
+ case PeerPushPaymentInitiationStatus.SuspendedReady:
+ case PeerPushPaymentInitiationStatus.SuspendedCreatePurse:
+ case PeerPushPaymentInitiationStatus.PendingCreatePurse:
+ case PeerPushPaymentInitiationStatus.Done:
+ case PeerPushPaymentInitiationStatus.Aborted:
+ // Do nothing
+ break;
+ default:
+ assertUnreachable(pushDebitRec.status);
+ }
+ if (newStatus != null) {
+ const oldTxState = computePeerPushDebitTransactionState(pushDebitRec);
+ pushDebitRec.status = newStatus;
+ const newTxState = computePeerPushDebitTransactionState(pushDebitRec);
+ await tx.peerPushPaymentInitiations.put(pushDebitRec);
+ return {
+ oldTxState,
+ newTxState,
+ };
+ }
+ return undefined;
+ });
+ notifyTransition(ws, transactionId, transitionInfo);
+}
+
+export async function suspendPeerPushDebitTransaction(
+ ws: InternalWalletState,
+ pursePub: string,
+) {
+ const taskId = constructTaskIdentifier({
+ tag: PendingTaskType.PeerPushDebit,
+ pursePub,
+ });
+ const transactionId = constructTransactionIdentifier({
+ tag: TransactionType.PeerPushDebit,
+ pursePub,
+ });
+ stopLongpolling(ws, taskId);
+ const transitionInfo = await ws.db
+ .mktx((x) => [x.peerPushPaymentInitiations])
+ .runReadWrite(async (tx) => {
+ const pushDebitRec = await tx.peerPushPaymentInitiations.get(pursePub);
+ if (!pushDebitRec) {
+ logger.warn(`peer push debit ${pursePub} not found`);
+ return;
+ }
+ let newStatus: PeerPushPaymentInitiationStatus | undefined = undefined;
+ switch (pushDebitRec.status) {
+ case PeerPushPaymentInitiationStatus.PendingCreatePurse:
+ newStatus = PeerPushPaymentInitiationStatus.SuspendedCreatePurse;
+ break;
+ case PeerPushPaymentInitiationStatus.AbortingRefresh:
+ newStatus = PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh;
+ break;
+ case PeerPushPaymentInitiationStatus.AbortingDeletePurse:
+ newStatus =
+ PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse;
+ break;
+ case PeerPushPaymentInitiationStatus.PendingReady:
+ newStatus = PeerPushPaymentInitiationStatus.SuspendedReady;
+ break;
+ case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse:
+ case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh:
+ case PeerPushPaymentInitiationStatus.SuspendedReady:
+ case PeerPushPaymentInitiationStatus.SuspendedCreatePurse:
+ case PeerPushPaymentInitiationStatus.Done:
+ case PeerPushPaymentInitiationStatus.Aborted:
+ // Do nothing
+ break;
+ default:
+ assertUnreachable(pushDebitRec.status);
+ }
+ if (newStatus != null) {
+ const oldTxState = computePeerPushDebitTransactionState(pushDebitRec);
+ pushDebitRec.status = newStatus;
+ const newTxState = computePeerPushDebitTransactionState(pushDebitRec);
+ await tx.peerPushPaymentInitiations.put(pushDebitRec);
+ return {
+ oldTxState,
+ newTxState,
+ };
+ }
+ return undefined;
+ });
+ notifyTransition(ws, transactionId, transitionInfo);
+}
+
+export async function resumePeerPushDebitTransaction(
+ ws: InternalWalletState,
+ pursePub: string,
+) {
+ const taskId = constructTaskIdentifier({
+ tag: PendingTaskType.PeerPushDebit,
+ pursePub,
+ });
+ const transactionId = constructTransactionIdentifier({
+ tag: TransactionType.PeerPushDebit,
+ pursePub,
+ });
+ stopLongpolling(ws, taskId);
+ const transitionInfo = await ws.db
+ .mktx((x) => [x.peerPushPaymentInitiations])
+ .runReadWrite(async (tx) => {
+ const pushDebitRec = await tx.peerPushPaymentInitiations.get(pursePub);
+ if (!pushDebitRec) {
+ logger.warn(`peer push debit ${pursePub} not found`);
+ return;
+ }
+ let newStatus: PeerPushPaymentInitiationStatus | undefined = undefined;
+ switch (pushDebitRec.status) {
+ case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse:
+ newStatus = PeerPushPaymentInitiationStatus.AbortingDeletePurse;
+ break;
+ case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh:
+ newStatus = PeerPushPaymentInitiationStatus.AbortingRefresh;
+ break;
+ case PeerPushPaymentInitiationStatus.SuspendedReady:
+ newStatus = PeerPushPaymentInitiationStatus.PendingReady;
+ break;
+ case PeerPushPaymentInitiationStatus.SuspendedCreatePurse:
+ newStatus = PeerPushPaymentInitiationStatus.PendingCreatePurse;
+ break;
+ case PeerPushPaymentInitiationStatus.PendingCreatePurse:
+ case PeerPushPaymentInitiationStatus.AbortingRefresh:
+ case PeerPushPaymentInitiationStatus.AbortingDeletePurse:
+ case PeerPushPaymentInitiationStatus.PendingReady:
+ case PeerPushPaymentInitiationStatus.Done:
+ case PeerPushPaymentInitiationStatus.Aborted:
+ // Do nothing
+ break;
+ default:
+ assertUnreachable(pushDebitRec.status);
+ }
+ if (newStatus != null) {
+ const oldTxState = computePeerPushDebitTransactionState(pushDebitRec);
+ pushDebitRec.status = newStatus;
+ const newTxState = computePeerPushDebitTransactionState(pushDebitRec);
+ await tx.peerPushPaymentInitiations.put(pushDebitRec);
+ return {
+ oldTxState,
+ newTxState,
+ };
+ }
+ return undefined;
+ });
+ notifyTransition(ws, transactionId, transitionInfo);
+}
+
+export function computePeerPushCreditTransactionState(
+ pushCreditRecord: PeerPushPaymentIncomingRecord,
+): TransactionState {
+ switch (pushCreditRecord.status) {
+ case PeerPushPaymentIncomingStatus.Proposed:
+ return {
+ major: TransactionMajorState.Dialog,
+ minor: TransactionMinorState.Proposed,
+ };
+ case PeerPushPaymentIncomingStatus.Accepted:
+ return {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.Merge,
+ };
+ case PeerPushPaymentIncomingStatus.Done:
+ return {
+ major: TransactionMajorState.Done,
+ };
+ case PeerPushPaymentIncomingStatus.MergeKycRequired:
+ return {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.KycRequired,
+ };
+ case PeerPushPaymentIncomingStatus.Withdrawing:
+ return {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.Withdraw,
+ };
+ }
+}
+
+export function computePeerPullCreditTransactionState(
+ pullCreditRecord: PeerPullPaymentInitiationRecord,
+): TransactionState {
+ switch (pullCreditRecord.status) {
+ case PeerPullPaymentInitiationStatus.PendingCreatePurse:
+ return {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.CreatePurse,
+ };
+ case PeerPullPaymentInitiationStatus.PendingMergeKycRequired:
+ return {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.MergeKycRequired,
+ };
+ case PeerPullPaymentInitiationStatus.PendingReady:
+ return {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.Ready,
+ };
+ case PeerPullPaymentInitiationStatus.DonePurseDeposited:
+ return {
+ major: TransactionMajorState.Done,
+ };
+ case PeerPullPaymentInitiationStatus.PendingWithdrawing:
+ return {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.Withdraw,
+ };
+ case PeerPullPaymentInitiationStatus.SuspendedCreatePurse:
+ return {
+ major: TransactionMajorState.Suspended,
+ minor: TransactionMinorState.CreatePurse,
+ };
+ case PeerPullPaymentInitiationStatus.SuspendedReady:
+ return {
+ major: TransactionMajorState.Suspended,
+ minor: TransactionMinorState.Ready,
+ };
+ case PeerPullPaymentInitiationStatus.SuspendedWithdrawing:
+ return {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.Withdraw,
+ };
+ }
+}
+
+export function computePeerPullDebitTransactionState(
+ pullDebitRecord: PeerPullPaymentIncomingRecord,
+): TransactionState {
+ switch (pullDebitRecord.status) {
+ case PeerPullPaymentIncomingStatus.Proposed:
+ return {
+ major: TransactionMajorState.Dialog,
+ minor: TransactionMinorState.Proposed,
+ };
+ case PeerPullPaymentIncomingStatus.Accepted:
+ return {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.Deposit,
+ };
+ case PeerPullPaymentIncomingStatus.Paid:
+ return {
+ major: TransactionMajorState.Done,
+ };
+ }
+}