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 0000000..f451982
Binary files /dev/null and b/res/paymentreq.gif differ
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 @@
+
+ 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);