aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts')
-rw-r--r--packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts188
1 files changed, 176 insertions, 12 deletions
diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts
index dead6313d..ac0aa9c87 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts
@@ -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,
+ };
}
-} \ No newline at end of file
+}
+