// @flow /* eslint-disable no-undef */ /* eslint-disable react/prop-types */ import React from 'react'; import Page from 'component/page'; import Card from 'component/common/card'; import { SETTINGS } from 'lbry-redux'; import { Lbryio } from 'lbryinc'; import { STRIPE_PUBLIC_KEY } from 'config'; import classnames from 'classnames'; import moment from 'moment'; let scriptLoading = false; let scriptLoaded = false; let scriptDidError = false; const dateFormat = { month: 'short', day: 'numeric', year: 'numeric', }; let stripeEnvironment = 'test'; // if the key contains pk_live it's a live key // update the environment for the calls to the backend to indicate which environment to hit if (STRIPE_PUBLIC_KEY.indexOf('pk_live') > -1) { stripeEnvironment = 'live'; } type Props = { disabled: boolean, label: ?string, email: ?string, } class CardVerify extends React.Component { constructor(props: Props) { super(props); this.state = { open: false, scriptFailedToLoad: false, currentFlowStage: 'loading', // loading, confirmingCard, cardConfirmed customerTransactions: [], pageTitle: 'Add Card', userCardDetails: {}, }; } componentDidMount() { var that = this; const script = document.createElement('script'); script.src = 'https://js.stripe.com/v3/'; script.async = true; // $FlowFixMe document.body.appendChild(script); // public key of the stripe account var publicKey = STRIPE_PUBLIC_KEY; // client secret of the SetupIntent (don't share with anyone but customer) var clientSecret = ''; // setting a timeout to let the client secret populate // TODO: fix this, should be a cleaner way setTimeout(function() { // check if customer has card setup already Lbryio.call('customer', 'status', { environment: stripeEnvironment, }, 'post').then(customerStatusResponse => { // user has a card saved if their defaultPaymentMethod has an id const defaultPaymentMethod = customerStatusResponse.Customer.invoice_settings.default_payment_method; var userHasAlreadySetupPayment = Boolean(defaultPaymentMethod && defaultPaymentMethod.id); // show different frontend if user already has card if (userHasAlreadySetupPayment) { var card = customerStatusResponse.PaymentMethods[0].card; var cardDetails = { brand: card.brand, expiryYear: card.exp_year, expiryMonth: card.exp_month, lastFour: card.last4 }; that.setState({ currentFlowStage: 'cardConfirmed', pageTitle: 'Tip History', userCardDetails: cardDetails, }); // get customer transactions Lbryio.call('customer', 'list', { environment: stripeEnvironment, }, 'post').then(customerTransactionsResponse => { that.setState({ customerTransactions: customerTransactionsResponse, }) console.log(customerTransactionsResponse); }); // otherwise, prompt them to save a card } else { that.setState({ currentFlowStage: 'confirmingCard', }); // get a payment method secret for frontend Lbryio.call('customer', 'setup', { environment: stripeEnvironment, }, 'post').then(customerSetupResponse => { console.log(customerSetupResponse); clientSecret = customerSetupResponse.client_secret; // instantiate stripe elements setupStripe(); }); } // if the status call fails, either an actual error or need to run setup first }).catch(function(error) { console.log(error); // errorString passed from the API (with a 403 error) const errorString = 'user as customer is not setup yet'; // if it's beamer's error indicating the account is not linked yet if (error.message.indexOf(errorString) > -1) { // send them to save a card that.setState({ currentFlowStage: 'confirmingCard', }); // get a payment method secret for frontend Lbryio.call('customer', 'setup', { environment: stripeEnvironment, }, 'post').then(customerSetupResponse => { console.log(customerSetupResponse); clientSecret = customerSetupResponse.client_secret; // instantiate stripe elements setupStripe(); }); } else { console.log('Unseen before error'); } }); }, 250); function setupStripe() { // TODO: have to fix this, using so that the script is available setTimeout(function() { var stripeElements = function(publicKey, setupIntent) { var stripe = Stripe(publicKey); var elements = stripe.elements(); // Element styles var style = { base: { fontSize: '16px', color: '#32325d', fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif', fontSmoothing: 'antialiased', '::placeholder': { color: 'rgba(0,0,0,0.4)', }, }, }; var card = elements.create('card', { style: style }); card.mount('#card-element'); // Element focus ring card.on('focus', function() { var el = document.getElementById('card-element'); el.classList.add('focused'); }); card.on('blur', function() { var el = document.getElementById('card-element'); el.classList.remove('focused'); }); card.on('ready', function() { card.focus(); }); var email = that.props.email; function submitForm(event) { event.preventDefault(); // if client secret wasn't loaded properly if (!clientSecret) { var displayErrorText = 'There was an error in generating your payment method. Please contact a developer'; var displayError = document.getElementById('card-errors'); displayError.textContent = displayErrorText; return; } changeLoadingState(true); stripe.confirmCardSetup(clientSecret, { payment_method: { card: card, billing_details: { email: email }, }, }).then(function(result) { if (result.error) { console.log(result); changeLoadingState(false); var displayError = document.getElementById('card-errors'); displayError.textContent = result.error.message; } else { // The PaymentMethod was successfully set up // hide and show the proper divs orderComplete(stripe, clientSecret); } }); } // Handle payment submission when user clicks the pay button. var button = document.getElementById('submit'); button.addEventListener('click', function(event) { submitForm(event); }); // currently doesn't work because the iframe javascript context is different // would be nice though if it's even technically possible // window.addEventListener('keyup', function(event) { // if (event.keyCode === 13) { // submitForm(event); // } // }, false); }; // TODO: possible bug here where clientSecret isn't done stripeElements(publicKey, clientSecret); // Show a spinner on payment submission var changeLoadingState = function(isLoading) { if (isLoading) { document.querySelector('button').disabled = true; document.querySelector('#spinner').classList.remove('hidden'); document.querySelector('#button-text').classList.add('hidden'); } else { document.querySelector('button').disabled = false; document.querySelector('#spinner').classList.add('hidden'); document.querySelector('#button-text').classList.remove('hidden'); } }; // shows a success / error message when the payment is complete var orderComplete = function(stripe, clientSecret) { stripe.retrieveSetupIntent(clientSecret).then(function(result) { Lbryio.call('customer', 'status', { environment: stripeEnvironment, }, 'post').then(customerStatusResponse => { var card = customerStatusResponse.PaymentMethods[0].card; var cardDetails = { brand: card.brand, expiryYear: card.exp_year, expiryMonth: card.exp_month, lastFour: card.last4, }; that.setState({ currentFlowStage: 'cardConfirmed', pageTitle: 'Tip History', userCardDetails: cardDetails, }); }); console.log(result); changeLoadingState(false); }); }; }, 0); } } componentDidUpdate() { if (!scriptLoading) { this.updateStripeHandler(); } } componentWillUnmount() { if (this.loadPromise) { this.loadPromise.reject(); } if (CardVerify.stripeHandler && this.state.open) { CardVerify.stripeHandler.close(); } } onScriptLoaded = () => { // if (!CardVerify.stripeHandler) { // CardVerify.stripeHandler = StripeCheckout.configure({ // key: 'pk_test_NoL1JWL7i1ipfhVId5KfDZgo', // }); // // if (this.hasPendingClick) { // this.showStripeDialog(); // } // } }; onScriptError = (...args) => { this.setState({ scriptFailedToLoad: true }); }; onClosed = () => { this.setState({ open: false }); }; updateStripeHandler() { // if (!CardVerify.stripeHandler) { // CardVerify.stripeHandler = StripeCheckout.configure({ // key: this.props.stripeKey, // }); // } } render() { const { scriptFailedToLoad } = this.props; const { currentFlowStage, customerTransactions, pageTitle, userCardDetails } = this.state; return (
{scriptFailedToLoad && (
There was an error connecting to Stripe. Please try again later.
)}
{currentFlowStage === 'loading' &&
} {currentFlowStage === 'confirmingCard' &&
} {currentFlowStage === 'cardConfirmed' &&

Brand: {userCardDetails.brand.toUpperCase()}   Last 4: {userCardDetails.lastFour}   Expires: {userCardDetails.expiryMonth}/{userCardDetails.expiryYear}  

} />
{(!customerTransactions || customerTransactions.length === 0) && } {customerTransactions && customerTransactions.length > 0 &&
{customerTransactions && customerTransactions.map((transaction) => ( ))}
{__('Date')} {<>{__('Receiving Channel Name')}} {__('Amount (USD)')} {__('Anonymous')}
{moment(transaction.created_at).format('LLL')} {transaction.channel_name} ${transaction.tipped_amount / 100} {transaction.private_tip ? 'Yes' : 'No'}
} />}
} ); } } export default CardVerify; /* eslint-enable no-undef */ /* eslint-enable react/prop-types */