From e46725f322665bd08e04429c0aed1eb4e55ee2f1 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Fri, 22 Mar 2019 15:37:21 -0700 Subject: [PATCH] Pay payment requests --- application.qrc | 1 + res/paymentreq.gif | Bin 0 -> 7633 bytes src/mainwindow.cpp | 81 ++++++--------------------- src/requestdialog.cpp | 61 ++++++++++++++++++++- src/requestdialog.h | 4 +- src/requestdialog.ui | 124 +++++++++++++++++++++++++++--------------- src/settings.cpp | 61 +++++++++++++++++++-- src/settings.h | 12 ++++ src/txtablemodel.cpp | 22 ++++++-- 9 files changed, 246 insertions(+), 120 deletions(-) create mode 100644 res/paymentreq.gif diff --git a/application.qrc b/application.qrc index e7e9b2c..c7201af 100644 --- a/application.qrc +++ b/application.qrc @@ -5,6 +5,7 @@ res/connected.gif res/loading.gif + res/paymentreq.gif res/icon.ico diff --git a/res/paymentreq.gif b/res/paymentreq.gif new file mode 100644 index 0000000000000000000000000000000000000000..f451982d5a421a22b58ebf01fed514207c6f0e00 GIT binary patch literal 7633 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 literal 0 HcmV?d00001 diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 01573e4..82ba03c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -804,12 +804,6 @@ void MainWindow::payZcashURI(QString uri) { return; } - // Error to display if something goes wrong. - auto payZcashURIError = [=] (QString errorDetail = "") { - QMessageBox::critical(this, tr("Error paying zcash URI"), - tr("URI should be of the form 'zcash:?amt=x&memo=y") + "\n" + errorDetail); - }; - // If there was no URI passed, ask the user for one. if (uri.isEmpty()) { uri = QInputDialog::getText(this, tr("Paste Zcash URI"), @@ -820,70 +814,21 @@ void MainWindow::payZcashURI(QString uri) { if (uri.isEmpty()) return; - // URI should be of the form zcash://address?amt=x&memo=y - if (!uri.startsWith("zcash:")) { - payZcashURIError(); - return; - } - // Extract the address qDebug() << "Recieved URI " << uri; - uri = uri.right(uri.length() - QString("zcash:").length()); - - QRegExp re("([a-zA-Z0-9]+)"); - int pos; - if ( (pos = re.indexIn(uri)) == -1 ) { - payZcashURIError(); - return; - } - - QString addr = re.cap(1); - if (!Settings::isValidAddress(addr)) { - payZcashURIError(tr("Could not understand address")); + PaymentURI paymentInfo = Settings::parseURI(uri); + if (!paymentInfo.error.isEmpty()) { + QMessageBox::critical(this, tr("Error paying zcash URI"), + tr("URI should be of the form 'zcash:?amt=x&memo=y") + "\n" + paymentInfo.error); return; } - uri = uri.right(uri.length() - addr.length()); - - double amount = 0.0; - QString memo = ""; - - if (!uri.isEmpty()) { - uri = uri.right(uri.length() - 1); // Eat the "?" - - QStringList args = uri.split("&"); - for (QString arg: args) { - QStringList kv = arg.split("="); - if (kv.length() != 2) { - payZcashURIError(); - return; - } - - if (kv[0].toLower() == "amt" || kv[0].toLower() == "amount") { - amount = kv[1].toDouble(); - } else if (kv[0].toLower() == "memo" || kv[0].toLower() == "message" || kv[0].toLower() == "msg") { - memo = kv[1]; - // Test if this is hex - - QRegularExpression hexMatcher("^[0-9A-F]+$", - QRegularExpression::CaseInsensitiveOption); - QRegularExpressionMatch match = hexMatcher.match(memo); - if (match.hasMatch()) { - // Encoded as hex, convert to string - memo = QByteArray::fromHex(memo.toUtf8()); - } - } else { - payZcashURIError(tr("Unknown field in URI:") + kv[0]); - return; - } - } - } // Now, set the fields on the send tab removeExtraAddresses(); - ui->Address1->setText(addr); + ui->Address1->setText(paymentInfo.addr); ui->Address1->setCursorPosition(0); - ui->Amount1->setText(QString::number(amount)); - ui->MemoTxt1->setText(memo); + ui->Amount1->setText(Settings::getDecimalString(paymentInfo.amt.toDouble())); + ui->MemoTxt1->setText(paymentInfo.memo); // And switch to the send tab. ui->tabWidget->setCurrentIndex(1); @@ -891,7 +836,7 @@ void MainWindow::payZcashURI(QString uri) { // And click the send button if the amount is > 0, to validate everything. If everything is OK, it will show the confirm box // else, show the error message; - if (amount > 0) { + if (paymentInfo.amt > 0) { sendButton(); } } @@ -1214,8 +1159,16 @@ void MainWindow::setupTransactionsTab() { QDesktopServices::openUrl(QUrl(url)); }); + // Payment Request + if (!memo.isEmpty() && memo.startsWith("zcash:")) { + menu.addAction(tr("View Payment Request"), [=] () { + RequestDialog::showPaymentConfirmation(this, memo); + }); + } + + // View Memo if (!memo.isEmpty()) { - menu.addAction(tr("View Memo"), [=] () { + menu.addAction(tr("View Memo"), [=] () { QMessageBox mb(QMessageBox::Information, tr("Memo"), memo, QMessageBox::Ok, this); mb.setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); mb.exec(); diff --git a/src/requestdialog.cpp b/src/requestdialog.cpp index abfe170..da95c3d 100644 --- a/src/requestdialog.cpp +++ b/src/requestdialog.cpp @@ -2,6 +2,10 @@ #include "ui_requestdialog.h" #include "settings.h" #include "addressbook.h" +#include "mainwindow.h" +#include "rpc.h" +#include "settings.h" + RequestDialog::RequestDialog(QWidget *parent) : QDialog(parent), @@ -15,6 +19,51 @@ RequestDialog::~RequestDialog() delete ui; } +void RequestDialog::setupDialog(QDialog* d, Ui_RequestDialog* req) { + req->setupUi(d); + Settings::saveRestore(d); + + // Setup + req->txtMemo->setLenDisplayLabel(req->lblMemoLen); + req->lblAmount->setText(req->lblAmount->text() + Settings::getTokenName()); +} + +// Static method that shows an incoming payment request and prompts the user to pay it +void RequestDialog::showPaymentConfirmation(MainWindow* main, QString paymentURI) { + PaymentURI payInfo = Settings::parseURI(paymentURI); + if (!payInfo.error.isEmpty()) { + QMessageBox::critical(main, tr("Error paying zcash URI"), + tr("URI should be of the form 'zcash:?amt=x&memo=y") + "\n" + payInfo.error); + return; + } + + QDialog d(main); + Ui_RequestDialog req; + setupDialog(&d, &req); + + // In the view mode, all fields are read-only + req.txtAmount->setReadOnly(true); + req.txtFrom->setReadOnly(true); + req.txtMemo->setReadOnly(true); + + // Payment is "to" + req.lblAddress->setText(tr("Pay To")); + + // No Addressbook + req.btnAddressBook->setVisible(false); + + req.txtFrom->setText(payInfo.addr); + req.txtMemo->setPlainText(payInfo.memo); + req.txtAmount->setText(payInfo.amt); + req.txtAmountUSD->setText(Settings::getUSDFormat(req.txtAmount->text().toDouble())); + + req.buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Pay")); + + if (d.exec() == QDialog::Accepted) { + main->payZcashURI(paymentURI); + } +} + // Static method that shows the request dialog void RequestDialog::showRequestZcash(MainWindow* main) { QDialog d(main); @@ -55,9 +104,15 @@ void RequestDialog::showRequestZcash(MainWindow* main) { if (d.exec() == QDialog::Accepted) { // Construct a zcash Payment URI with the data and pay it immediately. - QString paymentURI = "zcash:" + AddressBook::addressFromAddressLabel(req.txtFrom->text()) + QString memoURI = "zcash:" + main->getRPC()->getDefaultSaplingAddress() + "?amt=" + Settings::getDecimalString(req.txtAmount->text().toDouble()) + "&memo=" + QUrl::toPercentEncoding(req.txtMemo->toPlainText()); - main->payZcashURI(paymentURI); + + QString sendURI = "zcash:" + AddressBook::addressFromAddressLabel(req.txtFrom->text()) + + "?amt=0.0001" + + "&memo=" + QUrl::toPercentEncoding(memoURI); + + qDebug() << "Paying " << sendURI; + main->payZcashURI(sendURI); } -} \ No newline at end of file +} diff --git a/src/requestdialog.h b/src/requestdialog.h index eca2e5d..a05b3c7 100644 --- a/src/requestdialog.h +++ b/src/requestdialog.h @@ -3,6 +3,7 @@ #include #include "mainwindow.h" +#include "ui_requestdialog.h" namespace Ui { class RequestDialog; @@ -17,7 +18,8 @@ public: ~RequestDialog(); static void showRequestZcash(MainWindow* main); - + static void showPaymentConfirmation(MainWindow* main, QString paymentURI); + static void setupDialog(QDialog* d, Ui_RequestDialog* req); private: Ui::RequestDialog *ui; }; diff --git a/src/requestdialog.ui b/src/requestdialog.ui index dd3614f..f188f8b 100644 --- a/src/requestdialog.ui +++ b/src/requestdialog.ui @@ -6,41 +6,41 @@ 0 0 - 714 - 524 + 1052 + 509 - Dialog + Payment Request + + + + Qt::Vertical + + + + 20 + 40 + + + + - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Amount - - - - color: red; - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - + Qt::Horizontal @@ -67,8 +67,38 @@ - + + + + + 0 + 0 + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + z address + + + + + + + Request From + + + + + + + + AddressBook + + + @@ -82,35 +112,25 @@ - - - - AddressBook - - - - - - - - 0 - 0 - + + + + color: red; - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + - - z address + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - + - Request From + @@ -120,24 +140,37 @@ Amount USD - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - + - Amount in ZEC + Amount in - + Qt::Horizontal + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -147,6 +180,11 @@
memoedit.h
+ + txtFrom + txtAmount + txtMemo + diff --git a/src/settings.cpp b/src/settings.cpp index 6810d51..c291247 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -161,10 +161,7 @@ void Settings::saveRestore(QDialog* d) { } QString Settings::getUSDFormat(double bal) { - if (!Settings::getInstance()->isTestnet() && Settings::getInstance()->getZECPrice() > 0) - return "$" + QLocale(QLocale::English).toString(bal * Settings::getInstance()->getZECPrice(), 'f', 2); - else - return QString(); + return "$" + QLocale(QLocale::English).toString(bal * Settings::getInstance()->getZECPrice(), 'f', 2); } QString Settings::getDecimalString(double amt) { @@ -289,4 +286,60 @@ bool Settings::isValidAddress(QString addr) { ztsexp.exactMatch(addr) || zsexp.exactMatch(addr); } +// Get a pretty string representation of this Payment URI +QString Settings::paymentURIPretty(PaymentURI uri) { + return QString() + "Payment Request\n" + "Pay: " + uri.addr + "\nAmount: " + getZECDisplayFormat(uri.amt.toDouble()) + + "\nMemo:" + QUrl::fromPercentEncoding(uri.memo.toUtf8()); +} + +// Parse a payment URI string into its components +PaymentURI Settings::parseURI(QString uri) { + PaymentURI ans; + + if (!uri.startsWith("zcash:")) { + ans.error = "Not a zcash payment URI"; + return ans; + } + + uri = uri.right(uri.length() - QString("zcash:").length()); + + QRegExp re("([a-zA-Z0-9]+)"); + int pos; + if ( (pos = re.indexIn(uri)) == -1 ) { + ans.error = "Couldn't find an address"; + return ans; + } + + ans.addr = re.cap(1); + if (!Settings::isValidAddress(ans.addr)) { + ans.error = "Could not understand address"; + return ans; + } + uri = uri.right(uri.length() - ans.addr.length()); + + if (!uri.isEmpty()) { + uri = uri.right(uri.length() - 1); // Eat the "?" + + QStringList args = uri.split("&"); + for (QString arg: args) { + QStringList kv = arg.split("="); + if (kv.length() != 2) { + ans.error = "No value argument was seen"; + return ans; + } + + if (kv[0].toLower() == "amt" || kv[0].toLower() == "amount") { + ans.amt = kv[1]; + } else if (kv[0].toLower() == "memo" || kv[0].toLower() == "message" || kv[0].toLower() == "msg") { + ans.memo = QUrl::fromPercentEncoding(kv[1].toUtf8()); + } else { + ans.error = "Unknown field in URI:" + kv[0]; + return ans; + } + } + } + + return ans; +} + const QString Settings::labelRegExp("[a-zA-Z0-9\\-_]{0,40}"); diff --git a/src/settings.h b/src/settings.h index 03ba530..ee3a053 100644 --- a/src/settings.h +++ b/src/settings.h @@ -13,6 +13,15 @@ struct Config { struct ToFields; struct Tx; +struct PaymentURI { + QString addr; + QString amt; + QString memo; + + // Any errors are stored here + QString error; +}; + class Settings { public: @@ -68,6 +77,9 @@ public: static void saveRestore(QDialog* d); + static PaymentURI parseURI(QString paymentURI); + static QString paymentURIPretty(PaymentURI); + static bool isZAddress(QString addr); static bool isTAddress(QString addr); diff --git a/src/txtablemodel.cpp b/src/txtablemodel.cpp index cc099e7..f563921 100644 --- a/src/txtablemodel.cpp +++ b/src/txtablemodel.cpp @@ -138,8 +138,14 @@ void TxTableModel::updateAllData() { if (role == Qt::ToolTipRole) { switch (index.column()) { - case 0: return modeldata->at(index.row()).type + - (dat.memo.isEmpty() ? "" : " tx memo: \"" + dat.memo + "\""); + case 0: { + if (dat.memo.startsWith("zcash:")) { + return Settings::paymentURIPretty(Settings::parseURI(dat.memo)); + } else { + return modeldata->at(index.row()).type + + (dat.memo.isEmpty() ? "" : " tx memo: \"" + dat.memo + "\""); + } + } case 1: { auto addr = modeldata->at(index.row()).address; if (addr.trimmed().isEmpty()) @@ -154,9 +160,15 @@ void TxTableModel::updateAllData() { if (role == Qt::DecorationRole && index.column() == 0) { if (!dat.memo.isEmpty()) { - // Return the info pixmap to indicate memo - QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation); - return QVariant(icon.pixmap(16, 16)); + // If the memo is a Payment URI, then show a payment request icon + if (dat.memo.startsWith("zcash:")) { + QIcon icon(":/icons/res/paymentreq.gif"); + return QVariant(icon.pixmap(16, 16)); + } else { + // Return the info pixmap to indicate memo + QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation); + return QVariant(icon.pixmap(16, 16)); + } } else { // Empty pixmap to make it align QPixmap p(16, 16);