/* * This file is part of the Flowee project * Copyright (C) 2020-2024 Tom Zander * * This program 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 of the License, or * (at your option) any later version. * * This program 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 this program. If not, see . */ #ifndef PAYMENT_H #define PAYMENT_H #include "FloweePay.h" #include "RepeatPaymentDetails.h" #include #include #include class Wallet; class PaymentDetail; class PaymentDetailOutput; class PaymentDetailInputs; class PaymentDetailComment; class AccountInfo; class TxInfoObject; /** * This represents a single payment, possibly a complex one. * * Using the Payment object we allow building a single transaction that can vary in * complexity from paying one single address. Using 'PaymentDetails' the payment can * be enriched to excert more control over what funds are sent to what remote parties. * * To enable simple and direct usability of this class there is a small subset of features * available directly on the Payment object. Avoiding the need to create or iterate the child * details objects. * * For this we recognize two modes; either there is exactly one payment-destination (output) * or there are additional details. * In the case of the simple, 1-desination setup we provide the setPaymentAmount() and * setPaymentAmountFiat() methods. Represented in the properties paymentAmount and paymentAmountFiat. * * When the UI allows adding more details, or scanning a payment protocol (which may do the same) * you should avoid writing to those two properties and if you do so anyway we may throw an exception * which will cause the application to abort. */ class Payment : public QObject { Q_OBJECT Q_PROPERTY(int feePerByte READ feePerByte WRITE setFeePerByte NOTIFY feePerByteChanged) /// The payment-wide amount of funds being sent by this Payment. Q_PROPERTY(double paymentAmount READ paymentAmount WRITE setPaymentAmount NOTIFY amountChanged) /// The payment-wide amount of funds being sent by this Payment. Q_PROPERTY(int paymentAmountFiat READ paymentAmountFiat WRITE setPaymentAmountFiat NOTIFY amountFiatChanged) /// The single-output address we will send to Q_PROPERTY(QString targetAddress READ targetAddress WRITE setTargetAddress NOTIFY targetAddressChanged) // cleaned up and re-formatted Q_PROPERTY(QString formattedTargetAddress READ formattedTargetAddress NOTIFY targetAddressChanged) // cleaned but short. Q_PROPERTY(QString niceAddress READ niceAddress NOTIFY targetAddressChanged) Q_PROPERTY(bool preferSchnorr READ preferSchnorr WRITE setPreferSchnorr NOTIFY preferSchnorrChanged) /// If input is valid, tx can be prepared. \see prepare() Q_PROPERTY(bool isValid READ validate NOTIFY validChanged) Q_PROPERTY(QList details READ paymentDetails NOTIFY paymentDetailsChanged) Q_PROPERTY(FloweePay::BroadcastStatus broadcastStatus READ broadcastStatus NOTIFY broadcastStatusChanged) /// The price of on BCH Q_PROPERTY(int fiatPrice READ fiatPrice WRITE setFiatPrice NOTIFY fiatPriceChanged) Q_PROPERTY(AccountInfo *account READ currentAccount WRITE setCurrentAccount NOTIFY currentAccountChanged) Q_PROPERTY(QString userComment READ userComment WRITE setUserComment NOTIFY userCommentChanged) Q_PROPERTY(bool walletNeedsPin READ walletNeedsPin NOTIFY walletPinChanged) Q_PROPERTY(bool autoPrepare READ autoPrepare WRITE setAutoPrepare NOTIFY autoPrepareChanged) Q_PROPERTY(bool instaPay READ allowInstaPay WRITE setAllowInstaPay NOTIFY allowInstaPayChanged) Q_PROPERTY(bool simpleAddressTarget READ simpleAddressTarget NOTIFY simpleAddressTargetChanged FINAL) Q_PROPERTY(RepeatPaymentDetails *repeat READ repeatDetails NOTIFY repeatDetailsChanged FINAL) // --- Stuff that becomes available / useful after prepare has been called: /// Tx has been prepared Q_PROPERTY(bool txPrepared READ txPrepared NOTIFY txPreparedChanged) Q_PROPERTY(QString txid READ txid NOTIFY txCreated) Q_PROPERTY(int assignedFee READ assignedFee NOTIFY txCreated) Q_PROPERTY(int txSize READ txSize NOTIFY txCreated) /// If prepare() failed, this is set. Q_PROPERTY(QString error READ error NOTIFY errorChanged) Q_PROPERTY(QStringList warnings READ warnings NOTIFY warningsChanged) public: enum DetailType { InputSelector, PayToAddress, CommentOutput // aka op-return }; Q_ENUM(DetailType) enum Warning { InsecureTransport, DownloadFailed, OfflineWarning }; Q_ENUM(Warning) Payment(QObject *parent = nullptr); Payment(const Streaming::ConstBuffer &stored, const std::shared_ptr &assignedWallet); void setFeePerByte(int sats); int feePerByte(); /** * Sats the amount of BCH on our single-detail payment. * * This method can no longer be used after addExtraOutput() have been called. * This method assumes a single output, which is the default for this class. * * This sets the amount to pay, in Satoshis, to the target address. */ void setPaymentAmount(double amount); /** * Returns the total amount of satoshis that are selected by outputs. */ double paymentAmount() const; void setPaymentAmountFiat(int amount); int paymentAmountFiat() const; /** * Sets the address to pay to. * * This method can no longer be used after addExtraOutput has been called. * This method assumes a single output, which is the default for this class. * @see pasteTargetAddress() */ void setTargetAddress(const QString &address); /** * Returns the address to pay to, as the user typed it. * * This method can no longer be used after addExtraOutput has been called. * This method assumes a single output, which is the default for this class. */ QString targetAddress() const; /** * Returns the validated and formatted address to pay to. * * This method can no longer be used after addExtraOutput has been called. * This method assumes a single output, which is the default for this class. */ QString formattedTargetAddress() const; /** * The nicest to display address version. * This will always return (if available) the BCH style (cash-address) address, without * the bitcoincash: prefix. */ QString niceAddress() const; bool walletNeedsPin() const; /// return true if all fields are correctly populated and we can prepare() bool validate() const; /// A user error occured during prepare() const QString &error() const; Q_INVOKABLE void prepare(); Q_INVOKABLE void markUserApproved(); Q_INVOKABLE void reset(); Q_INVOKABLE PaymentDetail* addExtraOutput(); Q_INVOKABLE PaymentDetail* addInputSelector(); Q_INVOKABLE PaymentDetail* addCommentOutput(); Q_INVOKABLE void remove(PaymentDetail *detail); /** * Set a should-be-correct address and return true if valid. * * Calling thie function is very similar to setting the property 'targetAddress' * with the main difference that should the address pasted not be a valid one, * this method returns false. * @see setTargetAddress */ Q_INVOKABLE bool pasteTargetAddress(const QString &address); /** * Unlock the account in order to allow funding of this payment. */ Q_INVOKABLE void decrypt(const QString &password); /// return the txid, should there be a transaction (otherwise empty string) QString txid() const; /// The fee decided to be used in 'prepare()'. int assignedFee() const; /// The size of the transaction we prepare()d. int txSize() const; /// Return true if prepare() successfully completed. bool txPrepared() const; /** * This returns a total fiat amount input into the prepared transaction. */ int effectiveFiatAmount() const; /** * This returns a total BCH (in sats) amount that went into the prepared transaction. */ double effectiveBchAmount() const; /// Return the wallet used by the previous prepare() /// \sa currentAccount std::shared_ptr wallet() const; bool preferSchnorr() const; void setPreferSchnorr(bool preferSchnorr); QList paymentDetails() const; FloweePay::BroadcastStatus broadcastStatus() const; /// returns true if at least one output requires a proper fiat price for payment. bool usesFiat() const; /// The exchange rate. The amount of cents for one BCH. int fiatPrice() const; /// The exchange rate. The amount of cents for one BCH. void setFiatPrice(int pricePerCoin); AccountInfo *currentAccount() const; void setCurrentAccount(AccountInfo *account); const QString &userComment() const; void setUserComment(const QString &comment); bool autoPrepare() const; void setAutoPrepare(bool newAutoPrepare); bool allowInstaPay() const; void setAllowInstaPay(bool allowIt); Tx tx() const; /* * When an issue occurred with a consumer of this class and the error should be shown to the user. */ void forwardError(const QString &error); /** * When an warning occurred with a consumer of this class and the warning should be shown to the user. */ void addWarning(Warning warning); QStringList warnings() const; Q_INVOKABLE void clearWarnings(); /// Bypass the broadcast mechanism and mark the transaction as received. void confirmReceivedOk(); bool simpleAddressTarget() const; void setSimpleAddressTarget(bool newSimpleAddressTarget); Q_INVOKABLE void makeRepeating(); RepeatPaymentDetails *repeatDetails() const; void setRepeatDetails(RepeatPaymentDetails *newRepeatDetails); // Store the Payment and its details in a blob. Streaming::ConstBuffer save(const std::shared_ptr &pool) const; /** * Deep copy this Payment object and make it repeating, then return an instance of SavedPayment */ Q_INVOKABLE QObject *copyToRepeating(QObject *parent) const; /** * When true, a payment object that results in a transaction will save * the payment itself to the wallet that received the transaction. * @see Wallet::loadPaymentFor(int index) */ bool persistPaidPayment() const; void setPersistPaidPayment(bool newPersistPaidPayment); int paymentId() const; private slots: void recalcAmounts(); void broadcast(); signals: void feePerByteChanged(); void amountChanged(); void amountFiatChanged(); void targetAddressChanged(); void txPreparedChanged(); void preferSchnorrChanged(); void paymentDetailsChanged(); void broadcastStatusChanged(); void validChanged(); void errorChanged(); void txCreated(); void fiatPriceChanged(); void currentAccountChanged(); void userCommentChanged(); void walletPinChanged(); void autoPrepareChanged(); void allowInstaPayChanged(); void warningsChanged(); void simpleAddressTargetChanged(); void approvedByUser(); void repeatDetailsChanged(); private: void doAutoPrepare(); friend class PaymentDetailOutput; /// Helper methods to get the output, assuming that is the only output. /// Will throw if the Payment has more than one. PaymentDetailOutput *soleOut() const; PaymentDetailOutput *firstOut() const; void addDetail(PaymentDetail*); AccountInfo *m_account = nullptr; // Payment Variable initialization in reset() please QSet m_warnings; QList m_paymentDetails; bool m_autoPrepare = false; bool m_txPrepared; bool m_txBroadcastStarted; bool m_preferSchnorr; bool m_allowInstaPay = false; bool m_simpleAddressTarget = true; // only 'advanced' payment protocols set this to false bool m_persistPaidPayment = false; Tx m_tx; int m_fee; // in sats per byte int m_assignedFee; int m_fiatPrice = 50000; // price for one whole BCH int m_paymentId = -1; // how others can refer to this payment without a pointer. std::shared_ptr m_infoObject; std::shared_ptr m_wallet; QString m_error; QString m_userComment; RepeatPaymentDetails *m_repeatDetails = nullptr; }; class PaymentDetail : public QObject { Q_OBJECT Q_PROPERTY(Payment::DetailType type READ type CONSTANT) Q_PROPERTY(bool collapsable READ collapsable WRITE setCollapsable NOTIFY collapsableChanged) Q_PROPERTY(bool collapsed READ collapsed WRITE setCollapsed NOTIFY collapsedChanged) Q_PROPERTY(bool isOutput READ isOutput CONSTANT) Q_PROPERTY(bool isComment READ isComment CONSTANT) Q_PROPERTY(bool isInput READ isInputs CONSTANT) public: PaymentDetail(Payment *parent, Payment::DetailType type); Payment::DetailType type() const; bool collapsable() const; void setCollapsable(bool newCollapsable); bool collapsed() const; void setCollapsed(bool newCollapsed); inline bool isOutput() const { return m_type == Payment::PayToAddress; } inline bool isInputs() const { return m_type == Payment::InputSelector; } inline bool isComment() const { return m_type == Payment::CommentOutput; } PaymentDetailOutput *toOutput(); PaymentDetailInputs *toInputs(); PaymentDetailComment *toComment(); bool valid() const; /** * When the user selects another wallet (via Payment::setCurrentAccount) * default implementation is empty. */ virtual void setWallet(const std::shared_ptr &wallet); protected: void setValid(bool valid); signals: void collapsableChanged(); void collapsedChanged(); void validChanged(); private: const Payment::DetailType m_type; bool m_collapsable = true; bool m_collapsed = false; bool m_valid = false; // when all user-input is valid }; #endif