commit acd8d1da4cdfa4d6036a4ec9928962dd2417bc50
parent 6cdce3d0265cbc72b291b1bafaef86cf9e7b8d1d
Author: Florian Dold <florian@dold.me>
Date: Fri, 25 Apr 2025 16:06:35 +0200
wallet-core,harness: handle unoffered denominations in pay transaction, extend test
Diffstat:
4 files changed, 731 insertions(+), 595 deletions(-)
diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts
@@ -1877,20 +1877,26 @@ export class MerchantService implements MerchantServiceInterface {
async stop(): Promise<void> {
const httpd = this.procHttpd;
if (httpd) {
+ logger.info(`killing merchant httpd`);
httpd.proc.kill("SIGTERM");
await httpd.wait();
+ logger.info(`done killing merchant httpd`);
this.procHttpd = undefined;
}
const exchangekeyupdate = this.procExchangekeyupdate;
if (exchangekeyupdate) {
+ logger.info(`killing merchant exchangekeyupdate`);
exchangekeyupdate.proc.kill("SIGTERM");
await exchangekeyupdate.wait();
+ logger.info(`done killing merchant exchangekeyupdate`);
this.procExchangekeyupdate = undefined;
}
const kyccheck = this.procKyccheck;
if (kyccheck) {
+ logger.info(`killing merchant kyccheck`);
kyccheck.proc.kill("SIGTERM");
await kyccheck.wait();
+ logger.info(`done killing merchant kyccheck`);
this.procKyccheck = undefined;
}
}
diff --git a/packages/taler-harness/src/integrationtests/test-denom-unoffered.ts b/packages/taler-harness/src/integrationtests/test-denom-unoffered.ts
@@ -18,6 +18,7 @@
* Imports.
*/
import {
+ Duration,
j2s,
Logger,
PreparePayResultType,
@@ -28,6 +29,7 @@ import {
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import {
+ applyTimeTravelV2,
createSimpleTestkudosEnvironmentV3,
withdrawViaBankV3,
} from "../harness/environments.js";
@@ -71,6 +73,8 @@ export async function runDenomUnofferedTest(t: GlobalTestState) {
await merchant.start();
await merchant.pingUntilAvailable();
+ t.logStep("services restarted");
+
const orderResp = succeedOrThrow(
await merchantClient.createOrder(undefined, {
order: {
@@ -100,10 +104,14 @@ export async function runDenomUnofferedTest(t: GlobalTestState) {
preparePayResult.status === PreparePayResultType.PaymentPossible,
);
+ t.logStep("order prepared");
+
const confirmResp = await walletClient.call(WalletApiOperation.ConfirmPay, {
transactionId: preparePayResult.transactionId,
});
+ t.logStep("order confirmed");
+
const tx = await walletClient.call(WalletApiOperation.GetTransactionById, {
transactionId: confirmResp.transactionId,
});
@@ -136,18 +144,41 @@ export async function runDenomUnofferedTest(t: GlobalTestState) {
force: true,
});
- await walletClient.call(WalletApiOperation.DeleteTransaction, {
- transactionId: confirmResp.transactionId,
- });
+ // await walletClient.call(WalletApiOperation.DeleteTransaction, {
+ // transactionId: confirmResp.transactionId,
+ // });
+
+ logger.info(`withdrawing (with new denoms)`);
// Now withdrawal should work again.
- await withdrawViaBankV3(t, {
+ const wres2 = await withdrawViaBankV3(t, {
walletClient,
bankClient,
exchange,
amount: "TESTKUDOS:20",
});
+ await wres2.withdrawalFinishedCond;
+
+ logger.info(`withdrawal done`);
+
+ // Time trave so merchant doens't refuse to download /keys
+ // from the exchange anymore.
+ await applyTimeTravelV2(
+ Duration.toMilliseconds(Duration.fromSpec({ minutes: 6 })),
+ {
+ exchange,
+ merchant,
+ walletClient,
+ },
+ );
+
+ logger.info(`timetravel done`);
+
+ // Currently, the payment transaction will be aborted.
+ // In the future, the wallet should also do coin re-selection
+ // when a denomination is lost. But we might want to ask the user
+ // first, since they lost money.
await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
const txs = await walletClient.call(WalletApiOperation.GetTransactions, {
@@ -158,8 +189,9 @@ export async function runDenomUnofferedTest(t: GlobalTestState) {
t.assertDeepEqual(txs.transactions[0].type, TransactionType.Withdrawal);
t.assertDeepEqual(txs.transactions[1].type, TransactionType.Refresh);
- t.assertDeepEqual(txs.transactions[2].type, TransactionType.DenomLoss);
- t.assertDeepEqual(txs.transactions[3].type, TransactionType.Withdrawal);
+ t.assertDeepEqual(txs.transactions[2].type, TransactionType.Payment);
+ t.assertDeepEqual(txs.transactions[3].type, TransactionType.DenomLoss);
+ t.assertDeepEqual(txs.transactions[4].type, TransactionType.Withdrawal);
}
runDenomUnofferedTest.suites = ["wallet"];
diff --git a/packages/taler-util/src/taler-error-codes.ts b/packages/taler-util/src/taler-error-codes.ts
@@ -41,22 +41,6 @@ export enum TalerErrorCode {
/**
- * An internal failure happened on the client side. Details should be in the local logs. Check if you are using the latest available version or file a report with the developers.
- * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_CLIENT_INTERNAL_ERROR = 2,
-
-
- /**
- * The client does not support the protocol version advertised by the server.
- * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_CLIENT_UNSUPPORTED_PROTOCOL_VERSION = 3,
-
-
- /**
* The response we got from the server was not in the expected format. Most likely, the server does not speak the GNU Taler protocol. Check the URL and/or the network connection to the server.
* Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
* (A value of 0 indicates that the error is generated client-side).
@@ -65,326 +49,6 @@ export enum TalerErrorCode {
/**
- * The operation timed out. Trying again might help. Check the network connection.
- * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_TIMEOUT = 11,
-
-
- /**
- * The protocol version given by the server does not follow the required format. Most likely, the server does not speak the GNU Taler protocol. Check the URL and/or the network connection to the server.
- * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_VERSION_MALFORMED = 12,
-
-
- /**
- * The service responded with a reply that was in the right data format, but the content did not satisfy the protocol. Please file a bug report.
- * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_REPLY_MALFORMED = 13,
-
-
- /**
- * There is an error in the client-side configuration, for example an option is set to an invalid value. Check the logs and fix the local configuration.
- * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_CONFIGURATION_INVALID = 14,
-
-
- /**
- * The client made a request to a service, but received an error response it does not know how to handle. Please file a bug report.
- * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_UNEXPECTED_REQUEST_ERROR = 15,
-
-
- /**
- * The token used by the client to authorize the request does not grant the required permissions for the request. Check the requirements and obtain a suitable authorization token to proceed.
- * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_TOKEN_PERMISSION_INSUFFICIENT = 16,
-
-
- /**
- * The HTTP method used is invalid for this endpoint. This is likely a bug in the client implementation. Check if you are using the latest available version and/or file a report with the developers.
- * Returned with an HTTP status code of #MHD_HTTP_METHOD_NOT_ALLOWED (405).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_METHOD_INVALID = 20,
-
-
- /**
- * There is no endpoint defined for the URL provided by the client. Check if you used the correct URL and/or file a report with the developers of the client software.
- * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_ENDPOINT_UNKNOWN = 21,
-
-
- /**
- * The JSON in the client's request was malformed. This is likely a bug in the client implementation. Check if you are using the latest available version and/or file a report with the developers.
- * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_JSON_INVALID = 22,
-
-
- /**
- * Some of the HTTP headers provided by the client were malformed and caused the server to not be able to handle the request. This is likely a bug in the client implementation. Check if you are using the latest available version and/or file a report with the developers.
- * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_HTTP_HEADERS_MALFORMED = 23,
-
-
- /**
- * The payto:// URI provided by the client is malformed. Check that you are using the correct syntax as of RFC 8905 and/or that you entered the bank account number correctly.
- * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_PAYTO_URI_MALFORMED = 24,
-
-
- /**
- * A required parameter in the request was missing. This is likely a bug in the client implementation. Check if you are using the latest available version and/or file a report with the developers.
- * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_PARAMETER_MISSING = 25,
-
-
- /**
- * A parameter in the request was malformed. This is likely a bug in the client implementation. Check if you are using the latest available version and/or file a report with the developers.
- * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_PARAMETER_MALFORMED = 26,
-
-
- /**
- * The reserve public key was malformed.
- * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_RESERVE_PUB_MALFORMED = 27,
-
-
- /**
- * The body in the request could not be decompressed by the server. This is likely a bug in the client implementation. Check if you are using the latest available version and/or file a report with the developers.
- * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_COMPRESSION_INVALID = 28,
-
-
- /**
- * A segment in the path of the URL provided by the client is malformed. Check that you are using the correct encoding for the URL.
- * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_PATH_SEGMENT_MALFORMED = 29,
-
-
- /**
- * The currency involved in the operation is not acceptable for this server. Check your configuration and make sure the currency specified for a given service provider is one of the currencies supported by that provider.
- * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_CURRENCY_MISMATCH = 30,
-
-
- /**
- * The URI is longer than the longest URI the HTTP server is willing to parse. If you believe this was a legitimate request, contact the server administrators and/or the software developers to increase the limit.
- * Returned with an HTTP status code of #MHD_HTTP_URI_TOO_LONG (414).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_URI_TOO_LONG = 31,
-
-
- /**
- * The body is too large to be permissible for the endpoint. If you believe this was a legitimate request, contact the server administrators and/or the software developers to increase the limit.
- * Returned with an HTTP status code of #MHD_HTTP_CONTENT_TOO_LARGE (413).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_UPLOAD_EXCEEDS_LIMIT = 32,
-
-
- /**
- * The service refused the request due to lack of proper authorization. Accessing this endpoint requires an access token from the account owner.
- * Returned with an HTTP status code of #MHD_HTTP_UNAUTHORIZED (401).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_UNAUTHORIZED = 40,
-
-
- /**
- * The service refused the request as the given authorization token is unknown. You should request a valid access token from the account owner.
- * Returned with an HTTP status code of #MHD_HTTP_UNAUTHORIZED (401).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_TOKEN_UNKNOWN = 41,
-
-
- /**
- * The service refused the request as the given authorization token expired. You should request a fresh authorization token from the account owner.
- * Returned with an HTTP status code of #MHD_HTTP_UNAUTHORIZED (401).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_TOKEN_EXPIRED = 42,
-
-
- /**
- * The service refused the request as the given authorization token is invalid or malformed. You should check that you have the right credentials.
- * Returned with an HTTP status code of #MHD_HTTP_UNAUTHORIZED (401).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_TOKEN_MALFORMED = 43,
-
-
- /**
- * The service refused the request due to lack of proper rights on the resource. You may need different credentials to be allowed to perform this operation.
- * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_FORBIDDEN = 44,
-
-
- /**
- * The service failed initialize its connection to the database. The system administrator should check that the service has permissions to access the database and that the database is running.
- * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_DB_SETUP_FAILED = 50,
-
-
- /**
- * The service encountered an error event to just start the database transaction. The system administrator should check that the database is running.
- * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_DB_START_FAILED = 51,
-
-
- /**
- * The service failed to store information in its database. The system administrator should check that the database is running and review the service logs.
- * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_DB_STORE_FAILED = 52,
-
-
- /**
- * The service failed to fetch information from its database. The system administrator should check that the database is running and review the service logs.
- * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_DB_FETCH_FAILED = 53,
-
-
- /**
- * The service encountered an unrecoverable error trying to commit a transaction to the database. The system administrator should check that the database is running and review the service logs.
- * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_DB_COMMIT_FAILED = 54,
-
-
- /**
- * The service encountered an error event to commit the database transaction, even after repeatedly retrying it there was always a conflicting transaction. This indicates a repeated serialization error; it should only happen if some client maliciously tries to create conflicting concurrent transactions. It could also be a sign of a missing index. Check if you are using the latest available version and/or file a report with the developers.
- * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_DB_SOFT_FAILURE = 55,
-
-
- /**
- * The service's database is inconsistent and violates service-internal invariants. Check if you are using the latest available version and/or file a report with the developers.
- * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_DB_INVARIANT_FAILURE = 56,
-
-
- /**
- * The HTTP server experienced an internal invariant failure (bug). Check if you are using the latest available version and/or file a report with the developers.
- * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_INTERNAL_INVARIANT_FAILURE = 60,
-
-
- /**
- * The service could not compute a cryptographic hash over some JSON value. Check if you are using the latest available version and/or file a report with the developers.
- * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_FAILED_COMPUTE_JSON_HASH = 61,
-
-
- /**
- * The service could not compute an amount. Check if you are using the latest available version and/or file a report with the developers.
- * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_FAILED_COMPUTE_AMOUNT = 62,
-
-
- /**
- * The HTTP server had insufficient memory to parse the request. Restarting services periodically can help, especially if Postgres is using excessive amounts of memory. Check with the system administrator to investigate.
- * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_PARSER_OUT_OF_MEMORY = 70,
-
-
- /**
- * The HTTP server failed to allocate memory. Restarting services periodically can help, especially if Postgres is using excessive amounts of memory. Check with the system administrator to investigate.
- * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_ALLOCATION_FAILURE = 71,
-
-
- /**
- * The HTTP server failed to allocate memory for building JSON reply. Restarting services periodically can help, especially if Postgres is using excessive amounts of memory. Check with the system administrator to investigate.
- * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_JSON_ALLOCATION_FAILURE = 72,
-
-
- /**
- * The HTTP server failed to allocate memory for making a CURL request. Restarting services periodically can help, especially if Postgres is using excessive amounts of memory. Check with the system administrator to investigate.
- * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_CURL_ALLOCATION_FAILURE = 73,
-
-
- /**
- * The backend could not locate a required template to generate an HTML reply. The system administrator should check if the resource files are installed in the correct location and are readable to the service.
- * Returned with an HTTP status code of #MHD_HTTP_NOT_ACCEPTABLE (406).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_FAILED_TO_LOAD_TEMPLATE = 74,
-
-
- /**
- * The backend could not expand the template to generate an HTML reply. The system administrator should investigate the logs and check if the templates are well-formed.
- * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- * (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_FAILED_TO_EXPAND_TEMPLATE = 75,
-
-
- /**
* Exchange is badly configured and thus cannot operate.
* Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
* (A value of 0 indicates that the error is generated client-side).
@@ -737,6 +401,14 @@ export enum TalerErrorCode {
/**
+ * The operation timed out. Trying again might help. Check the network connection.
+ * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_TIMEOUT = 11,
+
+
+ /**
* The exchange did not find information about the specified transaction in the database.
* Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
* (A value of 0 indicates that the error is generated client-side).
@@ -929,6 +601,14 @@ export enum TalerErrorCode {
/**
+ * The protocol version given by the server does not follow the required format. Most likely, the server does not speak the GNU Taler protocol. Check the URL and/or the network connection to the server.
+ * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_VERSION_MALFORMED = 12,
+
+
+ /**
* The signature made by the coin over the deposit permission is not valid.
* Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
* (A value of 0 indicates that the error is generated client-side).
@@ -1025,6 +705,14 @@ export enum TalerErrorCode {
/**
+ * The service responded with a reply that was in the right data format, but the content did not satisfy the protocol. Please file a bug report.
+ * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_REPLY_MALFORMED = 13,
+
+
+ /**
* The exchange encountered melt fees exceeding the melted coin's contribution.
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
* (A value of 0 indicates that the error is generated client-side).
@@ -1153,6 +841,14 @@ export enum TalerErrorCode {
/**
+ * There is an error in the client-side configuration, for example an option is set to an invalid value. Check the logs and fix the local configuration.
+ * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_CONFIGURATION_INVALID = 14,
+
+
+ /**
* The coin specified in the link request is unknown to the exchange.
* Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
* (A value of 0 indicates that the error is generated client-side).
@@ -1209,6 +905,14 @@ export enum TalerErrorCode {
/**
+ * The client made a request to a service, but received an error response it does not know how to handle. Please file a bug report.
+ * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_UNEXPECTED_REQUEST_ERROR = 15,
+
+
+ /**
* The exchange knows literally nothing about the coin we were asked to refund. But without a transaction history, we cannot issue a refund. This is kind-of OK, the owner should just refresh it directly without executing the refund.
* Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
* (A value of 0 indicates that the error is generated client-side).
@@ -1377,6 +1081,14 @@ export enum TalerErrorCode {
/**
+ * The token used by the client to authorize the request does not grant the required permissions for the request. Check the requirements and obtain a suitable authorization token to proceed.
+ * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_TOKEN_PERMISSION_INSUFFICIENT = 16,
+
+
+ /**
* This exchange does not allow clients to request /keys for times other than the current (exchange) time.
* Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
* (A value of 0 indicates that the error is generated client-side).
@@ -2313,6 +2025,22 @@ export enum TalerErrorCode {
/**
+ * An internal failure happened on the client side. Details should be in the local logs. Check if you are using the latest available version or file a report with the developers.
+ * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_CLIENT_INTERNAL_ERROR = 2,
+
+
+ /**
+ * The HTTP method used is invalid for this endpoint. This is likely a bug in the client implementation. Check if you are using the latest available version and/or file a report with the developers.
+ * Returned with an HTTP status code of #MHD_HTTP_METHOD_NOT_ALLOWED (405).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_METHOD_INVALID = 20,
+
+
+ /**
* The backend could not find the merchant instance specified in the request.
* Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
* (A value of 0 indicates that the error is generated client-side).
@@ -2537,6 +2265,14 @@ export enum TalerErrorCode {
/**
+ * There is no endpoint defined for the URL provided by the client. Check if you used the correct URL and/or file a report with the developers of the client software.
+ * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_ENDPOINT_UNKNOWN = 21,
+
+
+ /**
* The exchange failed to provide a valid answer to the tracking request, thus those details are not in the response.
* Returned with an HTTP status code of #MHD_HTTP_OK (200).
* (A value of 0 indicates that the error is generated client-side).
@@ -2857,6 +2593,14 @@ export enum TalerErrorCode {
/**
+ * The JSON in the client's request was malformed. This is likely a bug in the client implementation. Check if you are using the latest available version and/or file a report with the developers.
+ * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_JSON_INVALID = 22,
+
+
+ /**
* The contract hash does not match the given order ID.
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
* (A value of 0 indicates that the error is generated client-side).
@@ -2993,6 +2737,14 @@ export enum TalerErrorCode {
/**
+ * Some of the HTTP headers provided by the client were malformed and caused the server to not be able to handle the request. This is likely a bug in the client implementation. Check if you are using the latest available version and/or file a report with the developers.
+ * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_HTTP_HEADERS_MALFORMED = 23,
+
+
+ /**
* We could not claim the order because the backend is unaware of it.
* Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
* (A value of 0 indicates that the error is generated client-side).
@@ -3021,7 +2773,15 @@ export enum TalerErrorCode {
* Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
* (A value of 0 indicates that the error is generated client-side).
*/
- MERCHANT_POST_ORDERS_ID_REFUND_SIGNATURE_FAILED = 2350,
+ MERCHANT_POST_ORDERS_ID_REFUND_SIGNATURE_FAILED = 2350,
+
+
+ /**
+ * The payto:// URI provided by the client is malformed. Check that you are using the correct syntax as of RFC 8905 and/or that you entered the bank account number correctly.
+ * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_PAYTO_URI_MALFORMED = 24,
/**
@@ -3073,6 +2833,14 @@ export enum TalerErrorCode {
/**
+ * A required parameter in the request was missing. This is likely a bug in the client implementation. Check if you are using the latest available version and/or file a report with the developers.
+ * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_PARAMETER_MISSING = 25,
+
+
+ /**
* The merchant instance has no active bank accounts configured. However, at least one bank account must be available to create new orders.
* Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
* (A value of 0 indicates that the error is generated client-side).
@@ -3321,6 +3089,14 @@ export enum TalerErrorCode {
/**
+ * A parameter in the request was malformed. This is likely a bug in the client implementation. Check if you are using the latest available version and/or file a report with the developers.
+ * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_PARAMETER_MALFORMED = 26,
+
+
+ /**
* The merchant backend cannot create an instance under the given identifier as one already exists. Use PATCH to modify the existing entry.
* Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
* (A value of 0 indicates that the error is generated client-side).
@@ -3441,6 +3217,14 @@ export enum TalerErrorCode {
/**
+ * The reserve public key was malformed.
+ * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_RESERVE_PUB_MALFORMED = 27,
+
+
+ /**
* The requested wire method is not supported by the exchange.
* Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
* (A value of 0 indicates that the error is generated client-side).
@@ -3497,6 +3281,14 @@ export enum TalerErrorCode {
/**
+ * The body in the request could not be decompressed by the server. This is likely a bug in the client implementation. Check if you are using the latest available version and/or file a report with the developers.
+ * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_COMPRESSION_INVALID = 28,
+
+
+ /**
* The merchant backend encountered a failure in computing the deposit total.
* Returned with an HTTP status code of #MHD_HTTP_OK (200).
* (A value of 0 indicates that the error is generated client-side).
@@ -3553,6 +3345,14 @@ export enum TalerErrorCode {
/**
+ * A segment in the path of the URL provided by the client is malformed. Check that you are using the correct encoding for the URL.
+ * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_PATH_SEGMENT_MALFORMED = 29,
+
+
+ /**
* The webhook ID elready exists.
* Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
* (A value of 0 indicates that the error is generated client-side).
@@ -3569,6 +3369,22 @@ export enum TalerErrorCode {
/**
+ * The client does not support the protocol version advertised by the server.
+ * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_CLIENT_UNSUPPORTED_PROTOCOL_VERSION = 3,
+
+
+ /**
+ * The currency involved in the operation is not acceptable for this server. Check your configuration and make sure the currency specified for a given service provider is one of the currencies supported by that provider.
+ * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_CURRENCY_MISMATCH = 30,
+
+
+ /**
* The auditor refused the connection due to a lack of authorization.
* Returned with an HTTP status code of #MHD_HTTP_UNAUTHORIZED (401).
* (A value of 0 indicates that the error is generated client-side).
@@ -3585,6 +3401,14 @@ export enum TalerErrorCode {
/**
+ * The URI is longer than the longest URI the HTTP server is willing to parse. If you believe this was a legitimate request, contact the server administrators and/or the software developers to increase the limit.
+ * Returned with an HTTP status code of #MHD_HTTP_URI_TOO_LONG (414).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_URI_TOO_LONG = 31,
+
+
+ /**
* The signature from the exchange on the deposit confirmation is invalid.
* Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
* (A value of 0 indicates that the error is generated client-side).
@@ -3617,6 +3441,70 @@ export enum TalerErrorCode {
/**
+ * The body is too large to be permissible for the endpoint. If you believe this was a legitimate request, contact the server administrators and/or the software developers to increase the limit.
+ * Returned with an HTTP status code of #MHD_HTTP_CONTENT_TOO_LARGE (413).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_UPLOAD_EXCEEDS_LIMIT = 32,
+
+
+ /**
+ * The service refused the request due to lack of proper authorization. Accessing this endpoint requires an access token from the account owner.
+ * Returned with an HTTP status code of #MHD_HTTP_UNAUTHORIZED (401).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_UNAUTHORIZED = 40,
+
+
+ /**
+ * The service refused the request as the given authorization token is unknown. You should request a valid access token from the account owner.
+ * Returned with an HTTP status code of #MHD_HTTP_UNAUTHORIZED (401).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_TOKEN_UNKNOWN = 41,
+
+
+ /**
+ * The service refused the request as the given authorization token expired. You should request a fresh authorization token from the account owner.
+ * Returned with an HTTP status code of #MHD_HTTP_UNAUTHORIZED (401).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_TOKEN_EXPIRED = 42,
+
+
+ /**
+ * The service refused the request as the given authorization token is invalid or malformed. You should check that you have the right credentials.
+ * Returned with an HTTP status code of #MHD_HTTP_UNAUTHORIZED (401).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_TOKEN_MALFORMED = 43,
+
+
+ /**
+ * The service refused the request due to lack of proper rights on the resource. You may need different credentials to be allowed to perform this operation.
+ * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_FORBIDDEN = 44,
+
+
+ /**
+ * The service failed initialize its connection to the database. The system administrator should check that the service has permissions to access the database and that the database is running.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_DB_SETUP_FAILED = 50,
+
+
+ /**
+ * The service encountered an error event to just start the database transaction. The system administrator should check that the database is running.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_DB_START_FAILED = 51,
+
+
+ /**
* Wire transfer attempted with credit and debit party being the same bank account.
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
* (A value of 0 indicates that the error is generated client-side).
@@ -4041,6 +3929,62 @@ export enum TalerErrorCode {
/**
+ * The service failed to store information in its database. The system administrator should check that the database is running and review the service logs.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_DB_STORE_FAILED = 52,
+
+
+ /**
+ * The service failed to fetch information from its database. The system administrator should check that the database is running and review the service logs.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_DB_FETCH_FAILED = 53,
+
+
+ /**
+ * The service encountered an unrecoverable error trying to commit a transaction to the database. The system administrator should check that the database is running and review the service logs.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_DB_COMMIT_FAILED = 54,
+
+
+ /**
+ * The service encountered an error event to commit the database transaction, even after repeatedly retrying it there was always a conflicting transaction. This indicates a repeated serialization error; it should only happen if some client maliciously tries to create conflicting concurrent transactions. It could also be a sign of a missing index. Check if you are using the latest available version and/or file a report with the developers.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_DB_SOFT_FAILURE = 55,
+
+
+ /**
+ * The service's database is inconsistent and violates service-internal invariants. Check if you are using the latest available version and/or file a report with the developers.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_DB_INVARIANT_FAILURE = 56,
+
+
+ /**
+ * The HTTP server experienced an internal invariant failure (bug). Check if you are using the latest available version and/or file a report with the developers.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_INTERNAL_INVARIANT_FAILURE = 60,
+
+
+ /**
+ * The service could not compute a cryptographic hash over some JSON value. Check if you are using the latest available version and/or file a report with the developers.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_FAILED_COMPUTE_JSON_HASH = 61,
+
+
+ /**
* The sync service failed find the account in its database.
* Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
* (A value of 0 indicates that the error is generated client-side).
@@ -4161,6 +4105,22 @@ export enum TalerErrorCode {
/**
+ * The service could not compute an amount. Check if you are using the latest available version and/or file a report with the developers.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_FAILED_COMPUTE_AMOUNT = 62,
+
+
+ /**
+ * The HTTP server had insufficient memory to parse the request. Restarting services periodically can help, especially if Postgres is using excessive amounts of memory. Check with the system administrator to investigate.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_PARSER_OUT_OF_MEMORY = 70,
+
+
+ /**
* The wallet does not implement a version of the exchange protocol that is compatible with the protocol version of the exchange.
* Returned with an HTTP status code of #MHD_HTTP_NOT_IMPLEMENTED (501).
* (A value of 0 indicates that the error is generated client-side).
@@ -4537,6 +4497,54 @@ export enum TalerErrorCode {
/**
+ * A transaction could not be processed due to an unrecoverable protocol violation.
+ * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ WALLET_TRANSACTION_PROTOCOL_VIOLATION = 7047,
+
+
+ /**
+ * The HTTP server failed to allocate memory. Restarting services periodically can help, especially if Postgres is using excessive amounts of memory. Check with the system administrator to investigate.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_ALLOCATION_FAILURE = 71,
+
+
+ /**
+ * The HTTP server failed to allocate memory for building JSON reply. Restarting services periodically can help, especially if Postgres is using excessive amounts of memory. Check with the system administrator to investigate.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_JSON_ALLOCATION_FAILURE = 72,
+
+
+ /**
+ * The HTTP server failed to allocate memory for making a CURL request. Restarting services periodically can help, especially if Postgres is using excessive amounts of memory. Check with the system administrator to investigate.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_CURL_ALLOCATION_FAILURE = 73,
+
+
+ /**
+ * The backend could not locate a required template to generate an HTML reply. The system administrator should check if the resource files are installed in the correct location and are readable to the service.
+ * Returned with an HTTP status code of #MHD_HTTP_NOT_ACCEPTABLE (406).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_FAILED_TO_LOAD_TEMPLATE = 74,
+
+
+ /**
+ * The backend could not expand the template to generate an HTML reply. The system administrator should investigate the logs and check if the templates are well-formed.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ GENERIC_FAILED_TO_EXPAND_TEMPLATE = 75,
+
+
+ /**
* We encountered a timeout with our payment backend.
* Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504).
* (A value of 0 indicates that the error is generated client-side).
@@ -5281,6 +5289,14 @@ export enum TalerErrorCode {
/**
+ * The client is not allowed to change the address being validated.
+ * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ CHALLENGER_CLIENT_FORBIDDEN_READ_ONLY = 9760,
+
+
+ /**
* End of error code range.
* Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
* (A value of 0 indicates that the error is generated client-side).
diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts
@@ -97,7 +97,6 @@ import {
TalerMerchantApi,
TalerMerchantInstanceHttpClient,
TalerPreciseTimestamp,
- TalerProtocolViolationError,
TalerUriAction,
TokenUseSig,
Transaction,
@@ -126,21 +125,21 @@ import {
selectPayCoinsInTx,
} from "./coinSelection.js";
import {
+ cancelableFetch,
+ cancelableLongPoll,
constructTaskIdentifier,
genericWaitForState,
genericWaitForStateVal,
- spendTokens,
LookupFullTransactionOpts,
PendingTaskType,
spendCoins,
+ spendTokens,
TaskIdentifiers,
TaskIdStr,
TaskRunResult,
TaskRunResultType,
TransactionContext,
TransitionResultType,
- cancelableLongPoll,
- cancelableFetch,
} from "./common.js";
import { EddsaKeyPairStrings } from "./crypto/cryptoImplementation.js";
import {
@@ -164,16 +163,18 @@ import {
WalletDbReadOnlyTransaction,
WalletDbReadWriteTransaction,
WalletDbStoresArr,
- WalletStoresV1,
} from "./db.js";
import { getScopeForAllCoins, getScopeForAllExchanges } from "./exchanges.js";
-import { DbReadWriteTransaction, StoreNames } from "./query.js";
import {
calculateRefreshOutput,
createRefreshGroup,
getTotalRefreshCost,
} from "./refresh.js";
import {
+ selectPayTokensInTx,
+ SelectPayTokensResult,
+} from "./tokenSelection.js";
+import {
BalanceEffect,
constructTransactionIdentifier,
isUnsuccessfulTransaction,
@@ -186,10 +187,6 @@ import {
getDenomInfo,
WalletExecutionContext,
} from "./wallet.js";
-import {
- selectPayTokensInTx,
- SelectPayTokensResult,
-} from "./tokenSelection.js";
/**
* Logger.
@@ -262,7 +259,8 @@ export class PayMerchantTransactionContext implements TransactionContext {
);
let amountRaw: AmountString = "UNKNOWN:0";
- if (contractData.version === undefined ||
+ if (
+ contractData.version === undefined ||
contractData.version === MerchantContractVersion.V0
) {
amountRaw = contractData.amount;
@@ -271,8 +269,7 @@ export class PayMerchantTransactionContext implements TransactionContext {
if (index !== undefined) {
if (!(index in contractData.choices))
throw Error(`invalid choice index ${index}`);
- amountRaw = contractData
- .choices[index].amount;
+ amountRaw = contractData.choices[index].amount;
}
}
@@ -376,9 +373,7 @@ export class PayMerchantTransactionContext implements TransactionContext {
* Transition a payment transition.
* Extra object stores may be accessed during the transition.
*/
- async transitionExtra<
- StoreNameArray extends WalletDbStoresArr = [],
- >(
+ async transitionExtra<StoreNameArray extends WalletDbStoresArr = []>(
opts: { extraStores: StoreNameArray },
f: (
rec: PurchaseRecord,
@@ -1004,7 +999,8 @@ async function processDownloadProposal(
if (proposal.purchaseStatus != PurchaseStatus.PendingDownloadingProposal) {
logger.error(
- `unexpected state ${proposal.purchaseStatus}/${PurchaseStatus[proposal.purchaseStatus]
+ `unexpected state ${proposal.purchaseStatus}/${
+ PurchaseStatus[proposal.purchaseStatus]
} for ${ctx.transactionId} in processDownloadProposal`,
);
return TaskRunResult.finished();
@@ -1030,7 +1026,7 @@ async function processDownloadProposal(
const httpResponse = await cancelableFetch(wex, orderClaimUrl, {
method: "POST",
- body: requestBody
+ body: requestBody,
});
const r = await readSuccessResponseJsonOrErrorCode(
httpResponse,
@@ -1196,7 +1192,8 @@ async function processDownloadProposal(
// v1: currency is resolved after choice selection
let currency: string = "UNKNOWN";
- if (contractData.version === undefined ||
+ if (
+ contractData.version === undefined ||
contractData.version === MerchantContractVersion.V0
) {
currency = Amounts.currencyOf(contractData.amount);
@@ -1273,13 +1270,14 @@ async function generateSlate(
"can't process slates without secretSeed",
);
- let slate = await wex.db.runReadOnlyTx({ storeNames: ["slates"] }, async (tx) => {
- return await tx.slates.indexes.byPurchaseIdAndChoiceIndexAndOutputIndex.get([
- purchase.proposalId,
- choiceIndex,
- outputIndex,
- ]);
- });
+ let slate = await wex.db.runReadOnlyTx(
+ { storeNames: ["slates"] },
+ async (tx) => {
+ return await tx.slates.indexes.byPurchaseIdAndChoiceIndexAndOutputIndex.get(
+ [purchase.proposalId, choiceIndex, outputIndex],
+ );
+ },
+ );
if (slate) {
return;
@@ -1300,8 +1298,7 @@ async function generateSlate(
tokenIssuePub: key,
genTokenUseSig: true,
contractTerms: contractData,
- contractTermsHash: ContractTermsUtil
- .hashContractTerms(contractTermsRaw),
+ contractTermsHash: ContractTermsUtil.hashContractTerms(contractTermsRaw),
});
const newSlate: SlateRecord = {
@@ -1327,11 +1324,12 @@ async function generateSlate(
};
await wex.db.runReadWriteTx({ storeNames: ["slates"] }, async (tx) => {
- const s = await tx.slates.indexes.byPurchaseIdAndChoiceIndexAndOutputIndex.get([
- purchase.proposalId,
- choiceIndex,
- outputIndex,
- ]);
+ const s =
+ await tx.slates.indexes.byPurchaseIdAndChoiceIndexAndOutputIndex.get([
+ purchase.proposalId,
+ choiceIndex,
+ outputIndex,
+ ]);
if (s) return;
await tx.slates.put(newSlate);
});
@@ -1350,8 +1348,8 @@ async function createOrReusePurchase(
claimToken: string | undefined,
noncePriv: string | undefined,
): Promise<{
- proposalId: string,
- transactionId: TransactionIdStr,
+ proposalId: string;
+ transactionId: TransactionIdStr;
}> {
const oldProposals = await wex.db.runReadOnlyTx(
{ storeNames: ["purchases"] },
@@ -1379,13 +1377,11 @@ async function createOrReusePurchase(
oldProposal.claimToken === claimToken
) {
logger.info(
- `Found old proposal (status=${PurchaseStatus[oldProposal.purchaseStatus]
+ `Found old proposal (status=${
+ PurchaseStatus[oldProposal.purchaseStatus]
}) for order ${orderId} at ${merchantBaseUrl}`,
);
- const ctx = new PayMerchantTransactionContext(
- wex,
- oldProposal.proposalId,
- );
+ const ctx = new PayMerchantTransactionContext(wex, oldProposal.proposalId);
if (oldProposal.shared || oldProposal.createdFromShared) {
const download = await expectProposalDownload(wex, oldProposal);
const paid = await checkIfOrderIsAlreadyPaid(
@@ -1609,7 +1605,7 @@ async function storePayReplaySuccess(
}
/**
- * Handle a 409 Conflict response from the merchant.
+ * Handle a 409 Conflict or 400 Bad Request response from the merchant.
*
* We do this by going through the coin history provided by the exchange and
* (1) verifying the signatures from the exchange
@@ -1620,7 +1616,7 @@ async function handleInsufficientFunds(
wex: WalletExecutionContext,
proposalId: string,
err: TalerErrorDetail,
-): Promise<void> {
+): Promise<TaskRunResult> {
logger.trace("handling insufficient funds, trying to re-select coins");
const ctx = new PayMerchantTransactionContext(wex, proposalId);
@@ -1632,28 +1628,54 @@ async function handleInsufficientFunds(
},
);
if (!proposal) {
- return;
+ return TaskRunResult.finished();
}
logger.trace(`got error details: ${j2s(err)}`);
- const exchangeReply = (err as any).exchange_reply;
if (
- exchangeReply.code !== TalerErrorCode.EXCHANGE_GENERIC_INSUFFICIENT_FUNDS
+ err.code === TalerErrorCode.MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_FUNDS
) {
- // FIXME: set as failed
- if (logger.shouldLogTrace()) {
- logger.trace("got exchange error reply (see below)");
- logger.trace(j2s(exchangeReply));
+ const exchangeReply = (err as any).exchange_reply;
+ if (
+ exchangeReply.code !== TalerErrorCode.EXCHANGE_GENERIC_INSUFFICIENT_FUNDS
+ ) {
+ if (logger.shouldLogTrace()) {
+ logger.trace("got exchange error reply (see below)");
+ logger.trace(j2s(exchangeReply));
+ }
+ await ctx.abortTransaction({
+ code: TalerErrorCode.WALLET_TRANSACTION_PROTOCOL_VIOLATION,
+ message: `unable to handle /pay exchange error response (${exchangeReply.code})`,
+ exchangeReply,
+ });
+ return TaskRunResult.progress();
}
- throw Error(`unable to handle /pay error response (${exchangeReply.code})`);
- }
- const brokenCoinPub = (exchangeReply as any).coin_pub;
- logger.trace(`excluded broken coin pub=${brokenCoinPub}`);
+ const brokenCoinPub = (exchangeReply as any).coin_pub;
+ logger.trace(`excluded broken coin pub=${brokenCoinPub}`);
- if (!brokenCoinPub) {
- throw new TalerProtocolViolationError();
+ if (!brokenCoinPub) {
+ await ctx.abortTransaction({
+ code: TalerErrorCode.WALLET_TRANSACTION_PROTOCOL_VIOLATION,
+ message: "Exchange claimed bad coin, but coin was not used.",
+ brokenCoinPub,
+ });
+ return TaskRunResult.progress();
+ }
+ } else if (
+ err.code ===
+ TalerErrorCode.MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_NOT_FOUND
+ ) {
+ // We might support this in the future.
+ await ctx.abortTransaction({
+ code: TalerErrorCode.WALLET_TRANSACTION_PROTOCOL_VIOLATION,
+ message: "Denomination used in payment became invalid.",
+ });
+ return TaskRunResult.progress();
+ } else {
+ // Caller should have checked.
+ throw Error(`unsupported error code: ${err.code}`);
}
const prevPayCoins: PreviousPayCoins = [];
@@ -1661,7 +1683,7 @@ async function handleInsufficientFunds(
const payInfo = proposal.payInfo;
if (!payInfo) {
- return;
+ return TaskRunResult.backoff();
}
const payCoinSelection = payInfo.payCoinSelection;
@@ -1698,8 +1720,8 @@ async function handleInsufficientFunds(
const index = p.choiceIndex;
if (index === undefined)
throw Error("choice index not specified for contract v1");
- if ((index in contractData.choices))
- throw Error(`invalid choice index ${index}`)
+ if (index in contractData.choices)
+ throw Error(`invalid choice index ${index}`);
amount = contractData.choices[index].amount;
maxFee = contractData.choices[index].max_fee;
break;
@@ -1720,7 +1742,7 @@ async function handleInsufficientFunds(
const res = await selectPayCoinsInTx(wex, tx, {
restrictExchanges: {
auditors: [],
- exchanges: contractData.exchanges.map(ex => ({
+ exchanges: contractData.exchanges.map((ex) => ({
exchangeBaseUrl: ex.url,
exchangePub: ex.master_pub,
})),
@@ -1750,15 +1772,16 @@ async function handleInsufficientFunds(
coinPubs: res.coinSel.coins.map((x) => x.coinPub),
};
payInfo.payCoinSelectionUid = encodeCrock(getRandomBytes(32));
- p.exchanges = [...new Set(res.coinSel.coins.map((x) => x.exchangeBaseUrl))];
+ p.exchanges = [
+ ...new Set(res.coinSel.coins.map((x) => x.exchangeBaseUrl)),
+ ];
p.exchanges.sort();
}
if (payTokenSelection) {
prevTokensPubs.push(...payTokenSelection.tokenPubs);
- if (p.choiceIndex === undefined)
- throw Error("assertion failed");
+ if (p.choiceIndex === undefined) throw Error("assertion failed");
if (contractData.version !== MerchantContractVersion.V1)
throw Error("assertion failed");
@@ -1781,7 +1804,7 @@ async function handleInsufficientFunds(
}
payInfo.payTokenSelection = {
- tokenPubs: res.tokens.map(t => t.tokenUsePub),
+ tokenPubs: res.tokens.map((t) => t.tokenUsePub),
};
}
@@ -1814,6 +1837,8 @@ async function handleInsufficientFunds(
proposalId,
}),
});
+
+ return TaskRunResult.progress();
}
// FIXME: Should take a transaction ID instead of a proposal ID
@@ -1898,7 +1923,7 @@ async function checkPaymentByProposalId(
const res = await selectPayCoins(wex, {
restrictExchanges: {
auditors: [],
- exchanges: contractData.exchanges.map(ex => ({
+ exchanges: contractData.exchanges.map((ex) => ({
exchangeBaseUrl: ex.url,
exchangePub: ex.master_pub,
})),
@@ -2017,7 +2042,7 @@ async function checkPaymentByProposalId(
const index = purchase.choiceIndex;
if (index === undefined)
throw Error("choice index not specified for contract v1");
- if ((index in contractData.choices))
+ if (index in contractData.choices)
throw Error(`invalid choice index ${index}`);
amount = contractData.choices[index].amount;
break;
@@ -2173,7 +2198,11 @@ export async function preparePayForUri(
await waitProposalDownloaded(wex, proposalRes.proposalId);
- return checkPaymentByProposalId(wex, proposalRes.proposalId, uriResult.sessionId);
+ return checkPaymentByProposalId(
+ wex,
+ proposalRes.proposalId,
+ uriResult.sessionId,
+ );
}
/**
@@ -2510,129 +2539,138 @@ export async function getChoicesForPayment(
}
const choices: ChoiceSelectionDetail[] = [];
- await wex.db.runAllStoresReadOnlyTx(
- {},
- async (tx) => {
- const tokenSels: SelectPayTokensResult[] = [];
- const contractTerms: MerchantContractTerms = d.contractData;
+ await wex.db.runAllStoresReadOnlyTx({}, async (tx) => {
+ const tokenSels: SelectPayTokensResult[] = [];
+ const contractTerms: MerchantContractTerms = d.contractData;
+ switch (contractTerms.version) {
+ case undefined:
+ case MerchantContractVersion.V0:
+ tokenSels.push({
+ type: "success",
+ tokens: [],
+ details: {
+ tokensRequested: 0,
+ tokensAvailable: 0,
+ tokensUnexpected: 0,
+ tokensUntrusted: 0,
+ perTokenFamily: {},
+ },
+ });
+ break;
+ case MerchantContractVersion.V1:
+ for (let i = 0; i < contractTerms.choices.length; i++) {
+ tokenSels.push(
+ await selectPayTokensInTx(tx, {
+ proposalId,
+ choiceIndex: i,
+ contractTerms: contractTerms,
+ }),
+ );
+ }
+ break;
+ default:
+ assertUnreachable(contractTerms);
+ }
+
+ for (let i = 0; i < tokenSels.length; i++) {
+ const tokenSelection = tokenSels[i];
+ logger.trace("token selection result", tokenSelection);
+
+ let balanceDetails: PaymentInsufficientBalanceDetails | undefined =
+ undefined;
+
+ let amount: AmountString;
+ let maxFee: AmountString;
switch (contractTerms.version) {
case undefined:
case MerchantContractVersion.V0:
- tokenSels.push({
- type: "success",
- tokens: [],
- details: {
- tokensRequested: 0,
- tokensAvailable: 0,
- tokensUnexpected: 0,
- tokensUntrusted: 0,
- perTokenFamily: {},
- },
- });
+ amount = contractTerms.amount;
+ maxFee = contractTerms.max_fee;
break;
case MerchantContractVersion.V1:
- for (let i = 0; i < contractTerms.choices.length; i++) {
- tokenSels.push(await selectPayTokensInTx(tx, {
- proposalId,
- choiceIndex: i,
- contractTerms: contractTerms,
- }));
- }
+ amount = contractTerms.choices[i].amount;
+ maxFee = contractTerms.choices[i].max_fee;
break;
default:
assertUnreachable(contractTerms);
}
- for (let i = 0; i < tokenSels.length; i++) {
- const tokenSelection = tokenSels[i];
- logger.trace("token selection result", tokenSelection);
-
- let balanceDetails: PaymentInsufficientBalanceDetails | undefined = undefined;
-
- let amount: AmountString;
- let maxFee: AmountString;
- switch (contractTerms.version) {
- case undefined:
- case MerchantContractVersion.V0:
- amount = contractTerms.amount;
- maxFee = contractTerms.max_fee;
- break;
- case MerchantContractVersion.V1:
- amount = contractTerms.choices[i].amount;
- maxFee = contractTerms.choices[i].max_fee;
- break;
- default:
- assertUnreachable(contractTerms);
- }
-
- let amountEffective: AmountJson | undefined = undefined;
- const currency = Amounts.currencyOf(amount);
+ let amountEffective: AmountJson | undefined = undefined;
+ const currency = Amounts.currencyOf(amount);
- const selectCoinsResult = await selectPayCoinsInTx(wex, tx, {
- restrictExchanges: {
- auditors: [],
- exchanges: contractTerms.exchanges.map(ex => ({
- exchangeBaseUrl: ex.url,
- exchangePub: ex.master_pub,
- })),
- },
- restrictWireMethod: contractTerms.wire_method,
- contractTermsAmount: Amounts.parseOrThrow(amount),
- depositFeeLimit: Amounts.parseOrThrow(maxFee),
- prevPayCoins: [],
- requiredMinimumAge: contractTerms.minimum_age,
- forcedSelection: forcedCoinSel,
- });
+ const selectCoinsResult = await selectPayCoinsInTx(wex, tx, {
+ restrictExchanges: {
+ auditors: [],
+ exchanges: contractTerms.exchanges.map((ex) => ({
+ exchangeBaseUrl: ex.url,
+ exchangePub: ex.master_pub,
+ })),
+ },
+ restrictWireMethod: contractTerms.wire_method,
+ contractTermsAmount: Amounts.parseOrThrow(amount),
+ depositFeeLimit: Amounts.parseOrThrow(maxFee),
+ prevPayCoins: [],
+ requiredMinimumAge: contractTerms.minimum_age,
+ forcedSelection: forcedCoinSel,
+ });
- logger.trace("coin selection result", selectCoinsResult);
+ logger.trace("coin selection result", selectCoinsResult);
- switch (selectCoinsResult.type) {
- case "success": {
- amountEffective = await getTotalPaymentCostInTx(wex,
- tx, currency, selectCoinsResult.coinSel.coins);
- break;
- }
- case "prospective": {
- amountEffective = await getTotalPaymentCostInTx(wex,
- tx, currency, selectCoinsResult.result.prospectiveCoins);
- break;
- }
- case "failure": {
- logger.info("choice not payable, insufficient coins");
- balanceDetails = selectCoinsResult.insufficientBalanceDetails;
- break;
- }
- default:
- assertUnreachable(selectCoinsResult);
+ switch (selectCoinsResult.type) {
+ case "success": {
+ amountEffective = await getTotalPaymentCostInTx(
+ wex,
+ tx,
+ currency,
+ selectCoinsResult.coinSel.coins,
+ );
+ break;
}
-
- let choice: ChoiceSelectionDetail;
- if (tokenSelection.type === "failure" || selectCoinsResult.type === "failure") {
- choice = {
- status: ChoiceSelectionDetailType.InsufficientBalance,
- amountRaw: amount,
- balanceDetails: balanceDetails,
- tokenDetails: tokenSelection.details,
- };
- } else {
- choice = {
- status: ChoiceSelectionDetailType.PaymentPossible,
- amountRaw: amount,
- amountEffective: Amounts.stringify(amountEffective!),
- tokenDetails: tokenSelection.details,
- };
+ case "prospective": {
+ amountEffective = await getTotalPaymentCostInTx(
+ wex,
+ tx,
+ currency,
+ selectCoinsResult.result.prospectiveCoins,
+ );
+ break;
+ }
+ case "failure": {
+ logger.info("choice not payable, insufficient coins");
+ balanceDetails = selectCoinsResult.insufficientBalanceDetails;
+ break;
}
+ default:
+ assertUnreachable(selectCoinsResult);
+ }
- choices.push(choice);
+ let choice: ChoiceSelectionDetail;
+ if (
+ tokenSelection.type === "failure" ||
+ selectCoinsResult.type === "failure"
+ ) {
+ choice = {
+ status: ChoiceSelectionDetailType.InsufficientBalance,
+ amountRaw: amount,
+ balanceDetails: balanceDetails,
+ tokenDetails: tokenSelection.details,
+ };
+ } else {
+ choice = {
+ status: ChoiceSelectionDetailType.PaymentPossible,
+ amountRaw: amount,
+ amountEffective: Amounts.stringify(amountEffective!),
+ tokenDetails: tokenSelection.details,
+ };
}
- },
- );
+
+ choices.push(choice);
+ }
+ });
return {
choices,
- ...(await calculateDefaultChoice(
- wex, choices, d.contractData,
- )),
+ ...(await calculateDefaultChoice(wex, choices, d.contractData)),
};
}
@@ -2641,8 +2679,8 @@ async function calculateDefaultChoice(
choiceDetails: ChoiceSelectionDetail[],
contractTerms: MerchantContractTerms,
): Promise<{
- defaultChoiceIndex?: number,
- automaticExecution?: boolean,
+ defaultChoiceIndex?: number;
+ automaticExecution?: boolean;
}> {
var defaultChoiceIndex: number | undefined = undefined;
var automaticExecution: boolean | undefined = undefined;
@@ -2658,7 +2696,8 @@ async function calculateDefaultChoice(
throw Error(`contract v1 has no choices`);
// If there's only one choice, use it.
- if (contractTerms.choices.length === 1 &&
+ if (
+ contractTerms.choices.length === 1 &&
choiceDetails[0].status === ChoiceSelectionDetailType.PaymentPossible
) {
defaultChoiceIndex = 0;
@@ -2671,8 +2710,10 @@ async function calculateDefaultChoice(
for (let i = 1; i < contractTerms.choices.length; i++) {
const choice = contractTerms.choices[i];
const details = choiceDetails[i];
- if (details.status === ChoiceSelectionDetailType.PaymentPossible &&
- choice.amount < cheapestPayableChoice.amount) {
+ if (
+ details.status === ChoiceSelectionDetailType.PaymentPossible &&
+ choice.amount < cheapestPayableChoice.amount
+ ) {
cheapestPayableIndex = i;
cheapestPayableChoice = choice;
}
@@ -2685,15 +2726,18 @@ async function calculateDefaultChoice(
// TODO: in the future, a setting should allow the user to specify
// merchants where discounts should be automatically spent.
defaultChoiceIndex = cheapestPayableIndex;
- automaticExecution = Amounts.isZero(cheapestPayableChoice.amount) &&
+ automaticExecution =
+ Amounts.isZero(cheapestPayableChoice.amount) &&
cheapestPayableChoice.inputs.length === 1 &&
cheapestPayableChoice.outputs.length === 1 &&
- cheapestPayableChoice.inputs[0].type === MerchantContractInputType.Token &&
- cheapestPayableChoice.outputs[0].type === MerchantContractOutputType.Token &&
+ cheapestPayableChoice.inputs[0].type ===
+ MerchantContractInputType.Token &&
+ cheapestPayableChoice.outputs[0].type ===
+ MerchantContractOutputType.Token &&
(cheapestPayableChoice.inputs[0].count ?? 1) === 1 &&
(cheapestPayableChoice.outputs[0].count ?? 1) === 1 &&
cheapestPayableChoice.inputs[0].token_family_slug ===
- cheapestPayableChoice.inputs[0].token_family_slug;
+ cheapestPayableChoice.inputs[0].token_family_slug;
break;
default:
assertUnreachable(contractTerms);
@@ -2762,7 +2806,10 @@ export async function confirmPay(
);
if (existingPurchase && existingPurchase.payInfo) {
- if (choiceIndex !== undefined && choiceIndex !== existingPurchase.choiceIndex)
+ if (
+ choiceIndex !== undefined &&
+ choiceIndex !== existingPurchase.choiceIndex
+ )
throw Error(`cannot change choice index of existing purchase`);
logger.trace("confirmPay: submitting payment for existing purchase");
@@ -2841,7 +2888,7 @@ export async function confirmPay(
const selectCoinsResult = await selectPayCoinsInTx(wex, tx, {
restrictExchanges: {
auditors: [],
- exchanges: contractData.exchanges.map(ex => ({
+ exchanges: contractData.exchanges.map((ex) => ({
exchangeBaseUrl: ex.url,
exchangePub: ex.master_pub,
})),
@@ -2896,7 +2943,7 @@ export async function confirmPay(
let tokenPubs: string[] | undefined = undefined;
if (selectTokensResult?.type === "success") {
const tokens = selectTokensResult.tokens;
- tokenPubs = tokens.map(t => t.tokenUsePub);
+ tokenPubs = tokens.map((t) => t.tokenUsePub);
p.payInfo.payTokenSelection = { tokenPubs };
}
if (selectCoinsResult.type === "success") {
@@ -2915,7 +2962,8 @@ export async function confirmPay(
await ctx.updateTransactionMeta(tx);
if (tokenPubs) {
await spendTokens(tx, {
- tokenPubs, transactionId: ctx.transactionId,
+ tokenPubs,
+ transactionId: ctx.transactionId,
});
}
if (p.payInfo.payCoinSelection) {
@@ -2941,10 +2989,20 @@ export async function confirmPay(
);
// TODO: pre-generate slates based on choice priority!
- if (choiceIndex !== undefined && contractData.version === MerchantContractVersion.V1) {
+ if (
+ choiceIndex !== undefined &&
+ contractData.version === MerchantContractVersion.V1
+ ) {
const choice = contractData.choices[choiceIndex];
for (let j = 0; j < choice.outputs.length; j++) {
- await generateSlate(wex, proposal, contractData, d.contractTermsRaw, choiceIndex!, j);
+ await generateSlate(
+ wex,
+ proposal,
+ contractData,
+ d.contractTermsRaw,
+ choiceIndex!,
+ j,
+ );
}
}
@@ -3136,7 +3194,7 @@ async function processPurchasePay(
const selectCoinsResult = await selectPayCoins(wex, {
restrictExchanges: {
auditors: [],
- exchanges: contractData.exchanges.map(ex => ({
+ exchanges: contractData.exchanges.map((ex) => ({
exchangeBaseUrl: ex.url,
exchangePub: ex.master_pub,
})),
@@ -3243,25 +3301,34 @@ async function processPurchasePay(
let slates: SlateRecord[] | undefined = undefined;
let wallet_data: PayWalletData | undefined = undefined;
- if (contractData.version === MerchantContractVersion.V1 && purchase.choiceIndex !== undefined) {
+ if (
+ contractData.version === MerchantContractVersion.V1 &&
+ purchase.choiceIndex !== undefined
+ ) {
const index = purchase.choiceIndex;
slates = [];
wallet_data = { choice_index: index, tokens_evs: [] };
- await wex.db.runReadOnlyTx({
- storeNames: ["slates"],
- }, async (tx) => {
- (await tx.slates.indexes.byPurchaseIdAndChoiceIndex.getAll(
- [purchase.proposalId, index],
- )).forEach(s => {
- slates?.push(s);
- wallet_data?.tokens_evs.push(s.tokenEv);
- });
- });
+ await wex.db.runReadOnlyTx(
+ {
+ storeNames: ["slates"],
+ },
+ async (tx) => {
+ (
+ await tx.slates.indexes.byPurchaseIdAndChoiceIndex.getAll([
+ purchase.proposalId,
+ index,
+ ])
+ ).forEach((s) => {
+ slates?.push(s);
+ wallet_data?.tokens_evs.push(s.tokenEv);
+ });
+ },
+ );
if (slates.length !== contractData.choices[index].outputs.length) {
- throw Error(`number of slates ${slates.length
- } doesn't match number of outputs ${contractData.choices[index].outputs.length
- }`);
+ throw Error(
+ `number of slates ${slates.length} doesn't match number of outputs ${contractData.choices[index].outputs.length}`,
+ );
}
}
@@ -3281,7 +3348,8 @@ async function processPurchasePay(
};
if (wallet_data && payInfo.payTokenSelection) {
- reqBody.tokens = await generateTokenSigs(wex,
+ reqBody.tokens = await generateTokenSigs(
+ wex,
proposalId,
contractData.contractTermsHash,
encodeCrock(hashPayWalletData(wallet_data)),
@@ -3322,16 +3390,17 @@ async function processPurchasePay(
err.code ===
TalerErrorCode.MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_FUNDS
) {
- // Do this in the background, as it might take some time
- // FIXME: Why? We're already in a (background) task!
- handleInsufficientFunds(wex, proposalId, err).catch(async (e) => {
- logger.error("handling insufficient funds failed");
- logger.error(`${e.toString()}`);
- });
-
- // FIXME: Should we really consider this to be pending?
+ return handleInsufficientFunds(wex, proposalId, err);
+ }
+ }
- return TaskRunResult.backoff();
+ if (resp.status === HttpStatusCode.BadRequest) {
+ const err = await readTalerErrorResponse(resp);
+ if (
+ err.code ===
+ TalerErrorCode.MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_NOT_FOUND
+ ) {
+ return handleInsufficientFunds(wex, proposalId, err);
}
}
@@ -3459,16 +3528,19 @@ export async function validateAndStoreToken(
const token: TokenRecord = {
tokenIssueSig,
- ...slate
+ ...slate,
};
// insert token and delete slate
- await wex.db.runReadWriteTx({
- storeNames: ["slates", "tokens"]
- }, async (tx) => {
- await tx.tokens.add(token);
- await tx.slates.delete(slate.tokenUsePub);
- });
+ await wex.db.runReadWriteTx(
+ {
+ storeNames: ["slates", "tokens"],
+ },
+ async (tx) => {
+ await tx.tokens.add(token);
+ await tx.slates.delete(slate.tokenUsePub);
+ },
+ );
}
export async function generateTokenSigs(
@@ -3480,15 +3552,18 @@ export async function generateTokenSigs(
): Promise<TokenUseSig[]> {
const tokens: TokenRecord[] = [];
const sigs: TokenUseSig[] = [];
- await wex.db.runReadOnlyTx({
- storeNames: ["tokens", "purchases"],
- }, async (tx) => {
- for (const pub of tokenPubs) {
- const token = await tx.tokens.get(pub);
- checkDbInvariant(!!token, `token not found for ${pub}`);
- tokens.push(token);
- }
- });
+ await wex.db.runReadOnlyTx(
+ {
+ storeNames: ["tokens", "purchases"],
+ },
+ async (tx) => {
+ for (const pub of tokenPubs) {
+ const token = await tx.tokens.get(pub);
+ checkDbInvariant(!!token, `token not found for ${pub}`);
+ tokens.push(token);
+ }
+ },
+ );
for (const token of tokens) {
if (token.tokenUseSig && token.purchaseId === proposalId) {
@@ -3510,16 +3585,19 @@ export async function generateTokenSigs(
});
}
- await wex.db.runReadWriteTx({
- storeNames: ["tokens"],
- }, async (tx) => {
- for (let i = 0; i < sigs.length; i++) {
- const token = tokens[i];
- const sig = sigs[i];
- token.tokenUseSig = sig;
- tx.tokens.put(token);
- }
- });
+ await wex.db.runReadWriteTx(
+ {
+ storeNames: ["tokens"],
+ },
+ async (tx) => {
+ for (let i = 0; i < sigs.length; i++) {
+ const token = tokens[i];
+ const sig = sigs[i];
+ token.tokenUseSig = sig;
+ tx.tokens.put(token);
+ }
+ },
+ );
return sigs;
}
@@ -3529,17 +3607,22 @@ export async function cleanupUsedTokens(
proposalId: string,
choiceIndex: number,
): Promise<void> {
- await wex.db.runReadWriteTx({
- storeNames: ["tokens"]
- }, async (tx) => {
- const tokenPubs = await tx.tokens.indexes.byPurchaseIdAndChoiceIndex.getAllKeys(
- [proposalId, choiceIndex],
- );
+ await wex.db.runReadWriteTx(
+ {
+ storeNames: ["tokens"],
+ },
+ async (tx) => {
+ const tokenPubs =
+ await tx.tokens.indexes.byPurchaseIdAndChoiceIndex.getAllKeys([
+ proposalId,
+ choiceIndex,
+ ]);
- for (const pub of tokenPubs) {
- tx.tokens.delete(pub);
- }
- });
+ for (const pub of tokenPubs) {
+ tx.tokens.delete(pub);
+ }
+ },
+ );
}
export async function refuseProposal(
@@ -4055,8 +4138,8 @@ async function processPurchaseAutoRefund(
const index = purchase.choiceIndex;
if (index === undefined)
throw Error("choice index not specified for contract v1");
- if ((index in contractData.choices))
- throw Error(`invalid choice index ${index}`)
+ if (index in contractData.choices)
+ throw Error(`invalid choice index ${index}`);
amount = contractData.choices[index].amount;
break;
default:
@@ -4090,8 +4173,7 @@ async function processPurchaseAutoRefund(
},
);
- const fullyRefunded =
- Amounts.cmp(amount, totalKnownRefund) <= 0;
+ const fullyRefunded = Amounts.cmp(amount, totalKnownRefund) <= 0;
// We stop with the auto-refund state when the auto-refund period
// is over or the product is already fully refunded.
@@ -4374,7 +4456,7 @@ async function processPurchaseAcceptRefund(
method: "POST",
body: {
h_contract: download.contractData.contractTermsHash,
- }
+ },
});
const refundResponse = await readSuccessResponseJsonOrThrow(
@@ -4532,8 +4614,8 @@ async function storeRefunds(
const index = purchase.choiceIndex;
if (index === undefined)
throw Error("choice index not specified for contract v1");
- if ((index in contractData.choices))
- throw Error(`invalid choice index ${index}`)
+ if (index in contractData.choices)
+ throw Error(`invalid choice index ${index}`);
amount = contractData.choices[index].amount;
break;
default: