bec50829c1
about to test something generate programatically beginning of the frontend stripe integration page seems to be working add user put functionality behind conditional tag connect frontend working well adding environment variables to save success and failure url bugfix bugfix final clean up adding credit card page seems to be coming along calls successfully coming from the frontend fixing up frontend cleaning up frontend coming along client secret working basic frontend in place adding tip page adding more to the tip frontend frontend almost done tabs coming along one last thing to do for frontend adding explainer text as custom function putting finishing touches on tabs support tabs working well disable fiat toggle when card not connected fix frontend gui bug bugfix and pull out label function fix symbol for tip gui modal when card is not yet saved fix fiat disabled bug knowing whether card is added programatically sending tip with frontend tip functionality working show unpaid balance add frontend for card add section update frontend update frontend bugfix change to use react instead of css update how stripe is instantiated fix bug use customer setup coming along working but needs optimization persist if card is saved adding anonymous tip functionality fix nan bug build stripe endpoints programatically show for all users for time being allow the stripe key to automatically switch to live environment bugfix bugfix fix jslint fix channel page support button better docs show customer transactions on frontend basic table in place various page updates per jeremys notes showing card details nicer tip history table add better prompt to add card on file viewer page some linting time put connect account behind fiat enabled no persist fiat mode wallet calls tip stuff
428 lines
14 KiB
JavaScript
428 lines
14 KiB
JavaScript
// @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<Props, State> {
|
|
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 (
|
|
|
|
<Page backout={{ title: pageTitle, backLabel: __('Done') }} noFooter noSideNavigation>
|
|
<div>
|
|
{scriptFailedToLoad && (
|
|
<div className="error__text">There was an error connecting to Stripe. Please try again later.</div>
|
|
)}
|
|
</div>
|
|
|
|
{currentFlowStage === 'loading' && <div className="headerCard toConfirmCard">
|
|
<Card
|
|
title={__('Connect your card with Odysee')}
|
|
subtitle={__('Getting your card connection status...')}
|
|
/>
|
|
</div>}
|
|
|
|
{currentFlowStage === 'confirmingCard' && <div className="sr-root">
|
|
<div className="sr-main">
|
|
<div className="sr-payment-form card cardInput">
|
|
<div className="sr-form-row">
|
|
<label className="payment-details">
|
|
Card Details
|
|
</label>
|
|
<div className="sr-input sr-element sr-card-element" id="card-element" />
|
|
</div>
|
|
<div className="sr-field-error" id="card-errors" role="alert" />
|
|
<button className="linkButton" id="submit">
|
|
<div className="spinner hidden" id="spinner" />
|
|
<span id="button-text">Add Card</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>}
|
|
|
|
{currentFlowStage === 'cardConfirmed' && <div className="successCard">
|
|
<Card
|
|
title={__('Card Details')}
|
|
body={<>
|
|
<h4 className="grey-text">Brand: {userCardDetails.brand.toUpperCase()}
|
|
Last 4: {userCardDetails.lastFour}
|
|
Expires: {userCardDetails.expiryMonth}/{userCardDetails.expiryYear}
|
|
</h4>
|
|
</>}
|
|
/>
|
|
<br />
|
|
|
|
{(!customerTransactions || customerTransactions.length === 0) && <Card
|
|
title={__('Tip History')}
|
|
subtitle={__('You have not sent any tips yet. When you do they will appear here. ')}
|
|
/>}
|
|
|
|
{customerTransactions && customerTransactions.length > 0 && <Card
|
|
title={__('Tip History')}
|
|
body={<><div className="table__wrapper">
|
|
<table className="table table--transactions">
|
|
<thead>
|
|
<tr>
|
|
<th className="date-header">{__('Date')}</th>
|
|
<th>{<>{__('Receiving Channel Name')}</>}</th>
|
|
<th>{__('Amount (USD)')} </th>
|
|
<th>{__('Anonymous')}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{customerTransactions &&
|
|
customerTransactions.map((transaction) => (
|
|
<tr key={transaction.name + transaction.created_at}>
|
|
<td>{moment(transaction.created_at).format('LLL')}</td>
|
|
<td>{transaction.channel_name}</td>
|
|
<td>${transaction.tipped_amount / 100}</td>
|
|
<td>{transaction.private_tip ? 'Yes' : 'No'}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div></>}
|
|
/>}
|
|
</div>}
|
|
|
|
</Page>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default CardVerify;
|
|
/* eslint-enable no-undef */
|
|
/* eslint-enable react/prop-types */
|