Contents

confirmPay#

Confirm a payment that was previously prepared with preparePayForUriV2, preparePayForTemplateV2 or preparePayForPaivana.

The wallet selects the coins (and, for a contract v1 order, the tokens) for the payment and submits it to the merchant. For a contract v1 order, choiceIndex must refer to one of the choices returned by getChoicesForPayment.

Request:

The args must be a ConfirmPayRequest object.

Response:

On success, the result is a ConfirmPayResult object.

Side effects:

Confirms the prepared payment: the wallet spends the coins and submits the payment to the merchant over the network.

Expected errors:

The caller can handle the following errors inline: WALLET_PAY_MERCHANT_INSUFFICIENT_BALANCE, WALLET_TRANSACTION_NOT_FOUND, WALLET_REQUEST_TRANSACTION_STATE_UNSUPPORTED, WALLET_CORE_API_BAD_REQUEST.

Details:

Unless noWait is set, the operation waits for the first payment result: when the payment succeeded, the result has type done; when the payment could not be completed (yet), the result has type pending and carries the last error of the payment. With noWait, the operation returns a pending result immediately and the payment status is communicated via notifications.

The WALLET_PAY_MERCHANT_INSUFFICIENT_BALANCE error carries insufficientBalanceDetails of type PaymentInsufficientBalanceDetails in its error detail.

// Detailed reason for why the wallet's balance is insufficient.
//
// Current wallet-core versions emit all structured fields.  The
// legacy-only alternative lets clients continue decoding responses
// from older cores without allowing partially populated structured
// diagnostics.
type PaymentInsufficientBalanceDetails =
  PaymentInsufficientBalanceCompatibilityDetails &
    (PaymentInsufficientBalanceStructuredDetails
     | PaymentInsufficientBalanceLegacyOnly);
// Structured explanation emitted by current wallet-core versions.
interface PaymentInsufficientBalanceStructuredDetails {
  // Balance in the requested sender scope before payment
  // restrictions.
  balance: CoinSelectionBalanceSnapshot;

  // Maximum contribution attainable under the failed request's
  // actual restrictions and fee policy.  For peer payments this is
  // the maximum at one exchange, since peer payments cannot combine
  // exchanges.
  maximumPayableAmount: AmountString;

  // Operation-wide reasons, in deterministic evaluation order.
  reasons: CoinSelectionFailureReason[];

  // Detailed analysis for every same-currency exchange known to
  // the wallet.
  exchanges: Record<string, CoinSelectionExchangeFailureDiagnostics>;
}
// Balance amounts before age, receiver, wire and fee restrictions.
interface CoinSelectionBalanceSnapshot {
  // Balance that the wallet believes it can spend immediately.
  material: AmountString;

  // Expected effective output of unfinished refresh operations.
  pendingRefresh: AmountString;

  // Material balance plus pending refresh output.
  available: AmountString;
}
interface CoinSelectionExchangeFailureDiagnostics {
  // Balance held at this exchange before payment restrictions.
  balance: CoinSelectionBalanceSnapshot;

  // Maximum contribution selectable from this exchange for
  // this request.
  maximumPayableAmount: AmountString;

  // Exchange-local reasons, in deterministic evaluation order.
  reasons: CoinSelectionFailureReason[];
}
// Request context and compatibility fields shared by old and current
// insufficient-balance responses.  Fields marked as deprecated are
// compatibility-only and planned for removal after consumers migrate.
interface PaymentInsufficientBalanceCompatibilityDetails {
  // Amount requested by the merchant.
  amountRequested: AmountString;

  // Wire method for the requested payment, only applicable
  // for merchant payments.
  wireMethod?: string | undefined;

  // Hint as to why the balance is insufficient.
  //
  // If this hint is not provided, the balance hints of the
  // individual exchanges should be shown, as the overall reason
  // might be a combination of the reasons for different exchanges.
  //
  // Deprecated: use reasons.
  causeHint?: InsufficientBalanceHint;

  // Balance of type "available".
  // Deprecated: use balance.available.
  balanceAvailable: AmountString;

  // Balance of type "material".
  // Deprecated: use balance.material.
  balanceMaterial: AmountString;

  // Balance of type "age-acceptable".
  // Deprecated: use reasons and exchanges.
  balanceAgeAcceptable: AmountString;

  // Balance of type "receiver-acceptable".
  // Deprecated: use reasons and exchanges.
  balanceReceiverAcceptable: AmountString;

  // Balance of type "receiver-exchange-url-acceptable".
  // Deprecated: use exchanges[url].reasons.
  balanceReceiverExchangeUrlAcceptable: AmountString;

  // Balance of type "receiver-exchange-pub-acceptable".
  // Deprecated: use exchanges[url].reasons.
  balanceReceiverExchangePubAcceptable: AmountString;

  // Balance of type "receiver-auditor-url-acceptable".
  // Deprecated: use exchanges[url].reasons.
  balanceReceiverAuditorUrlAcceptable: AmountString;

  // Balance of type "merchant-depositable".
  // Deprecated: use maximumPayableAmount and reasons.
  balanceReceiverDepositable: AmountString;

  // Deprecated: use maximumPayableAmount and reasons.
  balanceExchangeDepositable: AmountString;

  // Maximum effective amount that the wallet can spend,
  // when all fees are paid by the wallet.
  // Deprecated: use maximumPayableAmount.
  maxEffectiveSpendAmount: AmountString;

  // Deprecated: use exchanges.
  perExchange: {
    [url: string]: {
      // Deprecated: use exchanges[url].balance.available.
      balanceAvailable: AmountString;

      // Deprecated: use exchanges[url].balance.material.
      balanceMaterial: AmountString;

      // Deprecated: use exchanges[url].maximumPayableAmount
      // and exchanges[url].reasons.
      balanceExchangeDepositable: AmountString;

      // Deprecated: use exchanges[url].reasons.
      balanceAgeAcceptable: AmountString;

      // Deprecated: use exchanges[url].reasons.
      balanceReceiverAcceptable: AmountString;

      // Deprecated: use exchanges[url].reasons.
      balanceReceiverExchangeUrlAcceptable: AmountString;

      // Deprecated: use exchanges[url].reasons.
      balanceReceiverExchangePubAcceptable: AmountString;

      // Deprecated: use exchanges[url].reasons.
      balanceReceiverAuditorUrlAcceptable: AmountString;

      // Deprecated: use exchanges[url].maximumPayableAmount.
      balanceReceiverDepositable: AmountString;

      // Deprecated: use exchanges[url].maximumPayableAmount.
      maxEffectiveSpendAmount: AmountString;

      // The exchange master public key configured by the merchant
      // backend differs from the one of the coins stored in
      // the wallet.
      // Deprecated: use the receiver-exchange-master-pub-mismatch
      // reason.
      exchangeMasterPubMismatch: boolean;

      // Exchange doesn't have global fees configured for the
      // relevant year, p2p payments aren't possible.
      // Deprecated: use the exchange-global-fees-unavailable reason.
      missingGlobalFees: boolean;

      // Hint that UIs should show to explain the insufficient
      // balance.
      // Deprecated: use exchanges[url].reasons.
      causeHint?: InsufficientBalanceHint | undefined;
    };
  };
}
// Alternative emitted by older cores: none of the structured
// fields are present.
interface PaymentInsufficientBalanceLegacyOnly {
  balance?: undefined;
  maximumPayableAmount?: undefined;
  reasons?: undefined;
  exchanges?: undefined;
}
// Machine-readable reasons that prevented a requested coin
// selection.  Unlike InsufficientBalanceHint, these values are
// exhaustive and can be reported together.  Consumers must branch
// on the discriminator instead of assuming that the first entry
// is the only cause.
type CoinSelectionFailureReason =
  | {
      type: CoinSelectionFailureReasonType.AvailableBalanceInsufficient;
      amountAvailable: AmountString;
    }
  | {
      type: CoinSelectionFailureReasonType.PendingRefresh;
      amountPendingRefresh: AmountString;
    }
  | {
      type: CoinSelectionFailureReasonType.MinimumAge;
      requiredMinimumAge: number;
      amountAgeAcceptable: AmountString;
    }
  | {
      type: CoinSelectionFailureReasonType.ScopeRestricted;
      scopeInfo: ScopeInfo;
    }
  | { type: CoinSelectionFailureReasonType.ReceiverNotAccepted }
  | {
      type: CoinSelectionFailureReasonType.ReceiverExchangeMasterPubMismatch;
      walletMasterPub: string;
      receiverMasterPubs: string[];
    }
  | {
      type: CoinSelectionFailureReasonType.WireMethodUnsupported;
      wireMethod: string;
    }
  | {
      type: CoinSelectionFailureReasonType.WireFeeUnavailable;
      wireMethod: string;
    }
  | {
      type: CoinSelectionFailureReasonType.DepositAccountRestricted;
      wireMethod: string;
      accountRestrictions: Record<string, AccountRestriction[]>;
    }
  | { type: CoinSelectionFailureReasonType.ExchangeGlobalFeesUnavailable }
  | {
      type: CoinSelectionFailureReasonType.FeesNotCovered;
      maximumPayableAmount: AmountString;
    }
  | {
      type: CoinSelectionFailureReasonType.BalanceFragmented;
      combinedMaximumPayableAmount: AmountString;
    }
  | {
      type: CoinSelectionFailureReasonType.SupersededExchangeMasterPub;
      amountAffected: AmountString;
    }
  | { type: CoinSelectionFailureReasonType.SelectionFailed };
type CoinSelectionFailureReasonType =
  | "available-balance-insufficient"
  | "pending-refresh"
  | "minimum-age"
  | "scope-restricted"
  | "receiver-not-accepted"
  | "receiver-exchange-master-pub-mismatch"
  | "wire-method-unsupported"
  | "wire-fee-unavailable"
  | "deposit-account-restricted"
  | "exchange-global-fees-unavailable"
  | "fees-not-covered"
  | "balance-fragmented"
  | "superseded-exchange-master-pub"
  | "selection-failed";
// Deprecated: use CoinSelectionFailureReason instead.
type InsufficientBalanceHint =
  // Merchant doesn't accept money from exchange(s) that the
  // wallet supports.
  | "merchant-accept-insufficient"
  // Merchant accepts funds from a matching exchange, but the funds
  // can't be deposited with the wire method.
  | "merchant-deposit-insufficient"
  // While in principle the balance is sufficient, the age
  // restriction on coins causes the spendable balance to be
  // insufficient.
  | "age-restricted"
  // Wallet has enough available funds, but the material funds are
  // insufficient.  Usually because there is a pending refresh
  // operation.
  | "wallet-balance-material-insufficient"
  // The wallet simply doesn't have enough available funds.
  | "wallet-balance-available-insufficient"
  // Exchange is missing the global fee configuration, thus fees are
  // unknown and funds from this exchange can't be used for p2p
  // payments.
  | "exchange-missing-global-fees"
  // Even though the balance looks sufficient for the instructed
  // amount, the fees can be covered by neither the merchant nor
  // the remaining wallet balance.
  | "fees-not-covered";
interface ConfirmPayRequest {
  // Transaction identifier of the prepared payment.
  transactionId: TransactionIdStr;

  // Request that a donation receipt for this payment is collected
  // via the configured donau, if the selected choice offers a
  // matching tax-receipt output.
  useDonau?: boolean;

  // Session ID override for the payment.
  sessionId?: string;

  // Currently ignored by wallet-core.
  forcedCoinSel?: ForcedCoinSel;

  // Legacy compatibility option for v1 orders.  Ignored: tokens can
  // only be spent at their issuing merchant, even when this is true.
  forcedTokenSel?: boolean;

  // Only applies to v1 orders.
  choiceIndex?: number;

  // Do not wait for the first payment success or error
  // before returning a response.  Instead, status will
  // be communicated via notifications.
  //
  // Will become the default in future versions.
  noWait?: boolean;
}
// Result for confirmPay.
type ConfirmPayResult = ConfirmPayResultDone | ConfirmPayResultPending;
interface ConfirmPayResultDone {
  type: ConfirmPayResultType.Done;
  contractTerms: MerchantContractTermsV0;
  transactionId: TransactionIdStr;
}
interface ConfirmPayResultPending {
  type: ConfirmPayResultType.Pending;
  transactionId: TransactionIdStr;
  lastError?: TalerErrorDetail | undefined;
}
type ConfirmPayResultType = "done" | "pending";