Skip to content
This repository has been archived by the owner on Mar 27, 2023. It is now read-only.

Commit

Permalink
Default to one click if stored payment method is present.
Browse files Browse the repository at this point in the history
  • Loading branch information
osahyoun committed Dec 6, 2018
1 parent 60980a7 commit 5c00e3f
Show file tree
Hide file tree
Showing 13 changed files with 370 additions and 13 deletions.
2 changes: 2 additions & 0 deletions app/javascript/components/AmountSelection/AmountSelection.js
Expand Up @@ -15,6 +15,7 @@ export type Props = {
currency: string,
nextStepTitle?: React.Element<any>,
selectAmount: (amount: ?number) => void,
selectCustomAmount?: (amount: ?number) => void,
changeCurrency: (currency: string) => void,
proceed: () => void,
};
Expand Down Expand Up @@ -83,6 +84,7 @@ export default class AmountSelection extends React.Component<Props, State> {
proceed={this.props.proceed}
featuredAmount={this.props.donationFeaturedAmount}
selectAmount={this.props.selectAmount}
selectCustomAmount={this.props.selectCustomAmount}
/>
<p>
<FormattedMessage
Expand Down
90 changes: 90 additions & 0 deletions app/javascript/components/CurrencySelector/CurrencySelector.js
@@ -0,0 +1,90 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import { changeCurrency } from '../../state/fundraiser/actions';

import type { AppState } from '../../state/reducers';

type Props = {};

type State = {
currencyDropdVisible: boolean,
};

class CurrencySelector extends Component<Props> {
constructor(props: Props) {
super(props);
this.state = {
currencyDropdVisible: false,
};
}

toggleCurrencyDropd() {
this.setState({
currencyDropdVisible: !this.state.currencyDropdVisible,
});
}

onSelectCurrency(currency: string): void {
this.props.changeCurrency(currency);
}

selectElement() {
return (
this.state.currencyDropdVisible && (
<select
value={this.props.currency}
className="AmountSelection__currency-selector"
onChange={(e: SyntheticEvent<HTMLSelectElement>) =>
this.onSelectCurrency(e.currentTarget.value)
}
>
{Object.keys(this.props.donationBands).map(currency => {
return (
<option key={currency} value={currency}>
{currency}
</option>
);
})}
</select>
)
);
}

render() {
return (
<p>
<FormattedMessage
id="fundraiser.currency_in"
defaultMessage="Values shown in {currency}."
values={{ currency: this.props.currency }}
/>
.&nbsp;
<a
onClick={this.toggleCurrencyDropd.bind(this)}
className="AmountSelection__currency-toggle"
>
<FormattedMessage
id="fundraiser.switch_currency"
defaultMessage="Switch currency"
/>
</a>
{this.selectElement()}
</p>
);
}
}

const mapState = (state: AppState) => ({
currency: state.fundraiser.currency,
donationBands: state.fundraiser.donationBands,
});

const mapDispatch = (dispatch: Dispatch<*>) => ({
changeCurrency: (currency: string) => dispatch(changeCurrency(currency)),
});

export default connect(
mapState,
mapDispatch
)(CurrencySelector);
4 changes: 4 additions & 0 deletions app/javascript/components/DonationBands/DonationBands.css
Expand Up @@ -7,6 +7,10 @@
margin-bottom: 12px;
}

.DonationBands-container {
margin-top: 12px;
}

.AmountSelection__proceed-button, .AmountSelection__currency-selector {
margin-top: 12px;
}
Expand Down
8 changes: 6 additions & 2 deletions app/javascript/components/DonationBands/DonationBands.js
Expand Up @@ -20,13 +20,15 @@ type Props = {
customAmount?: number,
proceed: () => void,
intl: IntlShape,
selectAmount: (amount: ?number) => void,
selectAmount: (amount: ?number) => void | Promise<*>,
selectCustomAmount?: (amount: ?number) => void | Promise<*>,
featuredAmount?: number,
};

type State = {
customAmount?: number,
};

export class DonationBands extends Component<Props, State> {
constructor(props: Props) {
super(props);
Expand All @@ -47,7 +49,9 @@ export class DonationBands extends Component<Props, State> {
const number = value.replace(/\D/g, '');
const amount = number ? parseFloat(number) : undefined;
this.setState({ customAmount: amount });
if (this.props.selectAmount) {
if (this.props.selectCustomAmount) {
this.props.selectCustomAmount(amount);
} else {
this.props.selectAmount(amount);
}
}
Expand Down
Expand Up @@ -151,6 +151,8 @@ exports[`renders all amounts with the currency symbol 1`] = `
"fundraiser.loading": "Loading secure <br> payment portal",
"fundraiser.make_recurring": "Make my donation monthly",
"fundraiser.month": "month",
"fundraiser.one_click_failed": "We're sorry but we could not process your donation. Please try again with a different card.",
"fundraiser.one_click_warning": "Your donation will be processed immediately.",
"fundraiser.oneclick.credit_card_payment_method": "{card_type} ending in {last_four_digits}",
"fundraiser.oneclick.new_payment_method": "Add payment method",
"fundraiser.oneclick.paypal_payment_method": "Paypal ({email})",
Expand Down Expand Up @@ -586,6 +588,8 @@ exports[`renders correctly 1`] = `
"fundraiser.loading": "Loading secure <br> payment portal",
"fundraiser.make_recurring": "Make my donation monthly",
"fundraiser.month": "month",
"fundraiser.one_click_failed": "We're sorry but we could not process your donation. Please try again with a different card.",
"fundraiser.one_click_warning": "Your donation will be processed immediately.",
"fundraiser.oneclick.credit_card_payment_method": "{card_type} ending in {last_four_digits}",
"fundraiser.oneclick.new_payment_method": "Add payment method",
"fundraiser.oneclick.paypal_payment_method": "Paypal ({email})",
Expand Down
206 changes: 206 additions & 0 deletions app/javascript/components/OneClick/OneClick.js
@@ -0,0 +1,206 @@
// @flow

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import DonationBands from '../DonationBands/DonationBands';
import DonateButton from '../DonateButton';
import CurrencySelector from '../CurrencySelector/CurrencySelector';
import ee from '../../shared/pub_sub';

import Button from '../Button/Button';
import {
changeAmount,
setSubmitting,
oneClickFailed,
} from '../../state/fundraiser/actions';

import type { AppState } from '../../state/reducers';
import type { ChampaignPage } from '../../types';
import type { Dispatch } from 'redux';

type Props = {
donationBands: any,
currency: string,
donationAmount: number,
selectAmount: (amount: ?number) => void | Promise<*>,
paymentMethods: any[],
formId: number,
formValues: Object,
form: Object,
title: string,
submitting: boolean,
setSubmitting: boolean => void,
oneClickFailed: () => void,
donationFeaturedAmount?: number,
page: ChampaignPage,
};

type State = {
amountConfirmationRequired: boolean,
submitting: boolean,
};

class OneClick extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
amountConfirmationRequired: false,
submitting: false,
};
}

async onSelectCustomAmount(amount: ?number) {
await this.props.selectAmount(amount);
this.setState({ amountConfirmationRequired: true });
}

async selectAmount(amount: ?number) {
this.props.selectAmount(amount);
this.submit();
}

oneClickData() {
return {
payment: {
currency: this.props.currency,
amount: this.props.donationAmount,
recurring: false,
payment_method_id: this.props.paymentMethods[0].id,
// payment_method_id: 900,
},
user: {
form_id: this.props.formId,
// formValues will have the prefillValues
...this.props.formValues,
// form will have the user's submitted values
...this.props.form,
},
};
}

submit = () => {
const data = this.oneClickData();
if (data) {
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));
}
};

async onFailure(reason: any): any {
this.setState({ submitting: false });
this.props.setSubmitting(false);
this.props.oneClickFailed();

ee.emit('fundraiser:transaction_error', reason, this.oneClickData());
return reason;
}

async onSuccess(data: any): any {
ee.emit('fundraiser:transaction_success', data, this.oneClickData());
return data;
}

donateButton() {
if (!this.state.amountConfirmationRequired) return null;
if (!this.props.donationAmount) return null;

return (
<DonateButton
currency={this.props.currency}
amount={this.props.donationAmount || 0}
recurring={false}
submitting={this.state.submitting}
disabled={this.state.submitting}
onClick={() => this.submit()}
/>
);
}

procssingView() {
return (
<div className="submission-interstitial">
<h1 className="submission-interstitial__title">
<i className="fa fa-spin fa-cog" />
Processing
</h1>
<h4>
Please do not close this tab
<br />
or use the back button.
</h4>
</div>
);
}

paymentOptionsView() {
return (
<div className="OneClick">
<div className="StepWrapper-root">
<div className="overlay-toggle__mobile-ui">
<a className="overlay-toggle__close-button"></a>
</div>
<div className="Stepper fundraiser-bar__top">
<h2 className="Stepper__header">{this.props.title}</h2>
</div>
<div className="fundraiser-bar__main">
<p>
<FormattedMessage
id="fundraiser.one_click_warning"
defaultMessage="Your donation will be processed immediately."
/>
</p>

<DonationBands
amounts={this.props.donationBands[this.props.currency]}
currency={this.props.currency}
featuredAmount={this.props.donationFeaturedAmount}
proceed={() => {}}
selectAmount={this.selectAmount.bind(this)}
selectCustomAmount={this.onSelectCustomAmount.bind(this)}
/>

{this.donateButton()}
<CurrencySelector />
</div>
</div>
</div>
);
}

render() {
return this.props.submitting
? this.procssingView()
: this.paymentOptionsView();
}
}

const mapState = (state: AppState) => ({
currency: state.fundraiser.currency,
donationAmount: state.fundraiser.donationAmount,
donationBands: state.fundraiser.donationBands,
recurring: state.fundraiser.recurring,
paymentMethods: state.paymentMethods,
formId: state.fundraiser.formId,
formValues: state.fundraiser.formValues,
form: state.fundraiser.form,
page: state.page,
submitting: state.fundraiser.submitting,
title: state.fundraiser.title,
donationFeaturedAmount: state.fundraiser.donationFeaturedAmount,
});

const mapDispatch = (dispatch: Dispatch<*>) => ({
selectAmount: (amount: number) => dispatch(changeAmount(amount)),
setSubmitting: (submitting: boolean) => dispatch(setSubmitting(submitting)),
oneClickFailed: () => dispatch(oneClickFailed()),
});

export default connect(
mapState,
mapDispatch
)(OneClick);
Expand Up @@ -137,6 +137,8 @@ exports[`Snapshots: With default messages object 1`] = `
"fundraiser.loading": "Loading secure <br> payment portal",
"fundraiser.make_recurring": "Make my donation monthly",
"fundraiser.month": "month",
"fundraiser.one_click_failed": "We're sorry but we could not process your donation. Please try again with a different card.",
"fundraiser.one_click_warning": "Your donation will be processed immediately.",
"fundraiser.oneclick.credit_card_payment_method": "{card_type} ending in {last_four_digits}",
"fundraiser.oneclick.new_payment_method": "Add payment method",
"fundraiser.oneclick.paypal_payment_method": "Paypal ({email})",
Expand Down

0 comments on commit 5c00e3f

Please sign in to comment.