aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2019-08-28 02:49:27 +0200
committerFlorian Dold <florian.dold@gmail.com>2019-08-28 02:49:27 +0200
commit1390175a9afc53948dd1d6f8a2f88e51c1bf53cc (patch)
tree1e65581f11354ec61532dbbf3174e9bd26b515c4 /src
parent70c0a557f9c89a2a0006f74bd8b361b62660bde2 (diff)
rudimentary taler://withdraw support
Diffstat (limited to 'src')
-rw-r--r--src/crypto/cryptoApi-test.ts2
-rw-r--r--src/crypto/synchronousWorker.ts10
-rw-r--r--src/dbTypes.ts18
-rw-r--r--src/headless/bank.ts4
-rw-r--r--src/headless/helpers.ts74
-rw-r--r--src/headless/taler-wallet-cli.ts57
-rw-r--r--src/talerTypes.ts36
-rw-r--r--src/taleruri-test.ts12
-rw-r--r--src/taleruri.ts33
-rw-r--r--src/wallet.ts209
-rw-r--r--src/walletTypes.ts26
11 files changed, 399 insertions, 82 deletions
diff --git a/src/crypto/cryptoApi-test.ts b/src/crypto/cryptoApi-test.ts
index 48231e5ff..39f46c5c3 100644
--- a/src/crypto/cryptoApi-test.ts
+++ b/src/crypto/cryptoApi-test.ts
@@ -96,6 +96,8 @@ test("precoin creation", async t => {
reserve_pub: pub,
timestamp_confirmed: 0,
timestamp_depleted: 0,
+ timestamp_reserve_info_posted: 0,
+ exchangeWire: "payto://foo"
};
const precoin = await crypto.createPreCoin(denomValid1, r);
diff --git a/src/crypto/synchronousWorker.ts b/src/crypto/synchronousWorker.ts
index b697c8e16..41ebee4f3 100644
--- a/src/crypto/synchronousWorker.ts
+++ b/src/crypto/synchronousWorker.ts
@@ -93,13 +93,19 @@ export class SynchronousCryptoWorker {
return;
}
+ let result: any;
try {
- const result = (impl as any)[operation](...args);
- this.dispatchMessage({ result, id });
+ result = (impl as any)[operation](...args);
} catch (e) {
console.log("error during operation", e);
return;
}
+
+ try {
+ setImmediate(() => this.dispatchMessage({ result, id }));
+ } catch (e) {
+ console.log("got error during dispatch", e);
+ }
}
/**
diff --git a/src/dbTypes.ts b/src/dbTypes.ts
index 55b2ddbe3..d9fd2e9d9 100644
--- a/src/dbTypes.ts
+++ b/src/dbTypes.ts
@@ -81,6 +81,16 @@ export interface ReserveRecord {
*/
timestamp_depleted: number;
+
+ /**
+ * Time when the information about this reserve was posted to the bank.
+ *
+ * Only applies if bankWithdrawStatusUrl is defined.
+ *
+ * Set to 0 if that hasn't happened yet.
+ */
+ timestamp_reserve_info_posted: number;
+
/**
* Time when the reserve was confirmed.
*
@@ -117,6 +127,14 @@ export interface ReserveRecord {
* transfered funds for this reserve.
*/
senderWire?: string;
+
+ /**
+ * Wire information (as payto URI) for the exchange, specifically
+ * the account that was transferred to when creating the reserve.
+ */
+ exchangeWire: string;
+
+ bankWithdrawStatusUrl?: string;
}
diff --git a/src/headless/bank.ts b/src/headless/bank.ts
index 7d8db9fe5..f35021003 100644
--- a/src/headless/bank.ts
+++ b/src/headless/bank.ts
@@ -51,7 +51,7 @@ export class Bank {
reservePub: string,
exchangePaytoUri: string,
) {
- const reqUrl = new URI("taler/withdraw")
+ const reqUrl = new URI("api/withdraw-headless")
.absoluteTo(this.bankBaseUrl)
.href();
@@ -80,7 +80,7 @@ export class Bank {
}
async registerRandomUser(): Promise<BankUser> {
- const reqUrl = new URI("register").absoluteTo(this.bankBaseUrl).href();
+ const reqUrl = new URI("api/register").absoluteTo(this.bankBaseUrl).href();
const randId = makeId(8);
const bankUser: BankUser = {
username: `testuser-${randId}`,
diff --git a/src/headless/helpers.ts b/src/headless/helpers.ts
index 9652c630f..a86b26738 100644
--- a/src/headless/helpers.ts
+++ b/src/headless/helpers.ts
@@ -54,17 +54,21 @@ class ConsoleBadge implements Badge {
export class NodeHttpLib implements HttpRequestLibrary {
async get(url: string): Promise<import("../http").HttpResponse> {
enableTracing && console.log("making GET request to", url);
- const resp = await Axios({
- method: "get",
- url: url,
- responseType: "json",
- });
- enableTracing && console.log("got response", resp.data);
- enableTracing && console.log("resp type", typeof resp.data);
- return {
- responseJson: resp.data,
- status: resp.status,
- };
+ try {
+ const resp = await Axios({
+ method: "get",
+ url: url,
+ responseType: "json",
+ });
+ enableTracing && console.log("got response", resp.data);
+ enableTracing && console.log("resp type", typeof resp.data);
+ return {
+ responseJson: resp.data,
+ status: resp.status,
+ };
+ } catch (e) {
+ throw e;
+ }
}
async postJson(
@@ -72,37 +76,22 @@ export class NodeHttpLib implements HttpRequestLibrary {
body: any,
): Promise<import("../http").HttpResponse> {
enableTracing && console.log("making POST request to", url);
- const resp = await Axios({
- method: "post",
- url: url,
- responseType: "json",
- data: body,
- });
- enableTracing && console.log("got response", resp.data);
- enableTracing && console.log("resp type", typeof resp.data);
- return {
- responseJson: resp.data,
- status: resp.status,
- };
- }
-
- async postForm(
- url: string,
- form: any,
- ): Promise<import("../http").HttpResponse> {
- enableTracing && console.log("making POST request to", url);
- const resp = await Axios({
- method: "post",
- url: url,
- data: querystring.stringify(form),
- responseType: "json",
- });
- enableTracing && console.log("got response", resp.data);
- enableTracing && console.log("resp type", typeof resp.data);
- return {
- responseJson: resp.data,
- status: resp.status,
- };
+ try {
+ const resp = await Axios({
+ method: "post",
+ url: url,
+ responseType: "json",
+ data: body,
+ });
+ enableTracing && console.log("got response", resp.data);
+ enableTracing && console.log("resp type", typeof resp.data);
+ return {
+ responseJson: resp.data,
+ status: resp.status,
+ };
+ } catch (e) {
+ throw e;
+ }
}
}
@@ -221,6 +210,7 @@ export async function withdrawTestBalance(
const reserveResponse = await myWallet.createReserve({
amount: amounts.parseOrThrow(amount),
exchange: exchangeBaseUrl,
+ exchangeWire: "payto://unknown",
});
const bank = new Bank(bankBaseUrl);
diff --git a/src/headless/taler-wallet-cli.ts b/src/headless/taler-wallet-cli.ts
index 4a1f5d91e..65b2a0297 100644
--- a/src/headless/taler-wallet-cli.ts
+++ b/src/headless/taler-wallet-cli.ts
@@ -103,15 +103,14 @@ program
console.log("created new order with order ID", orderResp.orderId);
const checkPayResp = await merchantBackend.checkPayment(orderResp.orderId);
const qrcode = qrcodeGenerator(0, "M");
- const contractUrl = checkPayResp.contract_url;
- if (typeof contractUrl !== "string") {
- console.error("fata: no contract url received from backend");
+ const talerPayUri = checkPayResp.taler_pay_uri;
+ if (!talerPayUri) {
+ console.error("fatal: no taler pay URI received from backend");
process.exit(1);
return;
}
- const url = "talerpay:" + querystring.escape(contractUrl);
- console.log("contract url:", url);
- qrcode.addData(url);
+ console.log("taler pay URI:", talerPayUri);
+ qrcode.addData(talerPayUri);
qrcode.make();
console.log(qrcode.createASCII());
console.log("waiting for payment ...");
@@ -128,6 +127,45 @@ program
});
program
+ .command("withdraw-url <withdraw-url>")
+ .action(async (withdrawUrl, cmdObj) => {
+ applyVerbose(program.verbose);
+ console.log("withdrawing", withdrawUrl);
+ const wallet = await getDefaultNodeWallet({
+ persistentStoragePath: walletDbPath,
+ });
+
+ const withdrawInfo = await wallet.downloadWithdrawInfo(withdrawUrl);
+
+ console.log("withdraw info", withdrawInfo);
+
+ const selectedExchange = withdrawInfo.suggestedExchange;
+ if (!selectedExchange) {
+ console.error("no suggested exchange!");
+ process.exit(1);
+ return;
+ }
+
+ const {
+ reservePub,
+ confirmTransferUrl,
+ } = await wallet.createReserveFromWithdrawUrl(
+ withdrawUrl,
+ selectedExchange,
+ );
+
+ if (confirmTransferUrl) {
+ console.log("please confirm the transfer at", confirmTransferUrl);
+ }
+
+ await wallet.processReserve(reservePub);
+
+ console.log("finished withdrawing");
+
+ wallet.stop();
+ });
+
+program
.command("pay-url <pay-url>")
.option("-y, --yes", "automatically answer yes to prompts")
.action(async (payUrl, cmdObj) => {
@@ -153,6 +191,11 @@ program
process.exit(0);
return;
}
+ if (result.status === "session-replayed") {
+ console.log("already paid! (replayed in different session)");
+ process.exit(0);
+ return;
+ }
if (result.status === "payment-possible") {
console.log("paying ...");
} else {
@@ -179,7 +222,7 @@ program
if (pay) {
const payRes = await wallet.confirmPay(result.proposalId!, undefined);
- console.log("paid!");
+ console.log("paid!");
} else {
console.log("not paying");
}
diff --git a/src/talerTypes.ts b/src/talerTypes.ts
index 9176daf77..360be3338 100644
--- a/src/talerTypes.ts
+++ b/src/talerTypes.ts
@@ -924,6 +924,9 @@ export class CheckPaymentResponse {
contract_terms: ContractTerms | undefined;
@Checkable.Optional(Checkable.String())
+ taler_pay_uri: string | undefined;
+
+ @Checkable.Optional(Checkable.String())
contract_url: string | undefined;
/**
@@ -931,4 +934,37 @@ export class CheckPaymentResponse {
* member.
*/
static checked: (obj: any) => CheckPaymentResponse;
+}
+
+/**
+ * Response from the bank.
+ */
+@Checkable.Class({extra: true})
+export class WithdrawOperationStatusResponse {
+ @Checkable.Boolean()
+ selection_done: boolean;
+
+ @Checkable.Boolean()
+ transfer_done: boolean;
+
+ @Checkable.String()
+ amount: string;
+
+ @Checkable.Optional(Checkable.String())
+ sender_wire?: string;
+
+ @Checkable.Optional(Checkable.String())
+ suggested_exchange?: string;
+
+ @Checkable.Optional(Checkable.String())
+ confirm_transfer_url?: string;
+
+ @Checkable.List(Checkable.String())
+ wire_types: string[];
+
+ /**
+ * Verify that a value matches the schema of this class and convert it into a
+ * member.
+ */
+ static checked: (obj: any) => WithdrawOperationStatusResponse;
} \ No newline at end of file
diff --git a/src/taleruri-test.ts b/src/taleruri-test.ts
index a9fa0b1e3..27cd7d18b 100644
--- a/src/taleruri-test.ts
+++ b/src/taleruri-test.ts
@@ -15,7 +15,7 @@
*/
import test from "ava";
-import { parsePayUri } from "./taleruri";
+import { parsePayUri, parseWithdrawUri } from "./taleruri";
test("taler pay url parsing: http(s)", (t) => {
const url1 = "https://example.com/bar?spam=eggs";
@@ -77,3 +77,13 @@ test("taler pay url parsing: trailing parts", (t) => {
t.is(r1.downloadUrl, "https://example.com/public/proposal?instance=default&order_id=myorder");
t.is(r1.sessionId, "mysession");
});
+
+test("taler withdraw uri parsing", (t) => {
+ const url1 = "taler://withdraw/bank.example.com/-/12345";
+ const r1 = parseWithdrawUri(url1);
+ if (!r1) {
+ t.fail();
+ return;
+ }
+ t.is(r1.statusUrl, "https://bank.example.com/api/withdraw-operation/12345");
+}); \ No newline at end of file
diff --git a/src/taleruri.ts b/src/taleruri.ts
index fa3b09c31..fa305d1de 100644
--- a/src/taleruri.ts
+++ b/src/taleruri.ts
@@ -15,12 +15,39 @@
*/
import URI = require("urijs");
+import { string } from "prop-types";
export interface PayUriResult {
downloadUrl: string;
sessionId?: string;
}
+export interface WithdrawUriResult {
+ statusUrl: string;
+}
+
+export function parseWithdrawUri(s: string): WithdrawUriResult | undefined {
+ const parsedUri = new URI(s);
+ if (parsedUri.scheme() !== "taler") {
+ return undefined;
+ }
+ if (parsedUri.authority() != "withdraw") {
+ return undefined;
+ }
+
+ let [host, path, withdrawId] = parsedUri.segmentCoded();
+
+ if (path === "-") {
+ path = "/api/withdraw-operation";
+ }
+
+ return {
+ statusUrl: new URI({ protocol: "https", hostname: host, path: path })
+ .segmentCoded(withdrawId)
+ .href(),
+ };
+}
+
export function parsePayUri(s: string): PayUriResult | undefined {
const parsedUri = new URI(s);
if (parsedUri.scheme() === "http" || parsedUri.scheme() === "https") {
@@ -68,10 +95,12 @@ export function parsePayUri(s: string): PayUriResult | undefined {
const downloadUrl = new URI(
"https://" + host + "/" + decodeURIComponent(maybePath),
- ).addQuery({ instance: maybeInstance, order_id: orderId }).href();
+ )
+ .addQuery({ instance: maybeInstance, order_id: orderId })
+ .href();
return {
downloadUrl,
sessionId: maybeSessionid,
- }
+ };
}
diff --git a/src/wallet.ts b/src/wallet.ts
index faced994a..b6a9361c1 100644
--- a/src/wallet.ts
+++ b/src/wallet.ts
@@ -81,6 +81,7 @@ import {
TipPlanchetDetail,
TipResponse,
TipToken,
+ WithdrawOperationStatusResponse,
} from "./talerTypes";
import {
Badge,
@@ -103,9 +104,10 @@ import {
WalletBalance,
WalletBalanceEntry,
PreparePayResult,
+ DownloadedWithdrawInfo,
} from "./walletTypes";
import { openPromise } from "./promiseUtils";
-import Axios from "axios";
+import { parsePayUri, parseWithdrawUri } from "./taleruri";
interface SpeculativePayData {
payCoinInfo: PayCoinInfo;
@@ -183,12 +185,13 @@ export function getTotalRefreshCost(
...withdrawDenoms.map(d => d.value),
).amount;
const totalCost = Amounts.sub(amountLeft, resultingAmount).amount;
- Wallet.enableTracing && console.log(
- "total refresh cost for",
- amountToPretty(amountLeft),
- "is",
- amountToPretty(totalCost),
- );
+ Wallet.enableTracing &&
+ console.log(
+ "total refresh cost for",
+ amountToPretty(amountLeft),
+ "is",
+ amountToPretty(totalCost),
+ );
return totalCost;
}
@@ -255,7 +258,8 @@ export function selectPayCoins(
const depositFeeToCover = Amounts.sub(accDepositFee, depositFeeLimit)
.amount;
leftAmount = Amounts.sub(leftAmount, depositFeeToCover).amount;
- Wallet.enableTracing && console.log("deposit fee to cover", amountToPretty(depositFeeToCover));
+ Wallet.enableTracing &&
+ console.log("deposit fee to cover", amountToPretty(depositFeeToCover));
let totalFees: AmountJson = Amounts.getZero(currency);
if (coversAmountWithFee && !isBelowFee) {
@@ -714,17 +718,22 @@ export class Wallet {
}
async preparePay(url: string): Promise<PreparePayResult> {
- const talerpayPrefix = "talerpay:";
- let downloadSessionId: string | undefined;
- if (url.startsWith(talerpayPrefix)) {
- let [p1, p2] = url.substring(talerpayPrefix.length).split(";");
- url = decodeURIComponent(p1);
- downloadSessionId = p2;
+ const uriResult = parsePayUri(url);
+
+ if (!uriResult) {
+ return {
+ status: "error",
+ error: "URI not supported",
+ };
}
+
let proposalId: number;
let checkResult: CheckPayResult;
try {
- proposalId = await this.downloadProposal(url, downloadSessionId);
+ proposalId = await this.downloadProposal(
+ uriResult.downloadUrl,
+ uriResult.sessionId,
+ );
checkResult = await this.checkPay(proposalId);
} catch (e) {
return {
@@ -736,6 +745,27 @@ export class Wallet {
if (!proposal) {
throw Error("could not get proposal");
}
+
+ console.log("proposal", proposal);
+
+ if (uriResult.sessionId) {
+ const existingPayment = await this.q().getIndexed(
+ Stores.purchases.fulfillmentUrlIndex,
+ proposal.contractTerms.fulfillment_url,
+ );
+ if (existingPayment) {
+ console.log("existing payment", existingPayment);
+ await this.submitPay(
+ existingPayment.contractTermsHash,
+ uriResult.sessionId,
+ );
+ return {
+ status: "session-replayed",
+ contractTerms: existingPayment.contractTerms,
+ };
+ }
+ }
+
if (checkResult.status === "paid") {
return {
status: "paid",
@@ -1139,21 +1169,78 @@ export class Wallet {
const op = openPromise<void>();
const processReserveInternal = async (retryDelayMs: number = 250) => {
+ let isHardError = false;
+ // By default, do random, exponential backoff truncated at 3 minutes.
+ // Sometimes though, we want to try again faster.
+ let maxTimeout = 3000 * 60;
try {
- const reserve = await this.updateReserve(reservePub);
- await this.depleteReserve(reserve);
+ const reserve = await this.q().get<ReserveRecord>(
+ Stores.reserves,
+ reservePub,
+ );
+ if (!reserve) {
+ isHardError = true;
+ throw Error("reserve not in db");
+ }
+
+ if (reserve.timestamp_confirmed === 0) {
+ const bankStatusUrl = reserve.bankWithdrawStatusUrl;
+ if (!bankStatusUrl) {
+ isHardError = true;
+ throw Error(
+ "reserve not confirmed yet, and no status URL available.",
+ );
+ }
+ maxTimeout = 2000;
+ const now = new Date().getTime();
+ let status;
+ try {
+ const statusResp = await this.http.get(bankStatusUrl);
+ status = WithdrawOperationStatusResponse.checked(
+ statusResp.responseJson,
+ );
+ } catch (e) {
+ console.log("bank error response", e);
+ throw e;
+ }
+
+ if (status.transfer_done) {
+ await this.q().mutate(Stores.reserves, reservePub, r => {
+ r.timestamp_confirmed = now;
+ return r;
+ });
+ } else if (reserve.timestamp_reserve_info_posted === 0) {
+ try {
+ if (!status.selection_done) {
+ const bankResp = await this.http.postJson(bankStatusUrl, {
+ reserve_pub: reservePub,
+ selected_exchange: reserve.exchangeWire,
+ });
+ }
+ } catch (e) {
+ console.log("bank error response", e);
+ throw e;
+ }
+ await this.q().mutate(Stores.reserves, reservePub, r => {
+ r.timestamp_reserve_info_posted = now;
+ return r;
+ });
+ throw Error("waiting for reserve to be confirmed");
+ }
+ }
+
+ const updatedReserve = await this.updateReserve(reservePub);
+ await this.depleteReserve(updatedReserve);
op.resolve();
} catch (e) {
- // random, exponential backoff truncated at 3 minutes
+ if (isHardError) {
+ op.reject(e);
+ }
const nextDelay = Math.min(
2 * retryDelayMs + retryDelayMs * Math.random(),
- 3000 * 60,
+ maxTimeout,
);
- Wallet.enableTracing &&
- console.warn(
- `Failed to deplete reserve, trying again in ${retryDelayMs} ms`,
- );
- Wallet.enableTracing && console.info("Cause for retry was:", e);
+
this.timerGroup.after(retryDelayMs, () =>
processReserveInternal(nextDelay),
);
@@ -1346,7 +1433,10 @@ export class Wallet {
reserve_pub: keypair.pub,
senderWire: req.senderWire,
timestamp_confirmed: 0,
+ timestamp_reserve_info_posted: 0,
timestamp_depleted: 0,
+ bankWithdrawStatusUrl: req.bankWithdrawStatusUrl,
+ exchangeWire: req.exchangeWire,
};
const senderWire = req.senderWire;
@@ -1387,6 +1477,10 @@ export class Wallet {
.put(Stores.reserves, reserveRecord)
.finish();
+ if (req.bankWithdrawStatusUrl) {
+ this.processReserve(keypair.pub);
+ }
+
const r: CreateReserveResponse = {
exchange: canonExchange,
reservePub: keypair.pub,
@@ -1513,6 +1607,7 @@ export class Wallet {
}
const preCoin = await this.cryptoApi.createPreCoin(denom, reserve);
+
// This will fail and throw an exception if the remaining amount in the
// reserve is too low to create a pre-coin.
try {
@@ -1520,6 +1615,7 @@ export class Wallet {
.put(Stores.precoins, preCoin)
.mutate(Stores.reserves, reserve.reserve_pub, mutateReserve)
.finish();
+ console.log("created precoin", preCoin.coinPub);
} catch (e) {
console.log("can't create pre-coin:", e.name, e.message);
return;
@@ -1542,6 +1638,11 @@ export class Wallet {
if (!reserve) {
throw Error("reserve not in db");
}
+
+ if (reserve.timestamp_confirmed === 0) {
+ throw Error("");
+ }
+
const reqUrl = new URI("reserve/status").absoluteTo(
reserve.exchange_base_url,
);
@@ -2462,7 +2563,14 @@ export class Wallet {
refreshSession.exchangeBaseUrl,
);
Wallet.enableTracing && console.log("reveal request:", req);
- const resp = await this.http.postJson(reqUrl.href(), req);
+
+ let resp;
+ try {
+ resp = await this.http.postJson(reqUrl.href(), req);
+ } catch (e) {
+ console.error("got error during /refresh/reveal request");
+ return;
+ }
Wallet.enableTracing && console.log("session:", refreshSession);
Wallet.enableTracing && console.log("reveal response:", resp);
@@ -3427,6 +3535,57 @@ export class Wallet {
// strategy to test it.
}
+ async downloadWithdrawInfo(
+ talerWithdrawUri: string,
+ ): Promise<DownloadedWithdrawInfo> {
+ const uriResult = parseWithdrawUri(talerWithdrawUri);
+ if (!uriResult) {
+ throw Error("can't parse URL");
+ }
+ const resp = await this.http.get(uriResult.statusUrl);
+ console.log("resp:", resp.responseJson);
+ const status = WithdrawOperationStatusResponse.checked(resp.responseJson);
+ return {
+ amount: Amounts.parseOrThrow(status.amount),
+ confirmTransferUrl: status.confirm_transfer_url,
+ extractedStatusUrl: uriResult.statusUrl,
+ selectionDone: status.selection_done,
+ senderWire: status.sender_wire,
+ suggestedExchange: status.suggested_exchange,
+ transferDone: status.transfer_done,
+ wireTypes: status.wire_types,
+ };
+ }
+
+ async createReserveFromWithdrawUrl(
+ talerWithdrawUri: string,
+ selectedExchange: string,
+ ): Promise<{ reservePub: string; confirmTransferUrl?: string }> {
+ const withdrawInfo = await this.downloadWithdrawInfo(talerWithdrawUri);
+ const exchangeWire = await this.getExchangePaytoUri(
+ selectedExchange,
+ withdrawInfo.wireTypes,
+ );
+ const reserve = await this.createReserve({
+ amount: withdrawInfo.amount,
+ bankWithdrawStatusUrl: withdrawInfo.extractedStatusUrl,
+ exchange: selectedExchange,
+ senderWire: withdrawInfo.senderWire,
+ exchangeWire: exchangeWire,
+ });
+ return {
+ reservePub: reserve.reservePub,
+ confirmTransferUrl: withdrawInfo.confirmTransferUrl,
+ };
+ }
+
+ /**
+ * Reset the retry timeouts for ongoing operations.
+ */
+ resetRetryTimeouts(): void {
+ // FIXME: implement
+ }
+
clearNotification(): void {
this.badge.clearNotification();
}
diff --git a/src/walletTypes.ts b/src/walletTypes.ts
index a74f81136..abe9f2712 100644
--- a/src/walletTypes.ts
+++ b/src/walletTypes.ts
@@ -325,6 +325,13 @@ export class CreateReserveRequest {
exchange: string;
/**
+ * Payto URI that identifies the exchange's account that the funds
+ * for this reserve go into.
+ */
+ @Checkable.String()
+ exchangeWire: string;
+
+ /**
* Wire details (as a payto URI) for the bank account that sent the funds to
* the exchange.
*/
@@ -332,6 +339,12 @@ export class CreateReserveRequest {
senderWire?: string;
/**
+ * URL to fetch the withdraw status from the bank.
+ */
+ @Checkable.Optional(Checkable.String())
+ bankWithdrawStatusUrl?: string;
+
+ /**
* Verify that a value matches the schema of this class and convert it into a
* member.
*/
@@ -474,9 +487,20 @@ export interface NextUrlResult {
}
export interface PreparePayResult {
- status: "paid" | "insufficient-balance" | "payment-possible" | "error";
+ status: "paid" | "session-replayed" | "insufficient-balance" | "payment-possible" | "error";
contractTerms?: ContractTerms;
error?: string;
proposalId?: number;
totalFees?: AmountJson;
+}
+
+export interface DownloadedWithdrawInfo {
+ selectionDone: boolean;
+ transferDone: boolean;
+ amount: AmountJson;
+ senderWire?: string;
+ suggestedExchange?: string;
+ confirmTransferUrl?: string;
+ wireTypes: string[];
+ extractedStatusUrl: string;
} \ No newline at end of file