Skip to content

Commit

Permalink
Bip070 status monitoring (#32)
Browse files Browse the repository at this point in the history
* Correcting display bug introduced with BIP070 support addition

* BIP 70 status monitoring with websockets

* Updating PriceDisplay README to include InvoiceTimer info

* Removing debug comments

* Adding expired state to ButtonQR story

* Adding invoiceTimeLeftSeconds to prop types in BadgerBadge and BadgerButton

* Adding invoice amounts, coin type, removing timer and invoice display from PriceDisplay, putting in own component

* Cleaning up storybook

* Properly getting paymentId, invoice timer and amounts only for pay.bitcoin.com invoices

* Adding websocket to state and closing it on componentWillUnmount()

* Date.now() correction

* Handle timer outside of websocket msg, clear on unmount

* Softening and clarifying invoice expired

* Updating READMEs to reflect changes to PriceDisplay and InvoiceTimer

* BIP70 stories formatting consistency

* Fixing prop name in InvoiceTimer story

* CSS fix for PriceDisplay

* CSS fix for PriceDisplay

* Using styled H3 for expired

* Removing color comments

* Fixing case bug preventing SLP PriceDisplay for non-invoice cases

* Adding fiat price display for BCH bip70 invoices

* Updating version in package.json

* Removing extra 0 in changelog

* Better timer, ensuring that bip70 invoice over-rides any user-entered props even for invoices without websocket support

* Better info and formatting in stories for BadgerButton and BadgerBadge

* Cleaning up storybook, allowing paymentRequestUrl to change

* Handling empty paymentRequestUrl prop change

* allowing for no paymentRequestUrl

* Fixing bug in BadgerButton to ensure only BIP70 invoices are tagged as such
  • Loading branch information
Joey authored and SpicyPete committed Nov 18, 2019
1 parent 528c76d commit 2410642
Show file tree
Hide file tree
Showing 18 changed files with 591 additions and 100 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog - Badger Components React

## 0.7.0 (Nov 7, 2019)

* Added invoice timer for BIP 70 invoices
* Added status animations for BIP 70 invoices (paid, expired), checked via websocket (support for pay.bitcoin.com BIP70 invoices only)
* If `paymentRequestUrl` is passed to `BadgerButton` or `BadgerBadge`, countdown timer will display automatically

## 0.6.0 (September 19, 2019)

* Added support for BIP 70 invoices
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "badger-components-react",
"version": "0.6.0",
"version": "0.7.0",
"description": "Integrate the Badger Wallet into your React app with ease.",
"main": "dist/badger-components.js",
"scripts": {
Expand Down
18 changes: 16 additions & 2 deletions src/atoms/ButtonQR/ButtonQR.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { type ButtonStates } from '../../hoc/BadgerBase';
import colors from '../../styles/colors';

import CheckSVG from '../../images/CheckSVG';
import XSVG from '../../images/XSVG';
import LoadSVG from '../../images/LoadSVG';

import Text from '../Text';
Expand Down Expand Up @@ -89,6 +90,13 @@ const CompleteCover = styled.div`
background-color: ${colors.success500};
`;

const ExpiredCover = styled.div`
${cover};
border-radius: 5px;
border: 1px solid ${colors.expired700};
background-color: ${colors.expired500};
`;

const LoginCover = styled.div`
${cover};
font-size: 16px;
Expand Down Expand Up @@ -155,14 +163,15 @@ class ButtonQR extends React.PureComponent<Props> {
? `${uriBase}?amount=${amountSatoshis / 1e8}`
: uriBase;

if (paymentRequestUrl && paymentRequestUrl.length) {
if (paymentRequestUrl && paymentRequestUrl.length > 0) {
uri = `bitcoincash:?r=${paymentRequestUrl}`;
}
} else uri = `bitcoincash:?r=https://pleaseEnterBip70Url/`;

// State booleans
const isFresh = step === 'fresh';
const isPending = step === 'pending';
const isComplete = step === 'complete';
const isExpired = step === 'expired';
const isLogin = step === 'login';
const isInstall = step === 'install';

Expand All @@ -181,6 +190,11 @@ class ButtonQR extends React.PureComponent<Props> {
<CheckSVG />
</CompleteCover>
)}
{isExpired && (
<ExpiredCover>
<XSVG />
</ExpiredCover>
)}

<QRCodeWrapper>
<a href={uri}>
Expand Down
18 changes: 18 additions & 0 deletions src/atoms/ButtonQR/stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,22 @@ storiesOf('ButtonQR', module)
{
notes: 'Badger plugin not installed, prompt user to install Badger',
}
)
.add(
'expired',
() => (
<ButtonQR
paymentRequestUrl={text(
'Invoice URL',
// expired invoice
'https://pay.bitcoin.com/i/Fz4AaMpzuSde9DgpFwDt13'
)}
step={'expired'}
>
<Text>{ButtonText}</Text>
</ButtonQR>
),
{
notes: 'Shown for an expired BIP70 invoice',
}
);
85 changes: 67 additions & 18 deletions src/components/BadgerBadge/BadgerBadge.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import SLPLogoImage from '../../images/slp-logo.png';
import colors from '../../styles/colors';

import PriceDisplay from '../PriceDisplay';
import InvoiceTimer from '../InvoiceTimer';

import Button from '../../atoms/Button';
import ButtonQR from '../../atoms/ButtonQR';
Expand Down Expand Up @@ -109,6 +110,10 @@ type Props = BadgerBaseProps & {
showQR?: boolean,
showBorder?: boolean,

invoiceInfo: ?Object,
invoiceTimeLeftSeconds: ?number,
invoiceFiat: ?number,

handleClick: Function,
};

Expand Down Expand Up @@ -144,33 +149,77 @@ class BadgerBadge extends React.PureComponent<Props> {
showAmount,
showQR,
paymentRequestUrl,
invoiceInfo,
invoiceTimeLeftSeconds,
invoiceFiat,
showBorder,
showBrand,
} = this.props;

const CoinImage = coinType === 'BCH' ? BitcoinCashImage : SLPLogoImage;

// Handle cases for displaying price
// Case 1: no bip70 invoice
// Case 2: bip70 invoice, supported pay.bitcoin.com invoice with websocket info over-riding props, showAmount={true}
// Case 3: bip70 invoice, no supported websocket price updating, only display a "Bip70 invoice" label and no cointype or price, OR valid invoice but showAmount={false}; a bip70 invoice must be labeled as such even if showAmount={false} and price in props must be over-ridden

// Case 1: no bip70 invoice
let displayedPriceInfo = (
<Prices>
{price != undefined && (
<PriceDisplay
preSymbol={getCurrencyPreSymbol(currency)}
price={formatPriceDisplay(price)}
symbol={currency}
/>
)}
{showAmount && (
<PriceDisplay
coinType={coinType}
price={formatAmount(amount, coinDecimals)}
symbol={coinSymbol}
name={coinName}
/>
)}
</Prices>
);
// Case 2: bip70 invoice, supported pay.bitcoin.com invoice with websocket info over-riding props, showAmount={true}
if (showAmount && paymentRequestUrl && invoiceInfo.currency) {
displayedPriceInfo = (
<Prices>
{invoiceFiat != undefined && (
<PriceDisplay
preSymbol={getCurrencyPreSymbol(currency)}
price={formatPriceDisplay(invoiceFiat)}
symbol={currency}
/>
)}

<PriceDisplay
coinType={coinType}
price={formatAmount(amount, coinDecimals)}
symbol={coinSymbol}
name={coinName}
/>
</Prices>
);
// Case 3: bip70 invoice, no supported websocket price updating, only display a "Bip70 invoice" label and no cointype or price, OR valid invoice but showAmount={false}
// Bip70 invoice must be labeled as such if amounts are not shown
} else if (paymentRequestUrl) {
displayedPriceInfo = (
<Prices>
<PriceText>BIP70 Invoice</PriceText>
</Prices>
);
}
return (
<Outer>
<Main showBorder={showBorder}>
<H3>{text}</H3>
<Prices>
{price != undefined && (
<PriceDisplay
preSymbol={getCurrencyPreSymbol(currency)}
price={formatPriceDisplay(price)}
symbol={currency}
/>
)}
{showAmount && (
<PriceDisplay
coinType={coinType}
price={formatAmount(amount, coinDecimals)}
symbol={coinSymbol}
name={coinName}
paymentRequestUrl={paymentRequestUrl}
/>
)}
</Prices>
{displayedPriceInfo}
{invoiceTimeLeftSeconds !== null && (
<InvoiceTimer invoiceTimeLeftSeconds={invoiceTimeLeftSeconds} />
)}
<ButtonContainer>
{showQR ? (
<ButtonQR
Expand Down
29 changes: 24 additions & 5 deletions src/components/BadgerBadge/stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,14 +311,14 @@ storiesOf('BadgerBadge', module)
}
)
.add(
'BIP070 Invoicing',
'BIP70 Invoicing - BCH, expired',
() => (
<BadgerBadge
paymentRequestUrl={text(
'Invoice URL',
'https://yourInvoiceUrlHere.com/String'
//'https://yourInvoiceUrlHere.com/String'
'https://pay.bitcoin.com/i/7UG3Z5y56DoXLQzQJAJxoD'
)}
showQR={boolean('showQR', true)}
showAmount={boolean('showAmount', true)}
successFn={() => console.log('BIP70 Invoice successfully paid')}
failFn={() =>
Expand All @@ -327,7 +327,26 @@ storiesOf('BadgerBadge', module)
/>
),
{
notes:
'If paymentRequestUrl is set, this parameter defines the entire transaction.',
notes: 'Expired BCH invoice with no conflicting props',
}
)
.add(
'BIP70 Invoicing - SLP, Paid',
() => (
<BadgerBadge
paymentRequestUrl={text(
'Invoice URL',
//'https://yourInvoiceUrlHere.com/String'
'https://pay.bitcoin.com/i/DFFwn544tB2A2YvekWd3Y9'
)}
showAmount={boolean('showAmount', true)}
successFn={() => console.log('BIP70 Invoice successfully paid')}
failFn={() =>
console.log('BIP70 Invoice is expired or the URL is invalid')
}
/>
),
{
notes: 'Paid SLP invoice with no conflicting props',
}
);
87 changes: 63 additions & 24 deletions src/components/BadgerButton/BadgerButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import BadgerBase, {
} from '../../hoc/BadgerBase';

import PriceDisplay from '../PriceDisplay';
import InvoiceTimer from '../InvoiceTimer';

import Button from '../../atoms/Button';
import ButtonQR from '../../atoms/ButtonQR';
Expand Down Expand Up @@ -57,6 +58,18 @@ const Wrapper = styled.div`
border-radius: 4px;
`;

const PriceText = styled.p`
font-family: monospace;
font-size: 16px;
line-height: 1em;
margin: 0;
display: grid;
grid-gap: 5px;
grid-auto-flow: column;
justify-content: flex-end;
align-items: center;
`;

// Badger Button Props
type Props = BadgerBaseProps & {
text?: string,
Expand All @@ -70,6 +83,10 @@ type Props = BadgerBaseProps & {
coinDecimals?: number,
coinName?: string,

invoiceInfo: ?Object,
invoiceTimeLeftSeconds: ?number,
invoiceFiat: ?number,

handleClick: Function,
step: ButtonStates,
};
Expand Down Expand Up @@ -101,10 +118,50 @@ class BadgerButton extends React.PureComponent<Props> {
showBorder,
showQR,
paymentRequestUrl,

invoiceInfo,
invoiceTimeLeftSeconds,
invoiceFiat,
} = this.props;

const CoinImage = coinType === 'BCH' ? BitcoinCashImage : SLPLogoImage;

// buttonPriceDisplay -- handle different cases for BIP70 invoices

// buttonPriceDisplay if no price, or if a bip70 invoice is set from a server without supported websocket updates
let buttonPriceDisplay = <Text>Badger Pay</Text>;

// buttonPriceDisplay of price set in props and no invoice is set
if (price && !paymentRequestUrl) {
buttonPriceDisplay = (
<Text>
{getCurrencyPreSymbol(currency)} {formatPriceDisplay(price)}
<Small> {currency}</Small>
</Text>
);
// buttonPriceDisplay if valid bip70 invoice with price information is available
} else if (paymentRequestUrl && invoiceFiat != undefined) {
buttonPriceDisplay = (
<Text>
{getCurrencyPreSymbol(currency)} {formatPriceDisplay(invoiceFiat)}
<Small> {currency}</Small>
</Text>
);
}

let determinedShowAmount = (
<PriceDisplay
coinType={coinType}
price={formatAmount(amount, coinDecimals)}
symbol={coinSymbol}
name={coinName}
/>
);
if (!showAmount) {
determinedShowAmount = <React.Fragment></React.Fragment>;
} else if (showAmount && paymentRequestUrl && !invoiceInfo.currency) {
determinedShowAmount = <PriceText>BIP70 Invoice</PriceText>;
}
return (
<Outer>
<Wrapper hasBorder={showBorder}>
Expand All @@ -117,36 +174,18 @@ class BadgerButton extends React.PureComponent<Props> {
step={step}
paymentRequestUrl={paymentRequestUrl}
>
{price ? (
<Text>
{getCurrencyPreSymbol(currency)} {formatPriceDisplay(price)}
<Small> {currency}</Small>
</Text>
) : (
<Text>Badger Pay</Text>
)}
{buttonPriceDisplay}
</ButtonQR>
) : (
<Button onClick={handleClick} step={step}>
{price ? (
<Text>
{getCurrencyPreSymbol(currency)} {formatPriceDisplay(price)}
<Small> {currency}</Small>
</Text>
) : (
<Text>Badger Pay</Text>
)}
{buttonPriceDisplay}
</Button>
)}

{showAmount && (
<PriceDisplay
coinType={coinType}
price={formatAmount(amount, coinDecimals)}
symbol={coinSymbol}
name={coinName}
paymentRequestUrl={paymentRequestUrl}
/>
{determinedShowAmount}

{invoiceTimeLeftSeconds !== null && (
<InvoiceTimer invoiceTimeLeftSeconds={invoiceTimeLeftSeconds} />
)}
</Wrapper>
</Outer>
Expand Down
Loading

0 comments on commit 2410642

Please sign in to comment.