tipping API and integration test
This commit is contained in:
parent
be77ee284a
commit
b063382d25
@ -72,6 +72,10 @@ import {
|
||||
CoinDumpJson,
|
||||
ForceExchangeUpdateRequest,
|
||||
ForceRefreshRequest,
|
||||
PrepareTipResult,
|
||||
PrepareTipRequest,
|
||||
codecForPrepareTipResult,
|
||||
AcceptTipRequest,
|
||||
} from "taler-wallet-core";
|
||||
import { URL } from "url";
|
||||
import axios, { AxiosError } from "axios";
|
||||
@ -81,6 +85,9 @@ import {
|
||||
PostOrderRequest,
|
||||
PostOrderResponse,
|
||||
MerchantOrderPrivateStatusResponse,
|
||||
TippingReserveStatus,
|
||||
TipCreateConfirmation,
|
||||
TipCreateRequest,
|
||||
} from "./merchantApiTypes";
|
||||
import { ApplyRefundResponse } from "taler-wallet-core";
|
||||
import { PendingOperationsResponse } from "taler-wallet-core";
|
||||
@ -1216,10 +1223,46 @@ export namespace MerchantPrivateApi {
|
||||
};
|
||||
}
|
||||
|
||||
export async function createTippingReserve(merchantService: MerchantServiceInterface,
|
||||
|
||||
export async function createTippingReserve(
|
||||
merchantService: MerchantServiceInterface,
|
||||
instance: string,
|
||||
req: CreateMerchantTippingReserveRequest,
|
||||
): Promise<CreateMerchantTippingReserveConfirmation> {}
|
||||
): Promise<CreateMerchantTippingReserveConfirmation> {
|
||||
const reqUrl = new URL(
|
||||
`private/reserves`,
|
||||
merchantService.makeInstanceBaseUrl(instance),
|
||||
);
|
||||
const resp = await axios.post(reqUrl.href, req);
|
||||
// FIXME: validate
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
export async function queryTippingReserves(
|
||||
merchantService: MerchantServiceInterface,
|
||||
instance: string,
|
||||
): Promise<TippingReserveStatus> {
|
||||
const reqUrl = new URL(
|
||||
`private/reserves`,
|
||||
merchantService.makeInstanceBaseUrl(instance),
|
||||
);
|
||||
const resp = await axios.get(reqUrl.href);
|
||||
// FIXME: validate
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
export async function giveTip(
|
||||
merchantService: MerchantServiceInterface,
|
||||
instance: string,
|
||||
req: TipCreateRequest,
|
||||
): Promise<TipCreateConfirmation> {
|
||||
const reqUrl = new URL(
|
||||
`private/tips`,
|
||||
merchantService.makeInstanceBaseUrl(instance),
|
||||
);
|
||||
const resp = await axios.post(reqUrl.href, req);
|
||||
// FIXME: validate
|
||||
return resp.data;
|
||||
}
|
||||
}
|
||||
|
||||
export interface CreateMerchantTippingReserveRequest {
|
||||
@ -1238,7 +1281,7 @@ export interface CreateMerchantTippingReserveConfirmation {
|
||||
reserve_pub: string;
|
||||
|
||||
// Wire account of the exchange where to transfer the funds
|
||||
payto_url: string;
|
||||
payto_uri: string;
|
||||
}
|
||||
|
||||
export class MerchantService implements MerchantServiceInterface {
|
||||
@ -1594,6 +1637,22 @@ export class WalletCli {
|
||||
throw new OperationFailedError(resp.error);
|
||||
}
|
||||
|
||||
async prepareTip(req: PrepareTipRequest): Promise<PrepareTipResult> {
|
||||
const resp = await this.apiRequest("prepareTip", req);
|
||||
if (resp.type === "response") {
|
||||
return codecForPrepareTipResult().decode(resp.result);
|
||||
}
|
||||
throw new OperationFailedError(resp.error);
|
||||
}
|
||||
|
||||
async acceptTip(req: AcceptTipRequest): Promise<void> {
|
||||
const resp = await this.apiRequest("acceptTip", req);
|
||||
if (resp.type === "response") {
|
||||
return;
|
||||
}
|
||||
throw new OperationFailedError(resp.error);
|
||||
}
|
||||
|
||||
async dumpCoins(): Promise<CoinDumpJson> {
|
||||
const resp = await this.apiRequest("dumpCoins", {});
|
||||
if (resp.type === "response") {
|
||||
|
@ -223,3 +223,63 @@ export interface TransactionWireReport {
|
||||
// Public key of the coin for which we got the exchange error.
|
||||
coin_pub: CoinPublicKeyString;
|
||||
}
|
||||
|
||||
export interface TippingReserveStatus {
|
||||
// Array of all known reserves (possibly empty!)
|
||||
reserves: ReserveStatusEntry[];
|
||||
}
|
||||
|
||||
export interface ReserveStatusEntry {
|
||||
// Public key of the reserve
|
||||
reserve_pub: string;
|
||||
|
||||
// Timestamp when it was established
|
||||
creation_time: Timestamp;
|
||||
|
||||
// Timestamp when it expires
|
||||
expiration_time: Timestamp;
|
||||
|
||||
// Initial amount as per reserve creation call
|
||||
merchant_initial_amount: AmountString;
|
||||
|
||||
// Initial amount as per exchange, 0 if exchange did
|
||||
// not confirm reserve creation yet.
|
||||
exchange_initial_amount: AmountString;
|
||||
|
||||
// Amount picked up so far.
|
||||
pickup_amount: AmountString;
|
||||
|
||||
// Amount approved for tips that exceeds the pickup_amount.
|
||||
committed_amount: AmountString;
|
||||
|
||||
// Is this reserve active (false if it was deleted but not purged)
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
|
||||
export interface TipCreateConfirmation {
|
||||
// Unique tip identifier for the tip that was created.
|
||||
tip_id: string;
|
||||
|
||||
// taler://tip URI for the tip
|
||||
taler_tip_uri: string;
|
||||
|
||||
// URL that will directly trigger processing
|
||||
// the tip when the browser is redirected to it
|
||||
tip_status_url: string;
|
||||
|
||||
// when does the tip expire
|
||||
tip_expiration: Timestamp;
|
||||
}
|
||||
|
||||
export interface TipCreateRequest {
|
||||
// Amount that the customer should be tipped
|
||||
amount: AmountString;
|
||||
|
||||
// Justification for giving the tip
|
||||
justification: string;
|
||||
|
||||
// URL that the user should be directed to after tipping,
|
||||
// will be included in the tip_token.
|
||||
next_url: string;
|
||||
}
|
106
packages/taler-integrationtests/src/test-tipping.ts
Normal file
106
packages/taler-integrationtests/src/test-tipping.ts
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
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,
|
||||
MerchantPrivateApi,
|
||||
BankAccessApi,
|
||||
BankApi,
|
||||
} 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,
|
||||
exchangeBankAccount,
|
||||
} = await createSimpleTestkudosEnvironment(t);
|
||||
|
||||
const mbu = await BankApi.createRandomBankUser(bank);
|
||||
|
||||
const tipReserveResp = await MerchantPrivateApi.createTippingReserve(
|
||||
merchant,
|
||||
"default",
|
||||
{
|
||||
exchange_url: exchange.baseUrl,
|
||||
initial_balance: "TESTKUDOS:10",
|
||||
wire_method: "x-taler-bank",
|
||||
},
|
||||
);
|
||||
|
||||
console.log("tipReserveResp:", tipReserveResp);
|
||||
|
||||
t.assertDeepEqual(
|
||||
tipReserveResp.payto_uri,
|
||||
exchangeBankAccount.accountPaytoUri,
|
||||
);
|
||||
|
||||
await BankApi.adminAddIncoming(bank, {
|
||||
amount: "TESTKUDOS:10",
|
||||
debitAccountPayto: mbu.accountPaytoUri,
|
||||
exchangeBankAccount,
|
||||
reservePub: tipReserveResp.reserve_pub,
|
||||
});
|
||||
|
||||
await exchange.runWirewatchOnce();
|
||||
await merchant.stop();
|
||||
await merchant.start();
|
||||
await merchant.pingUntilAvailable();
|
||||
|
||||
const r = await MerchantPrivateApi.queryTippingReserves(merchant, "default");
|
||||
console.log("tipping reserves:", JSON.stringify(r, undefined, 2));
|
||||
|
||||
t.assertTrue(r.reserves.length === 1);
|
||||
t.assertDeepEqual(
|
||||
r.reserves[0].exchange_initial_amount,
|
||||
r.reserves[0].merchant_initial_amount,
|
||||
);
|
||||
|
||||
const tip = await MerchantPrivateApi.giveTip(merchant, "default", {
|
||||
amount: "TESTKUDOS:5",
|
||||
justification: "why not?",
|
||||
next_url: "https://example.com/after-tip",
|
||||
});
|
||||
|
||||
console.log("created tip", tip);
|
||||
|
||||
const ptr = await wallet.prepareTip({
|
||||
talerTipUri: tip.taler_tip_uri,
|
||||
});
|
||||
|
||||
console.log(ptr);
|
||||
|
||||
await wallet.acceptTip({
|
||||
walletTipId: ptr.walletTipId,
|
||||
});
|
||||
|
||||
await wallet.runUntilDone();
|
||||
|
||||
const bal = await wallet.getBalances();
|
||||
|
||||
console.log(bal);
|
||||
});
|
@ -288,9 +288,9 @@ walletCli
|
||||
break;
|
||||
case TalerUriType.TalerTip:
|
||||
{
|
||||
const res = await wallet.getTipStatus(uri);
|
||||
const res = await wallet.prepareTip(uri);
|
||||
console.log("tip status", res);
|
||||
await wallet.acceptTip(res.tipId);
|
||||
await wallet.acceptTip(res.walletTipId);
|
||||
}
|
||||
break;
|
||||
case TalerUriType.TalerRefund:
|
||||
|
@ -8,7 +8,7 @@ import { IDBFactory, IDBDatabase } from "idb-bridge";
|
||||
* with each major change. When incrementing the major version,
|
||||
* the wallet should import data from the previous version.
|
||||
*/
|
||||
const TALER_DB_NAME = "taler-walletdb-v10";
|
||||
const TALER_DB_NAME = "taler-walletdb-v11";
|
||||
|
||||
/**
|
||||
* Current database minor version, should be incremented
|
||||
|
@ -368,7 +368,7 @@ async function gatherTipPending(
|
||||
type: PendingOperationType.TipPickup,
|
||||
givesLifeness: true,
|
||||
merchantBaseUrl: tip.merchantBaseUrl,
|
||||
tipId: tip.tipId,
|
||||
tipId: tip.walletTipId,
|
||||
merchantTipId: tip.merchantTipId,
|
||||
});
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
import { InternalWalletState } from "./state";
|
||||
import { parseTipUri } from "../util/taleruri";
|
||||
import { TipStatus, TalerErrorDetails } from "../types/walletTypes";
|
||||
import { PrepareTipResult, TalerErrorDetails } from "../types/walletTypes";
|
||||
import {
|
||||
TipPlanchetDetail,
|
||||
codecForTipPickupGetResponse,
|
||||
@ -46,20 +46,23 @@ import { getTimestampNow } from "../util/time";
|
||||
import { readSuccessResponseJsonOrThrow } from "../util/http";
|
||||
import { URL } from "../util/url";
|
||||
import { Logger } from "../util/logging";
|
||||
import { checkDbInvariant } from "../util/invariants";
|
||||
|
||||
const logger = new Logger("operations/tip.ts");
|
||||
|
||||
export async function getTipStatus(
|
||||
export async function prepareTip(
|
||||
ws: InternalWalletState,
|
||||
talerTipUri: string,
|
||||
): Promise<TipStatus> {
|
||||
): Promise<PrepareTipResult> {
|
||||
const res = parseTipUri(talerTipUri);
|
||||
if (!res) {
|
||||
throw Error("invalid taler://tip URI");
|
||||
}
|
||||
|
||||
const tipStatusUrl = new URL("tip-pickup", res.merchantBaseUrl);
|
||||
tipStatusUrl.searchParams.set("tip_id", res.merchantTipId);
|
||||
const tipStatusUrl = new URL(
|
||||
`tips/${res.merchantTipId}`,
|
||||
res.merchantBaseUrl,
|
||||
);
|
||||
logger.trace("checking tip status from", tipStatusUrl.href);
|
||||
const merchantResp = await ws.http.get(tipStatusUrl.href);
|
||||
const tipPickupStatus = await readSuccessResponseJsonOrThrow(
|
||||
@ -68,7 +71,7 @@ export async function getTipStatus(
|
||||
);
|
||||
logger.trace(`status ${tipPickupStatus}`);
|
||||
|
||||
const amount = Amounts.parseOrThrow(tipPickupStatus.amount);
|
||||
const amount = Amounts.parseOrThrow(tipPickupStatus.tip_amount);
|
||||
|
||||
const merchantOrigin = new URL(res.merchantBaseUrl).origin;
|
||||
|
||||
@ -85,7 +88,7 @@ export async function getTipStatus(
|
||||
amount,
|
||||
);
|
||||
|
||||
const tipId = encodeCrock(getRandomBytes(32));
|
||||
const walletTipId = encodeCrock(getRandomBytes(32));
|
||||
const selectedDenoms = await selectWithdrawalDenoms(
|
||||
ws,
|
||||
tipPickupStatus.exchange_url,
|
||||
@ -93,11 +96,11 @@ export async function getTipStatus(
|
||||
);
|
||||
|
||||
tipRecord = {
|
||||
tipId,
|
||||
walletTipId: walletTipId,
|
||||
acceptedTimestamp: undefined,
|
||||
rejectedTimestamp: undefined,
|
||||
amount,
|
||||
deadline: tipPickupStatus.stamp_expire,
|
||||
deadline: tipPickupStatus.expiration,
|
||||
exchangeUrl: tipPickupStatus.exchange_url,
|
||||
merchantBaseUrl: res.merchantBaseUrl,
|
||||
nextUrl: undefined,
|
||||
@ -117,18 +120,13 @@ export async function getTipStatus(
|
||||
await ws.db.put(Stores.tips, tipRecord);
|
||||
}
|
||||
|
||||
const tipStatus: TipStatus = {
|
||||
const tipStatus: PrepareTipResult = {
|
||||
accepted: !!tipRecord && !!tipRecord.acceptedTimestamp,
|
||||
amount: Amounts.parseOrThrow(tipPickupStatus.amount),
|
||||
amountLeft: Amounts.parseOrThrow(tipPickupStatus.amount_left),
|
||||
exchangeUrl: tipPickupStatus.exchange_url,
|
||||
nextUrl: tipPickupStatus.extra.next_url,
|
||||
merchantOrigin: merchantOrigin,
|
||||
merchantTipId: res.merchantTipId,
|
||||
expirationTimestamp: tipPickupStatus.stamp_expire,
|
||||
timestamp: tipPickupStatus.stamp_created,
|
||||
totalFees: tipRecord.totalFees,
|
||||
tipId: tipRecord.tipId,
|
||||
amount: Amounts.stringify(tipPickupStatus.tip_amount),
|
||||
exchangeBaseUrl: tipPickupStatus.exchange_url,
|
||||
expirationTimestamp: tipPickupStatus.expiration,
|
||||
totalFees: Amounts.stringify(tipRecord.totalFees),
|
||||
walletTipId: tipRecord.walletTipId,
|
||||
};
|
||||
|
||||
return tipStatus;
|
||||
@ -152,7 +150,9 @@ async function incrementTipRetry(
|
||||
t.lastError = err;
|
||||
await tx.put(Stores.tips, t);
|
||||
});
|
||||
ws.notify({ type: NotificationType.TipOperationError });
|
||||
if (err) {
|
||||
ws.notify({ type: NotificationType.TipOperationError, error: err });
|
||||
}
|
||||
}
|
||||
|
||||
export async function processTip(
|
||||
@ -225,15 +225,8 @@ async function processTipImpl(
|
||||
}
|
||||
|
||||
tipRecord = await ws.db.get(Stores.tips, tipId);
|
||||
if (!tipRecord) {
|
||||
throw Error("tip not in database");
|
||||
}
|
||||
|
||||
if (!tipRecord.planchets) {
|
||||
throw Error("invariant violated");
|
||||
}
|
||||
|
||||
logger.trace("got planchets for tip!");
|
||||
checkDbInvariant(!!tipRecord, "tip record should be in database");
|
||||
checkDbInvariant(!!tipRecord.planchets, "tip record should have planchets");
|
||||
|
||||
// Planchets in the form that the merchant expects
|
||||
const planchetsDetail: TipPlanchetDetail[] = tipRecord.planchets.map((p) => ({
|
||||
@ -241,23 +234,17 @@ async function processTipImpl(
|
||||
denom_pub_hash: p.denomPubHash,
|
||||
}));
|
||||
|
||||
let merchantResp;
|
||||
const tipStatusUrl = new URL(
|
||||
`/tips/${tipRecord.merchantTipId}/pickup`,
|
||||
tipRecord.merchantBaseUrl,
|
||||
);
|
||||
|
||||
const tipStatusUrl = new URL("tip-pickup", tipRecord.merchantBaseUrl);
|
||||
|
||||
try {
|
||||
const req = { planchets: planchetsDetail, tip_id: tipRecord.merchantTipId };
|
||||
merchantResp = await ws.http.postJson(tipStatusUrl.href, req);
|
||||
if (merchantResp.status !== 200) {
|
||||
throw Error(`unexpected status ${merchantResp.status} for tip-pickup`);
|
||||
}
|
||||
logger.trace("got merchant resp:", merchantResp);
|
||||
} catch (e) {
|
||||
logger.warn("tipping failed", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
const response = codecForTipResponse().decode(await merchantResp.json());
|
||||
const req = { planchets: planchetsDetail };
|
||||
const merchantResp = await ws.http.postJson(tipStatusUrl.href, req);
|
||||
const response = await readSuccessResponseJsonOrThrow(
|
||||
merchantResp,
|
||||
codecForTipResponse(),
|
||||
);
|
||||
|
||||
if (response.reserve_sigs.length !== tipRecord.planchets.length) {
|
||||
throw Error("number of tip responses does not match requested planchets");
|
||||
@ -293,7 +280,7 @@ async function processTipImpl(
|
||||
exchangeBaseUrl: tipRecord.exchangeUrl,
|
||||
source: {
|
||||
type: WithdrawalSourceType.Tip,
|
||||
tipId: tipRecord.tipId,
|
||||
tipId: tipRecord.walletTipId,
|
||||
},
|
||||
timestampStart: getTimestampNow(),
|
||||
withdrawalGroupId: withdrawalGroupId,
|
||||
|
@ -986,7 +986,7 @@ export interface TipRecord {
|
||||
/**
|
||||
* Tip ID chosen by the wallet.
|
||||
*/
|
||||
tipId: string;
|
||||
walletTipId: string;
|
||||
|
||||
/**
|
||||
* The merchant's identifier for this tip.
|
||||
@ -1760,7 +1760,7 @@ class ReserveHistoryStore extends Store<ReserveHistoryRecord> {
|
||||
|
||||
class TipsStore extends Store<TipRecord> {
|
||||
constructor() {
|
||||
super("tips", { keyPath: "tipId" });
|
||||
super("tips", { keyPath: "walletTipId" });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,6 +186,7 @@ export interface ProposalOperationErrorNotification {
|
||||
|
||||
export interface TipOperationErrorNotification {
|
||||
type: NotificationType.TipOperationError;
|
||||
error: TalerErrorDetails;
|
||||
}
|
||||
|
||||
export interface WithdrawOperationErrorNotification {
|
||||
|
@ -773,17 +773,11 @@ export class WithdrawOperationStatusResponse {
|
||||
* Response from the merchant.
|
||||
*/
|
||||
export class TipPickupGetResponse {
|
||||
extra: any;
|
||||
|
||||
amount: string;
|
||||
|
||||
amount_left: string;
|
||||
tip_amount: string;
|
||||
|
||||
exchange_url: string;
|
||||
|
||||
stamp_expire: Timestamp;
|
||||
|
||||
stamp_created: Timestamp;
|
||||
expiration: Timestamp;
|
||||
}
|
||||
|
||||
export class WithdrawResponse {
|
||||
@ -1261,12 +1255,9 @@ export const codecForWithdrawOperationStatusResponse = (): Codec<
|
||||
|
||||
export const codecForTipPickupGetResponse = (): Codec<TipPickupGetResponse> =>
|
||||
buildCodecForObject<TipPickupGetResponse>()
|
||||
.property("extra", codecForAny())
|
||||
.property("amount", codecForString())
|
||||
.property("amount_left", codecForString())
|
||||
.property("tip_amount", codecForString())
|
||||
.property("exchange_url", codecForString())
|
||||
.property("stamp_expire", codecForTimestamp)
|
||||
.property("stamp_created", codecForTimestamp)
|
||||
.property("expiration", codecForTimestamp)
|
||||
.build("TipPickupGetResponse");
|
||||
|
||||
export const codecForRecoupConfirmation = (): Codec<RecoupConfirmation> =>
|
||||
|
@ -38,7 +38,7 @@ import {
|
||||
ExchangeWireInfo,
|
||||
DenominationSelectionInfo,
|
||||
} from "./dbTypes";
|
||||
import { Timestamp } from "../util/time";
|
||||
import { Timestamp, codecForTimestamp } from "../util/time";
|
||||
import {
|
||||
buildCodecForObject,
|
||||
codecForString,
|
||||
@ -348,23 +348,33 @@ export class ReturnCoinsRequest {
|
||||
static checked: (obj: any) => ReturnCoinsRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Status of processing a tip.
|
||||
*/
|
||||
export interface TipStatus {
|
||||
export interface PrepareTipResult {
|
||||
/**
|
||||
* Unique ID for the tip assigned by the wallet.
|
||||
* Typically different from the merchant-generated tip ID.
|
||||
*/
|
||||
walletTipId: string;
|
||||
|
||||
/**
|
||||
* Has the tip already been accepted?
|
||||
*/
|
||||
accepted: boolean;
|
||||
amount: AmountJson;
|
||||
amountLeft: AmountJson;
|
||||
nextUrl: string;
|
||||
exchangeUrl: string;
|
||||
tipId: string;
|
||||
merchantTipId: string;
|
||||
merchantOrigin: string;
|
||||
amount: AmountString;
|
||||
totalFees: AmountString;
|
||||
exchangeBaseUrl: string;
|
||||
expirationTimestamp: Timestamp;
|
||||
timestamp: Timestamp;
|
||||
totalFees: AmountJson;
|
||||
}
|
||||
|
||||
export const codecForPrepareTipResult = (): Codec<PrepareTipResult> =>
|
||||
buildCodecForObject<PrepareTipResult>()
|
||||
.property("accepted", codecForBoolean())
|
||||
.property("amount", codecForAmountString())
|
||||
.property("totalFees", codecForAmountString())
|
||||
.property("exchangeBaseUrl", codecForString())
|
||||
.property("expirationTimestamp", codecForTimestamp)
|
||||
.property("walletTipId", codecForString())
|
||||
.build("PrepareTipResult");
|
||||
|
||||
export interface BenchmarkResult {
|
||||
time: { [s: string]: number };
|
||||
repetitions: number;
|
||||
@ -903,3 +913,21 @@ export const codecForForceRefreshRequest = (): Codec<ForceRefreshRequest> =>
|
||||
buildCodecForObject<ForceRefreshRequest>()
|
||||
.property("coinPubList", codecForList(codecForString()))
|
||||
.build("ForceRefreshRequest");
|
||||
|
||||
export interface PrepareTipRequest {
|
||||
talerTipUri: string;
|
||||
}
|
||||
|
||||
export const codecForPrepareTipRequest = (): Codec<PrepareTipRequest> =>
|
||||
buildCodecForObject<PrepareTipRequest>()
|
||||
.property("talerTipUri", codecForString())
|
||||
.build("PrepareTipRequest");
|
||||
|
||||
export interface AcceptTipRequest {
|
||||
walletTipId: string;
|
||||
}
|
||||
|
||||
export const codecForAcceptTipRequest = (): Codec<AcceptTipRequest> =>
|
||||
buildCodecForObject<AcceptTipRequest>()
|
||||
.property("walletTipId", codecForString())
|
||||
.build("AcceptTipRequest");
|
||||
|
@ -59,7 +59,6 @@ import {
|
||||
ConfirmPayResult,
|
||||
ReturnCoinsRequest,
|
||||
SenderWireInfos,
|
||||
TipStatus,
|
||||
PreparePayResult,
|
||||
AcceptWithdrawalResponse,
|
||||
PurchaseDetails,
|
||||
@ -93,6 +92,9 @@ import {
|
||||
codecForSetCoinSuspendedRequest,
|
||||
codecForForceExchangeUpdateRequest,
|
||||
codecForForceRefreshRequest,
|
||||
PrepareTipResult,
|
||||
codecForPrepareTipRequest,
|
||||
codecForAcceptTipRequest,
|
||||
} from "./types/walletTypes";
|
||||
import { Logger } from "./util/logging";
|
||||
|
||||
@ -121,7 +123,7 @@ import {
|
||||
import { processWithdrawGroup } from "./operations/withdraw";
|
||||
import { getPendingOperations } from "./operations/pending";
|
||||
import { getBalances } from "./operations/balance";
|
||||
import { acceptTip, getTipStatus, processTip } from "./operations/tip";
|
||||
import { acceptTip, prepareTip, processTip } from "./operations/tip";
|
||||
import { TimerGroup } from "./util/timer";
|
||||
import { AsyncCondition } from "./util/promiseUtils";
|
||||
import { AsyncOpMemoSingle } from "./util/asyncMemo";
|
||||
@ -769,8 +771,8 @@ export class Wallet {
|
||||
}
|
||||
}
|
||||
|
||||
async getTipStatus(talerTipUri: string): Promise<TipStatus> {
|
||||
return getTipStatus(this.ws, talerTipUri);
|
||||
async prepareTip(talerTipUri: string): Promise<PrepareTipResult> {
|
||||
return prepareTip(this.ws, talerTipUri);
|
||||
}
|
||||
|
||||
async abortFailedPayment(contractTermsHash: string): Promise<void> {
|
||||
@ -1096,6 +1098,15 @@ export class Wallet {
|
||||
refreshGroupId,
|
||||
};
|
||||
}
|
||||
case "prepareTip": {
|
||||
const req = codecForPrepareTipRequest().decode(payload);
|
||||
return await this.prepareTip(req.talerTipUri);
|
||||
}
|
||||
case "acceptTip": {
|
||||
const req = codecForAcceptTipRequest().decode(payload);
|
||||
await this.acceptTip(req.walletTipId);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
throw OperationFailedError.fromCode(
|
||||
TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN,
|
||||
|
@ -22,12 +22,8 @@
|
||||
* Imports.
|
||||
*/
|
||||
import {
|
||||
AmountJson,
|
||||
ConfirmPayResult,
|
||||
BalancesResponse,
|
||||
PurchaseDetails,
|
||||
TipStatus,
|
||||
BenchmarkResult,
|
||||
PreparePayResult,
|
||||
AcceptWithdrawalResponse,
|
||||
WalletDiagnostics,
|
||||
|
Loading…
Reference in New Issue
Block a user