diff --git a/src/action/nav.js b/src/action/nav.js index d4560c79c..cba7268ad 100644 --- a/src/action/nav.js +++ b/src/action/nav.js @@ -73,6 +73,10 @@ class NavAction { this._store.route = 'PayLightningDone'; } + goPaymentFailed() { + this._store.route = 'PaymentFailed'; + } + goPayBitcoin() { this._store.route = 'PayBitcoin'; } diff --git a/src/action/payment.js b/src/action/payment.js index 0ac8b27bd..9a70014f1 100644 --- a/src/action/payment.js +++ b/src/action/payment.js @@ -3,7 +3,7 @@ * call the corresponding GRPC apis for payment management. */ -import { PREFIX_URI } from '../config'; +import { PREFIX_URI, PAYMENT_TIMEOUT } from '../config'; import { toSatoshis, toAmount, @@ -170,6 +170,11 @@ class PaymentAction { * @return {Promise} */ async payLightning() { + let failed = false; + const timeout = setTimeout(() => { + failed = true; + this._nav.goPaymentFailed(); + }, PAYMENT_TIMEOUT); try { this._nav.goWait(); const invoice = this._store.payment.address.replace(PREFIX_URI, ''); @@ -185,10 +190,14 @@ class PaymentAction { stream.on('error', reject); stream.write(JSON.stringify({ payment_request: invoice }), 'utf8'); }); + if (failed) return; this._nav.goPayLightningDone(); } catch (err) { + if (failed) return; this._nav.goPayLightningConfirm(); this._notification.display({ msg: 'Lightning payment failed!', err }); + } finally { + clearTimeout(timeout); } } } diff --git a/src/config.js b/src/config.js index 7a9f32b06..c5cf44238 100644 --- a/src/config.js +++ b/src/config.js @@ -6,6 +6,7 @@ module.exports.RETRY_DELAY = 1000; module.exports.LND_INIT_DELAY = 5000; module.exports.NOTIFICATION_DELAY = 5000; module.exports.RATE_DELAY = 15 * 60 * 1000; +module.exports.PAYMENT_TIMEOUT = 60 * 1000; module.exports.LND_PORT = 10006; module.exports.LND_PEER_PORT = 10016; diff --git a/src/view/main.js b/src/view/main.js index 3f79c28e6..00d41ca0a 100644 --- a/src/view/main.js +++ b/src/view/main.js @@ -19,6 +19,7 @@ import Home from './home'; import Payment from './payment'; import PayLightningConfirm from './pay-lightning-confirm'; import PayLightningDone from './pay-lightning-done'; +import PaymentFailed from './payment-failed'; import PayBitcoin from './pay-bitcoin'; import PayBitcoinConfirm from './pay-bitcoin-confirm'; import PayBitcoinDone from './pay-bitcoin-done'; @@ -111,6 +112,9 @@ class MainView extends Component { {route === 'PayLightningDone' && ( )} + {route === 'PaymentFailed' && ( + + )} {route === 'PayBitcoin' && ( )} diff --git a/src/view/no-route.js b/src/view/payment-failed.js similarity index 77% rename from src/view/no-route.js rename to src/view/payment-failed.js index af2de0a61..6d0b4f8c9 100644 --- a/src/view/no-route.js +++ b/src/view/payment-failed.js @@ -28,29 +28,32 @@ const styles = StyleSheet.create({ }, }); -const NoRouteView = ({ channel, payment }) => ( +const PaymentFailedView = ({ channel, nav }) => ( - No route found + Payment Failed - {"You'll need to manually create a channel"} + {'You may need to manually create a channel.'} channel.initCreate()}> Create channel - ); -NoRouteView.propTypes = { +PaymentFailedView.propTypes = { channel: PropTypes.object.isRequired, - payment: PropTypes.object.isRequired, + nav: PropTypes.object.isRequired, }; -export default observer(NoRouteView); +export default observer(PaymentFailedView); diff --git a/stories/screen-story.js b/stories/screen-story.js index 5da47ede7..689b31a1b 100644 --- a/stories/screen-story.js +++ b/stories/screen-story.js @@ -38,7 +38,7 @@ import PayLightningDone from '../src/view/pay-lightning-done'; import PayBitcoin from '../src/view/pay-bitcoin'; import PayBitcoinConfirm from '../src/view/pay-bitcoin-confirm'; import PayBitcoinDone from '../src/view/pay-bitcoin-done'; -import NoRoute from '../src/view/no-route'; +import PaymentFailed from '../src/view/payment-failed'; import Loader from '../src/view/loader'; import LoaderSyncing from '../src/view/loader-syncing'; import SelectSeed from '../src/view/select-seed'; @@ -149,7 +149,7 @@ storiesOf('Screens', module) .add('Pay Lightning Done', () => ( )) - .add('No Route Found', () => ) + .add('Payment Failed', () => ) .add('Pay Bitcoin', () => ( )) diff --git a/test/unit/action/nav.spec.js b/test/unit/action/nav.spec.js index 86e2b1c29..54163d50f 100644 --- a/test/unit/action/nav.spec.js +++ b/test/unit/action/nav.spec.js @@ -130,6 +130,13 @@ describe('Action Nav Unit Tests', () => { }); }); + describe('goPaymentFailed()', () => { + it('should set correct route', () => { + nav.goPaymentFailed(); + expect(store.route, 'to equal', 'PaymentFailed'); + }); + }); + describe('goPayBitcoin()', () => { it('should set correct route', () => { nav.goPayBitcoin(); diff --git a/test/unit/action/payment.spec.js b/test/unit/action/payment.spec.js index 3631b8fd1..815ddc6a2 100644 --- a/test/unit/action/payment.spec.js +++ b/test/unit/action/payment.spec.js @@ -22,6 +22,7 @@ describe('Action Payments Unit Tests', () => { store = new Store(); store.settings.displayFiat = false; require('../../../src/config').RETRY_DELAY = 1; + require('../../../src/config').PAYMENT_TIMEOUT = 10; grpc = sinon.createStubInstance(GrpcAction); notification = sinon.createStubInstance(NotificationAction); nav = sinon.createStubInstance(NavAction); @@ -240,5 +241,12 @@ describe('Action Payments Unit Tests', () => { expect(nav.goPayLightningConfirm, 'was called once'); expect(notification.display, 'was called once'); }); + + it('should go to error page on timeout', async () => { + payment.payLightning({ invoice: 'some-invoice' }); + await nap(100); + expect(nav.goPaymentFailed, 'was called once'); + expect(nav.goPayLightningDone, 'was not called'); + }); }); });