diff --git a/src/action/nav-mobile.js b/src/action/nav-mobile.js
index 9312cffbb..6328ec3af 100644
--- a/src/action/nav-mobile.js
+++ b/src/action/nav-mobile.js
@@ -119,6 +119,10 @@ class NavAction {
this._navigate('Pay');
}
+ goPayLightningSupplyAmount() {
+ this._navigate('PayLightningSupplyAmount');
+ }
+
goPayLightningConfirm() {
this._navigate('PayLightningConfirm');
}
diff --git a/src/action/nav.js b/src/action/nav.js
index 3fb5cc945..d16098e05 100644
--- a/src/action/nav.js
+++ b/src/action/nav.js
@@ -81,6 +81,10 @@ class NavAction {
this._store.route = 'Pay';
}
+ goPayLightningSupplyAmount() {
+ this._store.route = 'PayLightningSupplyAmount';
+ }
+
goPayLightningConfirm() {
this._store.route = 'PayLightningConfirm';
}
diff --git a/src/action/payment.js b/src/action/payment.js
index ea779b066..99b0202f8 100644
--- a/src/action/payment.js
+++ b/src/action/payment.js
@@ -106,6 +106,7 @@ class PaymentAction {
*/
init() {
this._store.payment.address = '';
+ this._store.payment.destination = '';
this._store.payment.amount = '';
this._store.payment.targetConf = MED_TARGET_CONF;
this._store.payment.fee = '';
@@ -165,7 +166,12 @@ class PaymentAction {
return this._notification.display({ msg: 'Enter an invoice or address' });
}
if (await this.decodeInvoice({ invoice: this._store.payment.address })) {
- this._nav.goPayLightningConfirm();
+ if (this._store.payment.amount === '0') {
+ this._store.payment.amount = '';
+ this._nav.goPayLightningSupplyAmount();
+ } else {
+ this._nav.goPayLightningConfirm();
+ }
} else if (isAddress(this._store.payment.address)) {
this._nav.goPayBitcoin();
} else {
@@ -173,6 +179,31 @@ class PaymentAction {
}
}
+ /**
+ * Check If payment amount was supplied and destination is set. Estimate
+ * the routing fee and go to the confirmation view for lightning payments.
+ *
+ * Note: This function is dependent on that an invoice has already been decoded.
+ * If payment amount is 0 the function will display a message and return.
+ */
+ async checkAmountSuppliedAndGoPayLightningConfirm() {
+ if (
+ this._store.payment.amount === '0' ||
+ this._store.payment.amount === ''
+ ) {
+ this._notification.display({ msg: 'Enter amount to pay.' });
+ } else if (this._store.payment.destination === '') {
+ this._notification.display({ msg: 'Internal Error, try again.' });
+ this._nav.goHome();
+ } else {
+ this.estimateLightningFee({
+ destination: this._store.payment.destination,
+ satAmt: toSatoshis(this._store.payment.amount, this._store.settings),
+ });
+ this._nav.goPayLightningConfirm();
+ }
+ }
+
/**
* Attempt to decode a lightning invoice using the lnd grpc api. If it is
* an invoice the amount and note store values will be set and the lightning
@@ -188,6 +219,7 @@ class PaymentAction {
});
payment.amount = toAmount(request.numSatoshis, settings);
payment.note = request.description;
+ payment.destination = request.destination;
this.estimateLightningFee({
destination: request.destination,
satAmt: request.numSatoshis,
@@ -321,7 +353,7 @@ class PaymentAction {
}
/**
- * Send the amount specified in the invoice as a lightning transaction and
+ * Send the amount specified in payment.amount as a lightning transaction and
* display the wait screen while the payment confirms.
* This action can be called from a view event handler as does all
* the necessary error handling and notification display.
@@ -336,6 +368,8 @@ class PaymentAction {
try {
this._nav.goWait();
const invoice = this._store.payment.address;
+ const { settings } = this._store;
+ const satAmt = toSatoshis(this._store.payment.amount, settings);
const stream = this._grpc.sendStreamCommand('sendPayment');
await new Promise((resolve, reject) => {
stream.on('data', data => {
@@ -346,7 +380,13 @@ class PaymentAction {
}
});
stream.on('error', reject);
- stream.write(JSON.stringify({ paymentRequest: invoice }), 'utf8');
+ stream.write(
+ JSON.stringify({
+ paymentRequest: invoice,
+ amt: satAmt,
+ }),
+ 'utf8'
+ );
});
if (failed) return;
this._nav.goPayLightningDone();
diff --git a/src/view/main-mobile.js b/src/view/main-mobile.js
index 7e9a2dee1..5e00347ea 100644
--- a/src/view/main-mobile.js
+++ b/src/view/main-mobile.js
@@ -33,6 +33,7 @@ import SettingUnitView from './setting-unit';
import SettingFiatView from './setting-fiat';
import CLIView from './cli';
import PaymentView from './payment-mobile';
+import PayLightningSupplyAmountView from './pay-lightning-supply-amount-mobile';
import PayLightningConfirmView from './pay-lightning-confirm-mobile';
import PayLightningDoneView from './pay-lightning-done-mobile';
import PaymentFailedView from './payment-failed-mobile';
@@ -171,6 +172,10 @@ const InvoiceQR = () => (
const Pay = () => ;
+const PayLightningSupplyAmount = () => (
+
+);
+
const PayLightningConfirm = () => (
);
@@ -236,6 +241,7 @@ const InvoiceStack = createStackNavigator(
const PayStack = createStackNavigator(
{
Pay,
+ PayLightningSupplyAmount,
PayLightningConfirm,
Wait,
PayLightningDone,
diff --git a/src/view/main.js b/src/view/main.js
index 8066ace05..4100609d6 100644
--- a/src/view/main.js
+++ b/src/view/main.js
@@ -21,6 +21,7 @@ import LoaderSyncing from './loader-syncing';
import Wait from './wait';
import Home from './home';
import Payment from './payment';
+import PayLightningSupplyAmount from './pay-lightning-supply-amount';
import PayLightningConfirm from './pay-lightning-confirm';
import PayLightningDone from './pay-lightning-done';
import PaymentFailed from './payment-failed';
@@ -130,6 +131,9 @@ class MainView extends Component {
{route === 'Pay' && (
)}
+ {route === 'PayLightningSupplyAmount' && (
+
+ )}
{route === 'PayLightningConfirm' && (
)}
diff --git a/src/view/pay-lightning-supply-amount-mobile.js b/src/view/pay-lightning-supply-amount-mobile.js
new file mode 100644
index 000000000..a92e520b3
--- /dev/null
+++ b/src/view/pay-lightning-supply-amount-mobile.js
@@ -0,0 +1,84 @@
+import React from 'react';
+import { StyleSheet } from 'react-native';
+import { observer } from 'mobx-react';
+import PropTypes from 'prop-types';
+import Background from '../component/background';
+import MainContent from '../component/main-content';
+import { NamedField, AmountInputField } from '../component/field';
+import { Header, Title } from '../component/header';
+import { CancelButton, Button, SmallGlasButton } from '../component/button';
+import Card from '../component/card';
+import LightningBoltIcon from '../asset/icon/lightning-bolt';
+import { FormStretcher, FormSubText } from '../component/form';
+import { BalanceLabel, BalanceLabelUnit } from '../component/label';
+import { color } from '../component/style';
+
+const styles = StyleSheet.create({
+ balance: {
+ marginTop: 15,
+ },
+ unit: {
+ color: color.blackText,
+ },
+ form: {
+ paddingTop: 10,
+ paddingBottom: 10,
+ },
+ subText: {
+ paddingTop: 40,
+ paddingBottom: 40,
+ },
+});
+
+const PayLightningSupplyAmountView = ({ store, nav, payment }) => (
+
+
+
+
+
+
+ payment.setAmount({ amount })}
+ onSubmitEditing={() =>
+ payment.checkAmountSuppliedAndGoPayLightningConfirm()
+ }
+ />
+
+ {store.unitFiatLabel}
+
+
+
+ {store.payment.note ? (
+
+ {store.payment.note}
+
+ ) : null}
+
+
+ Payment Request did not specify an amount. This is often used for
+ tips/donations.
+
+
+
+ payment.checkAmountSuppliedAndGoPayLightningConfirm()}
+ >
+ Pay
+
+
+);
+
+PayLightningSupplyAmountView.propTypes = {
+ store: PropTypes.object.isRequired,
+ nav: PropTypes.object.isRequired,
+ payment: PropTypes.object.isRequired,
+};
+
+export default observer(PayLightningSupplyAmountView);
diff --git a/src/view/pay-lightning-supply-amount.js b/src/view/pay-lightning-supply-amount.js
new file mode 100644
index 000000000..192de47ea
--- /dev/null
+++ b/src/view/pay-lightning-supply-amount.js
@@ -0,0 +1,92 @@
+import React from 'react';
+import { StyleSheet } from 'react-native';
+import { observer } from 'mobx-react';
+import PropTypes from 'prop-types';
+import Background from '../component/background';
+import MainContent from '../component/main-content';
+import { NamedField, AmountInputField } from '../component/field';
+import { Header, Title } from '../component/header';
+import { CancelButton, BackButton, PillButton } from '../component/button';
+import Card from '../component/card';
+import LightningBoltIcon from '../asset/icon/lightning-bolt';
+import { FormStretcher, FormSubText } from '../component/form';
+import { BalanceLabel, BalanceLabelUnit } from '../component/label';
+import { color } from '../component/style';
+
+const styles = StyleSheet.create({
+ description: {
+ paddingLeft: 20,
+ paddingRight: 20,
+ },
+ unit: {
+ color: color.blackText,
+ },
+ maxBtn: {
+ marginTop: 10,
+ marginBottom: 20,
+ },
+ nextBtn: {
+ marginTop: 20,
+ backgroundColor: color.purple,
+ },
+ subText: {
+ paddingTop: 20,
+ paddingBottom: 40,
+ paddingLeft: 40,
+ paddingRight: 40,
+ },
+});
+
+const PayLightningSupplyAmountView = ({ store, nav, payment }) => (
+
+
+ nav.goHome()} />
+
+
+
+ nav.goHome()} />
+
+
+
+
+ payment.setAmount({ amount })}
+ onSubmitEditing={() =>
+ payment.checkAmountSuppliedAndGoPayLightningConfirm()
+ }
+ />
+
+ {store.unitFiatLabel}
+
+
+
+ {store.payment.note ? (
+
+ {store.payment.note}
+
+ ) : null}
+
+ Payment Request did not specify an amount. This is often used for
+ tips/donations.
+
+
+ payment.checkAmountSuppliedAndGoPayLightningConfirm()}
+ >
+ Pay
+
+
+
+
+);
+
+PayLightningSupplyAmountView.propTypes = {
+ store: PropTypes.object.isRequired,
+ nav: PropTypes.object.isRequired,
+ payment: PropTypes.object.isRequired,
+};
+
+export default observer(PayLightningSupplyAmountView);
diff --git a/test/unit/action/nav.spec.js b/test/unit/action/nav.spec.js
index c65ffa2c5..f2bedb5ee 100644
--- a/test/unit/action/nav.spec.js
+++ b/test/unit/action/nav.spec.js
@@ -116,6 +116,13 @@ describe('Action Nav Unit Tests', () => {
});
});
+ describe('goPayLightningSupplyAmount()', () => {
+ it('should set correct route', () => {
+ nav.goPayLightningSupplyAmount();
+ expect(store.route, 'to equal', 'PayLightningSupplyAmount');
+ });
+ });
+
describe('goPayLightningConfirm()', () => {
it('should set correct route', () => {
nav.goPayLightningConfirm();
diff --git a/test/unit/action/payment.spec.js b/test/unit/action/payment.spec.js
index 93931b26a..486443b3f 100644
--- a/test/unit/action/payment.spec.js
+++ b/test/unit/action/payment.spec.js
@@ -302,6 +302,14 @@ describe('Action Payments Unit Tests', () => {
expect(payment.decodeInvoice, 'was not called');
});
+ it('should detect zero amount payment', async () => {
+ store.payment.address = 'some-address';
+ store.payment.amount = '0';
+ payment.decodeInvoice.resolves(true);
+ await payment.checkType();
+ expect(nav.goPayLightningSupplyAmount, 'was called once');
+ });
+
it('should decode successfully', async () => {
store.payment.address = 'some-address';
payment.decodeInvoice.resolves(true);
@@ -461,15 +469,33 @@ describe('Action Payments Unit Tests', () => {
});
});
- it('should send lightning payment', async () => {
+ it('should send lightning payment with amount 0 if not specified.', async () => {
+ paymentsOnStub.withArgs('data').yields({ paymentError: '' });
+ payment.setAddress({ address: 'lightning:some-invoice' });
+ await payment.payLightning();
+ expect(grpc.sendStreamCommand, 'was called with', 'sendPayment');
+ expect(
+ paymentsWriteStub,
+ 'was called with',
+ JSON.stringify({ paymentRequest: 'some-invoice', amt: 0 }),
+ 'utf8'
+ );
+ expect(nav.goWait, 'was called once');
+ expect(nav.goPayLightningDone, 'was called once');
+ expect(notification.display, 'was not called');
+ });
+
+ it('should send lightning payment with amount.', async () => {
paymentsOnStub.withArgs('data').yields({ paymentError: '' });
payment.setAddress({ address: 'lightning:some-invoice' });
+ store.settings.unit = 'btc';
+ payment.setAmount({ amount: '0.0001' });
await payment.payLightning();
expect(grpc.sendStreamCommand, 'was called with', 'sendPayment');
expect(
paymentsWriteStub,
'was called with',
- JSON.stringify({ paymentRequest: 'some-invoice' }),
+ JSON.stringify({ paymentRequest: 'some-invoice', amt: 10000 }),
'utf8'
);
expect(nav.goWait, 'was called once');