aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2020-08-13 00:26:55 +0530
committerFlorian Dold <florian.dold@gmail.com>2020-08-13 00:26:55 +0530
commite9ed3b18672af919efa12364b97fd2b7efe21cd9 (patch)
tree42cd7e7bdf4686f463080e0ad879d06ff624a85b
parent4891c4c7ceec46781fa2d7b7b5a3347616587681 (diff)
integration test for paywall flow
-rw-r--r--packages/taler-integrationtests/src/harness.ts32
-rw-r--r--packages/taler-integrationtests/src/scenario-prompt-payment.ts7
-rw-r--r--packages/taler-integrationtests/src/scenario-rerun-payment-multiple.ts14
-rw-r--r--packages/taler-integrationtests/src/test-payment-fault.ts14
-rw-r--r--packages/taler-integrationtests/src/test-payment-idempotency.ts14
-rw-r--r--packages/taler-integrationtests/src/test-payment-multiple.ts14
-rw-r--r--packages/taler-integrationtests/src/test-payment.ts14
-rw-r--r--packages/taler-integrationtests/src/test-paywall-flow.ts206
-rw-r--r--packages/taler-integrationtests/src/test-refund-incremental.ts21
-rw-r--r--packages/taler-integrationtests/src/test-refund.ts14
-rw-r--r--packages/taler-wallet-core/src/types/walletTypes.ts44
11 files changed, 319 insertions, 75 deletions
diff --git a/packages/taler-integrationtests/src/harness.ts b/packages/taler-integrationtests/src/harness.ts
index 0cf769163..8f9c540f6 100644
--- a/packages/taler-integrationtests/src/harness.ts
+++ b/packages/taler-integrationtests/src/harness.ts
@@ -50,6 +50,9 @@ import {
GetWithdrawalDetailsForUriRequest,
WithdrawUriInfoResponse,
codecForWithdrawUriInfoResponse,
+ ConfirmPayRequest,
+ ConfirmPayResult,
+ codecForConfirmPayResult,
} from "taler-wallet-core";
import { URL } from "url";
import axios from "axios";
@@ -58,6 +61,7 @@ import {
codecForPostOrderResponse,
PostOrderRequest,
PostOrderResponse,
+ MerchantOrderPrivateStatusResponse,
} from "./merchantApiTypes";
import {
EddsaKeyPair,
@@ -886,6 +890,13 @@ export interface MerchantConfig {
database: string;
}
+
+export interface PrivateOrderStatusQuery {
+ instance?: string,
+ orderId: string,
+ sessionId?: string,
+}
+
export class MerchantService {
static fromExistingConfig(gc: GlobalTestState, name: string) {
const cfgFilename = gc.testDir + `/merchant-${name}.conf`;
@@ -982,17 +993,20 @@ export class MerchantService {
});
}
- async queryPrivateOrderStatus(instanceName: string, orderId: string) {
+ async queryPrivateOrderStatus(query: PrivateOrderStatusQuery): Promise<MerchantOrderPrivateStatusResponse> {
const reqUrl = new URL(
- `private/orders/${orderId}`,
- this.makeInstanceBaseUrl(instanceName),
+ `private/orders/${query.orderId}`,
+ this.makeInstanceBaseUrl(query.instance),
);
+ if (query.sessionId) {
+ reqUrl.searchParams.set("session_id", query.sessionId);
+ }
const resp = await axios.get(reqUrl.href);
return codecForMerchantOrderPrivateStatusResponse().decode(resp.data);
}
- makeInstanceBaseUrl(instanceName: string): string {
- if (instanceName === "default") {
+ makeInstanceBaseUrl(instanceName?: string): string {
+ if (instanceName === undefined || instanceName === "default") {
return `http://localhost:${this.merchantConfig.httpPort}/`;
} else {
return `http://localhost:${this.merchantConfig.httpPort}/instances/${instanceName}/`;
@@ -1177,6 +1191,14 @@ export class WalletCli {
throw new OperationFailedError(resp.error);
}
+ async confirmPay(req: ConfirmPayRequest): Promise<ConfirmPayResult> {
+ const resp = await this.apiRequest("confirmPay", req);
+ if (resp.type === "response") {
+ return codecForConfirmPayResult().decode(resp.result);
+ }
+ throw new OperationFailedError(resp.error);
+ }
+
async addExchange(req: AddExchangeRequest): Promise<void> {
const resp = await this.apiRequest("addExchange", req);
if (resp.type === "response") {
diff --git a/packages/taler-integrationtests/src/scenario-prompt-payment.ts b/packages/taler-integrationtests/src/scenario-prompt-payment.ts
index f60c6704d..3e4bfc6c2 100644
--- a/packages/taler-integrationtests/src/scenario-prompt-payment.ts
+++ b/packages/taler-integrationtests/src/scenario-prompt-payment.ts
@@ -47,10 +47,9 @@ runTest(async (t: GlobalTestState) => {
},
});
- let orderStatus = await merchant.queryPrivateOrderStatus(
- "default",
- orderResp.order_id,
- );
+ let orderStatus = await merchant.queryPrivateOrderStatus({
+ orderId: orderResp.order_id,
+ });
t.assertTrue(orderStatus.order_status === "unpaid");
diff --git a/packages/taler-integrationtests/src/scenario-rerun-payment-multiple.ts b/packages/taler-integrationtests/src/scenario-rerun-payment-multiple.ts
index a755aa939..525ba9a25 100644
--- a/packages/taler-integrationtests/src/scenario-rerun-payment-multiple.ts
+++ b/packages/taler-integrationtests/src/scenario-rerun-payment-multiple.ts
@@ -60,10 +60,9 @@ async function withdrawAndPay(
},
});
- let orderStatus = await merchant.queryPrivateOrderStatus(
- "default",
- orderResp.order_id,
- );
+ let orderStatus = await merchant.queryPrivateOrderStatus({
+ orderId: orderResp.order_id,
+ });
t.assertTrue(orderStatus.order_status === "unpaid");
@@ -82,10 +81,9 @@ async function withdrawAndPay(
// Check if payment was successful.
- orderStatus = await merchant.queryPrivateOrderStatus(
- "default",
- orderResp.order_id,
- );
+ orderStatus = await merchant.queryPrivateOrderStatus({
+ orderId: orderResp.order_id,
+ });
t.assertTrue(orderStatus.order_status === "paid");
}
diff --git a/packages/taler-integrationtests/src/test-payment-fault.ts b/packages/taler-integrationtests/src/test-payment-fault.ts
index 2ee5c7055..4babdc500 100644
--- a/packages/taler-integrationtests/src/test-payment-fault.ts
+++ b/packages/taler-integrationtests/src/test-payment-fault.ts
@@ -153,10 +153,9 @@ runTest(async (t: GlobalTestState) => {
},
});
- let orderStatus = await merchant.queryPrivateOrderStatus(
- "default",
- orderResp.order_id,
- );
+ let orderStatus = await merchant.queryPrivateOrderStatus({
+ orderId: orderResp.order_id,
+ });
t.assertTrue(orderStatus.order_status === "unpaid");
@@ -196,10 +195,9 @@ runTest(async (t: GlobalTestState) => {
// Check if payment was successful.
- orderStatus = await merchant.queryPrivateOrderStatus(
- "default",
- orderResp.order_id,
- );
+ orderStatus = await merchant.queryPrivateOrderStatus({
+ orderId: orderResp.order_id,
+ });
t.assertTrue(orderStatus.order_status === "paid");
});
diff --git a/packages/taler-integrationtests/src/test-payment-idempotency.ts b/packages/taler-integrationtests/src/test-payment-idempotency.ts
index 4d6727715..bc641a356 100644
--- a/packages/taler-integrationtests/src/test-payment-idempotency.ts
+++ b/packages/taler-integrationtests/src/test-payment-idempotency.ts
@@ -49,10 +49,9 @@ runTest(async (t: GlobalTestState) => {
},
});
- let orderStatus = await merchant.queryPrivateOrderStatus(
- "default",
- orderResp.order_id,
- );
+ let orderStatus = await merchant.queryPrivateOrderStatus({
+ orderId: orderResp.order_id,
+ });
t.assertTrue(orderStatus.order_status === "unpaid");
@@ -85,10 +84,9 @@ runTest(async (t: GlobalTestState) => {
// Check if payment was successful.
- orderStatus = await merchant.queryPrivateOrderStatus(
- "default",
- orderResp.order_id,
- );
+ orderStatus = await merchant.queryPrivateOrderStatus({
+ orderId: orderResp.order_id,
+ });
t.assertTrue(orderStatus.order_status === "paid");
diff --git a/packages/taler-integrationtests/src/test-payment-multiple.ts b/packages/taler-integrationtests/src/test-payment-multiple.ts
index 80092a9a3..00b3c0b69 100644
--- a/packages/taler-integrationtests/src/test-payment-multiple.ts
+++ b/packages/taler-integrationtests/src/test-payment-multiple.ts
@@ -130,10 +130,9 @@ runTest(async (t: GlobalTestState) => {
},
});
- let orderStatus = await merchant.queryPrivateOrderStatus(
- "default",
- orderResp.order_id,
- );
+ let orderStatus = await merchant.queryPrivateOrderStatus({
+ orderId: orderResp.order_id,
+ });
t.assertTrue(orderStatus.order_status === "unpaid");
@@ -152,10 +151,9 @@ runTest(async (t: GlobalTestState) => {
// Check if payment was successful.
- orderStatus = await merchant.queryPrivateOrderStatus(
- "default",
- orderResp.order_id,
- );
+ orderStatus = await merchant.queryPrivateOrderStatus({
+ orderId: orderResp.order_id,
+ });
t.assertTrue(orderStatus.order_status === "paid");
diff --git a/packages/taler-integrationtests/src/test-payment.ts b/packages/taler-integrationtests/src/test-payment.ts
index 77645909c..12b4267b7 100644
--- a/packages/taler-integrationtests/src/test-payment.ts
+++ b/packages/taler-integrationtests/src/test-payment.ts
@@ -48,10 +48,9 @@ runTest(async (t: GlobalTestState) => {
},
});
- let orderStatus = await merchant.queryPrivateOrderStatus(
- "default",
- orderResp.order_id,
- );
+ let orderStatus = await merchant.queryPrivateOrderStatus({
+ orderId: orderResp.order_id,
+ });
t.assertTrue(orderStatus.order_status === "unpaid");
@@ -71,10 +70,9 @@ runTest(async (t: GlobalTestState) => {
// Check if payment was successful.
- orderStatus = await merchant.queryPrivateOrderStatus(
- "default",
- orderResp.order_id,
- );
+ orderStatus = await merchant.queryPrivateOrderStatus({
+ orderId: orderResp.order_id,
+ });
t.assertTrue(orderStatus.order_status === "paid");
diff --git a/packages/taler-integrationtests/src/test-paywall-flow.ts b/packages/taler-integrationtests/src/test-paywall-flow.ts
new file mode 100644
index 000000000..b329a9c6d
--- /dev/null
+++ b/packages/taler-integrationtests/src/test-paywall-flow.ts
@@ -0,0 +1,206 @@
+/*
+ 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";
+import {
+ PreparePayResultType,
+ codecForMerchantOrderStatusUnpaid,
+ ConfirmPayResultType,
+} from "taler-wallet-core";
+import axios from "axios";
+
+/**
+ * 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" });
+
+ /**
+ * =========================================================================
+ * Create an order and let the wallet pay under a session ID
+ *
+ * We check along the way that the JSON response to /orders/{order_id}
+ * returns the right thing.
+ * =========================================================================
+ */
+
+ let orderResp = await merchant.createOrder("default", {
+ order: {
+ summary: "Buy me!",
+ amount: "TESTKUDOS:5",
+ fulfillment_url: "https://example.com/article42",
+ },
+ });
+
+ const firstOrderId = orderResp.order_id;
+
+ let orderStatus = await merchant.queryPrivateOrderStatus({
+ orderId: orderResp.order_id,
+ sessionId: "mysession-one",
+ });
+
+ t.assertTrue(orderStatus.order_status === "unpaid");
+
+ t.assertTrue(orderStatus.already_paid_order_id === undefined);
+ let publicOrderStatusUrl = orderStatus.order_status_url;
+
+ let publicOrderStatusResp = await axios.get(publicOrderStatusUrl, {
+ validateStatus: () => true,
+ });
+
+ if (publicOrderStatusResp.status != 402) {
+ throw Error(
+ `expected status 402 (before claiming), but got ${publicOrderStatusResp.status}`,
+ );
+ }
+
+ let pubUnpaidStatusResp = codecForMerchantOrderStatusUnpaid().decode(
+ publicOrderStatusResp.data,
+ );
+
+ console.log(pubUnpaidStatusResp);
+
+ let preparePayResp = await wallet.preparePay({
+ talerPayUri: pubUnpaidStatusResp.taler_pay_uri,
+ });
+
+ t.assertTrue(preparePayResp.status === PreparePayResultType.PaymentPossible);
+
+ const proposalId = preparePayResp.proposalId;
+
+ publicOrderStatusResp = await axios.get(publicOrderStatusUrl, {
+ validateStatus: () => true,
+ });
+
+ if (publicOrderStatusResp.status != 402) {
+ throw Error(
+ `expected status 402 (after claiming), but got ${publicOrderStatusResp.status}`,
+ );
+ }
+
+ let pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
+ publicOrderStatusResp.data,
+ );
+
+ const confirmPayRes = await wallet.confirmPay({
+ proposalId: proposalId,
+ });
+
+ t.assertTrue(confirmPayRes.type === ConfirmPayResultType.Done);
+
+ publicOrderStatusResp = await axios.get(publicOrderStatusUrl, {
+ validateStatus: () => true,
+ });
+
+ if (publicOrderStatusResp.status != 410) {
+ throw Error(
+ `expected status 410 (after paying), but got ${publicOrderStatusResp.status}`,
+ );
+ }
+
+ /**
+ * =========================================================================
+ * Now change up the session ID!
+ * =========================================================================
+ */
+
+ orderStatus = await merchant.queryPrivateOrderStatus({
+ orderId: orderResp.order_id,
+ sessionId: "mysession-two",
+ });
+
+ // Should be unpaid because of a new session ID
+ t.assertTrue(orderStatus.order_status === "unpaid");
+
+ publicOrderStatusUrl = orderStatus.order_status_url;
+
+ // Pay with new taler://pay URI, which should
+ // have the new session ID!
+ // Wallet should now automatically re-play payment.
+ preparePayResp = await wallet.preparePay({
+ talerPayUri: orderStatus.taler_pay_uri,
+ });
+
+ t.assertTrue(preparePayResp.status === PreparePayResultType.AlreadyConfirmed);
+ t.assertTrue(preparePayResp.paid);
+
+ /**
+ * =========================================================================
+ * Now we test re-purchase detection.
+ * =========================================================================
+ */
+
+ orderResp = await merchant.createOrder("default", {
+ order: {
+ summary: "Buy me!",
+ amount: "TESTKUDOS:5",
+ // Same fulfillment URL as previously!
+ fulfillment_url: "https://example.com/article42",
+ },
+ });
+
+ orderStatus = await merchant.queryPrivateOrderStatus({
+ orderId: orderResp.order_id,
+ sessionId: "mysession-three",
+ });
+
+ t.assertTrue(orderStatus.order_status === "unpaid");
+
+ t.assertTrue(orderStatus.already_paid_order_id === undefined);
+ publicOrderStatusUrl = orderStatus.order_status_url;
+
+ // Here the re-purchase detection should kick in,
+ // and the wallet should re-pay for the old order
+ // under the new session ID (mysession-three).
+ preparePayResp = await wallet.preparePay({
+ talerPayUri: orderStatus.taler_pay_uri,
+ });
+
+ t.assertTrue(preparePayResp.status === PreparePayResultType.AlreadyConfirmed);
+ t.assertTrue(preparePayResp.paid);
+
+ // Ask the order status of the claimed-but-unpaid order
+ publicOrderStatusResp = await axios.get(publicOrderStatusUrl, {
+ validateStatus: () => true,
+ });
+
+ if (publicOrderStatusResp.status != 403) {
+ throw Error(
+ `expected status 403, but got ${publicOrderStatusResp.status}`,
+ );
+ }
+
+ pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
+ publicOrderStatusResp.data,
+ );
+
+ t.assertTrue(pubUnpaidStatusResp.already_paid_order_id === firstOrderId);
+});
diff --git a/packages/taler-integrationtests/src/test-refund-incremental.ts b/packages/taler-integrationtests/src/test-refund-incremental.ts
index 0667b10d8..59a36b942 100644
--- a/packages/taler-integrationtests/src/test-refund-incremental.ts
+++ b/packages/taler-integrationtests/src/test-refund-incremental.ts
@@ -47,10 +47,9 @@ runTest(async (t: GlobalTestState) => {
},
});
- let orderStatus = await merchant.queryPrivateOrderStatus(
- "default",
- orderResp.order_id,
- );
+ let orderStatus = await merchant.queryPrivateOrderStatus({
+ orderId: orderResp.order_id,
+ });
t.assertTrue(orderStatus.order_status === "unpaid");
@@ -69,10 +68,9 @@ runTest(async (t: GlobalTestState) => {
// Check if payment was successful.
- orderStatus = await merchant.queryPrivateOrderStatus(
- "default",
- orderResp.order_id,
- );
+ orderStatus = await merchant.queryPrivateOrderStatus({
+ orderId: orderResp.order_id,
+ });
t.assertTrue(orderStatus.order_status === "paid");
@@ -103,10 +101,9 @@ runTest(async (t: GlobalTestState) => {
});
console.log(r);
- orderStatus = await merchant.queryPrivateOrderStatus(
- "default",
- orderResp.order_id,
- );
+ orderStatus = await merchant.queryPrivateOrderStatus({
+ orderId: orderResp.order_id,
+ });
t.assertTrue(orderStatus.order_status === "paid");
diff --git a/packages/taler-integrationtests/src/test-refund.ts b/packages/taler-integrationtests/src/test-refund.ts
index e1fdbfc50..d0d0a0a05 100644
--- a/packages/taler-integrationtests/src/test-refund.ts
+++ b/packages/taler-integrationtests/src/test-refund.ts
@@ -47,10 +47,9 @@ runTest(async (t: GlobalTestState) => {
},
});
- let orderStatus = await merchant.queryPrivateOrderStatus(
- "default",
- orderResp.order_id,
- );
+ let orderStatus = await merchant.queryPrivateOrderStatus({
+ orderId: orderResp.order_id,
+ });
t.assertTrue(orderStatus.order_status === "unpaid");
@@ -69,10 +68,9 @@ runTest(async (t: GlobalTestState) => {
// Check if payment was successful.
- orderStatus = await merchant.queryPrivateOrderStatus(
- "default",
- orderResp.order_id,
- );
+ orderStatus = await merchant.queryPrivateOrderStatus({
+ orderId: orderResp.order_id,
+ });
t.assertTrue(orderStatus.order_status === "paid");
diff --git a/packages/taler-wallet-core/src/types/walletTypes.ts b/packages/taler-wallet-core/src/types/walletTypes.ts
index 6f6340520..8521af3ff 100644
--- a/packages/taler-wallet-core/src/types/walletTypes.ts
+++ b/packages/taler-wallet-core/src/types/walletTypes.ts
@@ -221,6 +221,29 @@ export interface ConfirmPayResultPending {
export type ConfirmPayResult = ConfirmPayResultDone | ConfirmPayResultPending;
+export const codecForConfirmPayResultPending = (): Codec<
+ ConfirmPayResultPending
+> =>
+ buildCodecForObject<ConfirmPayResultPending>()
+ .property("lastError", codecForAny())
+ .property("type", codecForConstString(ConfirmPayResultType.Pending))
+ .build("ConfirmPayResultPending");
+
+export const codecForConfirmPayResultDone = (): Codec<
+ ConfirmPayResultDone
+> =>
+ buildCodecForObject<ConfirmPayResultDone>()
+ .property("type", codecForConstString(ConfirmPayResultType.Done))
+ .property("nextUrl", codecForString())
+ .build("ConfirmPayResultDone");
+
+export const codecForConfirmPayResult = (): Codec<ConfirmPayResult> =>
+ buildCodecForUnion<ConfirmPayResult>()
+ .discriminateOn("type")
+ .alternative(ConfirmPayResultType.Pending, codecForConfirmPayResultPending())
+ .alternative(ConfirmPayResultType.Done, codecForConfirmPayResultDone())
+ .build("ConfirmPayResult");
+
/**
* Information about all sender wire details known to the wallet,
* as well as exchanges that accept these wire types.
@@ -400,13 +423,22 @@ export const codecForPreparePayResultAlreadyConfirmed = (): Codec<
.property("contractTerms", codecForAny())
.build("PreparePayResultAlreadyConfirmed");
-export const codecForPreparePayResult = (): Codec<PreparePayResult> =>
+export const codecForPreparePayResult = (): Codec<PreparePayResult> =>
buildCodecForUnion<PreparePayResult>()
- .discriminateOn("status")
- .alternative(PreparePayResultType.AlreadyConfirmed, codecForPreparePayResultAlreadyConfirmed())
- .alternative(PreparePayResultType.InsufficientBalance, codecForPreparePayResultInsufficientBalance())
- .alternative(PreparePayResultType.PaymentPossible, codecForPreparePayResultPaymentPossible())
- .build("PreparePayResult");
+ .discriminateOn("status")
+ .alternative(
+ PreparePayResultType.AlreadyConfirmed,
+ codecForPreparePayResultAlreadyConfirmed(),
+ )
+ .alternative(
+ PreparePayResultType.InsufficientBalance,
+ codecForPreparePayResultInsufficientBalance(),
+ )
+ .alternative(
+ PreparePayResultType.PaymentPossible,
+ codecForPreparePayResultPaymentPossible(),
+ )
+ .build("PreparePayResult");
export type PreparePayResult =
| PreparePayResultInsufficientBalance