diff options
Diffstat (limited to 'packages/taler-util/src/transactions-types.ts')
-rw-r--r-- | packages/taler-util/src/transactions-types.ts | 795 |
1 files changed, 795 insertions, 0 deletions
diff --git a/packages/taler-util/src/transactions-types.ts b/packages/taler-util/src/transactions-types.ts new file mode 100644 index 000000000..cee3de9fa --- /dev/null +++ b/packages/taler-util/src/transactions-types.ts @@ -0,0 +1,795 @@ +/* + This file is part of GNU Taler + (C) 2019 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Type and schema definitions for the wallet's transaction list. + * + * @author Florian Dold + * @author Torsten Grote + */ + +/** + * Imports. + */ +import { + Codec, + buildCodecForObject, + codecForAny, + codecForBoolean, + codecForConstString, + codecForEither, + codecForList, + codecForString, + codecOptional, +} from "./codec.js"; +import { + AmountString, + InternationalizedString, + MerchantInfo, + codecForInternationalizedString, + codecForMerchantInfo, +} from "./taler-types.js"; +import { TalerPreciseTimestamp, TalerProtocolTimestamp } from "./time.js"; +import { + RefreshReason, + ScopeInfo, + TalerErrorDetail, + TransactionIdStr, + TransactionStateFilter, + WithdrawalExchangeAccountDetails, + codecForScopeInfo, +} from "./wallet-types.js"; + +export interface TransactionsRequest { + /** + * return only transactions in the given currency + * + * it will be removed in next release + * + * @deprecated use scopeInfo + */ + currency?: string; + + /** + * return only transactions in the given scopeInfo + */ + scopeInfo?: ScopeInfo; + + /** + * if present, results will be limited to transactions related to the given search string + */ + search?: string; + + /** + * Sort order of the transaction items. + * By default, items are sorted ascending by their + * main timestamp. + * + * ascending: ascending by timestamp, but pending transactions first + * descending: ascending by timestamp, but pending transactions first + * stable-ascending: ascending by timestamp, with pending transactions amidst other transactions + * (stable in the sense of: pending transactions don't jump around) + */ + sort?: "ascending" | "descending" | "stable-ascending"; + + /** + * If true, include all refreshes in the transactions list. + */ + includeRefreshes?: boolean; + + filterByState?: TransactionStateFilter; +} + +export interface TransactionState { + major: TransactionMajorState; + minor?: TransactionMinorState; +} + +export enum TransactionMajorState { + // No state, only used when reporting transitions into the initial state + None = "none", + Pending = "pending", + Done = "done", + Aborting = "aborting", + Aborted = "aborted", + Suspended = "suspended", + Dialog = "dialog", + SuspendedAborting = "suspended-aborting", + Failed = "failed", + Expired = "expired", + // Only used for the notification, never in the transaction history + Deleted = "deleted", +} + +export enum TransactionMinorState { + // Placeholder until D37 is fully implemented + Unknown = "unknown", + Deposit = "deposit", + KycRequired = "kyc", + AmlRequired = "aml", + MergeKycRequired = "merge-kyc", + Track = "track", + SubmitPayment = "submit-payment", + RebindSession = "rebind-session", + Refresh = "refresh", + Pickup = "pickup", + AutoRefund = "auto-refund", + User = "user", + Bank = "bank", + Exchange = "exchange", + ClaimProposal = "claim-proposal", + CheckRefund = "check-refund", + CreatePurse = "create-purse", + DeletePurse = "delete-purse", + RefreshExpired = "refresh-expired", + Ready = "ready", + Merge = "merge", + Repurchase = "repurchase", + BankRegisterReserve = "bank-register-reserve", + BankConfirmTransfer = "bank-confirm-transfer", + WithdrawCoins = "withdraw-coins", + ExchangeWaitReserve = "exchange-wait-reserve", + AbortingBank = "aborting-bank", + Aborting = "aborting", + Refused = "refused", + Withdraw = "withdraw", + MerchantOrderProposed = "merchant-order-proposed", + Proposed = "proposed", + RefundAvailable = "refund-available", + AcceptRefund = "accept-refund", + PaidByOther = "paid-by-other", + CompletedByOtherWallet = "completed-by-other-wallet", +} + +export enum TransactionAction { + Delete = "delete", + Suspend = "suspend", + Resume = "resume", + Abort = "abort", + Fail = "fail", + Retry = "retry", +} + +export interface TransactionsResponse { + // a list of past and pending transactions sorted by pending, timestamp and transactionId. + // In case two events are both pending and have the same timestamp, + // they are sorted by the transactionId + // (lexically ascending and locale-independent comparison). + transactions: Transaction[]; +} + +export interface TransactionCommon { + // opaque unique ID for the transaction, used as a starting point for paginating queries + // and for invoking actions on the transaction (e.g. deleting/hiding it from the history) + transactionId: TransactionIdStr; + + // the type of the transaction; different types might provide additional information + type: TransactionType; + + // main timestamp of the transaction + timestamp: TalerPreciseTimestamp; + + /** + * Transaction state, as per DD37. + */ + txState: TransactionState; + + /** + * Possible transitions based on the current state. + */ + txActions: TransactionAction[]; + + /** + * Raw amount of the transaction (exclusive of fees or other extra costs). + */ + amountRaw: AmountString; + + /** + * Amount added or removed from the wallet's balance (including all fees and other costs). + */ + amountEffective: AmountString; + + error?: TalerErrorDetail; + + /** + * If the transaction minor state is in KycRequired this field is going to + * have the location where the user need to go to complete KYC information. + */ + kycUrl?: string; +} + +export type Transaction = + | TransactionWithdrawal + | TransactionPayment + | TransactionRefund + | TransactionRefresh + | TransactionDeposit + | TransactionPeerPullCredit + | TransactionPeerPullDebit + | TransactionPeerPushCredit + | TransactionPeerPushDebit + | TransactionInternalWithdrawal + | TransactionRecoup + | TransactionDenomLoss; + +export enum TransactionType { + Withdrawal = "withdrawal", + InternalWithdrawal = "internal-withdrawal", + Payment = "payment", + Refund = "refund", + Refresh = "refresh", + Deposit = "deposit", + PeerPushDebit = "peer-push-debit", + PeerPushCredit = "peer-push-credit", + PeerPullDebit = "peer-pull-debit", + PeerPullCredit = "peer-pull-credit", + Recoup = "recoup", + DenomLoss = "denom-loss", +} + +export enum WithdrawalType { + TalerBankIntegrationApi = "taler-bank-integration-api", + ManualTransfer = "manual-transfer", +} + +export type WithdrawalDetails = + | WithdrawalDetailsForManualTransfer + | WithdrawalDetailsForTalerBankIntegrationApi; + +interface WithdrawalDetailsForManualTransfer { + type: WithdrawalType.ManualTransfer; + + /** + * Payto URIs that the exchange supports. + * + * Already contains the amount and message. + * + * @deprecated in favor of exchangeCreditAccounts + */ + exchangePaytoUris: string[]; + + exchangeCreditAccountDetails?: WithdrawalExchangeAccountDetails[]; + + // Public key of the reserve + reservePub: string; + + /** + * Is the reserve ready for withdrawal? + */ + reserveIsReady: boolean; +} + +interface WithdrawalDetailsForTalerBankIntegrationApi { + type: WithdrawalType.TalerBankIntegrationApi; + + /** + * Set to true if the bank has confirmed the withdrawal, false if not. + * An unconfirmed withdrawal usually requires user-input and should be highlighted in the UI. + * See also bankConfirmationUrl below. + */ + confirmed: boolean; + + /** + * If the withdrawal is unconfirmed, this can include a URL for user + * initiated confirmation. + */ + bankConfirmationUrl?: string; + + // Public key of the reserve + reservePub: string; + + /** + * Is the reserve ready for withdrawal? + */ + reserveIsReady: boolean; + + exchangeCreditAccountDetails?: WithdrawalExchangeAccountDetails[]; +} + +export enum DenomLossEventType { + DenomExpired = "denom-expired", + DenomVanished = "denom-vanished", + DenomUnoffered = "denom-unoffered", +} + +/** + * A transaction to indicate financial loss due to denominations + * that became unusable for deposits. + */ +export interface TransactionDenomLoss extends TransactionCommon { + type: TransactionType.DenomLoss; + lossEventType: DenomLossEventType; + exchangeBaseUrl: string; +} + +/** + * A withdrawal transaction (either bank-integrated or manual). + */ +export interface TransactionWithdrawal extends TransactionCommon { + type: TransactionType.Withdrawal; + + /** + * Exchange of the withdrawal. + */ + exchangeBaseUrl: string; + + /** + * Amount that got subtracted from the reserve balance. + */ + amountRaw: AmountString; + + /** + * Amount that actually was (or will be) added to the wallet's balance. + */ + amountEffective: AmountString; + + withdrawalDetails: WithdrawalDetails; +} + +/** + * Internal withdrawal operation, only reported on request. + * + * Some transactions (peer-*-credit) internally do a withdrawal, + * but only the peer-*-credit transaction is reported. + * + * The internal withdrawal transaction allows to access the details of + * the underlying withdrawal for testing/debugging. + * + * It is usually not reported, so that amounts of transactions properly + * add up, since the amountEffecive of the withdrawal is already reported + * in the peer-*-credit transaction. + */ +export interface TransactionInternalWithdrawal extends TransactionCommon { + type: TransactionType.InternalWithdrawal; + + /** + * Exchange of the withdrawal. + */ + exchangeBaseUrl: string; + + /** + * Amount that got subtracted from the reserve balance. + */ + amountRaw: AmountString; + + /** + * Amount that actually was (or will be) added to the wallet's balance. + */ + amountEffective: AmountString; + + withdrawalDetails: WithdrawalDetails; +} + +export interface PeerInfoShort { + expiration: TalerProtocolTimestamp | undefined; + summary: string | undefined; +} + +/** + * Credit because we were paid for a P2P invoice we created. + */ +export interface TransactionPeerPullCredit extends TransactionCommon { + type: TransactionType.PeerPullCredit; + + info: PeerInfoShort; + /** + * Exchange used. + */ + exchangeBaseUrl: string; + + /** + * Amount that got subtracted from the reserve balance. + */ + amountRaw: AmountString; + + /** + * Amount that actually was (or will be) added to the wallet's balance. + */ + amountEffective: AmountString; + + /** + * URI to send to the other party. + * + * Only available in the right state. + */ + talerUri: string | undefined; +} + +/** + * Debit because we paid someone's invoice. + */ +export interface TransactionPeerPullDebit extends TransactionCommon { + type: TransactionType.PeerPullDebit; + + info: PeerInfoShort; + /** + * Exchange used. + */ + exchangeBaseUrl: string; + + amountRaw: AmountString; + + amountEffective: AmountString; +} + +/** + * We sent money via a P2P payment. + */ +export interface TransactionPeerPushDebit extends TransactionCommon { + type: TransactionType.PeerPushDebit; + + info: PeerInfoShort; + /** + * Exchange used. + */ + exchangeBaseUrl: string; + + /** + * Amount that got subtracted from the reserve balance. + */ + amountRaw: AmountString; + + /** + * Amount that actually was (or will be) added to the wallet's balance. + */ + amountEffective: AmountString; + + /** + * URI to accept the payment. + * + * Only present if the transaction is in a state where the other party can + * accept the payment. + */ + talerUri?: string; +} + +/** + * We received money via a P2P payment. + */ +export interface TransactionPeerPushCredit extends TransactionCommon { + type: TransactionType.PeerPushCredit; + + info: PeerInfoShort; + /** + * Exchange used. + */ + exchangeBaseUrl: string; + + /** + * Amount that got subtracted from the reserve balance. + */ + amountRaw: AmountString; + + /** + * Amount that actually was (or will be) added to the wallet's balance. + */ + amountEffective: AmountString; +} + +/** + * The exchange revoked a key and the wallet recoups funds. + */ +export interface TransactionRecoup extends TransactionCommon { + type: TransactionType.Recoup; +} + +export enum PaymentStatus { + /** + * Explicitly aborted after timeout / failure + */ + Aborted = "aborted", + + /** + * Payment failed, wallet will auto-retry. + * User should be given the option to retry now / abort. + */ + Failed = "failed", + + /** + * Paid successfully + */ + Paid = "paid", + + /** + * User accepted, payment is processing. + */ + Accepted = "accepted", +} + +export interface TransactionPayment extends TransactionCommon { + type: TransactionType.Payment; + + /** + * Additional information about the payment. + */ + info: OrderShortInfo; + + /** + * Wallet-internal end-to-end identifier for the payment. + */ + proposalId: string; + + /** + * Amount that must be paid for the contract + */ + amountRaw: AmountString; + + /** + * Amount that was paid, including deposit, wire and refresh fees. + */ + amountEffective: AmountString; + + /** + * Amount that has been refunded by the merchant + */ + totalRefundRaw: AmountString; + + /** + * Amount will be added to the wallet's balance after fees and refreshing + */ + totalRefundEffective: AmountString; + + /** + * Amount pending to be picked up + */ + refundPending: AmountString | undefined; + + /** + * Reference to applied refunds + */ + refunds: RefundInfoShort[]; + + /** + * Is the wallet currently checking for a refund? + */ + refundQueryActive: boolean; + + /** + * Does this purchase has an pos validation + */ + posConfirmation: string | undefined; +} + +export interface OrderShortInfo { + /** + * Order ID, uniquely identifies the order within a merchant instance + */ + orderId: string; + + /** + * Hash of the contract terms. + */ + contractTermsHash: string; + + /** + * More information about the merchant + */ + merchant: MerchantInfo; + + /** + * Summary of the order, given by the merchant + */ + summary: string; + + /** + * Map from IETF BCP 47 language tags to localized summaries + */ + summary_i18n?: InternationalizedString; + + /** + * URL of the fulfillment, given by the merchant + */ + fulfillmentUrl?: string; + + /** + * Plain text message that should be shown to the user + * when the payment is complete. + */ + fulfillmentMessage?: string; + + /** + * Translations of fulfillmentMessage. + */ + fulfillmentMessage_i18n?: InternationalizedString; +} + +export interface RefundInfoShort { + transactionId: string; + timestamp: TalerProtocolTimestamp; + amountEffective: AmountString; + amountRaw: AmountString; +} + +/** + * Summary information about the payment that we got a refund for. + */ +export interface RefundPaymentInfo { + summary: string; + summary_i18n?: InternationalizedString; + /** + * More information about the merchant + */ + merchant: MerchantInfo; +} + +export interface TransactionRefund extends TransactionCommon { + type: TransactionType.Refund; + + // Amount that has been refunded by the merchant + amountRaw: AmountString; + + // Amount will be added to the wallet's balance after fees and refreshing + amountEffective: AmountString; + + // ID for the transaction that is refunded + refundedTransactionId: string; + + paymentInfo: RefundPaymentInfo | undefined; +} + +/** + * A transaction shown for refreshes. + * Only shown for (1) refreshes not associated with other transactions + * and (2) refreshes in an error state. + */ +export interface TransactionRefresh extends TransactionCommon { + type: TransactionType.Refresh; + + refreshReason: RefreshReason; + + /** + * Transaction ID that caused this refresh. + */ + originatingTransactionId?: string; + + /** + * Always zero for refreshes + */ + amountRaw: AmountString; + + /** + * Fees, i.e. the effective, negative effect of the refresh + * on the balance. + * + * Only applicable for stand-alone refreshes, and zero for + * other refreshes where the transaction itself accounts for the + * refresh fee. + */ + amountEffective: AmountString; + + refreshInputAmount: AmountString; + refreshOutputAmount: AmountString; +} + +export interface DepositTransactionTrackingState { + // Raw wire transfer identifier of the deposit. + wireTransferId: string; + // When was the wire transfer given to the bank. + timestampExecuted: TalerProtocolTimestamp; + // Total amount transfer for this wtid (including fees) + amountRaw: AmountString; + // Wire fee amount for this exchange + wireFee: AmountString; +} + +/** + * Deposit transaction, which effectively sends + * money from this wallet somewhere else. + */ +export interface TransactionDeposit extends TransactionCommon { + type: TransactionType.Deposit; + + depositGroupId: string; + + /** + * Target for the deposit. + */ + targetPaytoUri: string; + + /** + * Raw amount that is being deposited + */ + amountRaw: AmountString; + + /** + * Effective amount that is being deposited + */ + amountEffective: AmountString; + + wireTransferDeadline: TalerProtocolTimestamp; + + wireTransferProgress: number; + + /** + * Did all the deposit requests succeed? + */ + deposited: boolean; + + trackingState: Array<DepositTransactionTrackingState>; +} + +export interface TransactionByIdRequest { + transactionId: string; +} + +export const codecForTransactionByIdRequest = + (): Codec<TransactionByIdRequest> => + buildCodecForObject<TransactionByIdRequest>() + .property("transactionId", codecForString()) + .build("TransactionByIdRequest"); + +export interface WithdrawalTransactionByURIRequest { + talerWithdrawUri: string; +} + +export const codecForWithdrawalTransactionByURIRequest = + (): Codec<WithdrawalTransactionByURIRequest> => + buildCodecForObject<WithdrawalTransactionByURIRequest>() + .property("talerWithdrawUri", codecForString()) + .build("WithdrawalTransactionByURIRequest"); + +export const codecForTransactionsRequest = (): Codec<TransactionsRequest> => + buildCodecForObject<TransactionsRequest>() + .property("currency", codecOptional(codecForString())) + .property("scopeInfo", codecOptional(codecForScopeInfo())) + .property("search", codecOptional(codecForString())) + .property( + "sort", + codecOptional( + codecForEither( + codecForConstString("ascending"), + codecForConstString("descending"), + codecForConstString("stable-ascending"), + ), + ), + ) + .property("includeRefreshes", codecOptional(codecForBoolean())) + .build("TransactionsRequest"); + +// FIXME: do full validation here! +export const codecForTransactionsResponse = (): Codec<TransactionsResponse> => + buildCodecForObject<TransactionsResponse>() + .property("transactions", codecForList(codecForAny())) + .build("TransactionsResponse"); + +export const codecForOrderShortInfo = (): Codec<OrderShortInfo> => + buildCodecForObject<OrderShortInfo>() + .property("contractTermsHash", codecForString()) + .property("fulfillmentMessage", codecOptional(codecForString())) + .property( + "fulfillmentMessage_i18n", + codecOptional(codecForInternationalizedString()), + ) + .property("fulfillmentUrl", codecOptional(codecForString())) + .property("merchant", codecForMerchantInfo()) + .property("orderId", codecForString()) + .property("summary", codecForString()) + .property("summary_i18n", codecOptional(codecForInternationalizedString())) + .build("OrderShortInfo"); + +export interface ListAssociatedRefreshesRequest { + transactionId: string; +} + +export const codecForListAssociatedRefreshesRequest = + (): Codec<ListAssociatedRefreshesRequest> => + buildCodecForObject<ListAssociatedRefreshesRequest>() + .property("transactionId", codecForString()) + .build("ListAssociatedRefreshesRequest"); + +export interface ListAssociatedRefreshesResponse { + transactionIds: string[]; +} |