Browse Source

Pay payment requests

pull/45/head
Aditya Kulkarni 5 years ago
parent
commit
e46725f322
  1. 1
      application.qrc
  2. BIN
      res/paymentreq.gif
  3. 81
      src/mainwindow.cpp
  4. 61
      src/requestdialog.cpp
  5. 4
      src/requestdialog.h
  6. 124
      src/requestdialog.ui
  7. 61
      src/settings.cpp
  8. 12
      src/settings.h
  9. 22
      src/txtablemodel.cpp

1
application.qrc

@ -5,6 +5,7 @@
<qresource prefix="/icons">
<file>res/connected.gif</file>
<file>res/loading.gif</file>
<file>res/paymentreq.gif</file>
<file>res/icon.ico</file>
</qresource>
<qresource prefix="/img">

BIN
res/paymentreq.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

81
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:<addr>?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:<addr>?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();

61
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:<addr>?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);
}
}
}

4
src/requestdialog.h

@ -3,6 +3,7 @@
#include <QDialog>
#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;
};

124
src/requestdialog.ui

@ -6,41 +6,41 @@
<rect>
<x>0</x>
<y>0</y>
<width>714</width>
<height>524</height>
<width>1052</width>
<height>509</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
<string>Payment Request</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="8" column="1" colspan="3">
<widget class="MemoEdit" name="txtMemo"/>
</item>
<item row="10" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="1" colspan="3">
<widget class="QLineEdit" name="txtAmount">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="placeholderText">
<string>Amount</string>
</property>
</widget>
</item>
<item row="0" column="2" colspan="2">
<widget class="QLabel" name="lblSaplingWarning">
<property name="styleSheet">
<string notr="true">color: red;</string>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="11" column="2" colspan="2">
<item row="13" column="2" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -67,8 +67,38 @@
</property>
</widget>
</item>
<item row="3" column="3">
<item row="1" column="1" colspan="3">
<widget class="QLineEdit" name="txtFrom">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="placeholderText">
<string>z address</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="lblAddress">
<property name="text">
<string>Request From</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="btnAddressBook">
<property name="text">
<string>AddressBook</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
@ -82,35 +112,25 @@
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnAddressBook">
<property name="text">
<string>AddressBook</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="1" colspan="3">
<widget class="QLineEdit" name="txtFrom">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<item row="0" column="1" colspan="3">
<widget class="QLabel" name="lblSaplingWarning">
<property name="styleSheet">
<string notr="true">color: red;</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>z address</string>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="0">
<item row="7" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>Request From</string>
<string/>
</property>
</widget>
</item>
@ -120,24 +140,37 @@
<string>Amount USD</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_2">
<widget class="QLabel" name="lblAmount">
<property name="text">
<string>Amount in ZEC</string>
<string>Amount in </string>
</property>
</widget>
</item>
<item row="10" column="0" colspan="4">
<item row="12" column="0" colspan="4">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="10" column="3">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
@ -147,6 +180,11 @@
<header>memoedit.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>txtFrom</tabstop>
<tabstop>txtAmount</tabstop>
<tabstop>txtMemo</tabstop>
</tabstops>
<resources/>
<connections>
<connection>

61
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}");

12
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);

22
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);

Loading…
Cancel
Save