lightning-auth-and-payment
Version:
Lightning Network authentication and payment processing library for modern web applications
220 lines • 18.4 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.BTCPayPaymentModal = BTCPayPaymentModal;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const use_btcpay_payment_1 = require("../hooks/use-btcpay-payment");
const QRCode = __importStar(require("qrcode"));
/**
* BTCPay Payment Modal Component
*
* A comprehensive payment modal for Lightning Network payments via BTCPay Server.
* Features automatic invoice creation, QR code generation, payment polling, and
* user-friendly success/error states with auto-close functionality.
*
* @example
* ```tsx
* import { BTCPayPaymentModal } from 'lightning-auth-and-payment';
*
* function MyComponent() {
* const [showPayment, setShowPayment] = useState(false);
*
* return (
* <BTCPayPaymentModal
* isOpen={showPayment}
* onClose={() => setShowPayment(false)}
* amount={10000}
* description="Purchase: My Product"
* onPaymentSuccess={() => {
* // Handle successful payment
* console.log('Payment completed!');
* }}
* autoCloseDelay={3000}
* successMessage="Your purchase is ready!"
* />
* );
* }
* ```
*/
function BTCPayPaymentModal({ isOpen, onClose, amount, description, metadata, onPaymentSuccess, onPaymentError, autoCloseDelay = 3000, successMessage, showSuccessInModal = true, }) {
const { paymentState, copied, timeLeft, createInvoice, copyBolt11, resetPayment, isCreating, isWaiting, isSettled, isExpired, hasError, } = (0, use_btcpay_payment_1.useBTCPayPayment)();
const [qrCodeDataUrl, setQrCodeDataUrl] = (0, react_1.useState)('');
// Generate QR code when bolt11 is available
(0, react_1.useEffect)(() => {
if (paymentState.bolt11) {
QRCode.toDataURL(paymentState.bolt11, {
width: 256,
margin: 2,
color: {
dark: '#000000',
light: '#FFFFFF'
}
})
.then((url) => setQrCodeDataUrl(url))
.catch((err) => console.error('QR code generation failed:', err));
}
}, [paymentState.bolt11]);
// Create invoice when modal opens
(0, react_1.useEffect)(() => {
console.log('🎯 Modal useEffect - isOpen:', isOpen, 'status:', paymentState.status);
if (isOpen && paymentState.status === 'idle') {
console.log('🚀 Creating invoice for amount:', amount, 'description:', description);
createInvoice({ amount, description, metadata });
}
}, [isOpen, paymentState.status, createInvoice, amount, description, metadata]);
// Reset payment state when modal closes
(0, react_1.useEffect)(() => {
if (!isOpen) {
resetPayment();
}
}, [isOpen, resetPayment]);
// Handle payment success/error callbacks
(0, react_1.useEffect)(() => {
console.log('💳 Payment state changed - isSettled:', isSettled, 'hasError:', hasError, 'status:', paymentState.status);
if (isSettled) {
console.log('✅ Payment successful!');
// Call success callback immediately
if (onPaymentSuccess) {
onPaymentSuccess();
}
// Auto-close modal after delay if enabled
if (showSuccessInModal && autoCloseDelay > 0) {
console.log(`⏰ Auto-closing modal in ${autoCloseDelay}ms`);
const timer = setTimeout(() => {
console.log('🚪 Auto-closing modal');
onClose();
}, autoCloseDelay);
return () => clearTimeout(timer);
}
}
if (hasError && onPaymentError && paymentState.error) {
console.log('❌ Payment error! Calling onPaymentError:', paymentState.error);
onPaymentError(paymentState.error);
}
}, [isSettled, hasError, paymentState.error, onPaymentSuccess, onPaymentError, showSuccessInModal, autoCloseDelay, onClose]);
// Format price display
const formatPrice = (sats) => {
if (sats >= 100000) {
return `${(sats / 100000).toFixed(3)} BTC`;
}
return `${sats.toLocaleString()} sats`;
};
// Get status color
const getStatusColor = () => {
switch (paymentState.status) {
case 'waiting':
return 'text-yellow-500';
case 'settled':
return 'text-green-500';
case 'expired':
return 'text-red-500';
case 'error':
return 'text-red-500';
default:
return 'text-slate-400';
}
};
// Get status icon
const getStatusIcon = () => {
switch (paymentState.status) {
case 'creating':
return (0, jsx_runtime_1.jsx)("div", { className: "w-5 h-5 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" });
case 'waiting':
return (0, jsx_runtime_1.jsx)("div", { className: "w-5 h-5 border-2 border-yellow-500 border-t-transparent rounded-full animate-spin" });
case 'settled':
return (0, jsx_runtime_1.jsx)("div", { className: "w-5 h-5 bg-green-500 rounded-full flex items-center justify-center", children: (0, jsx_runtime_1.jsx)("svg", { className: "w-3 h-3 text-white", fill: "currentColor", viewBox: "0 0 20 20", children: (0, jsx_runtime_1.jsx)("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }) });
case 'expired':
return (0, jsx_runtime_1.jsx)("div", { className: "w-5 h-5 bg-red-500 rounded-full flex items-center justify-center", children: (0, jsx_runtime_1.jsx)("svg", { className: "w-3 h-3 text-white", fill: "currentColor", viewBox: "0 0 20 20", children: (0, jsx_runtime_1.jsx)("path", { fillRule: "evenodd", d: "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z", clipRule: "evenodd" }) }) });
default:
return (0, jsx_runtime_1.jsx)("div", { className: "w-5 h-5 bg-slate-500 rounded-full" });
}
};
// Get status message
const getStatusMessage = () => {
switch (paymentState.status) {
case 'creating':
return 'Creating invoice...';
case 'waiting':
return 'Waiting for payment...';
case 'settled':
return 'Payment successful!';
case 'expired':
return 'Payment expired. Please try again.';
case 'error':
return `Error: ${paymentState.error}`;
default:
return 'Initializing payment...';
}
};
// Handle ESC key press
(0, react_1.useEffect)(() => {
const handleEscKey = (event) => {
if (event.key === 'Escape') {
resetPayment();
onClose();
}
};
if (isOpen) {
document.addEventListener('keydown', handleEscKey);
// Prevent body scroll when modal is open
document.body.style.overflow = 'hidden';
}
return () => {
document.removeEventListener('keydown', handleEscKey);
document.body.style.overflow = 'unset';
};
}, [isOpen, onClose, resetPayment]);
if (!isOpen)
return null;
return ((0, jsx_runtime_1.jsx)("div", { className: "fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4", onClick: (e) => {
if (e.target === e.currentTarget) {
onClose();
}
}, children: (0, jsx_runtime_1.jsxs)("div", { className: "bg-slate-800 border border-slate-600 rounded-xl w-full max-w-sm mx-auto max-h-[80vh] overflow-y-auto shadow-2xl", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between p-3 sm:p-4 border-b border-slate-700/50", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-base sm:text-lg font-bold text-white", children: description || 'Lightning Payment' }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs sm:text-sm text-slate-300", children: "Lightning Network" })] }), (0, jsx_runtime_1.jsx)("button", { onClick: () => {
resetPayment();
onClose();
}, className: "p-2 text-slate-400 hover:text-white hover:bg-slate-700 rounded-lg transition-colors", "aria-label": "Close modal", children: (0, jsx_runtime_1.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "p-3 sm:p-6", children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-center mb-6", children: [(0, jsx_runtime_1.jsxs)("div", { className: `flex items-center justify-center space-x-2 mb-3 ${getStatusColor()}`, children: [getStatusIcon(), (0, jsx_runtime_1.jsx)("span", { className: "font-medium", children: getStatusMessage() })] }), amount && ((0, jsx_runtime_1.jsx)("div", { className: "text-3xl font-bold text-orange-500 mb-2", children: formatPrice(amount) })), isWaiting && timeLeft > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "text-sm text-slate-400 mb-4", children: ["Payment expires in ", timeLeft, "s"] }))] }), isWaiting && ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "bg-slate-900/50 rounded-lg p-4", children: [(0, jsx_runtime_1.jsx)("h4", { className: "font-medium text-white mb-2", children: "Payment Instructions:" }), (0, jsx_runtime_1.jsxs)("ol", { className: "text-sm text-slate-300 space-y-1", children: [(0, jsx_runtime_1.jsx)("li", { children: "1. Scan the QR code with your Lightning wallet" }), (0, jsx_runtime_1.jsx)("li", { children: "2. Or copy the payment request below" }), (0, jsx_runtime_1.jsx)("li", { children: "3. Confirm the payment in your wallet" }), (0, jsx_runtime_1.jsx)("li", { children: "4. You'll receive instant confirmation" })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "bg-white p-3 sm:p-4 rounded-lg flex items-center justify-center", children: (0, jsx_runtime_1.jsx)("div", { className: "text-center", children: qrCodeDataUrl ? ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("img", { src: qrCodeDataUrl, alt: "Lightning Payment QR Code", className: "w-32 h-32 sm:w-40 sm:h-40 mx-auto mb-2 rounded" }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-slate-500", children: "Scan with Lightning wallet" })] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "text-slate-400", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-32 h-32 sm:w-40 sm:h-40 bg-slate-200 rounded flex items-center justify-center mb-2", children: (0, jsx_runtime_1.jsx)("span", { className: "text-slate-500 text-sm", children: "Generating QR..." }) }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs", children: "Please wait" })] })) }) }), paymentState.bolt11 && ((0, jsx_runtime_1.jsx)("div", { className: "bg-slate-700/50 p-3 rounded-lg", children: (0, jsx_runtime_1.jsx)("code", { className: "text-xs text-slate-300 break-all block hyphens-auto overflow-wrap-anywhere", children: paymentState.bolt11 }) })), (0, jsx_runtime_1.jsx)("button", { onClick: copyBolt11, className: "w-full flex items-center justify-center space-x-2 py-3 bg-slate-700 hover:bg-slate-600 text-white rounded-lg transition-colors", children: copied ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("svg", { className: "w-4 h-4", fill: "currentColor", viewBox: "0 0 20 20", children: (0, jsx_runtime_1.jsx)("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }), (0, jsx_runtime_1.jsx)("span", { children: "Copied!" })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" }) }), (0, jsx_runtime_1.jsx)("span", { children: "Copy Payment Request" })] })) })] })), isSettled && ((0, jsx_runtime_1.jsxs)("div", { className: "text-center space-y-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-16 h-16 bg-green-500/20 rounded-full flex items-center justify-center mx-auto", children: (0, jsx_runtime_1.jsx)("svg", { className: "w-8 h-8 text-green-500", fill: "currentColor", viewBox: "0 0 20 20", children: (0, jsx_runtime_1.jsx)("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }) }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { className: "font-bold text-white text-lg mb-2", children: "Payment Successful!" }), (0, jsx_runtime_1.jsx)("p", { className: "text-slate-300 mb-4", children: successMessage || "Your payment has been confirmed on the Lightning Network." }), autoCloseDelay > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "bg-green-500/10 border border-green-500/20 rounded-lg p-3 mb-4", children: (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-green-400", children: "This window will close automatically in a few seconds..." }) }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "bg-slate-800/50 rounded-lg p-4 text-left", children: [(0, jsx_runtime_1.jsx)("h4", { className: "font-semibold text-white mb-2", children: "Payment Details" }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-1 text-sm text-slate-300", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between", children: [(0, jsx_runtime_1.jsx)("span", { children: "Amount:" }), (0, jsx_runtime_1.jsx)("span", { className: "font-medium text-white", children: paymentState.amountSats ? formatPrice(paymentState.amountSats) : '...' })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between", children: [(0, jsx_runtime_1.jsx)("span", { children: "Description:" }), (0, jsx_runtime_1.jsx)("span", { className: "font-medium text-white", children: paymentState.description })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between", children: [(0, jsx_runtime_1.jsx)("span", { children: "Status:" }), (0, jsx_runtime_1.jsx)("span", { className: "font-medium text-green-400", children: "Confirmed" })] })] })] }), autoCloseDelay === 0 && ((0, jsx_runtime_1.jsx)("button", { onClick: () => {
resetPayment();
onClose();
}, className: "w-full py-3 bg-green-500 hover:bg-green-600 text-white rounded-lg transition-colors", children: "Continue" }))] })), (isExpired || hasError) && ((0, jsx_runtime_1.jsxs)("div", { className: "text-center space-y-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-16 h-16 bg-red-500/20 rounded-full flex items-center justify-center mx-auto", children: (0, jsx_runtime_1.jsx)("svg", { className: "w-8 h-8 text-red-500", fill: "currentColor", viewBox: "0 0 20 20", children: (0, jsx_runtime_1.jsx)("path", { fillRule: "evenodd", d: "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z", clipRule: "evenodd" }) }) }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { className: "font-bold text-white text-lg mb-2", children: isExpired ? 'Payment Expired' : 'Payment Error' }), (0, jsx_runtime_1.jsx)("p", { className: "text-slate-300 mb-4", children: isExpired
? 'The payment request has expired. Please try again.'
: paymentState.error || 'An error occurred during payment.' })] }), (0, jsx_runtime_1.jsx)("button", { onClick: () => {
resetPayment();
onClose();
}, className: "w-full py-3 bg-slate-700 hover:bg-slate-600 text-white rounded-lg transition-colors", children: "Close" })] }))] })] }) }));
}
exports.default = BTCPayPaymentModal;
//# sourceMappingURL=BTCPayPaymentModal.js.map