re-implement integration test functionalty that will be used by the exchange for testing

This commit is contained in:
Florian Dold 2020-08-14 13:06:42 +05:30
parent d5f894690e
commit e3850158c2
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
10 changed files with 665 additions and 342 deletions

View File

@ -38,7 +38,6 @@ import {
WalletNotification,
WALLET_EXCHANGE_PROTOCOL_VERSION,
WALLET_MERCHANT_PROTOCOL_VERSION,
handleCoreApiRequest,
} from "taler-wallet-core";
import fs from "fs";
@ -229,7 +228,7 @@ class AndroidWalletMessageHandler {
}
default: {
const wallet = await this.wp.promise;
return await handleCoreApiRequest(wallet, operation, id, args);
return await wallet.handleCoreApiRequest(operation, id, args);
}
}
}

View File

@ -43,6 +43,7 @@
"typescript": "^3.9.7"
},
"dependencies": {
"axios": "^0.19.2",
"source-map-support": "^0.5.19",
"taler-wallet-core": "workspace:*",
"tslib": "^2.0.0"

View File

@ -26,7 +26,6 @@ import {
NodeHttpLib,
PreparePayResultType,
setDangerousTimetravel,
handleCoreApiRequest,
classifyTalerUri,
TalerUriType,
decodeCrock,
@ -34,10 +33,10 @@ import {
codecForList,
codecForString,
printTestVectors,
NodeThreadCryptoWorkerFactory,
CryptoApi,
} from "taler-wallet-core";
import * as clk from "./clk";
import { NodeThreadCryptoWorkerFactory } from "taler-wallet-core/lib/crypto/workers/nodeThreadWorker";
import { CryptoApi } from "taler-wallet-core/lib/crypto/workers/cryptoApi";
// This module also serves as the entry point for the crypto
// thread worker, and thus must expose these two handlers.
@ -210,8 +209,7 @@ walletCli
console.error("Invalid JSON");
process.exit(1);
}
const resp = await handleCoreApiRequest(
wallet,
const resp = await wallet.handleCoreApiRequest(
args.api.operation,
"reqid-1",
requestJson,

View File

@ -37,8 +37,6 @@ export * from "./types/walletTypes";
export * from "./types/talerTypes";
export * from "./walletCoreApiHandler";
export * from "./util/taleruri";
export * from "./util/time";
@ -54,7 +52,7 @@ export * from "./util/testvectors";
export * from "./operations/versions";
export type { CryptoWorker } from "./crypto/workers/cryptoWorker";
export type { CryptoWorkerFactory } from "./crypto/workers/cryptoApi";
export { CryptoWorkerFactory, CryptoApi } from "./crypto/workers/cryptoApi";
export * from "./util/http";

View File

@ -21,10 +21,19 @@ import {
checkSuccessResponseOrThrow,
} from "../util/http";
import { codecForAny } from "../util/codec";
import { AmountString } from "../types/talerTypes";
import {
AmountString,
CheckPaymentResponse,
codecForCheckPaymentResponse,
} from "../types/talerTypes";
import { InternalWalletState } from "./state";
import { createTalerWithdrawReserve } from "./reserves";
import { URL } from "../util/url";
import { Wallet } from "../wallet";
import { Amounts } from "../util/amounts";
import { NodeHttpLib } from "../headless/NodeHttpLib";
import { getDefaultNodeWallet } from "../headless/helpers";
import { TestPayArgs, PreparePayResultType, IntegrationTestArgs } from "../types/walletTypes";
const logger = new Logger("operations/testing.ts");
@ -38,6 +47,11 @@ interface BankWithdrawalResponse {
withdrawal_id: string;
}
interface MerchantBackendInfo {
baseUrl: string;
apikey: string;
}
/**
* Generate a random alphanumeric ID. Does *not* use cryptographically
* secure randomness.
@ -154,3 +168,268 @@ async function registerRandomBankUser(
await checkSuccessResponseOrThrow(resp);
return bankUser;
}
async function refund(
http: HttpRequestLibrary,
merchantBackend: MerchantBackendInfo,
orderId: string,
reason: string,
refundAmount: string,
): Promise<string> {
const reqUrl = new URL(
`private/orders/${orderId}/refund`,
merchantBackend.baseUrl,
);
const refundReq = {
order_id: orderId,
reason,
refund: refundAmount,
};
const resp = await http.postJson(reqUrl.href, refundReq, {
headers: {
Authorization: `ApiKey ${merchantBackend.apikey}`,
},
});
const r = await readSuccessResponseJsonOrThrow(resp, codecForAny());
const refundUri = r.taler_refund_uri;
if (!refundUri) {
throw Error("no refund URI in response");
}
return refundUri;
}
async function createOrder(
http: HttpRequestLibrary,
merchantBackend: MerchantBackendInfo,
amount: string,
summary: string,
fulfillmentUrl: string,
): Promise<{ orderId: string }> {
const t = Math.floor(new Date().getTime() / 1000) + 15 * 60;
const reqUrl = new URL("private/orders", merchantBackend.baseUrl).href;
const orderReq = {
order: {
amount,
summary,
fulfillment_url: fulfillmentUrl,
refund_deadline: { t_ms: t * 1000 },
wire_transfer_deadline: { t_ms: t * 1000 },
},
};
const resp = await http.postJson(reqUrl, orderReq, {
headers: {
Authorization: `ApiKey ${merchantBackend.apikey}`,
},
});
const r = await readSuccessResponseJsonOrThrow(resp, codecForAny());
const orderId = r.order_id;
if (!orderId) {
throw Error("no order id in response");
}
return { orderId };
}
async function checkPayment(
http: HttpRequestLibrary,
merchantBackend: MerchantBackendInfo,
orderId: string,
): Promise<CheckPaymentResponse> {
const reqUrl = new URL(`/private/orders/${orderId}`, merchantBackend.baseUrl);
reqUrl.searchParams.set("order_id", orderId);
const resp = await http.get(reqUrl.href, {
headers: {
Authorization: `ApiKey ${merchantBackend.apikey}`,
},
});
return readSuccessResponseJsonOrThrow(resp, codecForCheckPaymentResponse());
}
interface BankUser {
username: string;
password: string;
}
interface BankWithdrawalResponse {
taler_withdraw_uri: string;
withdrawal_id: string;
}
async function makePayment(
http: HttpRequestLibrary,
wallet: Wallet,
merchant: MerchantBackendInfo,
amount: string,
summary: string,
): Promise<{ orderId: string }> {
const orderResp = await createOrder(
http,
merchant,
amount,
summary,
"taler://fulfillment-success/thx",
);
console.log("created order with orderId", orderResp.orderId);
let paymentStatus = await checkPayment(http, merchant, orderResp.orderId);
console.log("payment status", paymentStatus);
const talerPayUri = paymentStatus.taler_pay_uri;
if (!talerPayUri) {
throw Error("no taler://pay/ URI in payment response");
}
const preparePayResult = await wallet.preparePayForUri(talerPayUri);
console.log("prepare pay result", preparePayResult);
if (preparePayResult.status != "payment-possible") {
throw Error("payment not possible");
}
const confirmPayResult = await wallet.confirmPay(
preparePayResult.proposalId,
undefined,
);
console.log("confirmPayResult", confirmPayResult);
paymentStatus = await checkPayment(http, merchant, orderResp.orderId);
console.log("payment status after wallet payment:", paymentStatus);
if (paymentStatus.order_status !== "paid") {
throw Error("payment did not succeed");
}
return {
orderId: orderResp.orderId,
};
}
export async function runIntegrationTest(
http: HttpRequestLibrary,
wallet: Wallet,
args: IntegrationTestArgs,
): Promise<void> {
logger.info("running test with arguments", args);
const parsedSpendAmount = Amounts.parseOrThrow(args.amountToSpend);
const currency = parsedSpendAmount.currency;
const myHttpLib = new NodeHttpLib();
myHttpLib.setThrottling(false);
const myWallet = await getDefaultNodeWallet({ httpLib: myHttpLib });
myWallet.runRetryLoop().catch((e) => {
console.error("exception during retry loop:", e);
});
logger.info("withdrawing test balance");
await wallet.withdrawTestBalance(
args.amountToWithdraw,
args.bankBaseUrl,
args.exchangeBaseUrl,
);
logger.info("done withdrawing test balance");
const balance = await myWallet.getBalances();
console.log(JSON.stringify(balance, null, 2));
const myMerchant: MerchantBackendInfo = {
baseUrl: args.merchantBaseUrl,
apikey: args.merchantApiKey,
};
await makePayment(
http,
wallet,
myMerchant,
args.amountToSpend,
"hello world",
);
// Wait until the refresh is done
await myWallet.runUntilDone();
console.log("withdrawing test balance for refund");
const withdrawAmountTwo = Amounts.parseOrThrow(`${currency}:18`);
const spendAmountTwo = Amounts.parseOrThrow(`${currency}:7`);
const refundAmount = Amounts.parseOrThrow(`${currency}:6`);
const spendAmountThree = Amounts.parseOrThrow(`${currency}:3`);
await myWallet.withdrawTestBalance(
Amounts.stringify(withdrawAmountTwo),
args.bankBaseUrl,
args.exchangeBaseUrl,
);
// Wait until the withdraw is done
await myWallet.runUntilDone();
const { orderId: refundOrderId } = await makePayment(
http,
myWallet,
myMerchant,
Amounts.stringify(spendAmountTwo),
"order that will be refunded",
);
const refundUri = await refund(
http,
myMerchant,
refundOrderId,
"test refund",
Amounts.stringify(refundAmount),
);
console.log("refund URI", refundUri);
await myWallet.applyRefund(refundUri);
// Wait until the refund is done
await myWallet.runUntilDone();
await makePayment(
http,
myWallet,
myMerchant,
Amounts.stringify(spendAmountThree),
"payment after refund",
);
await myWallet.runUntilDone();
}
export async function testPay(
http: HttpRequestLibrary,
wallet: Wallet,
args: TestPayArgs,
) {
console.log("creating order");
const merchant = { apikey: args.apikey, baseUrl: args.merchant };
const orderResp = await createOrder(
http,
merchant,
args.amount,
args.summary,
"taler://fulfillment-success/thank+you",
);
console.log("created new order with order ID", orderResp.orderId);
const checkPayResp = await checkPayment(http, merchant, orderResp.orderId);
const talerPayUri = checkPayResp.taler_pay_uri;
if (!talerPayUri) {
console.error("fatal: no taler pay URI received from backend");
process.exit(1);
return;
}
console.log("taler pay URI:", talerPayUri);
const result = await wallet.preparePayForUri(talerPayUri);
if (result.status !== PreparePayResultType.PaymentPossible) {
throw Error(`unexpected prepare pay status: ${result.status}`);
}
await wallet.confirmPay(result.proposalId, undefined);
}

View File

@ -229,9 +229,7 @@ export const codecForConfirmPayResultPending = (): Codec<
.property("type", codecForConstString(ConfirmPayResultType.Pending))
.build("ConfirmPayResultPending");
export const codecForConfirmPayResultDone = (): Codec<
ConfirmPayResultDone
> =>
export const codecForConfirmPayResultDone = (): Codec<ConfirmPayResultDone> =>
buildCodecForObject<ConfirmPayResultDone>()
.property("type", codecForConstString(ConfirmPayResultType.Done))
.property("nextUrl", codecForString())
@ -240,7 +238,10 @@ export const codecForConfirmPayResultDone = (): Codec<
export const codecForConfirmPayResult = (): Codec<ConfirmPayResult> =>
buildCodecForUnion<ConfirmPayResult>()
.discriminateOn("type")
.alternative(ConfirmPayResultType.Pending, codecForConfirmPayResultPending())
.alternative(
ConfirmPayResultType.Pending,
codecForConfirmPayResultPending(),
)
.alternative(ConfirmPayResultType.Done, codecForConfirmPayResultDone())
.build("ConfirmPayResult");
@ -650,3 +651,181 @@ export interface GetExchangeTosResult {
*/
acceptedEtag: string | undefined;
}
export interface TestPayArgs {
merchant: string;
apikey: string;
amount: string;
summary: string;
}
export const codecForTestPayArgs = (): Codec<TestPayArgs> =>
buildCodecForObject<TestPayArgs>()
.property("merchant", codecForString())
.property("apikey", codecForString())
.property("amount", codecForString())
.property("summary", codecForString())
.build("TestPayArgs");
export interface IntegrationTestArgs {
exchangeBaseUrl: string;
bankBaseUrl: string;
merchantBaseUrl: string;
merchantApiKey: string;
amountToWithdraw: string;
amountToSpend: string;
}
export const codecForIntegrationTestArgs = (): Codec<IntegrationTestArgs> =>
buildCodecForObject<IntegrationTestArgs>()
.property("exchangeBaseUrl", codecForString())
.property("bankBaseUrl", codecForString())
.property("merchantBaseUrl", codecForString())
.property("merchantApiKey", codecForString())
.property("amountToSpend", codecForAmountString())
.property("amountToWithdraw", codecForAmountString())
.build("IntegrationTestArgs");
export interface AddExchangeRequest {
exchangeBaseUrl: string;
}
export const codecForAddExchangeRequest = (): Codec<AddExchangeRequest> =>
buildCodecForObject<AddExchangeRequest>()
.property("exchangeBaseUrl", codecForString())
.build("AddExchangeRequest");
export interface GetExchangeTosRequest {
exchangeBaseUrl: string;
}
export const codecForGetExchangeTosRequest = (): Codec<GetExchangeTosRequest> =>
buildCodecForObject<GetExchangeTosRequest>()
.property("exchangeBaseUrl", codecForString())
.build("GetExchangeTosRequest");
export interface AcceptManualWithdrawalRequest {
exchangeBaseUrl: string;
amount: string;
}
export const codecForAcceptManualWithdrawalRequet = (): Codec<
AcceptManualWithdrawalRequest
> =>
buildCodecForObject<AcceptManualWithdrawalRequest>()
.property("exchangeBaseUrl", codecForString())
.property("amount", codecForString())
.build("AcceptManualWithdrawalRequest");
export interface GetWithdrawalDetailsForAmountRequest {
exchangeBaseUrl: string;
amount: string;
}
export interface AcceptBankIntegratedWithdrawalRequest {
talerWithdrawUri: string;
exchangeBaseUrl: string;
}
export const codecForAcceptBankIntegratedWithdrawalRequest = (): Codec<
AcceptBankIntegratedWithdrawalRequest
> =>
buildCodecForObject<AcceptBankIntegratedWithdrawalRequest>()
.property("exchangeBaseUrl", codecForString())
.property("talerWithdrawUri", codecForString())
.build("AcceptBankIntegratedWithdrawalRequest");
export const codecForGetWithdrawalDetailsForAmountRequest = (): Codec<
GetWithdrawalDetailsForAmountRequest
> =>
buildCodecForObject<GetWithdrawalDetailsForAmountRequest>()
.property("exchangeBaseUrl", codecForString())
.property("amount", codecForString())
.build("GetWithdrawalDetailsForAmountRequest");
export interface AcceptExchangeTosRequest {
exchangeBaseUrl: string;
etag: string;
}
export const codecForAcceptExchangeTosRequest = (): Codec<
AcceptExchangeTosRequest
> =>
buildCodecForObject<AcceptExchangeTosRequest>()
.property("exchangeBaseUrl", codecForString())
.property("etag", codecForString())
.build("AcceptExchangeTosRequest");
export interface ApplyRefundRequest {
talerRefundUri: string;
}
export const codecForApplyRefundRequest = (): Codec<ApplyRefundRequest> =>
buildCodecForObject<ApplyRefundRequest>()
.property("talerRefundUri", codecForString())
.build("ApplyRefundRequest");
export interface GetWithdrawalDetailsForUriRequest {
talerWithdrawUri: string;
}
export const codecForGetWithdrawalDetailsForUri = (): Codec<
GetWithdrawalDetailsForUriRequest
> =>
buildCodecForObject<GetWithdrawalDetailsForUriRequest>()
.property("talerWithdrawUri", codecForString())
.build("GetWithdrawalDetailsForUriRequest");
export interface AbortProposalRequest {
proposalId: string;
}
export const codecForAbortProposalRequest = (): Codec<AbortProposalRequest> =>
buildCodecForObject<AbortProposalRequest>()
.property("proposalId", codecForString())
.build("AbortProposalRequest");
export interface PreparePayRequest {
talerPayUri: string;
}
export const codecForPreparePayRequest = (): Codec<PreparePayRequest> =>
buildCodecForObject<PreparePayRequest>()
.property("talerPayUri", codecForString())
.build("PreparePay");
export interface ConfirmPayRequest {
proposalId: string;
sessionId?: string;
}
export const codecForConfirmPayRequest = (): Codec<ConfirmPayRequest> =>
buildCodecForObject<ConfirmPayRequest>()
.property("proposalId", codecForString())
.property("sessionId", codecOptional(codecForString()))
.build("ConfirmPay");
export type CoreApiResponse = CoreApiResponseSuccess | CoreApiResponseError;
export type CoreApiEnvelope = CoreApiResponse | CoreApiNotification;
export interface CoreApiNotification {
type: "notification";
payload: unknown;
}
export interface CoreApiResponseSuccess {
// To distinguish the message from notifications
type: "response";
operation: string;
id: string;
result: unknown;
}
export interface CoreApiResponseError {
// To distinguish the message from notifications
type: "error";
operation: string;
id: string;
error: OperationErrorDetails;
}

View File

@ -70,6 +70,22 @@ import {
GetExchangeTosResult,
AcceptManualWithdrawalResult,
BalancesResponse,
TestPayArgs,
PreparePayResultType,
IntegrationTestArgs,
codecForAddExchangeRequest,
codecForGetWithdrawalDetailsForUri,
codecForAcceptManualWithdrawalRequet,
codecForGetWithdrawalDetailsForAmountRequest,
codecForAcceptExchangeTosRequest,
codecForApplyRefundRequest,
codecForAcceptBankIntegratedWithdrawalRequest,
codecForGetExchangeTosRequest,
codecForAbortProposalRequest,
codecForConfirmPayRequest,
CoreApiResponse,
codecForPreparePayRequest,
codecForIntegrationTestArgs,
} from "./types/walletTypes";
import { Logger } from "./util/logging";
@ -107,13 +123,23 @@ import { WalletNotification, NotificationType } from "./types/notifications";
import { processPurchaseQueryRefund, applyRefund } from "./operations/refund";
import { durationMin, Duration } from "./util/time";
import { processRecoupGroup } from "./operations/recoup";
import { OperationFailedAndReportedError } from "./operations/errors";
import {
OperationFailedAndReportedError,
OperationFailedError,
makeErrorDetails,
} from "./operations/errors";
import {
TransactionsRequest,
TransactionsResponse,
codecForTransactionsRequest,
} from "./types/transactions";
import { getTransactions } from "./operations/transactions";
import { withdrawTestBalance } from "./operations/testing";
import {
withdrawTestBalance,
runIntegrationTest,
testPay,
} from "./operations/testing";
import { TalerErrorCode } from ".";
const builtinCurrencies: CurrencyRecord[] = [
{
@ -879,4 +905,168 @@ export class Wallet {
): Promise<void> {
await withdrawTestBalance(this.ws, amount, bankBaseUrl, exchangeBaseUrl);
}
async runIntegrationtest(args: IntegrationTestArgs): Promise<void> {
return runIntegrationTest(this.ws.http, this, args);
}
async testPay(args: TestPayArgs) {
return testPay(this.ws.http, this, args);
}
/**
* Implementation of the "wallet-core" API.
*/
private async dispatchRequestInternal(
operation: string,
payload: unknown,
): Promise<Record<string, any>> {
switch (operation) {
case "withdrawTestkudos": {
await this.withdrawTestBalance();
return {};
}
case "runIntegrationtest": {
const req = codecForIntegrationTestArgs().decode(payload);
await this.runIntegrationtest(req);
return {}
}
case "testPay": {
const req = codecForIntegrationTestArgs().decode(payload);
await this.runIntegrationtest(req);
return {}
}
case "getTransactions": {
const req = codecForTransactionsRequest().decode(payload);
return await this.getTransactions(req);
}
case "addExchange": {
const req = codecForAddExchangeRequest().decode(payload);
await this.updateExchangeFromUrl(req.exchangeBaseUrl);
return {};
}
case "listExchanges": {
return await this.getExchanges();
}
case "getWithdrawalDetailsForUri": {
const req = codecForGetWithdrawalDetailsForUri().decode(payload);
return await this.getWithdrawalDetailsForUri(req.talerWithdrawUri);
}
case "acceptManualWithdrawal": {
const req = codecForAcceptManualWithdrawalRequet().decode(payload);
const res = await this.acceptManualWithdrawal(
req.exchangeBaseUrl,
Amounts.parseOrThrow(req.amount),
);
return res;
}
case "getWithdrawalDetailsForAmount": {
const req = codecForGetWithdrawalDetailsForAmountRequest().decode(
payload,
);
return await this.getWithdrawalDetailsForAmount(
req.exchangeBaseUrl,
Amounts.parseOrThrow(req.amount),
);
}
case "getBalances": {
return await this.getBalances();
}
case "getPendingOperations": {
return await this.getPendingOperations();
}
case "setExchangeTosAccepted": {
const req = codecForAcceptExchangeTosRequest().decode(payload);
await this.acceptExchangeTermsOfService(
req.exchangeBaseUrl,
req.etag,
);
return {};
}
case "applyRefund": {
const req = codecForApplyRefundRequest().decode(payload);
return await this.applyRefund(req.talerRefundUri);
}
case "acceptBankIntegratedWithdrawal": {
const req = codecForAcceptBankIntegratedWithdrawalRequest().decode(
payload,
);
return await this.acceptWithdrawal(
req.talerWithdrawUri,
req.exchangeBaseUrl,
);
}
case "getExchangeTos": {
const req = codecForGetExchangeTosRequest().decode(payload);
return this.getExchangeTos(req.exchangeBaseUrl);
}
case "abortProposal": {
const req = codecForAbortProposalRequest().decode(payload);
await this.refuseProposal(req.proposalId);
return {};
}
case "retryPendingNow": {
await this.runPending(true);
return {};
}
case "preparePay": {
const req = codecForPreparePayRequest().decode(payload);
return await this.preparePayForUri(req.talerPayUri);
}
case "confirmPay": {
const req = codecForConfirmPayRequest().decode(payload);
return await this.confirmPay(req.proposalId, req.sessionId);
}
}
throw OperationFailedError.fromCode(
TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN,
"unknown operation",
{
operation,
},
);
}
/**
* Handle a request to the wallet-core API.
*/
async handleCoreApiRequest(
operation: string,
id: string,
payload: unknown,
): Promise<CoreApiResponse> {
try {
const result = await this.dispatchRequestInternal(operation, payload);
return {
type: "response",
operation,
id,
result,
};
} catch (e) {
if (
e instanceof OperationFailedError ||
e instanceof OperationFailedAndReportedError
) {
return {
type: "error",
operation,
id,
error: e.operationError,
};
} else {
return {
type: "error",
operation,
id,
error: makeErrorDetails(
TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
`unexpected exception: ${e}`,
{},
),
};
}
}
}
}

View File

@ -1,318 +0,0 @@
/*
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/>
*/
import { Wallet } from "./wallet";
import {
OperationFailedError,
OperationFailedAndReportedError,
makeErrorDetails,
} from "./operations/errors";
import { TalerErrorCode } from "./TalerErrorCode";
import { codecForTransactionsRequest } from "./types/transactions";
import {
buildCodecForObject,
codecForString,
Codec,
codecOptional,
} from "./util/codec";
import { Amounts } from "./util/amounts";
import { OperationErrorDetails } from "./types/walletTypes";
export interface AddExchangeRequest {
exchangeBaseUrl: string;
}
export const codecForAddExchangeRequest = (): Codec<AddExchangeRequest> =>
buildCodecForObject<AddExchangeRequest>()
.property("exchangeBaseUrl", codecForString())
.build("AddExchangeRequest");
export interface GetExchangeTosRequest {
exchangeBaseUrl: string;
}
export const codecForGetExchangeTosRequest = (): Codec<GetExchangeTosRequest> =>
buildCodecForObject<GetExchangeTosRequest>()
.property("exchangeBaseUrl", codecForString())
.build("GetExchangeTosRequest");
export interface AcceptManualWithdrawalRequest {
exchangeBaseUrl: string;
amount: string;
}
export const codecForAcceptManualWithdrawalRequet = (): Codec<
AcceptManualWithdrawalRequest
> =>
buildCodecForObject<AcceptManualWithdrawalRequest>()
.property("exchangeBaseUrl", codecForString())
.property("amount", codecForString())
.build("AcceptManualWithdrawalRequest");
export interface GetWithdrawalDetailsForAmountRequest {
exchangeBaseUrl: string;
amount: string;
}
export interface AcceptBankIntegratedWithdrawalRequest {
talerWithdrawUri: string;
exchangeBaseUrl: string;
}
export const codecForAcceptBankIntegratedWithdrawalRequest = (): Codec<
AcceptBankIntegratedWithdrawalRequest
> =>
buildCodecForObject<AcceptBankIntegratedWithdrawalRequest>()
.property("exchangeBaseUrl", codecForString())
.property("talerWithdrawUri", codecForString())
.build("AcceptBankIntegratedWithdrawalRequest");
export const codecForGetWithdrawalDetailsForAmountRequest = (): Codec<
GetWithdrawalDetailsForAmountRequest
> =>
buildCodecForObject<GetWithdrawalDetailsForAmountRequest>()
.property("exchangeBaseUrl", codecForString())
.property("amount", codecForString())
.build("GetWithdrawalDetailsForAmountRequest");
export interface AcceptExchangeTosRequest {
exchangeBaseUrl: string;
etag: string;
}
export const codecForAcceptExchangeTosRequest = (): Codec<AcceptExchangeTosRequest> =>
buildCodecForObject<AcceptExchangeTosRequest>()
.property("exchangeBaseUrl", codecForString())
.property("etag", codecForString())
.build("AcceptExchangeTosRequest");
export interface ApplyRefundRequest {
talerRefundUri: string;
}
export const codecForApplyRefundRequest = (): Codec<ApplyRefundRequest> =>
buildCodecForObject<ApplyRefundRequest>()
.property("talerRefundUri", codecForString())
.build("ApplyRefundRequest");
export interface GetWithdrawalDetailsForUriRequest {
talerWithdrawUri: string;
}
export const codecForGetWithdrawalDetailsForUri = (): Codec<
GetWithdrawalDetailsForUriRequest
> =>
buildCodecForObject<GetWithdrawalDetailsForUriRequest>()
.property("talerWithdrawUri", codecForString())
.build("GetWithdrawalDetailsForUriRequest");
export interface AbortProposalRequest {
proposalId: string;
}
export const codecForAbortProposalRequest = (): Codec<AbortProposalRequest> =>
buildCodecForObject<AbortProposalRequest>()
.property("proposalId", codecForString())
.build("AbortProposalRequest");
export interface PreparePayRequest {
talerPayUri: string;
}
const codecForPreparePayRequest = (): Codec<PreparePayRequest> =>
buildCodecForObject<PreparePayRequest>()
.property("talerPayUri", codecForString())
.build("PreparePay");
export interface ConfirmPayRequest {
proposalId: string;
sessionId?: string;
}
export const codecForConfirmPayRequest = (): Codec<ConfirmPayRequest> =>
buildCodecForObject<ConfirmPayRequest>()
.property("proposalId", codecForString())
.property("sessionId", codecOptional(codecForString()))
.build("ConfirmPay");
/**
* Implementation of the "wallet-core" API.
*/
async function dispatchRequestInternal(
wallet: Wallet,
operation: string,
payload: unknown,
): Promise<Record<string, any>> {
switch (operation) {
case "withdrawTestkudos":
await wallet.withdrawTestBalance();
return {};
case "getTransactions": {
const req = codecForTransactionsRequest().decode(payload);
return await wallet.getTransactions(req);
}
case "addExchange": {
const req = codecForAddExchangeRequest().decode(payload);
await wallet.updateExchangeFromUrl(req.exchangeBaseUrl);
return {};
}
case "listExchanges": {
return await wallet.getExchanges();
}
case "getWithdrawalDetailsForUri": {
const req = codecForGetWithdrawalDetailsForUri().decode(payload);
return await wallet.getWithdrawalDetailsForUri(req.talerWithdrawUri);
}
case "acceptManualWithdrawal": {
const req = codecForAcceptManualWithdrawalRequet().decode(payload);
const res = await wallet.acceptManualWithdrawal(
req.exchangeBaseUrl,
Amounts.parseOrThrow(req.amount),
);
return res;
}
case "getWithdrawalDetailsForAmount": {
const req = codecForGetWithdrawalDetailsForAmountRequest().decode(
payload,
);
return await wallet.getWithdrawalDetailsForAmount(
req.exchangeBaseUrl,
Amounts.parseOrThrow(req.amount),
);
}
case "getBalances": {
return await wallet.getBalances();
}
case "getPendingOperations": {
return await wallet.getPendingOperations();
}
case "setExchangeTosAccepted": {
const req = codecForAcceptExchangeTosRequest().decode(payload);
await wallet.acceptExchangeTermsOfService(req.exchangeBaseUrl, req.etag);
return {};
}
case "applyRefund": {
const req = codecForApplyRefundRequest().decode(payload);
return await wallet.applyRefund(req.talerRefundUri);
}
case "acceptBankIntegratedWithdrawal": {
const req = codecForAcceptBankIntegratedWithdrawalRequest().decode(
payload,
);
return await wallet.acceptWithdrawal(
req.talerWithdrawUri,
req.exchangeBaseUrl,
);
}
case "getExchangeTos": {
const req = codecForGetExchangeTosRequest().decode(payload);
return wallet.getExchangeTos(req.exchangeBaseUrl);
}
case "abortProposal": {
const req = codecForAbortProposalRequest().decode(payload);
await wallet.refuseProposal(req.proposalId);
return {};
}
case "retryPendingNow": {
await wallet.runPending(true);
return {};
}
case "preparePay": {
const req = codecForPreparePayRequest().decode(payload);
return await wallet.preparePayForUri(req.talerPayUri);
}
case "confirmPay": {
const req = codecForConfirmPayRequest().decode(payload);
return await wallet.confirmPay(req.proposalId, req.sessionId);
}
}
throw OperationFailedError.fromCode(
TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN,
"unknown operation",
{
operation,
},
);
}
export type CoreApiResponse = CoreApiResponseSuccess | CoreApiResponseError;
export type CoreApiEnvelope = CoreApiResponse | CoreApiNotification;
export interface CoreApiNotification {
type: "notification";
payload: unknown;
}
export interface CoreApiResponseSuccess {
// To distinguish the message from notifications
type: "response";
operation: string;
id: string;
result: unknown;
}
export interface CoreApiResponseError {
// To distinguish the message from notifications
type: "error";
operation: string;
id: string;
error: OperationErrorDetails;
}
/**
* Handle a request to the wallet-core API.
*/
export async function handleCoreApiRequest(
w: Wallet,
operation: string,
id: string,
payload: unknown,
): Promise<CoreApiResponse> {
try {
const result = await dispatchRequestInternal(w, operation, payload);
return {
type: "response",
operation,
id,
result,
};
} catch (e) {
if (
e instanceof OperationFailedError ||
e instanceof OperationFailedAndReportedError
) {
return {
type: "error",
operation,
id,
error: e.operationError,
};
} else {
return {
type: "error",
operation,
id,
error: makeErrorDetails(
TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
`unexpected exception: ${e}`,
{},
),
};
}
}
}

View File

@ -24,7 +24,6 @@
* Imports.
*/
import { isFirefox, getPermissionsApi } from "./compat";
import * as wxApi from "./wxApi";
import MessageSender = chrome.runtime.MessageSender;
import { extendedPermissions } from "./permissions";
@ -32,16 +31,12 @@ import {
Wallet,
OpenedPromise,
openPromise,
deleteTalerDatabase,
WALLET_DB_MINOR_VERSION,
WalletDiagnostics,
openTalerDatabase,
Database,
classifyTalerUri,
TalerUriType,
makeErrorDetails,
TalerErrorCode,
handleCoreApiRequest,
} from "taler-wallet-core";
import { BrowserHttpLib } from "./browserHttpLib";
import { BrowserCryptoWorkerFactory } from "./browserCryptoWorkerFactory";
@ -82,7 +77,7 @@ async function dispatch(
return;
}
const r = await handleCoreApiRequest(w, req.operation, req.id, req.payload);
const r = await w.handleCoreApiRequest(req.operation, req.id, req.payload);
try {
sendResponse(r);
} catch (e) {
@ -251,7 +246,7 @@ function headerListener(
);
case TalerUriType.TalerRefund:
return makeSyncWalletRedirect(
"/static/refund.html",
"refund.html",
details.tabId,
details.url,
{

View File

@ -83,6 +83,7 @@ importers:
typescript: ^3.9.7
packages/taler-wallet-cli:
dependencies:
axios: 0.19.2
source-map-support: 0.5.19
taler-wallet-core: 'link:../taler-wallet-core'
tslib: 2.0.1
@ -105,6 +106,7 @@ importers:
'@rollup/plugin-node-resolve': ^8.4.0
'@rollup/plugin-replace': ^2.3.3
'@types/node': ^14.0.27
axios: ^0.19.2
prettier: ^2.0.5
rimraf: ^3.0.2
rollup: ^2.23.0