simplify refunds a bit, show in transaction history, add integration tests
This commit is contained in:
parent
5f8714091a
commit
66d76a3591
@ -4,40 +4,19 @@
|
|||||||
"description": "Integration tests and fault injection for GNU Taler components",
|
"description": "Integration tests and fault injection for GNU Taler components",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"compile": "tsc",
|
"compile": "tsc -b"
|
||||||
"test": "tsc && ava"
|
|
||||||
},
|
},
|
||||||
"author": "Florian Dold <dold@taler.net>",
|
"author": "Florian Dold <dold@taler.net>",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ava/typescript": "^1.1.1",
|
|
||||||
"ava": "^3.11.1",
|
|
||||||
"esm": "^3.2.25",
|
"esm": "^3.2.25",
|
||||||
"source-map-support": "^0.5.19",
|
"source-map-support": "^0.5.19",
|
||||||
"ts-node": "^8.10.2"
|
"ts-node": "^8.10.2",
|
||||||
|
"typescript": "^3.9.7"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.19.2",
|
"axios": "^0.19.2",
|
||||||
"taler-wallet-core": "workspace:*",
|
"taler-wallet-core": "workspace:*",
|
||||||
"tslib": "^2.0.0",
|
"tslib": "^2.0.0"
|
||||||
"typescript": "^3.9.7"
|
|
||||||
},
|
|
||||||
"ava": {
|
|
||||||
"require": [
|
|
||||||
"esm"
|
|
||||||
],
|
|
||||||
"files": [
|
|
||||||
"src/**/test-*"
|
|
||||||
],
|
|
||||||
"typescript": {
|
|
||||||
"extensions": [
|
|
||||||
"js",
|
|
||||||
"ts",
|
|
||||||
"tsx"
|
|
||||||
],
|
|
||||||
"rewritePaths": {
|
|
||||||
"src/": "lib/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
|||||||
|
|
||||||
cd $DIR
|
cd $DIR
|
||||||
|
|
||||||
./node_modules/.bin/tsc
|
./node_modules/.bin/tsc -b
|
||||||
|
|
||||||
export ESM_OPTIONS='{"sourceMap": true}'
|
export ESM_OPTIONS='{"sourceMap": true}'
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ import { EddsaKeyPair } from "taler-wallet-core/lib/crypto/talerCrypto";
|
|||||||
|
|
||||||
const exec = util.promisify(require("child_process").exec);
|
const exec = util.promisify(require("child_process").exec);
|
||||||
|
|
||||||
async function delay(ms: number): Promise<void> {
|
export async function delayMs(ms: number): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
setTimeout(() => resolve(), ms);
|
setTimeout(() => resolve(), ms);
|
||||||
});
|
});
|
||||||
@ -410,7 +410,7 @@ async function pingProc(
|
|||||||
return;
|
return;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`service ${serviceName} not ready:`, e.toString());
|
console.log(`service ${serviceName} not ready:`, e.toString());
|
||||||
await delay(1000);
|
await delayMs(1000);
|
||||||
}
|
}
|
||||||
if (!proc || proc.proc.exitCode !== null) {
|
if (!proc || proc.proc.exitCode !== null) {
|
||||||
throw Error(`service process ${serviceName} stopped unexpectedly`);
|
throw Error(`service process ${serviceName} stopped unexpectedly`);
|
||||||
@ -951,16 +951,41 @@ export class MerchantService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async queryPrivateOrderStatus(instanceName: string, orderId: string) {
|
async queryPrivateOrderStatus(instanceName: string, orderId: string) {
|
||||||
let url;
|
const reqUrl = new URL(
|
||||||
if (instanceName === "default") {
|
`private/orders/${orderId}`,
|
||||||
url = `http://localhost:${this.merchantConfig.httpPort}/private/orders/${orderId}`;
|
this.makeInstanceBaseUrl(instanceName),
|
||||||
} else {
|
);
|
||||||
url = `http://localhost:${this.merchantConfig.httpPort}/instances/${instanceName}/private/orders/${orderId}`;
|
const resp = await axios.get(reqUrl.href);
|
||||||
}
|
|
||||||
const resp = await axios.get(url);
|
|
||||||
return codecForMerchantOrderPrivateStatusResponse().decode(resp.data);
|
return codecForMerchantOrderPrivateStatusResponse().decode(resp.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
makeInstanceBaseUrl(instanceName: string): string {
|
||||||
|
if (instanceName === "default") {
|
||||||
|
return `http://localhost:${this.merchantConfig.httpPort}/`;
|
||||||
|
} else {
|
||||||
|
return `http://localhost:${this.merchantConfig.httpPort}/instances/${instanceName}/`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async giveRefund(r: {
|
||||||
|
instance: string;
|
||||||
|
orderId: string;
|
||||||
|
amount: string;
|
||||||
|
justification: string;
|
||||||
|
}): Promise<{ talerRefundUri: string }> {
|
||||||
|
const reqUrl = new URL(
|
||||||
|
`private/orders/${r.orderId}/refund`,
|
||||||
|
this.makeInstanceBaseUrl(r.instance),
|
||||||
|
);
|
||||||
|
const resp = await axios.post(reqUrl.href, {
|
||||||
|
refund: r.amount,
|
||||||
|
reason: r.justification,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
talerRefundUri: resp.data.taler_refund_uri,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async createOrder(
|
async createOrder(
|
||||||
instanceName: string,
|
instanceName: string,
|
||||||
req: PostOrderRequest,
|
req: PostOrderRequest,
|
||||||
|
126
packages/taler-integrationtests/src/test-refund-incremental.ts
Normal file
126
packages/taler-integrationtests/src/test-refund-incremental.ts
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Taler
|
||||||
|
(C) 2020 Taler Systems S.A.
|
||||||
|
|
||||||
|
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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports.
|
||||||
|
*/
|
||||||
|
import { runTest, GlobalTestState, delayMs } from "./harness";
|
||||||
|
import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run test for basic, bank-integrated withdrawal.
|
||||||
|
*/
|
||||||
|
runTest(async (t: GlobalTestState) => {
|
||||||
|
// Set up test environment
|
||||||
|
|
||||||
|
const {
|
||||||
|
wallet,
|
||||||
|
bank,
|
||||||
|
exchange,
|
||||||
|
merchant,
|
||||||
|
} = await createSimpleTestkudosEnvironment(t);
|
||||||
|
|
||||||
|
// Withdraw digital cash into the wallet.
|
||||||
|
|
||||||
|
await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" });
|
||||||
|
|
||||||
|
// Set up order.
|
||||||
|
|
||||||
|
const orderResp = await merchant.createOrder("default", {
|
||||||
|
order: {
|
||||||
|
summary: "Buy me!",
|
||||||
|
amount: "TESTKUDOS:5",
|
||||||
|
fulfillment_url: "taler://fulfillment-success/thx",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let orderStatus = await merchant.queryPrivateOrderStatus(
|
||||||
|
"default",
|
||||||
|
orderResp.order_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
t.assertTrue(orderStatus.order_status === "unpaid")
|
||||||
|
|
||||||
|
// Make wallet pay for the order
|
||||||
|
|
||||||
|
const r1 = await wallet.apiRequest("preparePay", {
|
||||||
|
talerPayUri: orderStatus.taler_pay_uri,
|
||||||
|
});
|
||||||
|
t.assertTrue(r1.type === "response");
|
||||||
|
|
||||||
|
const r2 = await wallet.apiRequest("confirmPay", {
|
||||||
|
// FIXME: should be validated, don't cast!
|
||||||
|
proposalId: (r1.result as any).proposalId,
|
||||||
|
});
|
||||||
|
t.assertTrue(r2.type === "response");
|
||||||
|
|
||||||
|
// Check if payment was successful.
|
||||||
|
|
||||||
|
orderStatus = await merchant.queryPrivateOrderStatus(
|
||||||
|
"default",
|
||||||
|
orderResp.order_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
t.assertTrue(orderStatus.order_status === "paid");
|
||||||
|
|
||||||
|
let ref = await merchant.giveRefund({
|
||||||
|
amount: "TESTKUDOS:2.5",
|
||||||
|
instance: "default",
|
||||||
|
justification: "foo",
|
||||||
|
orderId: orderResp.order_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("first refund increase response", ref);
|
||||||
|
|
||||||
|
// Wait at least a second, because otherwise the increased
|
||||||
|
// refund will be grouped with the previous one.
|
||||||
|
await delayMs(1.2);
|
||||||
|
|
||||||
|
ref = await merchant.giveRefund({
|
||||||
|
amount: "TESTKUDOS:5",
|
||||||
|
instance: "default",
|
||||||
|
justification: "bar",
|
||||||
|
orderId: orderResp.order_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("second refund increase response", ref);
|
||||||
|
|
||||||
|
let r = await wallet.apiRequest("applyRefund", {
|
||||||
|
talerRefundUri: ref.talerRefundUri,
|
||||||
|
});
|
||||||
|
console.log(r);
|
||||||
|
|
||||||
|
orderStatus = await merchant.queryPrivateOrderStatus(
|
||||||
|
"default",
|
||||||
|
orderResp.order_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
t.assertTrue(orderStatus.order_status === "paid");
|
||||||
|
|
||||||
|
t.assertAmountEquals(orderStatus.refund_amount, "TESTKUDOS:5");
|
||||||
|
|
||||||
|
console.log(JSON.stringify(orderStatus, undefined, 2));
|
||||||
|
|
||||||
|
await wallet.runUntilDone();
|
||||||
|
|
||||||
|
r = await wallet.apiRequest("getBalances", {});
|
||||||
|
console.log(JSON.stringify(r, undefined, 2));
|
||||||
|
|
||||||
|
r = await wallet.apiRequest("getTransactions", {});
|
||||||
|
console.log(JSON.stringify(r, undefined, 2));
|
||||||
|
|
||||||
|
await t.shutdown();
|
||||||
|
});
|
102
packages/taler-integrationtests/src/test-refund.ts
Normal file
102
packages/taler-integrationtests/src/test-refund.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Taler
|
||||||
|
(C) 2020 Taler Systems S.A.
|
||||||
|
|
||||||
|
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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports.
|
||||||
|
*/
|
||||||
|
import { runTest, GlobalTestState } from "./harness";
|
||||||
|
import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run test for basic, bank-integrated withdrawal.
|
||||||
|
*/
|
||||||
|
runTest(async (t: GlobalTestState) => {
|
||||||
|
// Set up test environment
|
||||||
|
|
||||||
|
const {
|
||||||
|
wallet,
|
||||||
|
bank,
|
||||||
|
exchange,
|
||||||
|
merchant,
|
||||||
|
} = await createSimpleTestkudosEnvironment(t);
|
||||||
|
|
||||||
|
// Withdraw digital cash into the wallet.
|
||||||
|
|
||||||
|
await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" });
|
||||||
|
|
||||||
|
// Set up order.
|
||||||
|
|
||||||
|
const orderResp = await merchant.createOrder("default", {
|
||||||
|
order: {
|
||||||
|
summary: "Buy me!",
|
||||||
|
amount: "TESTKUDOS:5",
|
||||||
|
fulfillment_url: "taler://fulfillment-success/thx",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let orderStatus = await merchant.queryPrivateOrderStatus(
|
||||||
|
"default",
|
||||||
|
orderResp.order_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
t.assertTrue(orderStatus.order_status === "unpaid")
|
||||||
|
|
||||||
|
// Make wallet pay for the order
|
||||||
|
|
||||||
|
const r1 = await wallet.apiRequest("preparePay", {
|
||||||
|
talerPayUri: orderStatus.taler_pay_uri,
|
||||||
|
});
|
||||||
|
t.assertTrue(r1.type === "response");
|
||||||
|
|
||||||
|
const r2 = await wallet.apiRequest("confirmPay", {
|
||||||
|
// FIXME: should be validated, don't cast!
|
||||||
|
proposalId: (r1.result as any).proposalId,
|
||||||
|
});
|
||||||
|
t.assertTrue(r2.type === "response");
|
||||||
|
|
||||||
|
// Check if payment was successful.
|
||||||
|
|
||||||
|
orderStatus = await merchant.queryPrivateOrderStatus(
|
||||||
|
"default",
|
||||||
|
orderResp.order_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
t.assertTrue(orderStatus.order_status === "paid");
|
||||||
|
|
||||||
|
const ref = await merchant.giveRefund({
|
||||||
|
amount: "TESTKUDOS:5",
|
||||||
|
instance: "default",
|
||||||
|
justification: "foo",
|
||||||
|
orderId: orderResp.order_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(ref);
|
||||||
|
|
||||||
|
let r = await wallet.apiRequest("applyRefund", {
|
||||||
|
talerRefundUri: ref.talerRefundUri,
|
||||||
|
});
|
||||||
|
console.log(r);
|
||||||
|
|
||||||
|
await wallet.runUntilDone();
|
||||||
|
|
||||||
|
r = await wallet.apiRequest("getBalances", {});
|
||||||
|
console.log(JSON.stringify(r, undefined, 2));
|
||||||
|
|
||||||
|
r = await wallet.apiRequest("getTransactions", {});
|
||||||
|
console.log(JSON.stringify(r, undefined, 2));
|
||||||
|
|
||||||
|
await t.shutdown();
|
||||||
|
});
|
@ -17,7 +17,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
|||||||
|
|
||||||
cd $DIR
|
cd $DIR
|
||||||
|
|
||||||
./node_modules/.bin/tsc
|
./node_modules/.bin/tsc -b
|
||||||
|
|
||||||
export ESM_OPTIONS='{"sourceMap": true}'
|
export ESM_OPTIONS='{"sourceMap": true}'
|
||||||
|
|
||||||
|
@ -24,9 +24,6 @@
|
|||||||
"typeRoots": ["./node_modules/@types"]
|
"typeRoots": ["./node_modules/@types"]
|
||||||
},
|
},
|
||||||
"references": [
|
"references": [
|
||||||
{
|
|
||||||
"path": "../idb-bridge/",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"path": "../taler-wallet-core"
|
"path": "../taler-wallet-core"
|
||||||
}
|
}
|
||||||
|
@ -203,7 +203,7 @@ async function acceptRefunds(
|
|||||||
refunds: MerchantCoinRefundStatus[],
|
refunds: MerchantCoinRefundStatus[],
|
||||||
reason: RefundReason,
|
reason: RefundReason,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
console.log("handling refunds", refunds);
|
logger.trace("handling refunds", refunds);
|
||||||
const now = getTimestampNow();
|
const now = getTimestampNow();
|
||||||
|
|
||||||
await ws.db.runWithWriteTransaction(
|
await ws.db.runWithWriteTransaction(
|
||||||
@ -302,37 +302,6 @@ async function acceptRefunds(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startRefundQuery(
|
|
||||||
ws: InternalWalletState,
|
|
||||||
proposalId: string,
|
|
||||||
): Promise<void> {
|
|
||||||
const success = await ws.db.runWithWriteTransaction(
|
|
||||||
[Stores.purchases],
|
|
||||||
async (tx) => {
|
|
||||||
const p = await tx.get(Stores.purchases, proposalId);
|
|
||||||
if (!p) {
|
|
||||||
logger.error("no purchase found for refund URL");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
p.refundStatusRequested = true;
|
|
||||||
p.lastRefundStatusError = undefined;
|
|
||||||
p.refundStatusRetryInfo = initRetryInfo();
|
|
||||||
await tx.put(Stores.purchases, p);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.notify({
|
|
||||||
type: NotificationType.RefundStarted,
|
|
||||||
});
|
|
||||||
|
|
||||||
await processPurchaseQueryRefund(ws, proposalId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accept a refund, return the contract hash for the contract
|
* Accept a refund, return the contract hash for the contract
|
||||||
* that was involved in the refund.
|
* that was involved in the refund.
|
||||||
@ -360,8 +329,31 @@ export async function applyRefund(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const proposalId = purchase.proposalId;
|
||||||
|
|
||||||
logger.info("processing purchase for refund");
|
logger.info("processing purchase for refund");
|
||||||
await startRefundQuery(ws, purchase.proposalId);
|
const success = await ws.db.runWithWriteTransaction(
|
||||||
|
[Stores.purchases],
|
||||||
|
async (tx) => {
|
||||||
|
const p = await tx.get(Stores.purchases, proposalId);
|
||||||
|
if (!p) {
|
||||||
|
logger.error("no purchase found for refund URL");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
p.refundStatusRequested = true;
|
||||||
|
p.lastRefundStatusError = undefined;
|
||||||
|
p.refundStatusRetryInfo = initRetryInfo();
|
||||||
|
await tx.put(Stores.purchases, p);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
ws.notify({
|
||||||
|
type: NotificationType.RefundStarted,
|
||||||
|
});
|
||||||
|
await processPurchaseQueryRefund(ws, proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
contractTermsHash: purchase.contractData.contractTermsHash,
|
contractTermsHash: purchase.contractData.contractTermsHash,
|
||||||
@ -422,7 +414,7 @@ async function processPurchaseQueryRefundImpl(
|
|||||||
|
|
||||||
const request = await ws.http.get(requestUrl.href);
|
const request = await ws.http.get(requestUrl.href);
|
||||||
|
|
||||||
console.log("got json", JSON.stringify(await request.json(), undefined, 2));
|
logger.trace("got json", JSON.stringify(await request.json(), undefined, 2));
|
||||||
|
|
||||||
const refundResponse = await readSuccessResponseJsonOrThrow(
|
const refundResponse = await readSuccessResponseJsonOrThrow(
|
||||||
request,
|
request,
|
||||||
|
@ -18,7 +18,12 @@
|
|||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { InternalWalletState } from "./state";
|
import { InternalWalletState } from "./state";
|
||||||
import { Stores, WithdrawalSourceType } from "../types/dbTypes";
|
import {
|
||||||
|
Stores,
|
||||||
|
WithdrawalSourceType,
|
||||||
|
WalletRefundItem,
|
||||||
|
RefundState,
|
||||||
|
} from "../types/dbTypes";
|
||||||
import { Amounts, AmountJson } from "../util/amounts";
|
import { Amounts, AmountJson } from "../util/amounts";
|
||||||
import { timestampCmp } from "../util/time";
|
import { timestampCmp } from "../util/time";
|
||||||
import {
|
import {
|
||||||
@ -29,8 +34,10 @@ import {
|
|||||||
PaymentStatus,
|
PaymentStatus,
|
||||||
WithdrawalType,
|
WithdrawalType,
|
||||||
WithdrawalDetails,
|
WithdrawalDetails,
|
||||||
|
PaymentShortInfo,
|
||||||
} from "../types/transactions";
|
} from "../types/transactions";
|
||||||
import { getFundingPaytoUris } from "./reserves";
|
import { getFundingPaytoUris } from "./reserves";
|
||||||
|
import { ResultLevel } from "idb-bridge";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an event ID from the type and the primary key for the event.
|
* Create an event ID from the type and the primary key for the event.
|
||||||
@ -224,6 +231,18 @@ export async function getTransactions(
|
|||||||
if (!proposal) {
|
if (!proposal) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const info: PaymentShortInfo = {
|
||||||
|
fulfillmentUrl: pr.contractData.fulfillmentUrl,
|
||||||
|
merchant: pr.contractData.merchant,
|
||||||
|
orderId: pr.contractData.orderId,
|
||||||
|
products: pr.contractData.products,
|
||||||
|
summary: pr.contractData.summary,
|
||||||
|
summary_i18n: pr.contractData.summaryI18n,
|
||||||
|
};
|
||||||
|
const paymentTransactionId = makeEventId(
|
||||||
|
TransactionType.Payment,
|
||||||
|
pr.proposalId,
|
||||||
|
);
|
||||||
transactions.push({
|
transactions.push({
|
||||||
type: TransactionType.Payment,
|
type: TransactionType.Payment,
|
||||||
amountRaw: Amounts.stringify(pr.contractData.amount),
|
amountRaw: Amounts.stringify(pr.contractData.amount),
|
||||||
@ -233,15 +252,62 @@ export async function getTransactions(
|
|||||||
: PaymentStatus.Accepted,
|
: PaymentStatus.Accepted,
|
||||||
pending: !pr.timestampFirstSuccessfulPay,
|
pending: !pr.timestampFirstSuccessfulPay,
|
||||||
timestamp: pr.timestampAccept,
|
timestamp: pr.timestampAccept,
|
||||||
transactionId: makeEventId(TransactionType.Payment, pr.proposalId),
|
transactionId: paymentTransactionId,
|
||||||
info: {
|
info: info,
|
||||||
fulfillmentUrl: pr.contractData.fulfillmentUrl,
|
});
|
||||||
merchant: pr.contractData.merchant,
|
|
||||||
orderId: pr.contractData.orderId,
|
const refundGroupKeys = new Set<string>();
|
||||||
products: pr.contractData.products,
|
|
||||||
summary: pr.contractData.summary,
|
for (const rk of Object.keys(pr.refunds)) {
|
||||||
summary_i18n: pr.contractData.summaryI18n,
|
const refund = pr.refunds[rk];
|
||||||
},
|
const groupKey = `${refund.executionTime.t_ms}`;
|
||||||
|
refundGroupKeys.add(groupKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
refundGroupKeys.forEach((groupKey: string) => {
|
||||||
|
const refundTransactionId = makeEventId(
|
||||||
|
TransactionType.Payment,
|
||||||
|
pr.proposalId,
|
||||||
|
groupKey,
|
||||||
|
);
|
||||||
|
let r0: WalletRefundItem | undefined;
|
||||||
|
let amountEffective = Amounts.getZero(
|
||||||
|
pr.contractData.amount.currency,
|
||||||
|
);
|
||||||
|
let amountRaw = Amounts.getZero(pr.contractData.amount.currency);
|
||||||
|
for (const rk of Object.keys(pr.refunds)) {
|
||||||
|
const refund = pr.refunds[rk];
|
||||||
|
if (!r0) {
|
||||||
|
r0 = refund;
|
||||||
|
}
|
||||||
|
if (refund.type === RefundState.Applied) {
|
||||||
|
amountEffective = Amounts.add(
|
||||||
|
amountEffective,
|
||||||
|
refund.refundAmount,
|
||||||
|
).amount;
|
||||||
|
amountRaw = Amounts.add(
|
||||||
|
amountRaw,
|
||||||
|
Amounts.sub(
|
||||||
|
refund.refundAmount,
|
||||||
|
refund.refundFee,
|
||||||
|
refund.totalRefreshCostBound,
|
||||||
|
).amount,
|
||||||
|
).amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!r0) {
|
||||||
|
throw Error("invariant violated");
|
||||||
|
}
|
||||||
|
transactions.push({
|
||||||
|
type: TransactionType.Refund,
|
||||||
|
info,
|
||||||
|
refundedTransactionId: paymentTransactionId,
|
||||||
|
transactionId: refundTransactionId,
|
||||||
|
timestamp: r0.executionTime,
|
||||||
|
amountEffective: Amounts.stringify(amountEffective),
|
||||||
|
amountRaw: Amounts.stringify(amountRaw),
|
||||||
|
pending: false,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// for (const rg of pr.refundGroups) {
|
// for (const rg of pr.refundGroups) {
|
||||||
|
@ -218,7 +218,7 @@ export interface TransactionPayment extends TransactionCommon {
|
|||||||
amountEffective: AmountString;
|
amountEffective: AmountString;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PaymentShortInfo {
|
export interface PaymentShortInfo {
|
||||||
/**
|
/**
|
||||||
* Order ID, uniquely identifies the order within a merchant instance
|
* Order ID, uniquely identifies the order within a merchant instance
|
||||||
*/
|
*/
|
||||||
@ -259,9 +259,6 @@ interface TransactionRefund extends TransactionCommon {
|
|||||||
// Additional information about the refunded payment
|
// Additional information about the refunded payment
|
||||||
info: PaymentShortInfo;
|
info: PaymentShortInfo;
|
||||||
|
|
||||||
// Part of the refund that couldn't be applied because the refund permissions were expired
|
|
||||||
amountInvalid: AmountString;
|
|
||||||
|
|
||||||
// Amount that has been refunded by the merchant
|
// Amount that has been refunded by the merchant
|
||||||
amountRaw: AmountString;
|
amountRaw: AmountString;
|
||||||
|
|
||||||
|
@ -36,16 +36,12 @@ importers:
|
|||||||
axios: 0.19.2
|
axios: 0.19.2
|
||||||
taler-wallet-core: 'link:../taler-wallet-core'
|
taler-wallet-core: 'link:../taler-wallet-core'
|
||||||
tslib: 2.0.0
|
tslib: 2.0.0
|
||||||
typescript: 3.9.7
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@ava/typescript': 1.1.1
|
|
||||||
ava: 3.11.1
|
|
||||||
esm: 3.2.25
|
esm: 3.2.25
|
||||||
source-map-support: 0.5.19
|
source-map-support: 0.5.19
|
||||||
ts-node: 8.10.2_typescript@3.9.7
|
ts-node: 8.10.2_typescript@3.9.7
|
||||||
|
typescript: 3.9.7
|
||||||
specifiers:
|
specifiers:
|
||||||
'@ava/typescript': ^1.1.1
|
|
||||||
ava: ^3.11.1
|
|
||||||
axios: ^0.19.2
|
axios: ^0.19.2
|
||||||
esm: ^3.2.25
|
esm: ^3.2.25
|
||||||
source-map-support: ^0.5.19
|
source-map-support: ^0.5.19
|
||||||
@ -1123,69 +1119,6 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
resolution:
|
resolution:
|
||||||
integrity: sha512-y5U8BGeSRjs/OypsC4CJxr+L1KtLKU5kUyHr5hcghXn7HNr2f4LE/4gvl0Q5lNkLX1obdRW1oODphNdU/glwmA==
|
integrity: sha512-y5U8BGeSRjs/OypsC4CJxr+L1KtLKU5kUyHr5hcghXn7HNr2f4LE/4gvl0Q5lNkLX1obdRW1oODphNdU/glwmA==
|
||||||
/ava/3.11.1:
|
|
||||||
dependencies:
|
|
||||||
'@concordance/react': 2.0.0
|
|
||||||
acorn: 7.3.1
|
|
||||||
acorn-walk: 7.2.0
|
|
||||||
ansi-styles: 4.2.1
|
|
||||||
arrgv: 1.0.2
|
|
||||||
arrify: 2.0.1
|
|
||||||
callsites: 3.1.0
|
|
||||||
chalk: 4.1.0
|
|
||||||
chokidar: 3.4.1
|
|
||||||
chunkd: 2.0.1
|
|
||||||
ci-info: 2.0.0
|
|
||||||
ci-parallel-vars: 1.0.1
|
|
||||||
clean-yaml-object: 0.1.0
|
|
||||||
cli-cursor: 3.1.0
|
|
||||||
cli-truncate: 2.1.0
|
|
||||||
code-excerpt: 3.0.0
|
|
||||||
common-path-prefix: 3.0.0
|
|
||||||
concordance: 5.0.0
|
|
||||||
convert-source-map: 1.7.0
|
|
||||||
currently-unhandled: 0.4.1
|
|
||||||
debug: 4.1.1
|
|
||||||
del: 5.1.0
|
|
||||||
emittery: 0.7.1
|
|
||||||
equal-length: 1.0.1
|
|
||||||
figures: 3.2.0
|
|
||||||
globby: 11.0.1
|
|
||||||
ignore-by-default: 2.0.0
|
|
||||||
import-local: 3.0.2
|
|
||||||
indent-string: 4.0.0
|
|
||||||
is-error: 2.2.2
|
|
||||||
is-plain-object: 4.1.1
|
|
||||||
is-promise: 4.0.0
|
|
||||||
lodash: 4.17.19
|
|
||||||
matcher: 3.0.0
|
|
||||||
md5-hex: 3.0.1
|
|
||||||
mem: 6.1.0
|
|
||||||
ms: 2.1.2
|
|
||||||
ora: 4.0.5
|
|
||||||
p-map: 4.0.0
|
|
||||||
picomatch: 2.2.2
|
|
||||||
pkg-conf: 3.1.0
|
|
||||||
plur: 4.0.0
|
|
||||||
pretty-ms: 7.0.0
|
|
||||||
read-pkg: 5.2.0
|
|
||||||
resolve-cwd: 3.0.0
|
|
||||||
slash: 3.0.0
|
|
||||||
source-map-support: 0.5.19
|
|
||||||
stack-utils: 2.0.2
|
|
||||||
strip-ansi: 6.0.0
|
|
||||||
supertap: 1.0.0
|
|
||||||
temp-dir: 2.0.0
|
|
||||||
trim-off-newlines: 1.0.1
|
|
||||||
update-notifier: 4.1.0
|
|
||||||
write-file-atomic: 3.0.3
|
|
||||||
yargs: 15.4.1
|
|
||||||
dev: true
|
|
||||||
engines:
|
|
||||||
node: '>=10.18.0 <11 || >=12.14.0 <12.17.0 || >=12.17.0 <13 || >=14.0.0'
|
|
||||||
hasBin: true
|
|
||||||
resolution:
|
|
||||||
integrity: sha512-yGPD0msa5Qronw7GHDNlLaB7oU5zryYtXeuvny40YV6TMskSghqK7Ky3NisM/sr+aqI3DY7sfmORx8dIWQgMoQ==
|
|
||||||
/axe-core/3.5.5:
|
/axe-core/3.5.5:
|
||||||
dev: true
|
dev: true
|
||||||
engines:
|
engines:
|
||||||
@ -4757,6 +4690,7 @@ packages:
|
|||||||
resolution:
|
resolution:
|
||||||
integrity: sha512-/OyrHCJ8jtzu+QZ+771YaxQ9s4g5Z3XsQE3Ma7q+BL392xxBn4UMvvCdVnqKC2T/dz03/VXSLVKOP3lHmDdc/w==
|
integrity: sha512-/OyrHCJ8jtzu+QZ+771YaxQ9s4g5Z3XsQE3Ma7q+BL392xxBn4UMvvCdVnqKC2T/dz03/VXSLVKOP3lHmDdc/w==
|
||||||
/typescript/3.9.7:
|
/typescript/3.9.7:
|
||||||
|
dev: true
|
||||||
engines:
|
engines:
|
||||||
node: '>=4.2.0'
|
node: '>=4.2.0'
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
Loading…
Reference in New Issue
Block a user