android
This commit is contained in:
parent
8683c93613
commit
829acdd3d9
@ -157,6 +157,7 @@ export function installAndroidWalletListener() {
|
|||||||
case "withdrawTestkudos": {
|
case "withdrawTestkudos": {
|
||||||
const wallet = await wp.promise;
|
const wallet = await wp.promise;
|
||||||
await withdrawTestBalance(wallet);
|
await withdrawTestBalance(wallet);
|
||||||
|
result = {};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "getHistory": {
|
case "getHistory": {
|
||||||
@ -164,6 +165,12 @@ export function installAndroidWalletListener() {
|
|||||||
result = await wallet.getHistory();
|
result = await wallet.getHistory();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "retryPendingNow": {
|
||||||
|
const wallet = await wp.promise;
|
||||||
|
await wallet.runPending(true);
|
||||||
|
result = {};
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "preparePay": {
|
case "preparePay": {
|
||||||
const wallet = await wp.promise;
|
const wallet = await wp.promise;
|
||||||
result = await wallet.preparePay(msg.args.url);
|
result = await wallet.preparePay(msg.args.url);
|
||||||
@ -197,9 +204,6 @@ export function installAndroidWalletListener() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "reset": {
|
case "reset": {
|
||||||
const wallet = await wp.promise;
|
|
||||||
wallet.stop();
|
|
||||||
wp = openPromise<Wallet>();
|
|
||||||
const oldArgs = walletArgs;
|
const oldArgs = walletArgs;
|
||||||
walletArgs = { ...oldArgs };
|
walletArgs = { ...oldArgs };
|
||||||
if (oldArgs && oldArgs.persistentStoragePath) {
|
if (oldArgs && oldArgs.persistentStoragePath) {
|
||||||
@ -211,6 +215,9 @@ export function installAndroidWalletListener() {
|
|||||||
// Prevent further storage!
|
// Prevent further storage!
|
||||||
walletArgs.persistentStoragePath = undefined;
|
walletArgs.persistentStoragePath = undefined;
|
||||||
}
|
}
|
||||||
|
const wallet = await wp.promise;
|
||||||
|
wallet.stop();
|
||||||
|
wp = openPromise<Wallet>();
|
||||||
maybeWallet = undefined;
|
maybeWallet = undefined;
|
||||||
const w = await getDefaultNodeWallet(walletArgs);
|
const w = await getDefaultNodeWallet(walletArgs);
|
||||||
maybeWallet = w;
|
maybeWallet = w;
|
||||||
@ -218,6 +225,7 @@ export function installAndroidWalletListener() {
|
|||||||
console.error("Error during wallet retry loop", e);
|
console.error("Error during wallet retry loop", e);
|
||||||
});
|
});
|
||||||
wp.resolve(w);
|
wp.resolve(w);
|
||||||
|
result = {};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -22,6 +22,8 @@ import {
|
|||||||
PayReq,
|
PayReq,
|
||||||
Proposal,
|
Proposal,
|
||||||
ContractTerms,
|
ContractTerms,
|
||||||
|
MerchantRefundPermission,
|
||||||
|
RefundRequest,
|
||||||
} from "../talerTypes";
|
} from "../talerTypes";
|
||||||
import {
|
import {
|
||||||
Timestamp,
|
Timestamp,
|
||||||
@ -39,6 +41,7 @@ import {
|
|||||||
runWithWriteTransaction,
|
runWithWriteTransaction,
|
||||||
oneShotPut,
|
oneShotPut,
|
||||||
oneShotGetIndexed,
|
oneShotGetIndexed,
|
||||||
|
oneShotMutate,
|
||||||
} from "../util/query";
|
} from "../util/query";
|
||||||
import {
|
import {
|
||||||
Stores,
|
Stores,
|
||||||
@ -59,9 +62,8 @@ import {
|
|||||||
} from "../util/helpers";
|
} from "../util/helpers";
|
||||||
import { Logger } from "../util/logging";
|
import { Logger } from "../util/logging";
|
||||||
import { InternalWalletState } from "./state";
|
import { InternalWalletState } from "./state";
|
||||||
import { parsePayUri } from "../util/taleruri";
|
import { parsePayUri, parseRefundUri } from "../util/taleruri";
|
||||||
import { getTotalRefreshCost, refresh } from "./refresh";
|
import { getTotalRefreshCost, refresh } from "./refresh";
|
||||||
import { acceptRefundResponse } from "./refund";
|
|
||||||
import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto";
|
import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto";
|
||||||
|
|
||||||
export interface SpeculativePayData {
|
export interface SpeculativePayData {
|
||||||
@ -856,3 +858,212 @@ export async function confirmPay(
|
|||||||
|
|
||||||
return submitPay(ws, proposalId, sessionId);
|
return submitPay(ws, proposalId, sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export async function getFullRefundFees(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
refundPermissions: MerchantRefundPermission[],
|
||||||
|
): Promise<AmountJson> {
|
||||||
|
if (refundPermissions.length === 0) {
|
||||||
|
throw Error("no refunds given");
|
||||||
|
}
|
||||||
|
const coin0 = await oneShotGet(
|
||||||
|
ws.db,
|
||||||
|
Stores.coins,
|
||||||
|
refundPermissions[0].coin_pub,
|
||||||
|
);
|
||||||
|
if (!coin0) {
|
||||||
|
throw Error("coin not found");
|
||||||
|
}
|
||||||
|
let feeAcc = Amounts.getZero(
|
||||||
|
Amounts.parseOrThrow(refundPermissions[0].refund_amount).currency,
|
||||||
|
);
|
||||||
|
|
||||||
|
const denoms = await oneShotIterIndex(
|
||||||
|
ws.db,
|
||||||
|
Stores.denominations.exchangeBaseUrlIndex,
|
||||||
|
coin0.exchangeBaseUrl,
|
||||||
|
).toArray();
|
||||||
|
|
||||||
|
for (const rp of refundPermissions) {
|
||||||
|
const coin = await oneShotGet(ws.db, Stores.coins, rp.coin_pub);
|
||||||
|
if (!coin) {
|
||||||
|
throw Error("coin not found");
|
||||||
|
}
|
||||||
|
const denom = await oneShotGet(ws.db, Stores.denominations, [
|
||||||
|
coin0.exchangeBaseUrl,
|
||||||
|
coin.denomPub,
|
||||||
|
]);
|
||||||
|
if (!denom) {
|
||||||
|
throw Error(`denom not found (${coin.denomPub})`);
|
||||||
|
}
|
||||||
|
// FIXME: this assumes that the refund already happened.
|
||||||
|
// When it hasn't, the refresh cost is inaccurate. To fix this,
|
||||||
|
// we need introduce a flag to tell if a coin was refunded or
|
||||||
|
// refreshed normally (and what about incremental refunds?)
|
||||||
|
const refundAmount = Amounts.parseOrThrow(rp.refund_amount);
|
||||||
|
const refundFee = Amounts.parseOrThrow(rp.refund_fee);
|
||||||
|
const refreshCost = getTotalRefreshCost(
|
||||||
|
denoms,
|
||||||
|
denom,
|
||||||
|
Amounts.sub(refundAmount, refundFee).amount,
|
||||||
|
);
|
||||||
|
feeAcc = Amounts.add(feeAcc, refreshCost, refundFee).amount;
|
||||||
|
}
|
||||||
|
return feeAcc;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitRefunds(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
proposalId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId);
|
||||||
|
if (!purchase) {
|
||||||
|
console.error(
|
||||||
|
"not submitting refunds, payment not found:",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pendingKeys = Object.keys(purchase.refundsPending);
|
||||||
|
if (pendingKeys.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const pk of pendingKeys) {
|
||||||
|
const perm = purchase.refundsPending[pk];
|
||||||
|
const req: RefundRequest = {
|
||||||
|
coin_pub: perm.coin_pub,
|
||||||
|
h_contract_terms: purchase.contractTermsHash,
|
||||||
|
merchant_pub: purchase.contractTerms.merchant_pub,
|
||||||
|
merchant_sig: perm.merchant_sig,
|
||||||
|
refund_amount: perm.refund_amount,
|
||||||
|
refund_fee: perm.refund_fee,
|
||||||
|
rtransaction_id: perm.rtransaction_id,
|
||||||
|
};
|
||||||
|
console.log("sending refund permission", perm);
|
||||||
|
// FIXME: not correct once we support multiple exchanges per payment
|
||||||
|
const exchangeUrl = purchase.payReq.coins[0].exchange_url;
|
||||||
|
const reqUrl = new URL("refund", exchangeUrl);
|
||||||
|
const resp = await ws.http.postJson(reqUrl.href, req);
|
||||||
|
if (resp.status !== 200) {
|
||||||
|
console.error("refund failed", resp);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transactionally mark successful refunds as done
|
||||||
|
const transformPurchase = (
|
||||||
|
t: PurchaseRecord | undefined,
|
||||||
|
): PurchaseRecord | undefined => {
|
||||||
|
if (!t) {
|
||||||
|
console.warn("purchase not found, not updating refund");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (t.refundsPending[pk]) {
|
||||||
|
t.refundsDone[pk] = t.refundsPending[pk];
|
||||||
|
delete t.refundsPending[pk];
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
};
|
||||||
|
const transformCoin = (
|
||||||
|
c: CoinRecord | undefined,
|
||||||
|
): CoinRecord | undefined => {
|
||||||
|
if (!c) {
|
||||||
|
console.warn("coin not found, can't apply refund");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const refundAmount = Amounts.parseOrThrow(perm.refund_amount);
|
||||||
|
const refundFee = Amounts.parseOrThrow(perm.refund_fee);
|
||||||
|
c.status = CoinStatus.Dirty;
|
||||||
|
c.currentAmount = Amounts.add(c.currentAmount, refundAmount).amount;
|
||||||
|
c.currentAmount = Amounts.sub(c.currentAmount, refundFee).amount;
|
||||||
|
|
||||||
|
return c;
|
||||||
|
};
|
||||||
|
|
||||||
|
await runWithWriteTransaction(
|
||||||
|
ws.db,
|
||||||
|
[Stores.purchases, Stores.coins],
|
||||||
|
async tx => {
|
||||||
|
await tx.mutate(Stores.purchases, proposalId, transformPurchase);
|
||||||
|
await tx.mutate(Stores.coins, perm.coin_pub, transformCoin);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
refresh(ws, perm.coin_pub);
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.badge.showNotification();
|
||||||
|
ws.notifier.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function acceptRefundResponse(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
refundResponse: MerchantRefundResponse,
|
||||||
|
): Promise<string> {
|
||||||
|
const refundPermissions = refundResponse.refund_permissions;
|
||||||
|
|
||||||
|
if (!refundPermissions.length) {
|
||||||
|
console.warn("got empty refund list");
|
||||||
|
throw Error("empty refund");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add refund to purchase if not already added.
|
||||||
|
*/
|
||||||
|
function f(t: PurchaseRecord | undefined): PurchaseRecord | undefined {
|
||||||
|
if (!t) {
|
||||||
|
console.error("purchase not found, not adding refunds");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
t.timestamp_refund = getTimestampNow();
|
||||||
|
|
||||||
|
for (const perm of refundPermissions) {
|
||||||
|
if (
|
||||||
|
!t.refundsPending[perm.merchant_sig] &&
|
||||||
|
!t.refundsDone[perm.merchant_sig]
|
||||||
|
) {
|
||||||
|
t.refundsPending[perm.merchant_sig] = perm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hc = refundResponse.h_contract_terms;
|
||||||
|
|
||||||
|
// Add the refund permissions to the purchase within a DB transaction
|
||||||
|
await oneShotMutate(ws.db, Stores.purchases, hc, f);
|
||||||
|
ws.notifier.notify();
|
||||||
|
|
||||||
|
await submitRefunds(ws, hc);
|
||||||
|
|
||||||
|
return hc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept a refund, return the contract hash for the contract
|
||||||
|
* that was involved in the refund.
|
||||||
|
*/
|
||||||
|
export async function applyRefund(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
talerRefundUri: string,
|
||||||
|
): Promise<string> {
|
||||||
|
const parseResult = parseRefundUri(talerRefundUri);
|
||||||
|
|
||||||
|
if (!parseResult) {
|
||||||
|
throw Error("invalid refund URI");
|
||||||
|
}
|
||||||
|
|
||||||
|
const refundUrl = parseResult.refundUrl;
|
||||||
|
|
||||||
|
logger.trace("processing refund");
|
||||||
|
let resp;
|
||||||
|
try {
|
||||||
|
resp = await ws.http.get(refundUrl);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("error downloading refund permission", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
const refundResponse = MerchantRefundResponse.checked(resp.responseJson);
|
||||||
|
return acceptRefundResponse(ws, refundResponse);
|
||||||
|
}
|
||||||
|
@ -1,244 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of GNU Taler
|
|
||||||
(C) 2019 GNUnet e.V.
|
|
||||||
|
|
||||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
|
||||||
terms of the GNU General Public License as published by the Free Software
|
|
||||||
Foundation; either version 3, or (at your option) any later version.
|
|
||||||
|
|
||||||
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along with
|
|
||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
MerchantRefundResponse,
|
|
||||||
RefundRequest,
|
|
||||||
MerchantRefundPermission,
|
|
||||||
} from "../talerTypes";
|
|
||||||
import { PurchaseRecord, Stores, CoinRecord, CoinStatus } from "../dbTypes";
|
|
||||||
import { getTimestampNow } from "../walletTypes";
|
|
||||||
import {
|
|
||||||
oneShotMutate,
|
|
||||||
oneShotGet,
|
|
||||||
runWithWriteTransaction,
|
|
||||||
oneShotIterIndex,
|
|
||||||
} from "../util/query";
|
|
||||||
import { InternalWalletState } from "./state";
|
|
||||||
import { parseRefundUri } from "../util/taleruri";
|
|
||||||
import { Logger } from "../util/logging";
|
|
||||||
import { AmountJson } from "../util/amounts";
|
|
||||||
import * as Amounts from "../util/amounts";
|
|
||||||
import { getTotalRefreshCost, refresh } from "./refresh";
|
|
||||||
|
|
||||||
const logger = new Logger("refund.ts");
|
|
||||||
|
|
||||||
export async function getFullRefundFees(
|
|
||||||
ws: InternalWalletState,
|
|
||||||
refundPermissions: MerchantRefundPermission[],
|
|
||||||
): Promise<AmountJson> {
|
|
||||||
if (refundPermissions.length === 0) {
|
|
||||||
throw Error("no refunds given");
|
|
||||||
}
|
|
||||||
const coin0 = await oneShotGet(
|
|
||||||
ws.db,
|
|
||||||
Stores.coins,
|
|
||||||
refundPermissions[0].coin_pub,
|
|
||||||
);
|
|
||||||
if (!coin0) {
|
|
||||||
throw Error("coin not found");
|
|
||||||
}
|
|
||||||
let feeAcc = Amounts.getZero(
|
|
||||||
Amounts.parseOrThrow(refundPermissions[0].refund_amount).currency,
|
|
||||||
);
|
|
||||||
|
|
||||||
const denoms = await oneShotIterIndex(
|
|
||||||
ws.db,
|
|
||||||
Stores.denominations.exchangeBaseUrlIndex,
|
|
||||||
coin0.exchangeBaseUrl,
|
|
||||||
).toArray();
|
|
||||||
|
|
||||||
for (const rp of refundPermissions) {
|
|
||||||
const coin = await oneShotGet(ws.db, Stores.coins, rp.coin_pub);
|
|
||||||
if (!coin) {
|
|
||||||
throw Error("coin not found");
|
|
||||||
}
|
|
||||||
const denom = await oneShotGet(ws.db, Stores.denominations, [
|
|
||||||
coin0.exchangeBaseUrl,
|
|
||||||
coin.denomPub,
|
|
||||||
]);
|
|
||||||
if (!denom) {
|
|
||||||
throw Error(`denom not found (${coin.denomPub})`);
|
|
||||||
}
|
|
||||||
// FIXME: this assumes that the refund already happened.
|
|
||||||
// When it hasn't, the refresh cost is inaccurate. To fix this,
|
|
||||||
// we need introduce a flag to tell if a coin was refunded or
|
|
||||||
// refreshed normally (and what about incremental refunds?)
|
|
||||||
const refundAmount = Amounts.parseOrThrow(rp.refund_amount);
|
|
||||||
const refundFee = Amounts.parseOrThrow(rp.refund_fee);
|
|
||||||
const refreshCost = getTotalRefreshCost(
|
|
||||||
denoms,
|
|
||||||
denom,
|
|
||||||
Amounts.sub(refundAmount, refundFee).amount,
|
|
||||||
);
|
|
||||||
feeAcc = Amounts.add(feeAcc, refreshCost, refundFee).amount;
|
|
||||||
}
|
|
||||||
return feeAcc;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function submitRefunds(
|
|
||||||
ws: InternalWalletState,
|
|
||||||
proposalId: string,
|
|
||||||
): Promise<void> {
|
|
||||||
const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId);
|
|
||||||
if (!purchase) {
|
|
||||||
console.error(
|
|
||||||
"not submitting refunds, payment not found:",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const pendingKeys = Object.keys(purchase.refundsPending);
|
|
||||||
if (pendingKeys.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const pk of pendingKeys) {
|
|
||||||
const perm = purchase.refundsPending[pk];
|
|
||||||
const req: RefundRequest = {
|
|
||||||
coin_pub: perm.coin_pub,
|
|
||||||
h_contract_terms: purchase.contractTermsHash,
|
|
||||||
merchant_pub: purchase.contractTerms.merchant_pub,
|
|
||||||
merchant_sig: perm.merchant_sig,
|
|
||||||
refund_amount: perm.refund_amount,
|
|
||||||
refund_fee: perm.refund_fee,
|
|
||||||
rtransaction_id: perm.rtransaction_id,
|
|
||||||
};
|
|
||||||
console.log("sending refund permission", perm);
|
|
||||||
// FIXME: not correct once we support multiple exchanges per payment
|
|
||||||
const exchangeUrl = purchase.payReq.coins[0].exchange_url;
|
|
||||||
const reqUrl = new URL("refund", exchangeUrl);
|
|
||||||
const resp = await ws.http.postJson(reqUrl.href, req);
|
|
||||||
if (resp.status !== 200) {
|
|
||||||
console.error("refund failed", resp);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transactionally mark successful refunds as done
|
|
||||||
const transformPurchase = (
|
|
||||||
t: PurchaseRecord | undefined,
|
|
||||||
): PurchaseRecord | undefined => {
|
|
||||||
if (!t) {
|
|
||||||
console.warn("purchase not found, not updating refund");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (t.refundsPending[pk]) {
|
|
||||||
t.refundsDone[pk] = t.refundsPending[pk];
|
|
||||||
delete t.refundsPending[pk];
|
|
||||||
}
|
|
||||||
return t;
|
|
||||||
};
|
|
||||||
const transformCoin = (
|
|
||||||
c: CoinRecord | undefined,
|
|
||||||
): CoinRecord | undefined => {
|
|
||||||
if (!c) {
|
|
||||||
console.warn("coin not found, can't apply refund");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const refundAmount = Amounts.parseOrThrow(perm.refund_amount);
|
|
||||||
const refundFee = Amounts.parseOrThrow(perm.refund_fee);
|
|
||||||
c.status = CoinStatus.Dirty;
|
|
||||||
c.currentAmount = Amounts.add(c.currentAmount, refundAmount).amount;
|
|
||||||
c.currentAmount = Amounts.sub(c.currentAmount, refundFee).amount;
|
|
||||||
|
|
||||||
return c;
|
|
||||||
};
|
|
||||||
|
|
||||||
await runWithWriteTransaction(
|
|
||||||
ws.db,
|
|
||||||
[Stores.purchases, Stores.coins],
|
|
||||||
async tx => {
|
|
||||||
await tx.mutate(Stores.purchases, proposalId, transformPurchase);
|
|
||||||
await tx.mutate(Stores.coins, perm.coin_pub, transformCoin);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
refresh(ws, perm.coin_pub);
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.badge.showNotification();
|
|
||||||
ws.notifier.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function acceptRefundResponse(
|
|
||||||
ws: InternalWalletState,
|
|
||||||
refundResponse: MerchantRefundResponse,
|
|
||||||
): Promise<string> {
|
|
||||||
const refundPermissions = refundResponse.refund_permissions;
|
|
||||||
|
|
||||||
if (!refundPermissions.length) {
|
|
||||||
console.warn("got empty refund list");
|
|
||||||
throw Error("empty refund");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add refund to purchase if not already added.
|
|
||||||
*/
|
|
||||||
function f(t: PurchaseRecord | undefined): PurchaseRecord | undefined {
|
|
||||||
if (!t) {
|
|
||||||
console.error("purchase not found, not adding refunds");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
t.timestamp_refund = getTimestampNow();
|
|
||||||
|
|
||||||
for (const perm of refundPermissions) {
|
|
||||||
if (
|
|
||||||
!t.refundsPending[perm.merchant_sig] &&
|
|
||||||
!t.refundsDone[perm.merchant_sig]
|
|
||||||
) {
|
|
||||||
t.refundsPending[perm.merchant_sig] = perm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hc = refundResponse.h_contract_terms;
|
|
||||||
|
|
||||||
// Add the refund permissions to the purchase within a DB transaction
|
|
||||||
await oneShotMutate(ws.db, Stores.purchases, hc, f);
|
|
||||||
ws.notifier.notify();
|
|
||||||
|
|
||||||
await submitRefunds(ws, hc);
|
|
||||||
|
|
||||||
return hc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accept a refund, return the contract hash for the contract
|
|
||||||
* that was involved in the refund.
|
|
||||||
*/
|
|
||||||
export async function applyRefund(
|
|
||||||
ws: InternalWalletState,
|
|
||||||
talerRefundUri: string,
|
|
||||||
): Promise<string> {
|
|
||||||
const parseResult = parseRefundUri(talerRefundUri);
|
|
||||||
|
|
||||||
if (!parseResult) {
|
|
||||||
throw Error("invalid refund URI");
|
|
||||||
}
|
|
||||||
|
|
||||||
const refundUrl = parseResult.refundUrl;
|
|
||||||
|
|
||||||
logger.trace("processing refund");
|
|
||||||
let resp;
|
|
||||||
try {
|
|
||||||
resp = await ws.http.get(refundUrl);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("error downloading refund permission", e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
const refundResponse = MerchantRefundResponse.checked(resp.responseJson);
|
|
||||||
return acceptRefundResponse(ws, refundResponse);
|
|
||||||
}
|
|
@ -47,6 +47,8 @@ import {
|
|||||||
preparePay,
|
preparePay,
|
||||||
confirmPay,
|
confirmPay,
|
||||||
processDownloadProposal,
|
processDownloadProposal,
|
||||||
|
applyRefund,
|
||||||
|
getFullRefundFees,
|
||||||
} from "./wallet-impl/pay";
|
} from "./wallet-impl/pay";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -88,8 +90,6 @@ import { Logger } from "./util/logging";
|
|||||||
|
|
||||||
import { assertUnreachable } from "./util/assertUnreachable";
|
import { assertUnreachable } from "./util/assertUnreachable";
|
||||||
|
|
||||||
import { applyRefund, getFullRefundFees } from "./wallet-impl/refund";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
updateExchangeFromUrl,
|
updateExchangeFromUrl,
|
||||||
getExchangeTrust,
|
getExchangeTrust,
|
||||||
@ -209,6 +209,7 @@ export class Wallet {
|
|||||||
*/
|
*/
|
||||||
async processOnePendingOperation(
|
async processOnePendingOperation(
|
||||||
pending: PendingOperationInfo,
|
pending: PendingOperationInfo,
|
||||||
|
forceNow: boolean = false,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
switch (pending.type) {
|
switch (pending.type) {
|
||||||
case "bug":
|
case "bug":
|
||||||
@ -247,11 +248,11 @@ export class Wallet {
|
|||||||
/**
|
/**
|
||||||
* Process pending operations.
|
* Process pending operations.
|
||||||
*/
|
*/
|
||||||
public async runPending(): Promise<void> {
|
public async runPending(forceNow: boolean = false): Promise<void> {
|
||||||
const pendingOpsResponse = await this.getPendingOperations();
|
const pendingOpsResponse = await this.getPendingOperations();
|
||||||
for (const p of pendingOpsResponse.pendingOperations) {
|
for (const p of pendingOpsResponse.pendingOperations) {
|
||||||
try {
|
try {
|
||||||
await this.processOnePendingOperation(p);
|
await this.processOnePendingOperation(p, forceNow);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,6 @@
|
|||||||
"src/wallet-impl/payback.ts",
|
"src/wallet-impl/payback.ts",
|
||||||
"src/wallet-impl/pending.ts",
|
"src/wallet-impl/pending.ts",
|
||||||
"src/wallet-impl/refresh.ts",
|
"src/wallet-impl/refresh.ts",
|
||||||
"src/wallet-impl/refund.ts",
|
|
||||||
"src/wallet-impl/reserves.ts",
|
"src/wallet-impl/reserves.ts",
|
||||||
"src/wallet-impl/return.ts",
|
"src/wallet-impl/return.ts",
|
||||||
"src/wallet-impl/state.ts",
|
"src/wallet-impl/state.ts",
|
||||||
|
Loading…
Reference in New Issue
Block a user