Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Payment security reassurance test #2165

Merged
merged 23 commits into from
Oct 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// @flow

// ----- Imports ----- //

import React from 'react';
import { classNameWithModifiers } from 'helpers/utilities';
import './secureTransactionIndicator.scss';
import SecurePadlock from './securePadlock.svg';

// ----- Component ----- //

type PropTypes = {
modifierClasses: Array<string>,
}

const text = 'Secure transaction';

export default function SecureTransactionIndicator(props: PropTypes) {
return (
<div className={classNameWithModifiers('component-secure-transaction', [...props.modifierClasses])}>
<SecurePadlock className="component-secure-transaction__padlock" />
<div className="component-secure-transaction__text">{text}</div>
</div>
);
}

SecureTransactionIndicator.defaultProps = {
modifierClasses: [],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
@import '~stylesheets/gu-sass/gu-sass';

.component-secure-transaction--top {
overflow: hidden;
flex-grow: 1;
margin: 0 auto -2px 10px;
border: none;
max-width: gu-span(9) + $gu-h-spacing;
height: $gu-v-spacing*3;
padding: 0;

@include mq($from: tablet) {
margin: 25px auto -25px;
padding: 0 $gu-h-spacing;
height: auto;
}
@include mq($from: desktop) {
max-width: gu-span(10) + $gu-h-spacing;
height: auto;
}
@include mq($from: leftCol) {
max-width: gu-span(12) + $gu-h-spacing;
height: auto;
}
}

.component-secure-transaction--middle {
margin-top: -12px;
}

.component-secure-transaction--bottom-grey {
justify-content: center;
margin: -35px auto 0;
padding-bottom: 15px;
}

.component-secure-transaction--bottom-regular {
justify-content: center;
margin: -14px auto 0;
padding-bottom: 15px;
}

.component-secure-transaction {
display: flex;
align-items: center;

.component-secure-transaction__padlock {
margin-right: 5px;
}

.component-secure-transaction__text {
font-size: 14px;
color: gu-colour(neutral-46);
font-weight: bold;
letter-spacing: 0.01em;
font-family: $gu-text-sans-web;
}


}
31 changes: 31 additions & 0 deletions support-frontend/assets/helpers/abTests/abtestDefinitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getCampaignName } from 'helpers/campaigns';
export type LandingPageCopyReturningSinglesTestVariants = 'control' | 'returningSingle' | 'notintest';
export type LandingPageMomentBackgroundColourTestVariants = 'control' | 'yellow' | 'notintest';
export type LandingPageStripeElementsRecurringTestVariants = 'control' | 'stripeElements' | 'notintest';
export type PaymentSecurityDesignTestVariants = 'control' | 'V1_securetop' | 'V2_securemiddle' | 'V3_securebottom' | 'V4_grey'

const contributionsLandingPageMatch = '/(uk|us|eu|au|ca|nz|int)/contribute(/.*)?$';

Expand Down Expand Up @@ -99,4 +100,34 @@ export const tests: Tests = {
targetPage: '/(uk|us|eu|au|ca|nz|int)/subscribe/digital$',
optimizeId: 'emQ5nZJCS5mZkhtwwqfx5Q',
},
paymentSecurityDesignTest: {
type: 'OTHER',
variants: [
{
id: 'control',
},
{
id: 'V1_securetop',
},
{
id: 'V2_securemiddle',
},
{
id: 'V3_securebottom',
},
{
id: 'V4_grey',
},
],
audiences: {
ALL: {
offset: 0,
size: 1,
},
},
isActive: true,
independent: true,
seed: 10,
targetPage: contributionsLandingPageMatch,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ import { DirectDebit, Stripe, ExistingCard, ExistingDirectDebit } from 'helpers/
import { getCampaignName } from 'helpers/campaigns';
import type { LandingPageStripeElementsRecurringTestVariants } from 'helpers/abTests/abtestDefinitions';

import SecureTransactionIndicator from 'components/secureTransactionIndicator/secureTransactionIndicator';
import type { PaymentSecurityDesignTestVariants } from 'helpers/abTests/abtestDefinitions';

// ----- Types ----- //
/* eslint-disable react/no-unused-prop-types */
Expand Down Expand Up @@ -83,7 +85,8 @@ type PropTypes = {|
isTestUser: boolean,
country: IsoCountry,
createStripePaymentMethod: () => void,
stripeElementsRecurringTestVariant: LandingPageStripeElementsRecurringTestVariants
stripeElementsRecurringTestVariant: LandingPageStripeElementsRecurringTestVariants,
paymentSecurityDesignTestVariant: PaymentSecurityDesignTestVariants,
|};

// We only want to use the user state value if the form state value has not been changed since it was initialised,
Expand Down Expand Up @@ -115,6 +118,7 @@ const mapStateToProps = (state: State) => ({
country: state.common.internationalisation.countryId,
stripeV3HasLoaded: state.page.form.stripeV3HasLoaded,
stripeElementsRecurringTestVariant: state.common.abParticipations.stripeElementsRecurring,
paymentSecurityDesignTestVariant: state.common.abParticipations.paymentSecurityDesignTest,
});


Expand Down Expand Up @@ -242,6 +246,11 @@ function withProps(props: PropTypes) {

const classModifiers = ['contribution', 'with-labels'];

const showSecureStripeContainer: boolean = props.paymentSecurityDesignTestVariant !== 'control';
const showSecureButtonBg: boolean = showSecureStripeContainer && props.paymentMethod === Stripe && (props.stripeElementsRecurringTestVariant === 'stripeElements' || props.contributionType === 'ONE_OFF');
const showSecureTransactionIndicator: boolean = props.paymentSecurityDesignTestVariant === 'V3_securebottom';
const secureTransactionIndicatorClassNames: string[] = showSecureButtonBg ? ['bottom-grey'] : ['bottom-regular'];

return (
<form onSubmit={onSubmit(props)} className={classNameWithModifiers(baseClass, classModifiers)} noValidate>
<div className="contributions-form-selectors">
Expand Down Expand Up @@ -273,11 +282,19 @@ function withProps(props: PropTypes) {
isTestUser={props.isTestUser}
country={props.country}
stripeElementsRecurringTestVariant={props.stripeElementsRecurringTestVariant}
showSecureBackground={showSecureStripeContainer}
/>

<ContributionErrorMessage />
<ContributionSubmit onPaymentAuthorisation={props.onPaymentAuthorisation} />
<ContributionSubmit
onPaymentAuthorisation={props.onPaymentAuthorisation}
showSecureBackground={showSecureButtonBg}
/>
{showSecureTransactionIndicator &&
<SecureTransactionIndicator modifierClasses={secureTransactionIndicatorClassNames} />
}
</div>

<TermsPrivacy
countryGroupId={props.countryGroupId}
contributionType={props.contributionType}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type PropTypes = {|
formIsSubmittable: boolean,
amount: number,
billingPeriod: BillingPeriod,
showSecureBackground: boolean,
|};

function mapStateToProps(state: State) {
Expand Down Expand Up @@ -95,11 +96,13 @@ function withProps(props: PropTypes) {
props.paymentMethod,
);

const classNames: string = props.showSecureBackground ? 'form__submit--secure' : 'form__submit';

// We have to show/hide PayPalExpressButton rather than conditionally rendering it
// because we don't want to destroy and replace the iframe each time.
// See PayPalExpressButton for more info.
return (
<div className="form__submit">
<div className={classNames}>
<div
id="component-paypal-button-checkout"
className={hiddenIf(!showPayPalRecurringButton, 'component-paypal-button-checkout')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import {
subscriptionsToExplainerList,
subscriptionToExplainerPart,
} from '../../../helpers/existingPaymentMethods/existingPaymentMethods';
import SecureTransactionIndicator from 'components/secureTransactionIndicator/secureTransactionIndicator';
import type { PaymentSecurityDesignTestVariants } from 'helpers/abTests/abtestDefinitions';


// ----- Types ----- //
Expand All @@ -56,6 +58,7 @@ type PropTypes = {|
updateSelectedExistingPaymentMethod: (RecentlySignedInExistingPaymentMethod | typeof undefined) => Action,
isTestUser: boolean,
switches: Switches,
paymentSecurityDesignTestVariant: PaymentSecurityDesignTestVariants,
|};
/* eslint-enable react/no-unused-prop-types */

Expand All @@ -68,6 +71,7 @@ const mapStateToProps = (state: State) => ({
existingPaymentMethod: state.page.form.existingPaymentMethod,
isTestUser: state.page.user.isTestUser || false,
switches: state.common.settings.switches,
paymentSecurityDesignTestVariant: state.common.abParticipations.paymentSecurityDesignTest,
});

const mapDispatchToProps = {
Expand Down Expand Up @@ -107,10 +111,21 @@ function withProps(props: PropTypes) {
const fullExistingPaymentMethods: RecentlySignedInExistingPaymentMethod[] =
((props.existingPaymentMethods || []).filter(isUsableExistingPaymentMethod): any);

const legendSimple = (
<legend className="form__legend">Payment method</legend>
);

const legend = props.paymentSecurityDesignTestVariant === 'V2_securemiddle' ?
(
<div className="secure-transaction">
{legendSimple} <SecureTransactionIndicator modifierClasses={['middle']} />
</div>
) :
legendSimple;

return (
<fieldset className={classNameWithModifiers('form__radio-group', ['buttons', 'contribution-pay'])}>
<legend className="form__legend">Payment method</legend>

{legend}
{ paymentMethods.length ?
<ul className="form__radio-group-list">
{contributionTypeIsRecurring(props.contributionType) && !props.existingPaymentMethods && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type PropTypes = {|
setStripeHasLoaded: () => void,
stripeHasLoaded: boolean,
stripeElementsRecurringTestVariant: LandingPageStripeElementsRecurringTestVariants,
showSecureBackground: boolean,
|};

class StripeCardFormContainer extends React.Component<PropTypes, void> {
Expand All @@ -49,12 +50,14 @@ class StripeCardFormContainer extends React.Component<PropTypes, void> {
this.props.isTestUser,
);

const classNames: string = this.props.showSecureBackground ? 'stripe-card-element-container stripe-card-element-container-secure' : 'stripe-card-element-container';

/**
* The `key` attribute is necessary here because you cannot modify the apiKey on StripeProvider.
* Instead, we must create separate instances for ONE_OFF and REGULAR.
*/
return (
<div className="stripe-card-element-container">
<div className={classNames}>
<StripeProvider apiKey={stripeKey} key={stripeAccount}>
<Elements>
<StripeCardForm stripeKey={stripeKey} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
margin-bottom: 6px;
}

.stripe-card-element-container-secure {
margin-left: -10px;
margin-right: -10px;
padding: 12px 10px;
background-color: gu-colour(neutral-97);
border-top: 1px solid gu-colour(neutral-86);
}

.stripe-card-element-container .form__error {
margin-top: 10px;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import ContributionThankYouContainer
import { setUserStateActions } from './setUserStateActions';
import ConsentBanner from '../../components/consentBanner/consentBanner';
import './contributionsLanding.scss';
import SecureTransactionIndicator from '../../../assets/components/secureTransactionIndicator/secureTransactionIndicator';

if (!isDetailsSupported) {
polyfillDetails();
Expand Down Expand Up @@ -116,6 +117,8 @@ function contributionsLandingPage(campaignCodeParameter: ?string) {
footer={<Footer disclaimer countryGroupId={countryGroupId} />}
backgroundImageSrc={backgroundImageSrc}
>
{store.getState().common.abParticipations.paymentSecurityDesignTest === 'V1_securetop' &&
<SecureTransactionIndicator modifierClasses={['top']} />}
<ContributionFormContainer
thankYouRoute={`/${countryGroups[countryGroupId].supportInternationalisationId}/thankyou`}
campaignCodeParameter={campaignCodeParameter}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,11 @@ form {
font-weight: bold;
}

.form__radio-group--contribution-pay .secure-transaction {
display: flex;
justify-content: space-between;
}

.form__label {
font-weight: bold;
line-height: 1;
Expand Down Expand Up @@ -837,6 +842,17 @@ form {
width: 100%;
}

.form__submit--secure {
margin: -6px -10px 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we achieve similar top and bottoms for margins and padding using $gu-v-spacing / 2 instead? For code and vis consistency?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe - I couldn't get $gu-v-spacing to work with negative values but maybe we can pair on this?

padding: 6px 10px 40px;
background-color: gu-colour(neutral-97);
border-bottom: 1px solid gu-colour(neutral-86);
}

.form__submit--secure .component-button {
width: 100%;
}

.form__radio-group--contribution-amount {
margin: 12px 0;
}
Expand Down