feat(donate): PayPal integration

This commit is contained in:
Ahmad Abdolsaheb
2020-03-13 12:25:57 +03:00
committed by Mrugesh Mohapatra
parent e3db423abf
commit 6c6eadfbe4
24 changed files with 1040 additions and 70 deletions

View File

@@ -212,6 +212,10 @@ li.disabled > a {
}
}
.donation-modal {
font-family: 'Lato', sans-serif;
}
.donation-modal .btn-link:focus {
outline-width: 1px;
outline-style: solid;

View File

@@ -59,12 +59,16 @@ function DonateModal({
executeGA
}) {
const [closeLabel, setCloseLabel] = React.useState(false);
const handleProcessing = (duration, amount) => {
const handleProcessing = (
duration,
amount,
action = 'stripe form submission'
) => {
executeGA({
type: 'event',
data: {
category: 'donation',
action: 'Modal strip form submission',
action: `Modal ${action}`,
label: duration,
value: amount
}
@@ -88,8 +92,8 @@ function DonateModal({
const donationText = (
<b>
Become a supporter and help us create even more learning resources for
you.
Become a $5 / month supporter and help us create even more learning
resources for you and your family.
</b>
);
const blockDonationText = (

View File

@@ -13,8 +13,12 @@ import {
import { stripePublicKey } from '../../../../config/env.json';
import { stripeScriptLoader } from '../../utils/scriptLoaders';
import DonateFormChildViewForHOC from './DonateFormChildViewForHOC';
import DonateCompletion from './DonateCompletion';
import PaypalButton from './PaypalButton';
import { userSelector } from '../../redux';
import { Spacer } from '../../components/helpers';
import './Donation.css';
const propTypes = {
@@ -33,6 +37,14 @@ const mapStateToProps = createSelector(
})
);
const initialState = {
donationState: {
processing: false,
success: false,
error: ''
}
};
class MinimalDonateForm extends Component {
constructor(...args) {
super(...args);
@@ -42,10 +54,13 @@ class MinimalDonateForm extends Component {
this.state = {
...modalDefaultStateConfig,
...initialState,
isDonating: this.props.isDonating,
stripe: null
};
this.handleStripeLoad = this.handleStripeLoad.bind(this);
this.onDonationStateChange = this.onDonationStateChange.bind(this);
this.resetDonation = this.resetDonation.bind(this);
}
componentDidMount() {
@@ -77,12 +92,54 @@ class MinimalDonateForm extends Component {
}
}
resetDonation() {
return this.setState({ ...initialState });
}
onDonationStateChange(success, processing, error) {
this.setState(state => ({
...state,
donationState: {
...state.donationState,
processing: processing,
success: success,
error: error
}
}));
}
renderCompletion(props) {
return <DonateCompletion {...props} />;
}
render() {
const { donationAmount, donationDuration, stripe } = this.state;
const { handleProcessing, defaultTheme } = this.props;
const {
donationState: { processing, success, error }
} = this.state;
if (processing || success || error) {
return this.renderCompletion({
processing,
success,
error,
reset: this.resetDonation
});
}
return (
<Row>
<Col lg={8} lgOffset={2} sm={10} smOffset={1} xs={12}>
<PaypalButton
handleProcessing={handleProcessing}
onDonationStateChange={this.onDonationStateChange}
/>
</Col>
<Col sm={10} smOffset={1} xs={12}>
<Spacer />
<b>Or donate with credit card number.</b>
<Spacer />
</Col>
<Col sm={10} smOffset={1} xs={12}>
<StripeProvider stripe={stripe}>
<Elements>
@@ -91,7 +148,7 @@ class MinimalDonateForm extends Component {
donationAmount={donationAmount}
donationDuration={donationDuration}
getDonationButtonLabel={() =>
`Confirm your donation of $5 per month`
`Confirm your donation of $5 / month`
}
handleProcessing={handleProcessing}
/>

View File

@@ -0,0 +1,112 @@
/* eslint-disable camelcase */
/* global ENVIRONMENT */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { PayPalButton } from 'react-paypal-button-v2';
import { paypalClientId } from '../../../config/env.json';
import { verifySubscriptionPaypal } from '../../utils/ajax';
import { paypalConfig } from '../../../../config/donation-settings';
import { signInLoadingSelector, userSelector, executeGA } from '../../redux';
const paypalDurationPlans =
ENVIRONMENT === 'production'
? paypalConfig.production.durationPlans
: paypalConfig.development.durationPlans;
export class PaypalButton extends Component {
constructor(...props) {
super(...props);
this.state = {
planId: paypalDurationPlans.month['500'].planId
};
this.handleApproval = this.handleApproval.bind(this);
}
handleApproval = data => {
this.props.handleProcessing('month', 500, 'Paypal payment submission');
this.props.onDonationStateChange(false, true, '');
verifySubscriptionPaypal(data)
.then(response => {
const data = response && response.data;
this.props.onDonationStateChange(
true,
false,
data.error ? data.error : ''
);
})
.catch(error => {
const data =
error.response && error.response.data
? error.response.data
: {
error:
'Something is not right. Please contact team@freecodecamp.org'
};
this.props.onDonationStateChange(false, false, data.error);
});
};
render() {
return (
<PayPalButton
createSubscription={(data, actions) => {
executeGA({
type: 'event',
data: {
category: 'Donation',
action: `Modal Paypal clicked`
}
});
return actions.subscription.create({
plan_id: this.state.planId
});
}}
onApprove={data => {
this.handleApproval(data);
}}
onCancel={() => {
this.props.onDonationStateChange(
false,
false,
'Payment has been canceled.'
);
}}
onError={() =>
this.props.onDonationStateChange(false, false, 'Please try again.')
}
options={{
vault: true,
disableFunding: 'card',
clientId: paypalClientId
}}
style={{
tagline: false,
height: 43
}}
/>
);
}
}
const propTypes = {
handleProcessing: PropTypes.func,
isDonating: PropTypes.bool,
onDonationStateChange: PropTypes.func
};
const mapStateToProps = createSelector(
userSelector,
signInLoadingSelector,
({ isDonating }, showLoading) => ({
isDonating,
showLoading
})
);
PaypalButton.displayName = 'PaypalButton';
PaypalButton.propTypes = propTypes;
export default connect(mapStateToProps)(PaypalButton);