Skip to content

Commit

Permalink
feat(donate): agree to pay fees feature (#928)
Browse files Browse the repository at this point in the history
Co-authored-by: Thomas Guillot <thomasguillot@users.noreply.github.com>
  • Loading branch information
adekbadek and thomasguillot authored Dec 9, 2021
1 parent b3f3edb commit 221c061
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 39 deletions.
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.26.1",
"fetch-mock-jest": "^1.5.1",
"html-entities": "^2.3.2",
"identity-obj-proxy": "^3.0.0",
"jest": "^27.3.1",
"jest-environment-jsdom": "^27.3.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ public function register_routes() {
'newsletter_opt_in' => [
'sanitize_callback' => 'rest_sanitize_boolean',
],
'agree_to_pay_fees' => [
'sanitize_callback' => 'rest_sanitize_boolean',
],
],
'permission_callback' => '__return_true',
],
Expand Down
65 changes: 54 additions & 11 deletions src/blocks/donate/streamlined.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,33 @@ const getCookies = () =>

const getClientIDValue = () => getCookies()[ 'newspack-cid' ];

const getAmount = formValues => {
const valueKey = `donation_value_${ formValues.donation_frequency }`;
formValues.amount = formValues[ valueKey ];
if ( formValues.amount === 'other' ) {
formValues.amount = formValues[ `${ valueKey }_other` ];
}
if ( ! formValues.amount ) {
formValues.amount = formValues[ `${ valueKey }_untiered` ];
}
return parseFloat( formValues.amount );
};

const getFeeAmount = formElement => {
const formValues = Object.fromEntries( new FormData( formElement ) );
const amount = getAmount( formValues );
// eslint-disable-next-line no-unused-vars, @wordpress/no-unused-vars-before-return
const [ CURRENCY_SYMBOL, FREQUENCIES, FEE_MULTIPLIER, FEE_STATIC ] = JSON.parse(
formElement.getAttribute( 'data-settings' )
);
return parseFloat(
(
( ( amount + parseFloat( FEE_STATIC ) ) / ( 100 - parseFloat( FEE_MULTIPLIER ) ) ) * 100 -
amount
).toFixed( 2 )
);
};

export const processStreamlinedElements = ( parentElement = document ) =>
[ ...parentElement.querySelectorAll( '.stripe-payment' ) ].forEach( async el => {
const disableForm = () => el.classList.add( 'stripe-payment--disabled' );
Expand Down Expand Up @@ -87,7 +114,7 @@ export const processStreamlinedElements = ( parentElement = document ) =>
);
const renderSuccessMessageWithEmail = emailAddress => {
const successMessge = sprintf(
/* Translators: %s is the email address of the current user. */
/* Translators: %s is the email address of the donor. */
__(
'Your payment has been processed. Thank you for your contribution! You will receive a confirmation email at %s.',
'newspack-blocks'
Expand All @@ -98,20 +125,32 @@ export const processStreamlinedElements = ( parentElement = document ) =>
};

const formElement = el.closest( 'form' );
const [ CURRENCY_SYMBOL, FREQUENCIES ] = JSON.parse(
formElement.getAttribute( 'data-settings' )
);

const updateFeesAmount = () => {
const feesAmountEl = el.querySelector( '#stripe-fees-amount' );
if ( feesAmountEl ) {
const formValues = Object.fromEntries( new FormData( formElement ) );
const feeAmount = getFeeAmount( formElement );
feesAmountEl.innerHTML = `(${ CURRENCY_SYMBOL }${ feeAmount } ${ FREQUENCIES[
formValues.donation_frequency
].toLowerCase() })`;
}
};

updateFeesAmount();
formElement.onchange = () => {
updateFeesAmount();
};
formElement.onsubmit = async e => {
e.preventDefault();
disableForm();
renderMessages( [ __( 'Processing payment…', 'newspack-blocks' ) ], messagesEl, 'info' );

const formValues = Object.fromEntries( new FormData( e.target ) );
const valueKey = `donation_value_${ formValues.donation_frequency }`;
formValues.amount = formValues[ valueKey ];
if ( formValues.amount === 'other' ) {
formValues.amount = formValues[ `${ valueKey }_other` ];
}
if ( ! formValues.amount ) {
formValues.amount = formValues[ `${ valueKey }_untiered` ];
}
const formValues = Object.fromEntries( new FormData( formElement ) );
formValues.amount = getAmount( formValues );
if ( formValues.cid.indexOf( 'CLIENT_ID' ) === 0 ) {
// In non-AMP environment, the value will not be dynamically substituted by AMP runtime.
formValues.cid = getClientIDValue();
Expand All @@ -131,14 +170,18 @@ export const processStreamlinedElements = ( parentElement = document ) =>
enableForm();
return;
}
let amount = formValues.amount;
if ( formValues.agree_to_pay_fees ) {
amount = amount + getFeeAmount( formElement );
}
const chargeResult = await fetch( '/wp-json/newspack-blocks/v1/donate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify( {
tokenData: stripeTokenCreationResult.token,
amount: formValues.amount,
amount,
email: formValues.email,
full_name: formValues.full_name,
frequency: formValues.donation_frequency,
Expand Down
40 changes: 33 additions & 7 deletions src/blocks/donate/streamlined.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
.stripe-payment {
&__card {
border: 1px solid $color__border;
border-radius: 3px;
padding: 0.34rem 0.66rem;
}
input[type='text'],
Expand All @@ -21,6 +22,7 @@
}
input[type='text'],
input[type='email'] {
border-radius: 3px;
font-family: sans-serif;

@include media( tablet ) {
Expand All @@ -36,8 +38,13 @@
}

&__checkbox {
align-items: center;
display: flex;

input {
margin-right: 5px;
height: 1em;
width: 1em;
margin-right: 0.5em;
}
}

Expand Down Expand Up @@ -73,38 +80,53 @@
}

&__row {
margin-bottom: 0.4rem;
margin-bottom: 0.5rem;
&--small {
line-height: 1em;
margin-bottom: 0;
}
&--flex {
display: flex;
align-items: flex-start;
align-items: flex-end;
justify-content: space-between;
flex-wrap: wrap;
@include media( tablet ) {
flex-wrap: nowrap;
}
input {
margin-bottom: 0.4rem;
margin-bottom: 0.5rem;
@include media( tablet ) {
margin-bottom: 0;
width: 50%;
&:last-child {
margin-left: 0.76rem;
margin-left: 0.5rem;
}
}
}
}
}

&__info {
color: $color__text-light;
font-size: 0.7em;
font-style: italic;
padding-left: 1.5em;
}

&__footer {
margin-top: 1.2em;
margin-bottom: 1.32rem;
margin-top: 1.5rem;
margin-bottom: 1.5rem;
}
button[type='submit'] {
margin: 0 10px 0 0 !important;
}
&__branding {
display: block;
text-decoration: none;

img {
display: block;
}
}
}

Expand All @@ -122,3 +144,7 @@
}
}
}

#stripe-fees-amount {
padding-left: 3px;
}
57 changes: 39 additions & 18 deletions src/blocks/donate/streamlined.test.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,43 @@
import * as testingLibrary from '@testing-library/dom';
import userEvent from '@testing-library/user-event';
import fetchMock from 'fetch-mock-jest';
import { encode } from 'html-entities';

import { processStreamlinedElements } from './streamlined';

const createDOM = () => {
const MONTHLY_AMOUNT = 7;

const createDOM = settings => {
const parentElement = document.createElement( 'div' );
parentElement.innerHTML = `
<style>.stripe-payment__inputs--hidden {display:none;}</style>
<form>
<div class='frequencies'>
<input type="radio" value="once" id="once" name="donation_frequency">
<label for="once">Once</label>
<input type="radio" value="monthly" id="monthly" name="donation_frequency" checked>
<label for="monthly">Monthly</label>
</div>
<div class="stripe-payment">
<div class="stripe-payment__inputs--hidden">
<style>.stripe-payment__inputs--hidden {display:none;}</style>
<form data-settings="${ encode( JSON.stringify( settings ) ) }">
<div class='frequencies'>
<div class='frequency'>
<input type="radio" value="once" id="once" name="donation_frequency">
<label for="once">Once</label>
</div>
<div class='frequency'>
<input type="radio" value="month" id="month" name="donation_frequency" checked>
<label for="month">Monthly</label>
<input type="radio" name="donation_value_month" value="${ MONTHLY_AMOUNT }" checked />
</div>
</div>
<div class="stripe-payment">
<div class="stripe-payment__inputs--hidden">
<input required="" placeholder="Email" type="email" name="email" value="">
<input required="" placeholder="Full Name" type="text" name="full_name" value="">
</div>
<div class="stripe-payment__messages"></div>
<button type="submit">Donate</button>
</div>
<input name="cid" type="hidden" value="amp-123" />
</form>
`;
<label>
<input type="checkbox" name="agree_to_pay_fees" checked value="true">Agree to pay fees?
<span id="stripe-fees-amount">($0)</span>
</label>
<div class="stripe-payment__messages"></div>
<button type="submit">Donate</button>
</div>
<input name="cid" type="hidden" value="amp-123" />
</form>
`;
document.body.appendChild( parentElement );
return document.body;
};
Expand All @@ -35,7 +47,12 @@ describe( 'Streamlined Donate block processing', () => {
return { data: { status: 200, client_secret: 'sec_123' } };
} );

const container = createDOM();
const currencySymbol = '$';
const frequencies = { once: 'Once', month: 'Monthly', year: 'Annually' };
const feeMultiplier = '2.9';
const feeStatic = '0.3';
const settings = [ currencySymbol, frequencies, feeMultiplier, feeStatic ];
const container = createDOM( settings );
processStreamlinedElements( container );

const button = testingLibrary.getByText( container, 'Donate' );
Expand Down Expand Up @@ -64,6 +81,10 @@ describe( 'Streamlined Donate block processing', () => {
).not.toBeInTheDocument();
} );

it( 'the fee amount is updated', () => {
expect( testingLibrary.getByText( container, '($0.52 monthly)' ) ).toBeInTheDocument();
} );

it( 'form can be submitted after validation passes', () => {
userEvent.type( nameInput, 'Bax' );
userEvent.click( button );
Expand Down
Loading

0 comments on commit 221c061

Please sign in to comment.