SumOfUs/Champaign

View on GitHub
app/javascript/components/ExpressDonation/ExpressDonation.js

Summary

Maintainability
F
3 days
Test Coverage
import $ from 'jquery';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import { isEmpty, snakeCase } from 'lodash';
import ee from '../../shared/pub_sub';
import DonateButton from '../DonateButton';
import PaymentMethodWrapper from './PaymentMethodWrapper';
import PaymentMethodItem from './PaymentMethod';
import { setRecurring } from '../../state/fundraiser/actions';
import CurrencyAmount from '../CurrencyAmount';
import ShowIf from '../ShowIf';
import Popup from 'reactjs-popup';
import Button from '../../components/Button/Button';
import { getErrorsByCode } from '../../util/getBraintreeErrorMessages';

import './ExpressDonation.scss';

const style = {
  width: 'auto',
  padding: 30,
};

export class ExpressDonation extends Component {
  constructor(props) {
    super(props);

    this.state = {
      currentPaymentMethod: props.paymentMethods
        ? props.paymentMethods[0]
        : null,
      submitting: false,
      openPopup: false,
      recurringDonor: false,
      recurringDefault: null,
      onlyRecurring: false,
      akid: null,
      source: null,
      optForRedonation: false,
      failureReason: '',
      failureCode: '',
      errors: [],
    };
  }

  getMemberName(member, formValues) {
    if (member) {
      return `${member.fullName}:`;
    } else if (formValues && formValues.member) {
      return `${formValues.member.name}:`;
    } else {
      return null;
    }
  }

  oneClickData() {
    if (!this.state.currentPaymentMethod) return null;

    return {
      payment: {
        currency: this.props.fundraiser.currency,
        amount: this.props.getFinalDonationAmount,
        recurring: this.props.fundraiser.recurring,
        payment_method_id: this.state.currentPaymentMethod.id,
      },
      user: {
        form_id: this.props.fundraiser.formId,
        // formValues will have the prefillValues
        ...this.props.fundraiser.formValues,
        // form will have the user's submitted values
        ...this.props.fundraiser.form,
      },
      allow_duplicate: this.state.optForRedonation,
    };
  }

  async onSuccess(data) {
    ee.emit('fundraiser:transaction_success', data, this.props.formData);
    let donationType;
    if (this.props.fundraiser.recurring) {
      donationType = this.props.weekly ? 'weekly' : 'monthly';
    } else {
      donationType = 'one_time';
    }

    const label = `successful_${donationType}_donation_submitted`;
    const event = `fundraiser:${donationType}_transaction_submitted`;

    ee.emit(event, label);

    const { original, forced } =
      window.champaign.plugins?.fundraiser?.default?.config?.fundraiser
        ?.forcedDonateLayout || {};
    const emitForcedLayoutSuccess = () => {
      ee.emit(`${event}_forced_layout`, {
        label: `${snakeCase(original)}_template_used_scroll_to_donate`,
        amount: this.props.fundraiser.donationAmount,
      });
    };

    if (forced === true) {
      emitForcedLayoutSuccess();
    }

    return data;
  }

  async onFailure(reason) {
    this.setState({
      submitting: false,
      openPopup: reason.responseJSON
        ? reason.responseJSON.immediate_redonation
        : false,
      failureReason: reason.responseJSON.message,
      failureCode: reason.responseJSON.errorCode,
      errors: getErrorsByCode(reason.responseJSON.errorCode),
      optForRedonation:
        reason.responseJSON && reason.responseJSON.immediate_redonation
          ? reason.responseJSON.immediate_redonation
          : false,
    });
    this.props.setSubmitting(false);
    ee.emit('fundraiser:transaction_error', reason, this.props.formData);
    return reason;
  }

  clearRecurringEvent() {
    ee.removeListener('fundraiser:change_recurring', this.submit, this);
  }

  onClickHandle(e) {
    const isRecurring = e.currentTarget.name === 'recurring';
    this.props.setRecurring(isRecurring);
    ee.on('fundraiser:change_recurring', this.submit, this);
  }

  submit() {
    const data = this.oneClickData();

    if (data) {
      if (data.allow_duplicate == false) delete data.allow_duplicate;
      ee.emit(
        'fundraiser:transaction_submitted',
        data.payment,
        this.props.formData
      );
      this.setState({ submitting: true });
      this.props.setSubmitting(true);
      $.post(
        `/api/payment/braintree/pages/${this.props.page.id}/one_click`,
        data
      ).then(this.onSuccess.bind(this), this.onFailure.bind(this));
    }
    this.clearRecurringEvent();
  }

  selectPaymentMethod(paymentMethod) {
    this.setState({ currentPaymentMethod: paymentMethod });
  }

  renderPaymentMethodRadio(paymentMethod) {
    return (
      <div className="ExpressDonation__payment-method-radio">
        <input
          type="radio"
          checked={this.state.currentPaymentMethod === paymentMethod}
          onChange={() => this.selectPaymentMethod(paymentMethod)}
        />
        <PaymentMethodItem paymentMethod={paymentMethod} />
      </div>
    );
  }

  renderSingle() {
    const item = this.state.currentPaymentMethod;
    if (!item) return null;
    return (
      <PaymentMethodWrapper>
        <div className="ExpressDonation__single-item">
          <PaymentMethodItem paymentMethod={item} />
        </div>
      </PaymentMethodWrapper>
    );
  }

  renderChoices() {
    return (
      <PaymentMethodWrapper>
        <span className="ExpressDonation__prompt">
          <FormattedMessage
            id="fundraiser.oneclick.select_payment"
            defaultMessage="Select a saved payment method"
          />
        </span>

        {this.props.paymentMethods.map(paymentMethod => (
          <PaymentMethodItem
            key={paymentMethod.id}
            paymentMethod={paymentMethod}
            checked={this.state.currentPaymentMethod === paymentMethod}
            onChange={() => this.selectPaymentMethod(paymentMethod)}
          />
        ))}
      </PaymentMethodWrapper>
    );
  }

  componentDidMount() {
    this.setState({
      akid: this.props.data.akid,
      source: this.props.data.source,
      recurringDefault: this.props.data.recurringDefault,
      onlyRecurring: this.props.data.onlyRecurring,
      recurringDonor: this.props.data.recurringDonor,
    });
  }

  render() {
    if (!this.props.paymentMethods.length || this.props.hidden) return null;

    return (
      <div className="ExpressDonation">
        <ShowIf condition={!isEmpty(this.state.errors)}>
          <div className="fundraiser-bar__errors">
            <div className="fundraiser-bar__error-intro">
              <span className="fa fa-exclamation-triangle" />
              <FormattedMessage
                id="fundraiser.error_intro"
                defaultMessage="Unable to process donation!"
              />
            </div>
            {this.state.errors.map((e, i) => {
              return (
                <div key={i} className="fundraiser-bar__error-detail">
                  {e}
                </div>
              );
            })}
          </div>
        </ShowIf>
        <div className="ExpressDonation__payment-methods">
          {this.props.paymentMethods.length === 1
            ? this.renderSingle()
            : this.renderChoices()}
          <a
            className="ExpressDonation__toggle"
            onClick={() => this.props.onHide()}
          >
            <FormattedMessage
              id="fundraiser.oneclick.new_payment_method"
              defaultMessage="Add payment method"
            />
          </a>
        </div>
        <div className="payment-message">
          <br />
          {this.props.showMonthlyButton && (
            <FormattedMessage
              id={'fundraiser.make_monthly_donation'}
              defaultMessage={`{name} a monthly donation will support our movement to plan ahead, so we can more effectively take on the biggest corporations that threaten people and planet.`}
              values={{
                name: this.getMemberName(
                  this.props.member,
                  this.props.formData
                ),
              }}
            />
          )}

          <div className="PaymentMethod__complete-donation">
            <FormattedMessage
              id={'fundraiser.complete_donation'}
              defaultMessage={`Complete your {amount} donation`}
              values={{
                amount: (
                  <CurrencyAmount
                    amount={this.props.getFinalDonationAmount}
                    currency={this.props.fundraiser.currency}
                  />
                ),
              }}
            />
          </div>
        </div>
        <>
          <ShowIf condition={this.props.showMonthlyButton}>
            <DonateButton
              currency={this.props.fundraiser.currency}
              amount={this.props.fundraiser.donationAmount || 0}
              recurring={true}
              name="recurring"
              recurringDonor={this.state.recurringDonor}
              weekly={this.props.weekly}
              submitting={this.state.submitting}
              disabled={
                !this.state.currentPaymentMethod || this.state.submitting
              }
              onClick={e => this.onClickHandle(e)}
              theme={'primary'}
            />
          </ShowIf>

          <ShowIf condition={this.props.showOneOffButton}>
            <DonateButton
              currency={this.props.fundraiser.currency}
              amount={this.props.fundraiser.donationAmount || 0}
              name="one_time"
              recurring={false}
              submitting={this.state.submitting}
              recurringDonor={this.state.recurringDonor}
              disabled={
                !this.state.currentPaymentMethod || this.state.submitting
              }
              onClick={e => this.onClickHandle(e)}
              theme={this.props.showMonthlyButton ? 'secondary' : 'primary'}
            />
          </ShowIf>
        </>

        <Popup
          open={this.state.openPopup}
          closeOnDocumentClick
          contentStyle={style}
          onClose={() => {
            this.setState({
              optForRedonation: false,
              openPopup: false,
            });
          }}
        >
          <div className="PaymentExpressDonationConflict">
            <div className="PaymentExpressDonationConflict--reason">
              {this.state.failureReason}
            </div>
            <Button
              className="PaymentExpressDonationConflict--accept"
              onClick={() => this.submit()}
            >
              <FormattedMessage
                id="consent.existing.accept"
                defaultMessage="Yes"
              />
            </Button>
            <Button
              className="PaymentExpressDonationConflict--decline"
              onClick={() => {
                this.setState({
                  optForRedonation: false,
                  openPopup: false,
                });
              }}
            >
              <FormattedMessage
                id="consent.existing.decline"
                defaultMessage="Not right now"
              />
            </Button>
          </div>
        </Popup>
      </div>
    );
  }
}

const mapStateToProps = state => ({
  paymentMethods: state.paymentMethods,
  fundraiser: state.fundraiser,
  page: state.page,
  formData: {
    storeInVault: state.fundraiser.storeInVault,
    member: {
      ...state.fundraiser.formValues,
      ...state.fundraiser.form,
    },
  },
});

const mapDispatchToProps = dispatch => ({
  setRecurring: value => dispatch(setRecurring(value)),
});

export default connect(mapStateToProps, mapDispatchToProps)(ExpressDonation);