correct refund amounts and better testing

This commit is contained in:
Florian Dold 2020-09-01 20:37:50 +05:30
parent 7f4ebca0c4
commit 044b723657
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
5 changed files with 126 additions and 21 deletions

View File

@ -42,7 +42,6 @@ import {
CoreApiResponse, CoreApiResponse,
PreparePayResult, PreparePayResult,
PreparePayRequest, PreparePayRequest,
codecForPreparePayResultPaymentPossible,
codecForPreparePayResult, codecForPreparePayResult,
OperationFailedError, OperationFailedError,
AddExchangeRequest, AddExchangeRequest,
@ -67,6 +66,8 @@ import {
codecForTransactionsResponse, codecForTransactionsResponse,
WithdrawTestBalanceRequest, WithdrawTestBalanceRequest,
AmountString, AmountString,
ApplyRefundRequest,
codecForApplyRefundResponse,
} from "taler-wallet-core"; } from "taler-wallet-core";
import { URL } from "url"; import { URL } from "url";
import axios, { AxiosError } from "axios"; import axios, { AxiosError } from "axios";
@ -77,6 +78,7 @@ import {
PostOrderResponse, PostOrderResponse,
MerchantOrderPrivateStatusResponse, MerchantOrderPrivateStatusResponse,
} from "./merchantApiTypes"; } from "./merchantApiTypes";
import { ApplyRefundResponse } from "taler-wallet-core";
const exec = util.promisify(require("child_process").exec); const exec = util.promisify(require("child_process").exec);
@ -384,6 +386,32 @@ export class GlobalTestState {
} }
} }
assertAmountLeq(
amtExpected: string | AmountJson,
amtActual: string | AmountJson,
): void {
let ja1: AmountJson;
let ja2: AmountJson;
if (typeof amtExpected === "string") {
ja1 = Amounts.parseOrThrow(amtExpected);
} else {
ja1 = amtExpected;
}
if (typeof amtActual === "string") {
ja2 = Amounts.parseOrThrow(amtActual);
} else {
ja2 = amtActual;
}
if (Amounts.cmp(ja1, ja2) > 0) {
throw Error(
`test assertion failed: expected ${Amounts.stringify(
ja1,
)} to be less or equal (leq) than ${Amounts.stringify(ja2)}`,
);
}
}
private shutdownSync(): void { private shutdownSync(): void {
for (const s of this.servers) { for (const s of this.servers) {
s.close(); s.close();
@ -1512,6 +1540,14 @@ export class WalletCli {
); );
} }
async applyRefund(req: ApplyRefundRequest): Promise<ApplyRefundResponse> {
const resp = await this.apiRequest("applyRefund", req);
if (resp.type === "response") {
return codecForApplyRefundResponse().decode(resp.result);
}
throw new OperationFailedError(resp.error);
}
async preparePay(req: PreparePayRequest): Promise<PreparePayResult> { async preparePay(req: PreparePayRequest): Promise<PreparePayResult> {
const resp = await this.apiRequest("preparePay", req); const resp = await this.apiRequest("preparePay", req);
if (resp.type === "response") { if (resp.type === "response") {

View File

@ -83,8 +83,8 @@ export const codecForCheckPaymentPaidResponse = (): Codec<
> => > =>
buildCodecForObject<CheckPaymentPaidResponse>() buildCodecForObject<CheckPaymentPaidResponse>()
.property("order_status", codecForConstString("paid")) .property("order_status", codecForConstString("paid"))
.property("refunded", codecForBoolean) .property("refunded", codecForBoolean())
.property("wired", codecForBoolean) .property("wired", codecForBoolean())
.property("deposit_total", codecForAmountString()) .property("deposit_total", codecForAmountString())
.property("exchange_ec", codecForNumber()) .property("exchange_ec", codecForNumber())
.property("exchange_hc", codecForNumber()) .property("exchange_hc", codecForNumber())

View File

@ -24,6 +24,7 @@ import {
MerchantPrivateApi, MerchantPrivateApi,
} from "./harness"; } from "./harness";
import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers"; import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers";
import { TransactionType, Amounts } from "taler-wallet-core";
/** /**
* Run test for basic, bank-integrated withdrawal. * Run test for basic, bank-integrated withdrawal.
@ -47,7 +48,7 @@ runTest(async (t: GlobalTestState) => {
const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", { const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", {
order: { order: {
summary: "Buy me!", summary: "Buy me!",
amount: "TESTKUDOS:5", amount: "TESTKUDOS:10",
fulfillment_url: "taler://fulfillment-success/thx", fulfillment_url: "taler://fulfillment-success/thx",
}, },
}); });
@ -88,9 +89,21 @@ runTest(async (t: GlobalTestState) => {
console.log("first refund increase response", ref); console.log("first refund increase response", ref);
{
let wr = await wallet.applyRefund({
talerRefundUri: ref.talerRefundUri,
});
console.log(wr);
const txs = await wallet.getTransactions();
console.log(
"transactions after applying first refund:",
JSON.stringify(txs, undefined, 2),
);
}
// Wait at least a second, because otherwise the increased // Wait at least a second, because otherwise the increased
// refund will be grouped with the previous one. // refund will be grouped with the previous one.
await delayMs(1.2); await delayMs(1200);
ref = await MerchantPrivateApi.giveRefund(merchant, { ref = await MerchantPrivateApi.giveRefund(merchant, {
amount: "TESTKUDOS:5", amount: "TESTKUDOS:5",
@ -101,10 +114,25 @@ runTest(async (t: GlobalTestState) => {
console.log("second refund increase response", ref); console.log("second refund increase response", ref);
let r = await wallet.apiRequest("applyRefund", { // Wait at least a second, because otherwise the increased
// refund will be grouped with the previous one.
await delayMs(1200);
ref = await MerchantPrivateApi.giveRefund(merchant, {
amount: "TESTKUDOS:10",
instance: "default",
justification: "bar",
orderId: orderResp.order_id,
});
console.log("third refund increase response", ref);
{
let wr = await wallet.applyRefund({
talerRefundUri: ref.talerRefundUri, talerRefundUri: ref.talerRefundUri,
}); });
console.log(r); console.log(wr);
}
orderStatus = await MerchantPrivateApi.queryPrivateOrderStatus(merchant, { orderStatus = await MerchantPrivateApi.queryPrivateOrderStatus(merchant, {
orderId: orderResp.order_id, orderId: orderResp.order_id,
@ -112,17 +140,43 @@ runTest(async (t: GlobalTestState) => {
t.assertTrue(orderStatus.order_status === "paid"); t.assertTrue(orderStatus.order_status === "paid");
t.assertAmountEquals(orderStatus.refund_amount, "TESTKUDOS:5"); t.assertAmountEquals(orderStatus.refund_amount, "TESTKUDOS:10");
console.log(JSON.stringify(orderStatus, undefined, 2)); console.log(JSON.stringify(orderStatus, undefined, 2));
await wallet.runUntilDone(); await wallet.runUntilDone();
r = await wallet.apiRequest("getBalances", {}); const bal = await wallet.getBalances();
console.log(JSON.stringify(r, undefined, 2)); console.log(JSON.stringify(bal, undefined, 2));
r = await wallet.apiRequest("getTransactions", {}); {
console.log(JSON.stringify(r, undefined, 2)); const txs = await wallet.getTransactions();
console.log(JSON.stringify(txs, undefined, 2));
const txTypes = txs.transactions.map((x) => x.type);
t.assertDeepEqual(txTypes, [
"withdrawal",
"payment",
"refund",
"refund",
"refund",
]);
for (const tx of txs.transactions) {
if (tx.type !== TransactionType.Refund) {
continue;
}
t.assertAmountLeq(tx.amountEffective, tx.amountRaw);
}
const raw = Amounts.sum(
txs.transactions
.filter((x) => x.type === TransactionType.Refund)
.map((x) => x.amountRaw),
).amount;
t.assertAmountEquals(raw, "TESTKUDOS:10");
}
await t.shutdown(); await t.shutdown();
}); });

View File

@ -281,22 +281,27 @@ export async function getTransactions(
groupKey, groupKey,
); );
let r0: WalletRefundItem | undefined; let r0: WalletRefundItem | undefined;
let amountEffective = Amounts.getZero( let amountRaw = Amounts.getZero(
pr.contractData.amount.currency, pr.contractData.amount.currency,
); );
let amountRaw = Amounts.getZero(pr.contractData.amount.currency); let amountEffective = Amounts.getZero(pr.contractData.amount.currency);
for (const rk of Object.keys(pr.refunds)) { for (const rk of Object.keys(pr.refunds)) {
const refund = pr.refunds[rk]; const refund = pr.refunds[rk];
const myGroupKey = `${refund.executionTime.t_ms}`;
if (myGroupKey !== groupKey) {
continue;
}
if (!r0) { if (!r0) {
r0 = refund; r0 = refund;
} }
if (refund.type === RefundState.Applied) { if (refund.type === RefundState.Applied) {
amountEffective = Amounts.add(
amountEffective,
refund.refundAmount,
).amount;
amountRaw = Amounts.add( amountRaw = Amounts.add(
amountRaw, amountRaw,
refund.refundAmount,
).amount;
amountEffective = Amounts.add(
amountEffective,
Amounts.sub( Amounts.sub(
refund.refundAmount, refund.refundAmount,
refund.refundFee, refund.refundFee,

View File

@ -101,11 +101,21 @@ export function getZero(currency: string): AmountJson {
}; };
} }
export function sum(amounts: AmountJson[]): Result { export type AmountLike = AmountString | AmountJson;
export function jsonifyAmount(amt: AmountLike): AmountJson {
if (typeof amt === "string") {
return parseOrThrow(amt);
}
return amt;
}
export function sum(amounts: AmountLike[]): Result {
if (amounts.length <= 0) { if (amounts.length <= 0) {
throw Error("can't sum zero amounts"); throw Error("can't sum zero amounts");
} }
return add(amounts[0], ...amounts.slice(1)); const jsonAmounts = amounts.map((x) => jsonifyAmount(x));
return add(jsonAmounts[0], ...jsonAmounts.slice(1));
} }
/** /**