Files
pay/PaymentRequest.cpp
T

307 lines
8.0 KiB
C++
Raw Permalink Normal View History

/*
* This file is part of the Flowee project
* Copyright (C) 2020-2021 Tom Zander <tom@flowee.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "PaymentRequest.h"
#include "FloweePay.h"
#include "Wallet.h"
2021-04-21 17:17:47 +02:00
#include "AccountInfo.h"
#include <QTimer>
#include <QUrl>
#include <base58.h>
#include <cashaddr.h>
2021-01-07 20:10:09 +01:00
PaymentRequest::PaymentRequest(QObject *parent)
2021-04-21 17:17:47 +02:00
: QObject(parent),
m_wallet(nullptr)
2021-01-07 20:10:09 +01:00
{
}
PaymentRequest::PaymentRequest(Wallet *wallet, QObject *parent)
: QObject(parent),
2021-04-21 17:17:47 +02:00
m_wallet(nullptr)
{
2021-04-21 17:17:47 +02:00
assert(wallet);
setWallet(wallet);
2021-02-05 16:54:07 +01:00
#if 0
// by enabling this you can simulate the payment request being fulfilled
QTimer::singleShot(5000, [=]() {
setPaymentState(PaymentSeen);
QTimer::singleShot(3000, [=]() {
// setPaymentState(PaymentSeenOk);
setPaymentState(DoubleSpentSeen);
});
});
#endif
2021-01-06 23:15:54 +01:00
}
// constructor used to 'load' a request, already owned by the wallet.
PaymentRequest::PaymentRequest(Wallet *wallet, int /* type */)
: QObject(wallet),
2021-01-07 20:10:09 +01:00
m_wallet(wallet),
2021-05-05 12:42:26 +02:00
m_saveState(Stored)
2021-01-06 23:15:54 +01:00
{
// TODO remember type when we start to support more than just BIP21
m_unusedRequest = false;
}
2021-04-21 17:17:47 +02:00
PaymentRequest::~PaymentRequest()
{
// free address again, if the ownership hasn't moved to be the wallet itself.
if (m_unusedRequest)
setWallet(nullptr);
}
void PaymentRequest::setPaymentState(PaymentState newState)
{
if (newState == m_paymentState)
return;
m_paymentState = newState;
m_dirty = true;
emit paymentStateChanged();
}
2021-04-21 17:17:47 +02:00
void PaymentRequest::setWallet(Wallet *wallet)
{
if (m_wallet == wallet)
return;
if (!m_unusedRequest) {
logFatal() << "Can't change a wallet on an already saved payment request";
assert(m_unusedRequest);
return;
}
if (m_wallet) {
m_wallet->removePaymentRequest(this);
if (m_paymentState == Unpaid)
m_wallet->unreserveAddress(m_privKeyId);
2021-04-21 17:17:47 +02:00
}
m_wallet = wallet;
if (m_wallet) {
m_privKeyId = m_wallet->reserveUnusedAddress(m_address);
m_wallet->addPaymentRequest(this);
}
emit walletChanged();
emit qrCodeStringChanged();
}
2021-01-13 17:43:10 +01:00
qint64 PaymentRequest::amountSeen() const
{
return m_amountSeen;
}
2021-01-07 20:10:09 +01:00
PaymentRequest::PaymentState PaymentRequest::paymentState() const
{
return m_paymentState;
}
2021-01-13 17:43:10 +01:00
void PaymentRequest::addPayment(uint64_t ref, int64_t value, int blockHeight)
{
assert(value > 0);
2021-12-01 10:57:21 +01:00
if (m_wallet->isSingleAddressWallet()
&& m_unusedRequest && m_message.isEmpty()
&& m_amountRequested == 0) {
// This is a completely empty payment-request and since we are
// connected to a single-address wallet it is common for transactions
// to arrive that match our 'reserved' address.
// This means that we have zero indication that incoming transactions
// are in response to this request.
// Just return and keep us showing a QR of our only address.
return;
}
2021-01-13 17:43:10 +01:00
if (m_incomingOutputRefs.contains(ref)) {
if (m_paymentState >= PaymentSeen && blockHeight != -1)
setPaymentState(Confirmed);
2021-01-13 17:43:10 +01:00
return;
}
m_incomingOutputRefs.append(ref);
m_amountSeen += value;
m_dirty = true;
2021-01-13 17:43:10 +01:00
if (m_paymentState == Unpaid && m_amountSeen >= m_amountRequested) {
if (blockHeight == -1) {
setPaymentState(PaymentSeen);
2021-04-21 17:17:47 +02:00
QTimer::singleShot(FloweePay::instance()->dspTimeout(), this, [=]() {
if (m_paymentState == PaymentSeen)
setPaymentState(PaymentSeenOk);
});
} else {
setPaymentState(Confirmed);
}
2021-01-13 17:43:10 +01:00
}
emit amountSeenChanged();
}
void PaymentRequest::paymentRejected(uint64_t ref, int64_t value)
{
if (!m_incomingOutputRefs.contains(ref))
return;
m_amountSeen -= value;
m_dirty = true;
if (m_paymentState >= PaymentSeen && m_amountSeen < m_amountRequested)
setPaymentState(Unpaid);
2021-01-13 17:43:10 +01:00
emit amountSeenChanged();
}
2021-05-05 12:42:26 +02:00
bool PaymentRequest::stored() const
{
return !m_unusedRequest;
}
void PaymentRequest::setStored(bool on)
{
if (on != m_unusedRequest)
return;
m_unusedRequest = !on;
setParent(m_wallet);
setSaveState(on ? Stored : Temporary);
if (!on) {
m_wallet->removePaymentRequest(this);
// deleteLater(); // possible memory leak, but better than crashes
}
emit storedChanged();
}
2021-01-07 20:10:09 +01:00
PaymentRequest::SaveState PaymentRequest::saveState() const
{
return m_saveState;
}
2021-05-05 12:42:26 +02:00
void PaymentRequest::setSaveState(SaveState saveState)
2021-01-07 20:10:09 +01:00
{
if (m_saveState == saveState)
return;
m_saveState = saveState;
m_dirty = true;
2021-01-07 20:10:09 +01:00
emit saveStateChanged();
}
QString PaymentRequest::message() const
{
return m_message;
}
void PaymentRequest::setMessage(const QString &message)
{
if (m_message == message)
return;
m_message = message;
m_dirty = true;
emit messageChanged();
emit qrCodeStringChanged();
}
void PaymentRequest::setAmountFP(double amount)
{
qint64 newAmount = amount;
if (newAmount == m_amountRequested)
return;
m_amountRequested = newAmount;
emit amountChanged();
emit qrCodeStringChanged();
}
double PaymentRequest::amountFP() const
{
return m_amountRequested;
}
2021-01-13 17:43:10 +01:00
double PaymentRequest::amountSeenFP() const
{
return m_amountSeen;
}
qint64 PaymentRequest::amount() const
{
return m_amountRequested;
}
QString PaymentRequest::qrCodeString() const
{
QString rc;
// add address
if (m_useLegacyAddressFormat) {
CBase58Data legacy;
legacy.setData(m_address, CBase58Data::PubkeyType,
FloweePay::instance()->chain() == P2PNet::MainChain
? CBase58Data::Mainnet : CBase58Data::Testnet);
rc += QString::fromStdString(legacy.ToString());
}
else {
CashAddress::Content c;
c.hash = std::vector<uint8_t>(m_address.begin(), m_address.end());
c.type = CashAddress::PUBKEY_TYPE;
rc += QString::fromStdString(CashAddress::encodeCashAddr(FloweePay::instance()->chainPrefix(), c));
}
bool separatorInserted = false; // the questionmark.
if (m_amountRequested > 0) {
// Amount is in whole BCHs
QString price = FloweePay::priceToString(m_amountRequested, FloweePay::BCH);
2021-05-07 13:06:08 +02:00
// in case locale states we use commas:
price.replace(',', '.');
int length = price.size() - 1;
// we strip trailing zero's
while (length >= 0) {
2021-05-07 13:06:08 +02:00
if (price.at(length) == '.') {
--length;
break;
}
if (price.at(length) != '0')
break;
--length;
}
rc += QString("?amount=%1").arg(price.left(length + 1));
separatorInserted = true;
}
if (!m_message.isEmpty()) {
if (separatorInserted)
rc += "&";
else
rc += "?";
rc += QString("message=%1").arg(m_message);
QUrl url(rc);
rc = QString::fromLatin1(url.toEncoded());
}
return rc;
}
bool PaymentRequest::useLegacyAddress()
{
return m_useLegacyAddressFormat;
}
void PaymentRequest::setUseLegacyAddress(bool on)
{
if (on == m_useLegacyAddressFormat)
return;
m_useLegacyAddressFormat = on;
emit legacyChanged();
emit qrCodeStringChanged();
}
2021-01-06 23:15:54 +01:00
2021-04-21 17:17:47 +02:00
void PaymentRequest::switchAccount(AccountInfo *ai)
{
assert(ai);
setWallet(ai->wallet());
}