aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-06-19 16:03:06 +0200
committerFlorian Dold <florian@dold.me>2023-06-19 16:03:06 +0200
commit54f0c82999833132baf83995526025ac56d6fe06 (patch)
treeb0138031c4a0432ec5ecddb62be14b0432112a4b /packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts
parentffa68ce8ddc77bf622af4234696a065cde482554 (diff)
downloadwallet-core-54f0c82999833132baf83995526025ac56d6fe06.tar.gz
wallet-core-54f0c82999833132baf83995526025ac56d6fe06.tar.bz2
wallet-core-54f0c82999833132baf83995526025ac56d6fe06.zip
wallet-core: fix peer-(push,pull)-debit withdrawal states
Diffstat (limited to 'packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts')
-rw-r--r--packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts267
1 files changed, 185 insertions, 82 deletions
diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts
index 1a79c7b87..9b563b37e 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts
@@ -15,76 +15,74 @@
*/
import {
- PreparePeerPushCredit,
- PreparePeerPushCreditResponse,
- parsePayPushUri,
- codecForPeerContractTerms,
- TransactionType,
- encodeCrock,
- eddsaGetPublic,
- decodeCrock,
- codecForExchangeGetContractResponse,
- getRandomBytes,
- ContractTermsUtil,
- Amounts,
- TalerPreciseTimestamp,
AcceptPeerPushPaymentResponse,
+ Amounts,
ConfirmPeerPushCreditRequest,
+ ContractTermsUtil,
ExchangePurseMergeRequest,
HttpStatusCode,
+ Logger,
PeerContractTerms,
+ PreparePeerPushCredit,
+ PreparePeerPushCreditResponse,
+ TalerErrorCode,
+ TalerPreciseTimestamp,
TalerProtocolTimestamp,
- WalletAccountMergeFlags,
- codecForAny,
- codecForWalletKycUuid,
- j2s,
- Logger,
- ExchangePurseDeposits,
TransactionAction,
TransactionMajorState,
TransactionMinorState,
TransactionState,
- TalerError,
- TalerErrorCode,
+ TransactionType,
+ WalletAccountMergeFlags,
WalletKycUuid,
+ codecForAny,
+ codecForExchangeGetContractResponse,
+ codecForPeerContractTerms,
+ codecForWalletKycUuid,
+ decodeCrock,
+ eddsaGetPublic,
+ encodeCrock,
+ getRandomBytes,
+ j2s,
makeErrorDetail,
+ parsePayPushUri,
} from "@gnu-taler/taler-util";
import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
import {
InternalWalletState,
KycPendingInfo,
KycUserType,
- PeerPullDebitRecordStatus,
PeerPushPaymentIncomingRecord,
PeerPushPaymentIncomingStatus,
PendingTaskType,
WithdrawalGroupStatus,
WithdrawalRecordType,
} from "../index.js";
+import { assertUnreachable } from "../util/assertUnreachable.js";
+import { checkDbInvariant } from "../util/invariants.js";
+import {
+ OperationAttemptResult,
+ OperationAttemptResultType,
+ constructTaskIdentifier,
+} from "../util/retries.js";
+import { runLongpollAsync } from "./common.js";
import { updateExchangeFromUrl } from "./exchanges.js";
import {
codecForExchangePurseStatus,
getMergeReserveInfo,
- queryCoinInfosForSelection,
talerPaytoFromExchangeReserve,
} from "./pay-peer-common.js";
import {
+ TransitionInfo,
constructTransactionIdentifier,
notifyTransition,
stopLongpolling,
} from "./transactions.js";
import {
getExchangeWithdrawalInfo,
- internalCreateWithdrawalGroup,
+ internalPerformCreateWithdrawalGroup,
+ internalPrepareCreateWithdrawalGroup,
} from "./withdraw.js";
-import { checkDbInvariant } from "../util/invariants.js";
-import {
- OperationAttemptResult,
- OperationAttemptResultType,
- constructTaskIdentifier,
-} from "../util/retries.js";
-import { assertUnreachable } from "../util/assertUnreachable.js";
-import { runLongpollAsync } from "./common.js";
const logger = new Logger("pay-peer-push-credit.ts");
@@ -148,7 +146,7 @@ export async function preparePeerPushCredit(
const getContractUrl = new URL(`contracts/${contractPub}`, exchangeBaseUrl);
- const contractHttpResp = await ws.http.get(getContractUrl.href);
+ const contractHttpResp = await ws.http.fetch(getContractUrl.href);
const contractResp = await readSuccessResponseJsonOrThrow(
contractHttpResp,
@@ -375,51 +373,19 @@ async function processPeerPushCreditKycRequired(
}
}
-export async function processPeerPushCredit(
+async function handlePendingMerge(
ws: InternalWalletState,
- peerPushPaymentIncomingId: string,
+ peerInc: PeerPushPaymentIncomingRecord,
+ contractTerms: PeerContractTerms,
): Promise<OperationAttemptResult> {
- let peerInc: PeerPushPaymentIncomingRecord | undefined;
- let contractTerms: PeerContractTerms | undefined;
- await ws.db
- .mktx((x) => [x.contractTerms, x.peerPushPaymentIncoming])
- .runReadWrite(async (tx) => {
- peerInc = await tx.peerPushPaymentIncoming.get(peerPushPaymentIncomingId);
- if (!peerInc) {
- return;
- }
- const ctRec = await tx.contractTerms.get(peerInc.contractTermsHash);
- if (ctRec) {
- contractTerms = ctRec.contractTermsRaw;
- }
- await tx.peerPushPaymentIncoming.put(peerInc);
- });
-
- if (!peerInc) {
- throw Error(
- `can't accept unknown incoming p2p push payment (${peerPushPaymentIncomingId})`,
- );
- }
-
- checkDbInvariant(!!contractTerms);
+ const { peerPushPaymentIncomingId } = peerInc;
+ const transactionId = constructTransactionIdentifier({
+ tag: TransactionType.PeerPushCredit,
+ peerPushPaymentIncomingId,
+ });
const amount = Amounts.parseOrThrow(contractTerms.amount);
- if (
- peerInc.status === PeerPushPaymentIncomingStatus.PendingMergeKycRequired
- ) {
- if (!peerInc.kycInfo) {
- throw Error("invalid state, kycInfo required");
- }
- return await longpollKycStatus(
- ws,
- peerPushPaymentIncomingId,
- peerInc.exchangeBaseUrl,
- peerInc.kycInfo,
- "individual",
- );
- }
-
const mergeReserveInfo = await getMergeReserveInfo(ws, {
exchangeBaseUrl: peerInc.exchangeBaseUrl,
});
@@ -475,7 +441,7 @@ export async function processPeerPushCredit(
);
logger.trace(`merge response: ${j2s(res)}`);
- await internalCreateWithdrawalGroup(ws, {
+ const withdrawalGroupPrep = await internalPrepareCreateWithdrawalGroup(ws, {
amount,
wgInfo: {
withdrawalType: WithdrawalRecordType.PeerPushCredit,
@@ -490,23 +456,51 @@ export async function processPeerPushCredit(
},
});
- await ws.db
- .mktx((x) => [x.contractTerms, x.peerPushPaymentIncoming])
+ const txRes = await ws.db
+ .mktx((x) => [
+ x.contractTerms,
+ x.peerPushPaymentIncoming,
+ x.withdrawalGroups,
+ x.reserves,
+ x.exchanges,
+ x.exchangeDetails,
+ x.exchangeTrust,
+ ])
.runReadWrite(async (tx) => {
const peerInc = await tx.peerPushPaymentIncoming.get(
peerPushPaymentIncomingId,
);
if (!peerInc) {
- return;
+ return undefined;
}
- if (
- peerInc.status === PeerPushPaymentIncomingStatus.PendingMerge ||
- peerInc.status === PeerPushPaymentIncomingStatus.PendingMergeKycRequired
- ) {
- peerInc.status = PeerPushPaymentIncomingStatus.Done;
+ let withdrawalTransition: TransitionInfo | undefined;
+ const oldTxState = computePeerPushCreditTransactionState(peerInc);
+ switch (peerInc.status) {
+ case PeerPushPaymentIncomingStatus.PendingMerge:
+ case PeerPushPaymentIncomingStatus.PendingMergeKycRequired: {
+ peerInc.status = PeerPushPaymentIncomingStatus.PendingWithdrawing;
+ const wgRes = await internalPerformCreateWithdrawalGroup(
+ ws,
+ tx,
+ withdrawalGroupPrep,
+ );
+ peerInc.withdrawalGroupId = wgRes.withdrawalGroup.withdrawalGroupId;
+ break;
+ }
}
await tx.peerPushPaymentIncoming.put(peerInc);
+ const newTxState = computePeerPushCreditTransactionState(peerInc);
+ return {
+ peerPushCreditTransition: { oldTxState, newTxState },
+ withdrawalTransition,
+ };
});
+ notifyTransition(
+ ws,
+ withdrawalGroupPrep.transactionId,
+ txRes?.withdrawalTransition,
+ );
+ notifyTransition(ws, transactionId, txRes?.peerPushCreditTransition);
return {
type: OperationAttemptResultType.Finished,
@@ -514,6 +508,115 @@ export async function processPeerPushCredit(
};
}
+async function handlePendingWithdrawing(
+ ws: InternalWalletState,
+ peerInc: PeerPushPaymentIncomingRecord,
+): Promise<OperationAttemptResult> {
+ if (!peerInc.withdrawalGroupId) {
+ throw Error("invalid db state (withdrawing, but no withdrawal group ID");
+ }
+ const transactionId = constructTransactionIdentifier({
+ tag: TransactionType.PeerPushCredit,
+ peerPushPaymentIncomingId: peerInc.peerPushPaymentIncomingId,
+ });
+ const wgId = peerInc.withdrawalGroupId;
+ let finished: boolean = false;
+ const transitionInfo = await ws.db
+ .mktx((x) => [x.peerPushPaymentIncoming, x.withdrawalGroups])
+ .runReadWrite(async (tx) => {
+ const ppi = await tx.peerPushPaymentIncoming.get(
+ peerInc.peerPushPaymentIncomingId,
+ );
+ if (!ppi) {
+ finished = true;
+ return;
+ }
+ if (ppi.status !== PeerPushPaymentIncomingStatus.PendingWithdrawing) {
+ finished = true;
+ return;
+ }
+ const oldTxState = computePeerPushCreditTransactionState(ppi);
+ const wg = await tx.withdrawalGroups.get(wgId);
+ if (!wg) {
+ // FIXME: Fail the operation instead?
+ return undefined;
+ }
+ switch (wg.status) {
+ case WithdrawalGroupStatus.Finished:
+ finished = true;
+ ppi.status = PeerPushPaymentIncomingStatus.Done;
+ break;
+ // FIXME: Also handle other final states!
+ }
+ await tx.peerPushPaymentIncoming.put(ppi);
+ const newTxState = computePeerPushCreditTransactionState(ppi);
+ return {
+ oldTxState,
+ newTxState,
+ };
+ });
+ notifyTransition(ws, transactionId, transitionInfo);
+ if (finished) {
+ return OperationAttemptResult.finishedEmpty();
+ } else {
+ // FIXME: Return indicator that we depend on the other operation!
+ return OperationAttemptResult.pendingEmpty();
+ }
+}
+
+export async function processPeerPushCredit(
+ ws: InternalWalletState,
+ peerPushPaymentIncomingId: string,
+): Promise<OperationAttemptResult> {
+ let peerInc: PeerPushPaymentIncomingRecord | undefined;
+ let contractTerms: PeerContractTerms | undefined;
+ await ws.db
+ .mktx((x) => [x.contractTerms, x.peerPushPaymentIncoming])
+ .runReadWrite(async (tx) => {
+ peerInc = await tx.peerPushPaymentIncoming.get(peerPushPaymentIncomingId);
+ if (!peerInc) {
+ return;
+ }
+ const ctRec = await tx.contractTerms.get(peerInc.contractTermsHash);
+ if (ctRec) {
+ contractTerms = ctRec.contractTermsRaw;
+ }
+ await tx.peerPushPaymentIncoming.put(peerInc);
+ });
+
+ checkDbInvariant(!!contractTerms);
+
+ if (!peerInc) {
+ throw Error(
+ `can't accept unknown incoming p2p push payment (${peerPushPaymentIncomingId})`,
+ );
+ }
+
+ switch (peerInc.status) {
+ case PeerPushPaymentIncomingStatus.PendingMergeKycRequired: {
+ if (!peerInc.kycInfo) {
+ throw Error("invalid state, kycInfo required");
+ }
+ return await longpollKycStatus(
+ ws,
+ peerPushPaymentIncomingId,
+ peerInc.exchangeBaseUrl,
+ peerInc.kycInfo,
+ "individual",
+ );
+ }
+
+ case PeerPushPaymentIncomingStatus.PendingMerge:
+ return handlePendingMerge(ws, peerInc, contractTerms);
+
+ case PeerPushPaymentIncomingStatus.PendingWithdrawing:
+ return handlePendingWithdrawing(ws, peerInc);
+
+ default:
+ return OperationAttemptResult.finishedEmpty();
+ }
+}
+
export async function confirmPeerPushCredit(
ws: InternalWalletState,
req: ConfirmPeerPushCreditRequest,