From 8ba2e690610d5419df33058406711006336e3b6f Mon Sep 17 00:00:00 2001 From: Jordi Montes Date: Mon, 29 Apr 2024 17:59:32 -0700 Subject: [PATCH 1/7] react bc-send-payment: refactor component render Break the component structure in multipe composable parts. --- src/components/pages/bc-send-payment.ts | 264 +++++++++++++++--------- 1 file changed, 162 insertions(+), 102 deletions(-) diff --git a/src/components/pages/bc-send-payment.ts b/src/components/pages/bc-send-payment.ts index cf496b7..11302cc 100644 --- a/src/components/pages/bc-send-payment.ts +++ b/src/components/pages/bc-send-payment.ts @@ -31,6 +31,9 @@ export class SendPayment extends withTwind()(BitcoinConnectElement) { @state() _showQR = false; + @state() + _qr = null as QRCode | null; + @property({ type: String, }) @@ -51,11 +54,143 @@ export class SendPayment extends withTwind()(BitcoinConnectElement) { } } + private renderHeading(decodedInvoice: Invoice) { + return html` +

+ ${decodedInvoice.satoshi.toLocaleString(undefined, { + useGrouping: true, + })} sats +

+ `; + } + + private renderPaidState() { + return html` +
+

Paid!

+ ${successAnimation} +
+ ` + } + + private renderPayingState() { + return html` +
+

Paying...

+ ${waitingIcon(`w-48 h-48 ${classes['text-brand-mixed']}`)} +
+ `; + } + + private renderPaymentConfirmation() { + return html` + + ${bcIcon} + Confirm Payment + + ${disconnectSection(this._connectorName)} + `; + } + + private renderWaitingForPayment() { + return html` +
+ ${waitingIcon(`w-7 h-7 ${classes['text-brand-mixed']}`)} +

+ Waiting for payment +

+
+ `; + } + + private renderConnectWalletMobile() { + let displayInvoiceBtn = null + let qrSection = null + + if (this._showQR) { + qrSection = this.renderQR(); + } else { + displayInvoiceBtn = html` + + ${qrIcon} Copy & Display Invoice + + `; + } + + return html` +
+ + + ${walletIcon} Open in a Bitcoin Wallet + + + + ${bcIcon}Connect Wallet + + ${displayInvoiceBtn} +
+ ${qrSection} + `; + } + + private renderConnectWalletDesktop() { + return html` +
+ + ${bcIcon} + Connect Wallet to Pay + +
+
${hr('or')}
+

+ Scan to Pay +

+ ${this.renderQR()} + `; + } + + private renderQR() { + if (!this._showQR || !this.invoice || !this._qr) { + return null; + } + + return html` + + + + + + ${this._hasCopiedInvoice ? copiedIcon : copyIcon} + ${this._hasCopiedInvoice ? 'Copied!' : 'Copy Invoice'} + + `; + } + override render() { if (!this.invoice) { return null; } + const errorCorrectionLevel = 'L'; + const qr = qrcode(0, errorCorrectionLevel); + qr.addData(this.invoice); + qr.make(); + + this._qr = qr; + let decodedInvoice: Invoice; try { decodedInvoice = new Invoice({pr: this.invoice}); @@ -64,111 +199,36 @@ export class SendPayment extends withTwind()(BitcoinConnectElement) { store.getState().setError((error as Error).message); return null; } - const errorCorrectionLevel = 'L'; - const qr = qrcode(0, errorCorrectionLevel); - qr.addData(this.invoice); - qr.make(); const isMobileView = window.innerWidth < 600; + if (!isMobileView) { + this._showQR = true; + } - return html`
-

- ${decodedInvoice.satoshi.toLocaleString(undefined, { - useGrouping: true, - })} sats -

- ${this._connected || this.paid - ? this.paid - ? html`
-

Paid!

- ${successAnimation} -
` - : this._isPaying - ? html`
-

Paying...

- ${waitingIcon(`w-48 h-48 ${classes['text-brand-mixed']}`)} -
` - : html` - ${bcIcon} - Confirm Payment - - ${disconnectSection(this._connectorName)} ` - : html` -
- ${waitingIcon(`w-7 h-7 ${classes['text-brand-mixed']}`)} -

- Waiting for payment -

-
- - ${!isMobileView - ? html`
- - ${bcIcon} - Connect Wallet to Pay - -
-
${hr('or')}
- -

- Scan to Pay -

-
` - : html` -
- - - ${walletIcon} Open in a Bitcoin Wallet - - - - ${bcIcon}Connect Wallet - - ${this._showQR - ? null - : html` - ${qrIcon}Copy & Display Invoice - `} -
- `} - ${!isMobileView || this._showQR - ? html` - - - - - - ${this._hasCopiedInvoice ? copiedIcon : copyIcon} - ${this._hasCopiedInvoice ? 'Copied!' : 'Copy Invoice'} - - ` - : null} - `} - `; + let paymentStateElement; + + if (this.paid) { + paymentStateElement = this.renderPaidState(); + } else if (this._isPaying) { + paymentStateElement = this.renderPayingState(); + } else if (this._connected) { + paymentStateElement = this.renderPaymentConfirmation(); + } else { + paymentStateElement = html` + ${this.renderWaitingForPayment()} + ${isMobileView ? + this.renderConnectWalletMobile() + : this.renderConnectWalletDesktop() + } + `; + } + + return html` +
+ ${this.renderHeading(decodedInvoice)} + ${paymentStateElement} +
+ `; } private _onClickConnectWallet() { From 3b9b7c58c5ba6853195b35dfef27a1bc74caf2fd Mon Sep 17 00:00:00 2001 From: Jordi Montes Date: Mon, 29 Apr 2024 22:16:59 -0700 Subject: [PATCH 2/7] react bc-send-payment: add payment-methods property The new property can have three values: all, internal or external. Internal payment methods refer to "connect" buttons. External payment methods refer to "show QR/Copy Invoice" All payment methods refer to show Internal and External options. For mobile, the "Open in a Bitcoin Wallet" is always available. --- dev/vite/index.html | 15 +++++ src/components/pages/bc-send-payment.ts | 81 ++++++++++++++++++------- 2 files changed, 75 insertions(+), 21 deletions(-) diff --git a/dev/vite/index.html b/dev/vite/index.html index 2f81ed9..2773b45 100644 --- a/dev/vite/index.html +++ b/dev/vite/index.html @@ -222,6 +222,21 @@

Request Payment Screen

+ +

Request Payment Screen Internal (no QR)

+ + +

Request Payment Screen External (no connect)

+

Start screen

diff --git a/src/components/pages/bc-send-payment.ts b/src/components/pages/bc-send-payment.ts index 11302cc..b769619 100644 --- a/src/components/pages/bc-send-payment.ts +++ b/src/components/pages/bc-send-payment.ts @@ -44,6 +44,12 @@ export class SendPayment extends withTwind()(BitcoinConnectElement) { }) paid?: boolean; + @property({ + type: String, + attribute: 'payment-methods', + }) + paymentMethods: 'all' | 'internal' | "external" = 'all'; + protected override updated(changedProperties: PropertyValues): void { super.updated(changedProperties); @@ -109,17 +115,29 @@ export class SendPayment extends withTwind()(BitcoinConnectElement) { } private renderConnectWalletMobile() { - let displayInvoiceBtn = null + let internalMethods = null + let externalMethods = null let qrSection = null - - if (this._showQR) { - qrSection = this.renderQR(); - } else { - displayInvoiceBtn = html` + + + if (this.paymentMethods === 'all' || this.paymentMethods === 'internal') { + internalMethods = html` + + ${bcIcon}Connect Wallet + + ` + } + + if (this.paymentMethods === 'all' || this.paymentMethods === 'external') { + externalMethods = html` ${qrIcon} Copy & Display Invoice `; + + if (this._showQR) { + qrSection = this.renderQR(); + } } return html` @@ -129,28 +147,49 @@ export class SendPayment extends withTwind()(BitcoinConnectElement) { ${walletIcon} Open in a Bitcoin Wallet - - ${bcIcon}Connect Wallet - - ${displayInvoiceBtn} + ${internalMethods} + ${externalMethods} ${qrSection} `; } private renderConnectWalletDesktop() { + let internalMethods = null; + if (this.paymentMethods === 'all' || this.paymentMethods === 'internal') { + internalMethods = html` +
+ + ${bcIcon} + Connect Wallet to Pay + +
+ `; + } + + let separator = null; + if (this.paymentMethods === 'all') { + separator = html` +
${hr('or')}
+ `; + } + + let externalMethods = null; + if (this.paymentMethods === 'all' || this.paymentMethods === 'external') { + externalMethods = html` +
+

+ Scan to Pay +

+ ${this.renderQR()} +
+ `; + } + return html` -
- - ${bcIcon} - Connect Wallet to Pay - -
-
${hr('or')}
-

- Scan to Pay -

- ${this.renderQR()} + ${internalMethods} + ${separator} + ${externalMethods} `; } From 19705f4709707a5db24cc2d1e37e2a6a6608ac6a Mon Sep 17 00:00:00 2001 From: Jordi Montes Date: Mon, 29 Apr 2024 22:22:12 -0700 Subject: [PATCH 3/7] script: `npm run format` --- dev/vite/index.html | 4 +- src/components/pages/bc-send-payment.ts | 62 ++++++++++++------------- 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/dev/vite/index.html b/dev/vite/index.html index 2773b45..45caedf 100644 --- a/dev/vite/index.html +++ b/dev/vite/index.html @@ -225,14 +225,14 @@

Request Payment Screen

payment-methods="all" > -

Request Payment Screen Internal (no QR)

+

Request Payment Screen Internal (no QR)

-

Request Payment Screen External (no connect)

+

Request Payment Screen External (no connect)

+

Paid!

${successAnimation}
- ` + `; } private renderPayingState() { @@ -107,25 +111,22 @@ export class SendPayment extends withTwind()(BitcoinConnectElement) { return html`
${waitingIcon(`w-7 h-7 ${classes['text-brand-mixed']}`)} -

- Waiting for payment -

+

Waiting for payment

`; } private renderConnectWalletMobile() { - let internalMethods = null - let externalMethods = null - let qrSection = null - + let internalMethods = null; + let externalMethods = null; + let qrSection = null; if (this.paymentMethods === 'all' || this.paymentMethods === 'internal') { internalMethods = html` ${bcIcon}Connect Wallet - ` + `; } if (this.paymentMethods === 'all' || this.paymentMethods === 'external') { @@ -147,8 +148,7 @@ export class SendPayment extends withTwind()(BitcoinConnectElement) { ${walletIcon} Open in a Bitcoin Wallet - ${internalMethods} - ${externalMethods} + ${internalMethods} ${externalMethods} ${qrSection} `; @@ -169,15 +169,17 @@ export class SendPayment extends withTwind()(BitcoinConnectElement) { let separator = null; if (this.paymentMethods === 'all') { - separator = html` -
${hr('or')}
- `; + separator = html`
${hr('or')}
`; } let externalMethods = null; if (this.paymentMethods === 'all' || this.paymentMethods === 'external') { externalMethods = html` -
+

Scan to Pay

@@ -186,11 +188,7 @@ export class SendPayment extends withTwind()(BitcoinConnectElement) { `; } - return html` - ${internalMethods} - ${separator} - ${externalMethods} - `; + return html` ${internalMethods} ${separator} ${externalMethods} `; } private renderQR() { @@ -209,8 +207,8 @@ export class SendPayment extends withTwind()(BitcoinConnectElement) { flex gap-1 mt-4 ${classes['text-brand-mixed']} ${ - classes.interactive - } font-semibold text-xs" + classes.interactive + } font-semibold text-xs" > ${this._hasCopiedInvoice ? copiedIcon : copyIcon} ${this._hasCopiedInvoice ? 'Copied!' : 'Copy Invoice'} @@ -253,19 +251,17 @@ export class SendPayment extends withTwind()(BitcoinConnectElement) { } else if (this._connected) { paymentStateElement = this.renderPaymentConfirmation(); } else { - paymentStateElement = html` - ${this.renderWaitingForPayment()} - ${isMobileView ? - this.renderConnectWalletMobile() - : this.renderConnectWalletDesktop() - } - `; + paymentStateElement = html` + ${this.renderWaitingForPayment()} + ${isMobileView + ? this.renderConnectWalletMobile() + : this.renderConnectWalletDesktop()} + `; } return html`
- ${this.renderHeading(decodedInvoice)} - ${paymentStateElement} + ${this.renderHeading(decodedInvoice)} ${paymentStateElement}
`; } From 12a47c3c39539ec26ce761943faf9f1fe31051b9 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Wed, 1 May 2024 12:03:03 +0700 Subject: [PATCH 4/7] feat: improve payment methods support - only show mobile open in wallet button with correct payment methods - add payment methods to bc-payment component - add payment methods option to pay button - add payment methods option to launch payment modal function - add payment methods option to dev interface and apply to all related elements - minor ui fixes - update README --- README.md | 3 ++ dev/vite/index.html | 40 ++++++++++++++++--------- src/api.ts | 9 ++++++ src/components/bc-pay-button.ts | 8 +++++ src/components/flows/bc-payment.ts | 8 +++++ src/components/pages/bc-send-payment.ts | 39 +++++++++++++----------- src/components/routes.ts | 8 ++--- src/types/PaymentMethods.ts | 1 + 8 files changed, 80 insertions(+), 36 deletions(-) create mode 100644 src/types/PaymentMethods.ts diff --git a/README.md b/README.md index 125e471..8d07ef3 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,7 @@ Bitcoin Connect exposes the following web components for allowing users to conne - `` - launches the Bitcoin Connect Payment Modal on click - Arguments: - `invoice` - BOLT11 invoice. Modal will only open if an invoice is set + - `payment-methods` (optional) "all" | "external" | "internal" - `title` - (optional) change the title of the button - `preimage` - (optional) set this if you received an external payment - Events: @@ -213,6 +214,7 @@ Bitcoin Connect exposes the following web components for allowing users to conne - `` - render a payment request UI without modal - Arguments: - `invoice` - BOLT11 invoice + - `payment-methods` (optional) "all" | "external" | "internal" - `paid` - **Experimental** set to true to mark payment was made externally (This will change to `preimage` in v4) - Events: - `bc:onpaid` - fires event with WebLN payment response in `event.detail` (contains `preimage`) @@ -274,6 +276,7 @@ import {launchPaymentModal} from '@getalby/bitcoin-connect'; const {setPaid} = launchPaymentModal({ invoice: 'lnbc...', + //paymentMethods: "all" // "all" | "external" | "internal" onPaid: (response) => { clearInterval(checkPaymentInterval); alert('Received payment! ' + response.preimage); diff --git a/dev/vite/index.html b/dev/vite/index.html index 45caedf..c022bcb 100644 --- a/dev/vite/index.html +++ b/dev/vite/index.html @@ -153,6 +153,11 @@

Settings

id="invoice" onchange="localStorage.setItem('invoice', document.getElementById('invoice').value); window.location.reload()" /> +

Payment methods (all, external, internal)

+

Show Balance

Router outlet

Request Payment Screen

+

+ Note: connect button will do nothing because requires higher component. + Use "Payment Flow" above instead. +

-

Request Payment Screen Internal (no QR)

- - -

Request Payment Screen External (no connect)

- -

Start screen

@@ -287,6 +282,9 @@

Try it yourself

const invoice = localStorage.getItem('invoice'); document.getElementById('invoice').value = invoice; + const paymentMethods = localStorage.getItem('payment-methods'); + document.getElementById('payment-methods').value = paymentMethods; + const showBalance = localStorage.getItem('show-balance') !== false.toString(); document.getElementById('show-balance').checked = showBalance; @@ -331,6 +329,7 @@

Try it yourself

} launchPaymentModal({ invoice: invoiceToPay, + paymentMethods, onPaid: (response) => { alert('Paid! ' + response.preimage); }, @@ -349,6 +348,7 @@

Try it yourself

const {setPaid} = launchPaymentModal({ invoice: invoice.paymentRequest, + paymentMethods, onPaid: (response) => { clearInterval(checkPaymentInterval); alert('Received payment! ' + response.preimage); @@ -411,6 +411,18 @@

Try it yourself

}); document.addEventListener('bc:onpaid', (e) => console.log('PAID!', e)); + + if (paymentMethods) { + document + .getElementById('send-payment') + .setAttribute('payment-methods', paymentMethods); + document + .getElementById('payment-flow') + .setAttribute('payment-methods', paymentMethods); + document + .getElementById('pay-button') + .setAttribute('payment-methods', paymentMethods); + } diff --git a/package.json b/package.json index 81d10c5..94c0c8b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@getalby/bitcoin-connect", - "version": "3.3.0-beta.2", + "version": "3.3.0", "description": "Web components to connect to a lightning wallet and power a website with WebLN", "type": "module", "source": "src/index.ts", diff --git a/react/package.json b/react/package.json index 557f217..db02dc3 100644 --- a/react/package.json +++ b/react/package.json @@ -1,6 +1,6 @@ { "name": "@getalby/bitcoin-connect-react", - "version": "3.3.0-beta.4", + "version": "3.3.0", "type": "module", "source": "src/index.ts", "main": "./dist/index.cjs", @@ -23,10 +23,11 @@ ], "scripts": { "dev": "microbundle --globals react=React --jsx React.createElement --jsxFragment React.Fragment --jsxImportSource react watch", + "prepack": "yarn build", "build": "microbundle --globals react=React --jsx React.createElement --jsxFragment React.Fragment --jsxImportSource react" }, "dependencies": { - "@getalby/bitcoin-connect": "^3.3.0-beta.2" + "@getalby/bitcoin-connect": "^3.3.0" }, "devDependencies": { "@types/react": "^18.2.21", diff --git a/react/yarn.lock b/react/yarn.lock index 14184f3..48ce92c 100644 --- a/react/yarn.lock +++ b/react/yarn.lock @@ -1018,10 +1018,10 @@ "@babel/helper-validator-identifier" "^7.22.5" to-fast-properties "^2.0.0" -"@getalby/bitcoin-connect@^3.3.0-beta.2": - version "3.3.0-beta.2" - resolved "https://registry.yarnpkg.com/@getalby/bitcoin-connect/-/bitcoin-connect-3.3.0-beta.2.tgz#9491763ddc731b9cf9c425f6de1cbe5c47af5cd8" - integrity sha512-U/ZzIGUQjM42KeN6F08bi//86MTmHxMmm9wrQZE4ir+DjrkXABcLXu6yxRkMV5MVWtpUlCj5OBO89+TVMciptA== +"@getalby/bitcoin-connect@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@getalby/bitcoin-connect/-/bitcoin-connect-3.3.0.tgz#7a53bf884729e8ff3e692e059d2b9c02ba75fa4b" + integrity sha512-3e4oGasmz6EdX+xwzS/bFv9nZmZcFSafSgrabpX9ejvBGS93sntZ8yFwLf0ynyvpxcUbeCq4Ww0Vt3TlQmwgug== dependencies: "@getalby/lightning-tools" "^5.0.3" "@getalby/sdk" "^3.2.3"