* Add new payment method cards

* Refactor payment methods

* Track payment configuration and completion client-side

* Record tasklist_payment_connect_method event in markConfigured

* Record event on payment setup

* Add recommended payment method ribbon

* Return to payments task even when previously configured
This commit is contained in:
Joshua T Flowers 2020-03-15 22:45:19 +01:00 committed by GitHub
parent 7266042b79
commit da6f113d18
11 changed files with 812 additions and 875 deletions

View File

@ -2,7 +2,7 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Component, Fragment } from '@wordpress/element';
import { Component, cloneElement, Fragment } from '@wordpress/element';
import { get } from 'lodash';
import { compose } from '@wordpress/compose';
import classNames from 'classnames';
@ -334,7 +334,7 @@ class TaskDashboard extends Component {
}
render() {
const { inline } = this.props;
const { inline, query } = this.props;
const { isCartModalOpen, isWelcomeModalOpen } = this.state;
const currentTask = this.getCurrentTask();
const listTasks = this.getTasks().map( ( task ) => {
@ -362,7 +362,9 @@ class TaskDashboard extends Component {
<Fragment>
<div className="woocommerce-task-dashboard__container">
{ currentTask ? (
currentTask.container
cloneElement( currentTask.container, {
query,
} )
) : (
<Fragment>
<Card

View File

@ -295,98 +295,110 @@
}
}
.woocommerce-task-payments {
.woocommerce-list__item .woocommerce-list__item-after {
align-self: start;
margin-left: $gap;
margin-top: $gap-large;
.woocommerce-task-payment {
.woocommerce-card__body {
display: flex;
padding: $gap-large $gap-larger;
align-items: center;
position: relative;
overflow: hidden;
}
.woocommerce-list__item .woocommerce-list__item-before {
max-width: 96px;
background: $studio-gray-0;
padding: $gap-small;
align-self: start;
margin-top: $gap;
.woocommerce-task-payment__recommended-ribbon {
position: absolute;
transform: rotate(-45deg) translate(-50%, -50%);
background: $studio-gray-80;
color: $studio-white;
font-size: 11px;
line-height: 20px;
position: absolute;
top: 0;
left: 0;
line-height: 1;
padding: 7px $gap-largest;
transform-origin: top left;
margin-top: 36px;
margin-left: 36px;
span {
max-width: 70px;
}
}
.woocommerce-task-payment__before {
margin-right: $gap-larger;
img {
max-width: 72px;
max-width: 100px;
}
}
.woocommerce-list__item-title {
border-top: 1px solid $studio-gray-5;
padding-top: $gap;
.woocommerce-task-payment__title {
font-size: 16px;
font-weight: 400;
color: $studio-gray-80;
margin-top: 0;
margin-bottom: $gap-smaller;
}
.woocommerce-task-payments__woocommerce-services-options {
border-top: 1px solid $studio-gray-5;
margin-top: $gap;
.woocommerce-task-payment__content {
font-size: 14px;
color: $studio-gray-60;
margin: 0;
}
.woocommerce-task-payment__after {
margin-left: $gap-larger;
padding-top: $gap;
svg.dashicon.components-checkbox-control__checked {
display: block;
left: -38px;
top: -1px;
}
.components-checkbox-control__input {
margin-left: -$gap-larger;
}
.components-checkbox-control__label {
font-size: 16px;
line-height: 22px;
padding-left: 0;
margin-left: -$gap-large;
color: #1a1a1a;
}
.muriel-component {
margin-left: $gap-smallest;
.components-button.is-button {
margin: 0;
}
}
@include breakpoint( '<600px' ) {
.woocommerce-list__item > .woocommerce-list__item-inner {
.woocommerce-card__body {
flex-direction: column;
}
.woocommerce-list__item-title {
border-top: 0;
.woocommerce-task-payment__recommended-ribbon {
display: none;
}
.woocommerce-list__item .woocommerce-list__item-before {
margin-top: 0;
.woocommerce-task-payment__before {
order: 1;
margin-right: auto;
margin-bottom: $gap-large;
}
.woocommerce-task-payments__woocommerce-services-options {
margin-top: $gap-smaller;
border-top: 0;
.woocommerce-task-payment__after {
position: absolute;
right: $gap-larger;
top: $gap-large;
.components-form-toggle {
margin-top: $gap-smallest;
}
}
.woocommerce-task-payments__woocommerce-services-options .muriel-component {
margin-left: -$gap-larger;
}
.woocommerce-list__item .woocommerce-list__item-after {
margin-left: 0;
order: 2;
align-self: flex-end;
margin-top: -$gap-larger;
}
.woocommerce-list__item .woocommerce-list__item-text {
.woocommerce-task-payment__text {
order: 3;
margin-top: $gap;
}
}
}
.woocommerce-task-payments__woocommerce-services-options .components-checkbox-control__label {
margin-left: -$gap-larger;
margin-top: -$gap-smaller;
}
.woocommerce-task-dashboard__container .woocommerce-task-payments .woocommerce-task-payments__actions {
text-align: center;
button.components-button.is-primary {
margin: 0;
}
button.components-button.is-link {
margin: 0;
height: auto;
color: $studio-gray-60;
font-weight: normal;
}
}

View File

@ -1,27 +1,24 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Fragment, Component } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { Fragment, cloneElement, Component } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import { get, filter, noop, keys, pickBy, difference } from 'lodash';
import { Button, FormToggle, CheckboxControl } from '@wordpress/components';
import { get, filter } from 'lodash';
import { Button, FormToggle } from '@wordpress/components';
import { withDispatch } from '@wordpress/data';
/**
* WooCommerce dependencies
*/
import { Card, H } from '@woocommerce/components';
import {
Form,
Card,
Stepper,
TextControl,
List,
} from '@woocommerce/components';
import { getHistory, getNewPath } from '@woocommerce/navigation';
getHistory,
getNewPath,
updateQueryString,
} from '@woocommerce/navigation';
import {
WC_ASSET_URL as wcAssetUrl,
getAdminLink,
getSetting,
} from '@woocommerce/wc-admin-settings';
@ -35,6 +32,7 @@ import Plugins from '../steps/plugins';
import Stripe from './stripe';
import Square from './square';
import PayPal from './paypal';
import { pluginNames } from 'wc-api/onboarding/constants';
import Klarna from './klarna';
import PayFast from './payfast';
@ -42,64 +40,25 @@ class Payments extends Component {
constructor() {
super( ...arguments );
this.chooseMethods = this.chooseMethods.bind( this );
this.completeStep = this.completeStep.bind( this );
this.recommendedMethod = 'stripe';
this.completeTask = this.completeTask.bind( this );
this.markConfigured = this.markConfigured.bind( this );
this.setMethodRequestPending = this.setMethodRequestPending.bind(
this
);
this.completePluginInstall = this.completePluginInstall.bind( this );
const { methods, installed, configured } = this.props;
let step = 'choose';
let showIndividualConfigs = false;
// Figure out which step to show initially if there are still steps to be configured, or redirect back to the task list.
if ( methods.length > 0 && configured.length > 0 ) {
step = difference( methods, configured )[ 0 ] || '';
showIndividualConfigs = true;
const stepsLeft = difference( methods, configured ).length;
if ( stepsLeft === 0 ) {
this.state = {
step: 'done',
methodRequestPending: false,
};
this.completeTask();
return;
}
} else if ( installed === 1 && methods.length > 0 ) {
// Methods have been installed but not configured yet.
step = methods[ 0 ];
showIndividualConfigs = true;
}
this.state = {
step,
showIndividualConfigs,
methodRequestPending: false,
};
}
componentDidUpdate( prevProps ) {
const { methods, configured } = this.props;
if (
prevProps.configured.length !== configured.length &&
methods.length > 0 &&
configured.length > 0
) {
const stepsLeft = difference( methods, configured );
const nextStep = stepsLeft[ 0 ];
/* eslint-disable react/no-did-update-set-state */
this.setState( {
step: nextStep,
} );
/* eslint-enable react/no-did-update-set-state */
}
this.skipTask = this.skipTask.bind( this );
}
completeTask() {
const { createNotice } = this.props;
const { configured, createNotice, options, updateOptions } = this.props;
updateOptions( {
woocommerce_task_list_payments: {
...options.woocommerce_task_list_payments,
completed: 1,
},
} );
recordEvent( 'tasklist_payment_done', {
configured,
} );
createNotice(
'success',
@ -112,6 +71,23 @@ class Payments extends Component {
getHistory().push( getNewPath( {}, '/', {} ) );
}
skipTask() {
const { options, updateOptions } = this.props;
updateOptions( {
woocommerce_task_list_payments: {
...options.woocommerce_task_list_payments,
completed: 1,
},
} );
recordEvent( 'tasklist_payment_skip_task', {
options: this.getMethodOptions().map( ( method ) => method.key ),
} );
getHistory().push( getNewPath( {}, '/', {} ) );
}
isStripeEnabled() {
const { countryCode } = this.props;
const stripeCountries = getSetting( 'onboarding', {
@ -120,116 +96,29 @@ class Payments extends Component {
return stripeCountries.includes( countryCode );
}
getInitialValues() {
const stripeEmail = getSetting( 'onboarding', { userEmail: '' } )
.userEmail;
const values = {
stripe: this.isStripeEnabled(),
paypal: false,
klarna_checkout: false,
klarna_payments: false,
square: false,
create_stripe: this.isStripeEnabled(),
stripe_email: ( this.isStripeEnabled() && stripeEmail ) || '',
payfast: false,
};
return values;
}
validate() {
const errors = {};
return errors;
}
completeStep() {
const { step } = this.state;
const steps = this.getSteps();
const currentStepIndex = steps.findIndex( ( s ) => s.key === step );
const nextStep = steps[ currentStepIndex + 1 ];
if ( nextStep ) {
this.setState( { step: nextStep.key } );
} else {
getHistory().push( getNewPath( {}, '/', {} ) );
}
}
completePluginInstall() {
const { completed } = this.props;
this.props.updateOptions( {
woocommerce_task_list_payments: {
completed: completed || false,
installed: 1,
methods: this.getMethodsToConfigure(),
},
} );
this.setState( { showIndividualConfigs: true }, function() {
this.completeStep();
} );
}
markConfigured( method ) {
const { options, methods, configured } = this.props;
configured.push( method );
const stepsLeft = difference( methods, configured );
const { options, configured, updateOptions } = this.props;
this.props.updateOptions( {
getHistory().push( getNewPath( { task: 'payments' }, '/', {} ) );
if ( configured.includes( method ) ) {
return;
}
recordEvent( 'tasklist_payment_connect_method', {
payment_method: method,
} );
configured.push( method );
updateOptions( {
woocommerce_task_list_payments: {
...options.woocommerce_task_list_payments,
configured,
completed: stepsLeft.length === 0 ? 1 : 0,
},
} );
if ( stepsLeft.length === 0 ) {
this.completeTask();
}
}
setMethodRequestPending( status ) {
this.setState( {
methodRequestPending: status,
} );
}
// If Jetpack is connected and WCS is enabled, we will offer a streamlined option.
renderWooCommerceServicesStripeConnect() {
const { getInputProps, values } = this.formData;
if ( ! values.stripe ) {
return null;
}
const { isJetpackConnected, activePlugins } = this.props;
if (
! isJetpackConnected ||
! activePlugins.includes( 'woocommerce-services' )
) {
return null;
}
return (
<div className="woocommerce-task-payments__woocommerce-services-options">
<CheckboxControl
label={ __(
'Create a Stripe account for me',
'woocommerce-admin'
) }
{ ...getInputProps( 'create_stripe' ) }
/>
{ values.create_stripe && (
<TextControl
label={ __( 'Email address', 'woocommerce-admin' ) }
{ ...getInputProps( 'stripe_email' ) }
/>
) }
</div>
);
}
getMethodOptions() {
const { getInputProps } = this.formData;
const { countryCode, profileItems } = this.props;
const methods = [
@ -246,12 +135,12 @@ class Payments extends Component {
'and one-touch checkout with Apple Pay.',
'woocommerce-admin'
) }
{ this.renderWooCommerceServicesStripeConnect() }
</Fragment>
),
before: <img src={ wcAssetUrl + 'images/stripe.png' } alt="" />,
after: <FormToggle { ...getInputProps( 'stripe' ) } />,
visible: this.isStripeEnabled(),
plugins: [ 'woocommerce-gateway-stripe' ],
container: <Stripe markConfigured={ this.markConfigured } />,
},
{
key: 'paypal',
@ -265,8 +154,9 @@ class Payments extends Component {
</Fragment>
),
before: <img src={ wcAssetUrl + 'images/paypal.png' } alt="" />,
after: <FormToggle { ...getInputProps( 'paypal' ) } />,
visible: true,
plugins: [ 'woocommerce-gateway-paypal-express-checkout' ],
container: <PayPal markConfigured={ this.markConfigured } />,
},
{
key: 'klarna_checkout',
@ -281,8 +171,14 @@ class Payments extends Component {
alt=""
/>
),
after: <FormToggle { ...getInputProps( 'klarna_checkout' ) } />,
visible: [ 'SE', 'FI', 'NO', 'NL' ].includes( countryCode ),
plugins: [ 'klarna-checkout-for-woocommerce' ],
container: (
<Klarna
markConfigured={ this.markConfigured }
plugin={ 'checkout' }
/>
),
},
{
key: 'klarna_payments',
@ -297,8 +193,14 @@ class Payments extends Component {
alt=""
/>
),
after: <FormToggle { ...getInputProps( 'klarna_payments' ) } />,
visible: [ 'DK', 'DE', 'AT' ].includes( countryCode ),
plugins: [ 'klarna-payments-for-woocommerce' ],
container: (
<Klarna
markConfigured={ this.markConfigured }
plugin={ 'payments' }
/>
),
},
{
key: 'square',
@ -314,12 +216,13 @@ class Payments extends Component {
alt=""
/>
),
after: <FormToggle { ...getInputProps( 'square' ) } />,
visible:
[ 'brick-mortar', 'brick-mortar-other' ].includes(
profileItems.selling_venues
) &&
[ 'US', 'CA', 'JP', 'GB', 'AU' ].includes( countryCode ),
plugins: [ 'woocommerce-square' ],
container: <Square markConfigured={ this.markConfigured } />,
},
{
key: 'payfast',
@ -344,279 +247,180 @@ class Payments extends Component {
alt="PayFast logo"
/>
),
after: <FormToggle { ...getInputProps( 'payfast' ) } />,
visible: [ 'ZA' ].includes( countryCode ),
plugins: [ 'woocommerce-payfast-gateway' ],
container: <PayFast markConfigured={ this.markConfigured } />,
},
];
return filter( methods, ( method ) => method.visible );
}
getMethodsToConfigure() {
const { options } = this.props;
if (
options &&
options.woocommerce_task_list_payments &&
options.woocommerce_task_list_payments.methods
) {
return options.woocommerce_task_list_payments.methods;
getCurrentMethod() {
const { query } = this.props;
if ( ! query.method ) {
return;
}
const { values } = this.formData;
const methods = {
stripe: values.stripe,
paypal: values.paypal,
'klarna-checkout': values.klarna_checkout,
'klarna-payments': values.klarna_payments,
square: values.square,
payfast: values.payfast,
};
return keys( pickBy( methods ) );
const methods = this.getMethodOptions();
return methods.find( ( method ) => method.key === query.method );
}
getPluginsToInstall() {
const { values } = this.formData;
const pluginSlugs = {
'woocommerce-gateway-stripe': values.stripe,
'woocommerce-gateway-paypal-express-checkout': values.paypal,
'klarna-checkout-for-woocommerce': values.klarna_checkout,
'klarna-payments-for-woocommerce': values.klarna_payments,
'woocommerce-square': values.square,
'woocommerce-payfast-gateway': values.payfast,
};
return keys( pickBy( pluginSlugs ) );
}
getInstallStep() {
const currentMethod = this.getCurrentMethod();
chooseMethods() {
const methodsDisplayed = this.getMethodOptions().map(
( method ) => method.key
if ( ! currentMethod.plugins || ! currentMethod.plugins.length ) {
return;
}
const { activePlugins } = this.props;
const pluginsToInstall = currentMethod.plugins.filter(
( method ) => ! activePlugins.includes( method )
);
const methodsChosen = this.getMethodsToConfigure();
const { values } = this.formData;
const createAccount = values.create_stripe || false;
const pluginNamesString = currentMethod.plugins
.map( ( pluginSlug ) => pluginNames[ pluginSlug ] )
.join( ' ' + __( 'and', 'woocommerce-admin' ) + ' ' );
recordEvent( 'wcadmin_tasklist_payment_choose_method', {
payment_methods_displayed: methodsDisplayed,
payment_methods_chosen: methodsChosen,
create_stripe_account: createAccount,
} );
this.completeStep();
}
getSteps() {
const { values } = this.formData;
const isMethodSelected =
values.stripe ||
values.paypal ||
values.klarna_checkout ||
values.klarna_payments ||
values.square ||
values.payfast;
const { showIndividualConfigs } = this.state;
const { activePlugins, countryCode, isJetpackConnected } = this.props;
const manualConfig =
isJetpackConnected &&
activePlugins.includes( 'woocommerce-services' )
? false
: true;
const methods = this.getMethodsToConfigure();
const steps = [
{
key: 'choose',
label: __( 'Choose payment methods', 'woocommerce-admin' ),
description: __(
"Select which payment methods you'd like to use",
'woocommerce-admin'
),
content: (
<Fragment>
<List items={ this.getMethodOptions() } />
<Button
onClick={ this.chooseMethods }
isPrimary
disabled={ ! isMethodSelected }
>
{ __( 'Proceed', 'woocommerce-admin' ) }
</Button>
</Fragment>
),
visible: true,
},
{
key: 'install',
label: __( 'Install selected methods', 'woocommerce-admin' ),
description: __(
'Install plugins required to offer the selected payment methods',
'woocommerce-admin'
),
content: ! showIndividualConfigs && (
<Plugins
onComplete={ () => {
this.completePluginInstall();
recordEvent( 'tasklist_payment_install_method' );
} }
autoInstall
pluginSlugs={ this.getPluginsToInstall() }
/>
),
visible: true,
},
{
key: 'configure',
label: __( 'Configure payment methods', 'woocommerce-admin' ),
description: __(
'Set up your chosen payment methods',
'woocommerce-admin'
),
content: <Fragment />,
visible: ! showIndividualConfigs,
},
{
key: 'stripe',
label: __( 'Enable Stripe', 'woocommerce-admin' ),
description: __(
'Connect your store to your Stripe account',
'woocommerce-admin'
),
content: (
<Stripe
manualConfig={ manualConfig }
markConfigured={ this.markConfigured }
setRequestPending={ this.setMethodRequestPending }
createAccount={ values.create_stripe && ! manualConfig }
email={ values.stripe_email }
countryCode={ countryCode }
returnUrl={ getAdminLink(
'admin.php?page=wc-admin&task=payments&stripe-connect=1'
) }
/>
),
visible: showIndividualConfigs && methods.includes( 'stripe' ),
},
{
key: 'paypal',
label: __( 'Enable PayPal Checkout', 'woocommerce-admin' ),
description: __(
'Connect your store to your PayPal account',
'woocommerce-admin'
),
content: (
<PayPal
markConfigured={ this.markConfigured }
setRequestPending={ this.setMethodRequestPending }
/>
),
visible: showIndividualConfigs && methods.includes( 'paypal' ),
},
{
key: 'square',
label: __( 'Enable Square', 'woocommerce-admin' ),
description: __(
'Connect your store to your Square account',
'woocommerce-admin'
),
content: (
<Square
markConfigured={ this.markConfigured }
setRequestPending={ this.setMethodRequestPending }
/>
),
visible: showIndividualConfigs && methods.includes( 'square' ),
},
{
key: 'klarna-checkout',
label: __( 'Klarna', 'woocommerce-admin' ),
description: '',
content: (
<Klarna
markConfigured={ this.markConfigured }
setRequestPending={ this.setMethodRequestPending }
plugin={ 'checkout' }
/>
),
visible:
showIndividualConfigs &&
methods.includes( 'klarna-checkout' ),
},
{
key: 'klarna-payments',
label: __( 'Klarna', 'woocommerce-admin' ),
description: '',
content: (
<Klarna
markConfigured={ this.markConfigured }
setRequestPending={ this.setMethodRequestPending }
plugin={ 'payments' }
/>
),
visible:
showIndividualConfigs &&
methods.includes( 'klarna-payments' ),
},
{
key: 'payfast',
label: __( 'Enable PayFast', 'woocommerce-admin' ),
description: __(
'Connect your store to your PayFast account',
'woocommerce-admin'
),
content: (
<PayFast
markConfigured={ this.markConfigured }
setRequestPending={ this.setMethodRequestPending }
/>
),
visible: showIndividualConfigs && methods.includes( 'payfast' ),
},
];
return filter( steps, ( step ) => step.visible );
return {
key: 'install',
label: sprintf(
__( 'Install %s', 'woocommerce-admin' ),
pluginNamesString
),
content: (
<Plugins
onComplete={ () => {
recordEvent( 'tasklist_payment_install_method', {
plugins: currentMethod.plugins,
} );
} }
autoInstall
pluginSlugs={ currentMethod.plugins }
/>
),
isComplete: ! pluginsToInstall.length,
};
}
render() {
const { step, methodRequestPending } = this.state;
const currentMethod = this.getCurrentMethod();
const { configured, query } = this.props;
if ( currentMethod ) {
return (
<Card className="woocommerce-task-payment-method is-narrow">
{ cloneElement( currentMethod.container, {
query,
installStep: this.getInstallStep(),
} ) }
</Card>
);
}
const methods = this.getMethodOptions();
return (
<Form
initialValues={ this.getInitialValues() }
onSubmitCallback={ noop }
validate={ this.validate }
>
{ ( formData ) => {
this.formData = formData;
<div className="woocommerce-task-payments">
{ methods.map( ( method ) => {
const {
before,
container,
content,
key,
title,
visible,
} = method;
if ( ! visible ) {
return null;
}
return (
<div className="woocommerce-task-payments">
<Card className="is-narrow">
<Stepper
isVertical
isPending={
methodRequestPending ||
step === 'install'
}
currentStep={ step }
steps={ this.getSteps() }
/>
</Card>
</div>
<Card
key={ key }
className="woocommerce-task-payment is-narrow"
>
<div className="woocommerce-task-payment__before">
{ key === this.recommendedMethod &&
! configured.includes( key ) && (
<div className="woocommerce-task-payment__recommended-ribbon">
<span>
{ __(
'Recommended',
'woocommerce-admin'
) }
</span>
</div>
) }
{ before }
</div>
<div className="woocommerce-task-payment__text">
<H className="woocommerce-task-payment__title">
{ title }
</H>
<p className="woocommerce-task-payment__content">
{ content }
</p>
</div>
<div className="woocommerce-task-payment__after">
{ container ? (
<Button
isPrimary={
key === this.recommendedMethod
}
isDefault={
key !== this.recommendedMethod
}
onClick={ () => {
recordEvent(
'tasklist_payment_setup',
{
options: this.getMethodOptions().map(
( option ) => option.key
),
selected: key,
}
);
updateQueryString( {
method: key,
} );
} }
>
{ __( 'Set up', 'woocommerce-admin' ) }
</Button>
) : (
<FormToggle />
) }
</div>
</Card>
);
} }
</Form>
} ) }
<div className="woocommerce-task-payments__actions">
{ configured.length === 0 ? (
<Button isLink onClick={ this.skipTask }>
{ __(
'My store doesnt take payments',
'woocommerce-admin'
) }
</Button>
) : (
<Button isPrimary onClick={ this.completeTask }>
{ __( 'Done', 'woocommerce-admin' ) }
</Button>
) }
</div>
</div>
);
}
}
export default compose(
withSelect( ( select ) => {
const {
getProfileItems,
isJetpackConnected,
getActivePlugins,
getOptions,
} = select( 'wc-api' );
const { getProfileItems, getActivePlugins, getOptions } = select(
'wc-api'
);
const options = getOptions( [
'woocommerce_task_list_payments',
@ -626,38 +430,18 @@ export default compose(
options.woocommerce_default_country
);
const methods = get(
options,
[ 'woocommerce_task_list_payments', 'methods' ],
[]
);
const installed = get(
options,
[ 'woocommerce_task_list_payments', 'installed' ],
false
);
const configured = get(
options,
[ 'woocommerce_task_list_payments', 'configured' ],
[]
);
const completed = get(
options,
[ 'woocommerce_task_list_payments', 'completed' ],
false
);
return {
countryCode,
profileItems: getProfileItems(),
activePlugins: getActivePlugins(),
isJetpackConnected: isJetpackConnected(),
options,
methods,
installed,
configured,
completed,
};
} ),
withDispatch( ( dispatch ) => {

View File

@ -10,12 +10,7 @@ import interpolateComponents from 'interpolate-components';
* WooCommerce dependencies
*/
import { ADMIN_URL as adminUrl } from '@woocommerce/wc-admin-settings';
import { Link } from '@woocommerce/components';
/**
* Internal dependencies
*/
import { recordEvent } from 'lib/tracks';
import { Link, Stepper } from '@woocommerce/components';
class Klarna extends Component {
constructor( props ) {
@ -24,23 +19,20 @@ class Klarna extends Component {
}
continue() {
const { markConfigured, plugin } = this.props;
const slug =
this.props.plugin === 'checkout'
? 'klarna-checkout'
: 'klarna-payments';
recordEvent( 'tasklist_payment_connect_method', {
payment_method: slug,
} );
this.props.markConfigured( slug );
plugin === 'checkout' ? 'klarna-checkout' : 'klarna-payments';
markConfigured( slug );
}
render() {
renderConnectStep() {
const { plugin } = this.props;
const slug =
this.props.plugin === 'checkout'
? 'klarna-checkout'
: 'klarna-payments';
const section =
this.props.plugin === 'checkout' ? 'kco' : 'klarna_payments';
plugin === 'checkout' ? 'klarna-checkout' : 'klarna-payments';
const section = plugin === 'checkout' ? 'kco' : 'klarna_payments';
const link = (
<Link
@ -85,6 +77,29 @@ class Klarna extends Component {
</Fragment>
);
}
render() {
const { installStep } = this.props;
return (
<Stepper
isVertical
isPending={ ! installStep.isComplete }
currentStep={ installStep.isComplete ? 'connect' : 'install' }
steps={ [
installStep,
{
key: 'connect',
label: __(
'Connect your Klarna account',
'woocommerce-admin'
),
content: this.renderConnectStep(),
},
] }
/>
);
}
}
export default Klarna;

View File

@ -11,12 +11,12 @@ import { withDispatch } from '@wordpress/data';
/**
* WooCommerce dependencies
*/
import { Form, Link, TextControl } from '@woocommerce/components';
import { Form, Link, Stepper, TextControl } from '@woocommerce/components';
/**
* Internal dependencies
*/
import { recordEvent } from 'lib/tracks';
import withSelect from 'wc-api/with-select';
class PayFast extends Component {
getInitialConfigValues = () => {
@ -54,20 +54,39 @@ class PayFast extends Component {
return errors;
};
updateSettings = async ( values ) => {
componentDidUpdate( prevProps ) {
const {
createNotice,
isSettingsError,
updateOptions,
isOptionsRequesting,
hasOptionsError,
markConfigured,
setRequestPending,
} = this.props;
setRequestPending( true );
if ( prevProps.isOptionsRequesting && ! isOptionsRequesting ) {
if ( ! hasOptionsError ) {
markConfigured( 'payfast' );
createNotice(
'success',
__( 'PayFast connected successfully', 'woocommerce-admin' )
);
} else {
createNotice(
'error',
__(
'There was a problem saving your payment setings',
'woocommerce-admin'
)
);
}
}
}
updateSettings = ( values ) => {
const { updateOptions } = this.props;
// Because the PayFast extension only works with the South African Rand
// currency, force the store to use it while setting the PayFast settings
await updateOptions( {
updateOptions( {
woocommerce_currency: 'ZAR',
woocommerce_payfast_settings: {
merchant_id: values.merchant_id,
@ -76,30 +95,10 @@ class PayFast extends Component {
enabled: 'yes',
},
} );
if ( ! isSettingsError ) {
recordEvent( 'tasklist_payment_connect_method', {
payment_method: 'payfast',
} );
setRequestPending( false );
markConfigured( 'payfast' );
createNotice(
'success',
__( 'PayFast connected successfully', 'woocommerce-admin' )
);
} else {
setRequestPending( false );
createNotice(
'error',
__(
'There was a problem saving your payment setings',
'woocommerce-admin'
)
);
}
};
render() {
renderConnectStep() {
const { isOptionsRequesting } = this.props;
const helpText = interpolateComponents( {
mixedString: __(
'Your API details can be obtained from your {{link}}PayFast account{{/link}}',
@ -149,16 +148,14 @@ class PayFast extends Component {
required
{ ...getInputProps( 'pass_phrase' ) }
/>
<Button onClick={ handleSubmit } isPrimary>
<Button
isPrimary
isBusy={ isOptionsRequesting }
onClick={ handleSubmit }
>
{ __( 'Proceed', 'woocommerce-admin' ) }
</Button>
<Button
onClick={ () => {
this.props.markConfigured( 'payfast' );
} }
>
{ __( 'Skip', 'woocommerce-admin' ) }
</Button>
<p>{ helpText }</p>
</Fragment>
);
@ -166,9 +163,52 @@ class PayFast extends Component {
</Form>
);
}
render() {
const { installStep, isOptionsRequesting } = this.props;
return (
<Stepper
isVertical
isPending={ ! installStep.isComplete || isOptionsRequesting }
currentStep={ installStep.isComplete ? 'connect' : 'install' }
steps={ [
installStep,
{
key: 'connect',
label: __(
'Connect your PayFast account',
'woocommerce-admin'
),
content: this.renderConnectStep(),
},
] }
/>
);
}
}
export default compose(
withSelect( ( select ) => {
const { getOptionsError, isUpdateOptionsRequesting } = select(
'wc-api'
);
const isOptionsRequesting = Boolean(
isUpdateOptionsRequesting( [
'woocommerce_currency',
'woocommerce_payfast_settings',
] )
);
const hasOptionsError = getOptionsError( [
'woocommerce_currency',
'woocommerce_payfast_settings',
] );
return {
hasOptionsError,
isOptionsRequesting,
};
} ),
withDispatch( ( dispatch ) => {
const { createNotice } = dispatch( 'core/notices' );
const { updateOptions } = dispatch( 'wc-api' );

View File

@ -2,110 +2,84 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Component, Fragment } from '@wordpress/element';
import apiFetch from '@wordpress/api-fetch';
import { Button } from '@wordpress/components';
import { withDispatch } from '@wordpress/data';
import { Component, Fragment } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import interpolateComponents from 'interpolate-components';
import { withDispatch } from '@wordpress/data';
/**
* WooCommerce dependencies
*/
import { WC_ADMIN_NAMESPACE } from 'wc-api/constants';
import { Form, Link, Stepper, TextControl } from '@woocommerce/components';
import { getQuery } from '@woocommerce/navigation';
import { Form, Link, TextControl } from '@woocommerce/components';
import { WC_ADMIN_NAMESPACE } from 'wc-api/constants';
import withSelect from 'wc-api/with-select';
import { recordEvent } from 'lib/tracks';
class PayPal extends Component {
constructor( props ) {
super( props );
this.state = {
autoConnectFailed: false,
connectURL: '',
showManualConfiguration: props.manualConfig,
isPending: false,
};
this.updateSettings = this.updateSettings.bind( this );
}
componentDidMount() {
const { showManualConfiguration } = this.state;
const { autoConnectFailed } = this.state;
const { createNotice, markConfigured } = this.props;
const query = getQuery();
// Handle redirect back from PayPal
if ( query[ 'paypal-connect' ] ) {
if ( query[ 'paypal-connect' ] === '1' ) {
recordEvent( 'tasklist_payment_connect_method', {
payment_method: 'paypal',
} );
this.props.markConfigured( 'paypal' );
this.props.createNotice(
createNotice(
'success',
__( 'PayPal connected successfully.', 'woocommerce-admin' )
);
markConfigured( 'paypal' );
return;
}
/* eslint-disable react/no-did-mount-set-state */
this.setState( {
showManualConfiguration: true,
autoConnectFailed: true,
} );
/* eslint-enable react/no-did-mount-set-state */
return;
}
if ( ! showManualConfiguration ) {
if ( ! autoConnectFailed ) {
this.fetchOAuthConnectURL();
}
}
componentDidUpdate( prevProps, prevState ) {
if (
prevState.showManualConfiguration === true &&
this.state.showManualConfiguration === false
) {
this.fetchOAuthConnectURL();
}
if (
prevProps.optionsIsRequesting === false &&
this.props.optionsIsRequesting === true
) {
this.props.setRequestPending( true );
}
if (
prevProps.optionsIsRequesting === true &&
this.props.optionsIsRequesting === false
) {
this.props.setRequestPending( false );
}
}
async fetchOAuthConnectURL() {
this.props.setRequestPending( true );
this.setState( { isPending: true } );
try {
const result = await apiFetch( {
path: WC_ADMIN_NAMESPACE + '/onboarding/plugins/connect-paypal',
method: 'POST',
} );
if ( ! result || ! result.connectUrl ) {
this.props.setRequestPending( false );
this.setState( {
showManualConfiguration: true,
autoConnectFailed: true,
} );
return;
}
this.props.setRequestPending( false );
this.setState( {
connectURL: result.connectUrl,
isPending: false,
} );
} catch ( error ) {
this.props.setRequestPending( false );
this.setState( {
showManualConfiguration: true,
autoConnectFailed: true,
isPending: false,
} );
}
}
@ -123,14 +97,14 @@ class PayPal extends Component {
const {
createNotice,
isSettingsError,
options,
updateOptions,
markConfigured,
} = this.props;
this.props.setRequestPending( true );
await updateOptions( {
woocommerce_ppec_paypal_settings: {
...this.props.options.woocommerce_ppec_paypal_settings,
...options.woocommerce_ppec_paypal_settings,
api_username: values.api_username,
api_password: values.api_password,
enabled: 'yes',
@ -138,17 +112,12 @@ class PayPal extends Component {
} );
if ( ! isSettingsError ) {
recordEvent( 'tasklist_payment_connect_method', {
payment_method: 'paypal',
} );
this.props.setRequestPending( false );
markConfigured( 'paypal' );
this.props.createNotice(
createNotice(
'success',
__( 'PayPal connected successfully.', 'woocommerce-admin' )
);
markConfigured( 'paypal' );
} else {
this.props.setRequestPending( false );
createNotice(
'error',
__(
@ -186,7 +155,7 @@ class PayPal extends Component {
}
renderManualConfig() {
const { optionsIsRequesting } = this.props;
const { isOptionsRequesting } = this.props;
const link = (
<Link
href="https://docs.woocommerce.com/document/paypal-express-checkout/#section-8"
@ -233,19 +202,11 @@ class PayPal extends Component {
<Button
onClick={ handleSubmit }
isPrimary
disabled={ optionsIsRequesting }
disabled={ isOptionsRequesting }
>
{ __( 'Proceed', 'woocommerce-admin' ) }
</Button>
<Button
onClick={ () => {
this.props.markConfigured( 'paypal' );
} }
>
{ __( 'Skip', 'woocommerce-admin' ) }
</Button>
<p>{ help }</p>
</Fragment>
);
@ -254,18 +215,50 @@ class PayPal extends Component {
);
}
getConnectStep() {
const { autoConnectFailed, connectURL, isPending } = this.state;
const connectStep = {
key: 'connect',
label: __( 'Connect your PayPal account', 'woocommerce-admin' ),
};
if ( isPending ) {
return connectStep;
}
if ( ! autoConnectFailed && connectURL ) {
return {
...connectStep,
description: __(
'A Paypal account is required to process payments. You will be redirected to the Paypal website to create the connection.',
'woocommerce-admin'
),
content: this.renderConnectButton(),
};
}
return {
...connectStep,
description: __(
'Connect your store to your PayPal account. Dont have a PayPal account? Create one.',
'woocommerce-admin'
),
content: this.renderManualConfig(),
};
}
render() {
const { connectURL, showManualConfiguration } = this.state;
const { installStep } = this.props;
const { isPending } = this.state;
if ( connectURL && ! showManualConfiguration ) {
return this.renderConnectButton();
}
if ( showManualConfiguration ) {
return this.renderManualConfig();
}
return null;
return (
<Stepper
isVertical
isPending={ ! installStep.isComplete || isPending }
currentStep={ installStep.isComplete ? 'connect' : 'install' }
steps={ [ installStep, this.getConnectStep() ] }
/>
);
}
}
@ -277,13 +270,13 @@ export default compose(
withSelect( ( select ) => {
const { getOptions, isGetOptionsRequesting } = select( 'wc-api' );
const options = getOptions( [ 'woocommerce_ppec_paypal_settings' ] );
const optionsIsRequesting = Boolean(
const isOptionsRequesting = Boolean(
isGetOptionsRequesting( [ 'woocommerce_ppec_paypal_settings' ] )
);
return {
options,
optionsIsRequesting,
isOptionsRequesting,
};
} ),
withDispatch( ( dispatch ) => {

View File

@ -5,68 +5,50 @@ import { __ } from '@wordpress/i18n';
import { Component, Fragment } from '@wordpress/element';
import apiFetch from '@wordpress/api-fetch';
import { Button } from '@wordpress/components';
import { getQuery } from '@woocommerce/navigation';
import { withDispatch } from '@wordpress/data';
import { compose } from '@wordpress/compose';
/**
* WooCommerce dependencies
*/
import { getQuery } from '@woocommerce/navigation';
import { WC_ADMIN_NAMESPACE } from 'wc-api/constants';
import withSelect from 'wc-api/with-select';
import { recordEvent } from 'lib/tracks';
import { Stepper } from '@woocommerce/components';
class Square extends Component {
constructor( props ) {
super( props );
this.state = {
showSkipButton: false,
isPending: false,
};
this.connect = this.connect.bind( this );
}
componentDidMount() {
const { createNotice, markConfigured } = this.props;
const query = getQuery();
// Handle redirect back from Square
if ( query[ 'square-connect' ] ) {
if ( query[ 'square-connect' ] === '1' ) {
recordEvent( 'tasklist_payment_connect_method', {
payment_method: 'square',
} );
this.props.markConfigured( 'square' );
this.props.createNotice(
createNotice(
'success',
__( 'Square connected successfully.', 'woocommerce-admin' )
);
markConfigured( 'square' );
}
}
}
componentDidUpdate( prevProps ) {
if (
prevProps.optionsIsRequesting === false &&
this.props.optionsIsRequesting === true
) {
this.props.setRequestPending( true );
}
if (
prevProps.optionsIsRequesting === true &&
this.props.optionsIsRequesting === false
) {
this.props.setRequestPending( false );
}
}
async connect() {
const { updateOptions } = this.props;
this.props.setRequestPending( true );
const { createNotice, options, updateOptions } = this.props;
this.setState( { isPending: true } );
updateOptions( {
woocommerce_stripe_settings: {
...this.props.options.woocommerce_stripe_settings,
...options.woocommerce_stripe_settings,
enabled: 'yes',
},
} );
@ -83,39 +65,55 @@ class Square extends Component {
} );
if ( ! result || ! result.connectUrl ) {
this.props.setRequestPending( false );
this.setState( { showSkipButton: true } );
this.props.createNotice( 'error', errorMessage );
this.setState( { isPending: false } );
createNotice( 'error', errorMessage );
return;
}
this.props.setRequestPending( false );
this.setState( { isPending: true } );
window.location = result.connectUrl;
} catch ( error ) {
this.props.setRequestPending( false );
this.setState( { showSkipButton: true } );
this.props.createNotice( 'error', errorMessage );
this.setState( { isPending: false } );
createNotice( 'error', errorMessage );
}
}
render() {
const { showSkipButton } = this.state;
const { installStep } = this.props;
const { isPending } = this.state;
return (
<Fragment>
<Button isPrimary isDefault onClick={ this.connect }>
{ __( 'Connect', 'woocommerce-admin' ) }
</Button>
{ showSkipButton && (
<Button
onClick={ () => {
this.props.markConfigured( 'square' );
} }
>
{ __( 'Skip', 'woocommerce-admin' ) }
</Button>
) }
</Fragment>
<Stepper
isVertical
isPending={ ! installStep.isComplete || isPending }
currentStep={ installStep.isComplete ? 'connect' : 'install' }
steps={ [
installStep,
{
key: 'connect',
label: __(
'Connect your Square account',
'woocommerce-admin'
),
description: __(
'A Square account is required to process payments. You will be redirected to the Square website to create the connection.',
'woocommerce-admin'
),
content: (
<Fragment>
<Button
isPrimary
isDefault
isBusy={ isPending }
onClick={ this.connect }
>
{ __( 'Connect', 'woocommerce-admin' ) }
</Button>
</Fragment>
),
},
] }
/>
);
}
}

View File

@ -8,124 +8,149 @@ import apiFetch from '@wordpress/api-fetch';
import { withDispatch } from '@wordpress/data';
import interpolateComponents from 'interpolate-components';
import { Button, Modal } from '@wordpress/components';
import { getQuery } from '@woocommerce/navigation';
import { get } from 'lodash';
/**
* WooCommerce dependencies
*/
import { Form, Link, TextControl } from '@woocommerce/components';
import withSelect from 'wc-api/with-select';
import { Form, Link, Stepper, TextControl } from '@woocommerce/components';
import { getAdminLink } from '@woocommerce/wc-admin-settings';
import { getQuery } from '@woocommerce/navigation';
import { WCS_NAMESPACE } from 'wc-api/constants';
import { recordEvent } from 'lib/tracks';
import withSelect from 'wc-api/with-select';
/**
* Internal dependencies
*/
import { getCountryCode } from 'dashboard/utils';
class Stripe extends Component {
constructor( props ) {
super( props );
this.state = {
errorMessage: '',
connectURL: '',
showConnectionButtons:
! props.manualConfig && ! props.createAccount,
showManualConfiguration: props.manualConfig,
autoConnectFailed: false,
connectURL: null,
errorTitle: null,
errorMessage: null,
isPending: true,
};
this.autoCreateAccount = this.autoCreateAccount.bind( this );
this.updateSettings = this.updateSettings.bind( this );
}
componentDidMount() {
const { createAccount, options } = this.props;
const { showConnectionButtons } = this.state;
const { stripeSettings } = this.props;
const query = getQuery();
// Handle redirect back from Stripe.
if ( query[ 'stripe-connect' ] && query[ 'stripe-connect' ] === '1' ) {
const stripeSettings = get(
options,
[ 'woocommerce_stripe_settings' ],
[]
);
const isStripeConnected =
stripeSettings.publishable_key && stripeSettings.secret_key;
if ( isStripeConnected ) {
recordEvent( 'tasklist_payment_connect_method', {
payment_method: 'stripe',
} );
this.props.markConfigured( 'stripe' );
this.props.createNotice(
'success',
__( 'Stripe connected successfully.', 'woocommerce-admin' )
);
this.completeMethod();
return;
}
/* eslint-disable react/no-did-mount-set-state */
this.setState( {
showConnectionButtons: false,
showManualConfiguration: true,
autoConnectFailed: true,
} );
/* eslint-enable react/no-did-mount-set-state */
return;
}
if ( createAccount ) {
this.autoCreateAccount();
}
if ( showConnectionButtons ) {
if ( ! this.requiresManualConfig() ) {
this.fetchOAuthConnectURL();
}
}
componentDidUpdate( prevProps, prevState ) {
if (
prevState.showConnectionButtons === false &&
this.state.showConnectionButtons
) {
this.fetchOAuthConnectURL();
componentDidUpdate( prevProps ) {
const {
createNotice,
isOptionsRequesting,
hasOptionsError,
} = this.props;
if ( prevProps.isOptionsRequesting && ! isOptionsRequesting ) {
if ( ! hasOptionsError ) {
this.completeMethod();
} else {
createNotice(
'error',
__(
'There was a problem saving your payment setings',
'woocommerce-admin'
)
);
}
}
}
requiresManualConfig() {
const { activePlugins, isJetpackConnected } = this.props;
const { autoConnectFailed } = this.state;
return (
! isJetpackConnected ||
! activePlugins.includes( 'woocommerce-services' ) ||
autoConnectFailed
);
}
completeMethod() {
const { createNotice, markConfigured } = this.props;
this.setState( { isPending: false } );
createNotice(
'success',
__( 'Stripe connected successfully.', 'woocommerce-admin' )
);
markConfigured( 'stripe' );
}
async fetchOAuthConnectURL() {
const { returnUrl } = this.props;
try {
this.props.setRequestPending( true );
this.setState( { isPending: true } );
const result = await apiFetch( {
path: WCS_NAMESPACE + '/connect/stripe/oauth/init',
method: 'POST',
data: {
returnUrl,
returnUrl: getAdminLink(
'admin.php?page=wc-admin&task=payments&method=stripe&stripe-connect=1'
),
},
} );
if ( ! result || ! result.oauthUrl ) {
this.props.setRequestPending( false );
this.setState( {
showConnectionButtons: false,
showManualConfiguration: true,
autoConnectFailed: true,
isPending: false,
} );
return;
}
this.props.setRequestPending( false );
this.setState( {
connectURL: result.oauthUrl,
isPending: false,
} );
} catch ( error ) {
this.props.setRequestPending( false );
// Fallback to manual configuration if the OAuth URL cannot be grabbed.
this.setState( {
showConnectionButtons: false,
showManualConfiguration: true,
autoConnectFailed: true,
isPending: false,
} );
}
}
async autoCreateAccount() {
const { email, countryCode } = this.props;
async autoCreateAccount( values ) {
const { countryCode } = this.props;
const { connectURL } = this.state;
const { email } = values;
try {
this.props.setRequestPending( true );
this.setState( { isPending: true } );
const result = await apiFetch( {
path: WCS_NAMESPACE + '/connect/stripe/account',
method: 'POST',
@ -136,29 +161,13 @@ class Stripe extends Component {
} );
if ( result ) {
recordEvent( 'tasklist_payment_connect_method', {
payment_method: 'stripe',
} );
this.props.setRequestPending( false );
this.props.markConfigured( 'stripe' );
this.props.createNotice(
'success',
__( 'Stripe connected successfully.', 'woocommerce-admin' )
);
this.completeMethod();
return;
}
} catch ( error ) {
this.props.setRequestPending( false );
let errorTitle, errorMessage;
// This seems to be the best way to handle this.
// github.com/Automattic/woocommerce-services/blob/cfb6173deb3c72897ee1d35b8fdcf29c5a93dea2/woocommerce-services.php#L563-L570
if (
error.message.indexOf(
'Account already exists for the provided email'
) === -1
) {
errorTitle = __( 'Stripe', 'woocommerce-admin' );
errorMessage = interpolateComponents( {
} catch {
if ( ! connectURL ) {
const errorTitle = __( 'Stripe', 'woocommerce-admin' );
const errorMessage = interpolateComponents( {
mixedString: sprintf(
__(
'We tried to create a Stripe account automatically for {{strong}}%s{{/strong}}, but an error occured. Please try connecting manually to continue.',
@ -170,30 +179,17 @@ class Stripe extends Component {
strong: <strong />,
},
} );
} else {
errorTitle = __(
'You already have a Stripe account',
'woocommerce-admin'
);
errorMessage = interpolateComponents( {
mixedString: sprintf(
__(
'We tried to create a Stripe account automatically for {{strong}}%s{{/strong}}, but one already exists. Please sign in and connect to continue.',
'woocommerce-admin'
),
email
),
components: {
strong: <strong />,
},
} );
}
this.setState( {
showConnectionButtons: true,
errorTitle,
errorMessage,
} );
this.setState( {
autoConnectFailed: true,
errorTitle,
errorMessage,
isPending: false,
} );
} else {
// An account with that email may exist so send them to Stripe to connect via oAuth.
window.location = connectURL;
}
}
}
@ -203,7 +199,7 @@ class Stripe extends Component {
<Modal
title={ errorTitle }
onRequestClose={ () =>
this.setState( { errorMessage: '', errorTitle: '' } )
this.setState( { errorMessage: null, errorTitle: null } )
}
className="woocommerce-task-payments__stripe-error-modal"
>
@ -216,8 +212,8 @@ class Stripe extends Component {
isDefault
onClick={ () =>
this.setState( {
errorMessage: '',
errorTitle: '',
errorMessage: null,
errorTitle: null,
} )
}
>
@ -228,53 +224,53 @@ class Stripe extends Component {
);
}
renderConnectButton() {
const { connectURL } = this.state;
renderAutoConnect() {
const { isPending } = this.state;
return (
<Button isPrimary isDefault href={ connectURL }>
{ __( 'Connect', 'woocommerce-admin' ) }
</Button>
<Form
initialValues={ {
email: '',
} }
onSubmitCallback={ this.autoCreateAccount }
validate={ this.validateAutoConnect }
>
{ ( { getInputProps, handleSubmit } ) => {
return (
<div className="woocommerce-task-payments__woocommerce-services-options">
<TextControl
label={ __(
'Email address',
'woocommerce-admin'
) }
{ ...getInputProps( 'email' ) }
/>
<Button
isPrimary
isDefault
isBusy={ isPending }
onClick={ handleSubmit }
>
{ __( 'Connect', 'woocommerce-admin' ) }
</Button>
</div>
);
} }
</Form>
);
}
async updateSettings( values ) {
const {
createNotice,
isSettingsError,
updateOptions,
markConfigured,
} = this.props;
updateSettings( values ) {
const { updateOptions, stripeSettings } = this.props;
this.props.setRequestPending( true );
await updateOptions( {
updateOptions( {
woocommerce_stripe_settings: {
...this.props.options.woocommerce_stripe_settings,
...stripeSettings,
publishable_key: values.publishable_key,
secret_key: values.secret_key,
enabled: 'yes',
},
} );
if ( ! isSettingsError ) {
recordEvent( 'tasklist_payment_connect_method', {
payment_method: 'stripe',
} );
this.props.setRequestPending( false );
markConfigured( 'stripe' );
this.props.createNotice(
'success',
__( 'Stripe connected successfully.', 'woocommerce-admin' )
);
} else {
this.props.setRequestPending( false );
createNotice(
'error',
__(
'There was a problem saving your payment settings.',
'woocommerce-admin'
)
);
}
}
getInitialConfigValues() {
@ -284,7 +280,7 @@ class Stripe extends Component {
};
}
validate( values ) {
validateManualConfig( values ) {
const errors = {};
if ( ! values.publishable_key ) {
@ -303,16 +299,34 @@ class Stripe extends Component {
return errors;
}
validateAutoConnect( values ) {
const errors = {};
if ( ! values.email ) {
errors.email = __( 'Please enter your email', 'woocommerce-admin' );
}
return errors;
}
renderManualConfig() {
const { isOptionsRequesting } = this.props;
const stripeHelp = interpolateComponents( {
mixedString: __(
'Your API details can be obtained from your {{link}}Stripe account{{/link}}',
'Your API details can be obtained from your {{docsLink}}Stripe account{{/docsLink}}. Dont have a Stripe account? {{registerLink}}Create one.{{/registerLink}}',
'woocommerce-admin'
),
components: {
link: (
docsLink: (
<Link
href="https://stripe.com/docs/account"
href="https://stripe.com/docs/keys"
target="_blank"
type="external"
/>
),
registerLink: (
<Link
href="https://dashboard.stripe.com/register"
target="_blank"
type="external"
/>
@ -324,7 +338,7 @@ class Stripe extends Component {
<Form
initialValues={ this.getInitialConfigValues() }
onSubmitCallback={ this.updateSettings }
validate={ this.validate }
validate={ this.validateManualConfig }
>
{ ( { getInputProps, handleSubmit } ) => {
return (
@ -346,16 +360,12 @@ class Stripe extends Component {
{ ...getInputProps( 'secret_key' ) }
/>
<Button onClick={ handleSubmit } isPrimary>
{ __( 'Proceed', 'woocommerce-admin' ) }
</Button>
<Button
onClick={ () => {
this.props.markConfigured( 'stripe' );
} }
isPrimary
isBusy={ isOptionsRequesting }
onClick={ handleSubmit }
>
{ __( 'Skip', 'woocommerce-admin' ) }
{ __( 'Proceed', 'woocommerce-admin' ) }
</Button>
<p>{ stripeHelp }</p>
@ -366,36 +376,98 @@ class Stripe extends Component {
);
}
render() {
const {
errorMessage,
showConnectionButtons,
connectURL,
showManualConfiguration,
} = this.state;
getConnectStep() {
const { autoConnectFailed, connectURL, errorMessage } = this.state;
const connectStep = {
key: 'connect',
label: __( 'Connect your Stripe account', 'woocommerce-admin' ),
};
if ( errorMessage ) {
return this.renderErrorModal();
return {
...connectStep,
content: this.renderErrorModal(),
};
}
if ( showConnectionButtons && connectURL ) {
return this.renderConnectButton();
if ( ! this.requiresManualConfig() ) {
// We may still be fetching the connect URL.
if ( ! autoConnectFailed && ! connectURL ) {
return connectStep;
}
return {
...connectStep,
description: __(
'A Stripe account is required to process payments. Well create an account for you if you dont have one already.',
'woocommerce-admin'
),
content: this.renderAutoConnect(),
};
}
if ( showManualConfiguration ) {
return this.renderManualConfig();
}
return {
...connectStep,
description: __(
'Connect your store to your Stripe account. Dont have a Stripe account? Create one.',
'woocommerce-admin'
),
content: this.renderManualConfig(),
};
}
return null;
render() {
const { installStep, isOptionsRequesting } = this.props;
const { isPending } = this.state;
return (
<Stepper
isVertical
isPending={
! installStep.isComplete || isOptionsRequesting || isPending
}
currentStep={ installStep.isComplete ? 'connect' : 'install' }
steps={ [ installStep, this.getConnectStep() ] }
/>
);
}
}
export default compose(
withSelect( ( select ) => {
const { getOptions } = select( 'wc-api' );
const options = getOptions( [ 'woocommerce_stripe_settings' ] );
return {
const {
getActivePlugins,
getOptions,
getOptionsError,
isJetpackConnected,
isUpdateOptionsRequesting,
} = select( 'wc-api' );
const options = getOptions( [
'woocommerce_stripe_settings',
'woocommerce_default_country',
] );
const countryCode = getCountryCode(
options.woocommerce_default_country
);
const stripeSettings = get(
options,
[ 'woocommerce_stripe_settings' ],
[]
);
const isOptionsRequesting = Boolean(
isUpdateOptionsRequesting( [ 'woocommerce_stripe_settings' ] )
);
const hasOptionsError = getOptionsError( [
'woocommerce_stripe_settings',
] );
return {
activePlugins: getActivePlugins(),
countryCode,
hasOptionsError,
isJetpackConnected: isJetpackConnected(),
isOptionsRequesting,
stripeSettings,
};
} ),
withDispatch( ( dispatch ) => {

View File

@ -7,18 +7,39 @@ import { __ } from '@wordpress/i18n';
* Plugin slugs and names as key/value pairs.
*/
export const pluginNames = {
jetpack: __( 'Jetpack', 'woocommerce-admin' ),
'woocommerce-services': __( 'WooCommerce Services', 'woocommerce-admin' ),
'mailchimp-for-woocommerce': __(
'Mailchimp for WooCommerce',
'woocommerce-admin'
),
'facebook-for-woocommerce': __(
'Facebook for WooCommerce',
'woocommerce-admin'
),
jetpack: __( 'Jetpack', 'woocommerce-admin' ),
'klarna-checkout-for-woocommerce': __(
'Klarna Checkout for WooCommerce',
'woocommerce-admin'
),
'klarna-payments-for-woocommerce': __(
'Klarna Payments for WooCommerce',
'woocommerce-admin'
),
'mailchimp-for-woocommerce': __(
'Mailchimp for WooCommerce',
'woocommerce-admin'
),
'woocommerce-gateway-paypal-express-checkout': __(
'WooCommerce PayPal',
'woocommerce-admin'
),
'woocommerce-gateway-stripe': __(
'WooCommerce Stripe',
'woocommerce-admin'
),
'woocommerce-payfast-gateway': __(
'WooCommerce PayFast',
'woocommerce-admin'
),
'woocommerce-services': __( 'WooCommerce Services', 'woocommerce-admin' ),
'woocommerce-shipstation-integration': __(
'WooCommerce ShipStation Gateway',
'woocommerce-admin'
),
'woocommerce-square': __( 'WooCommerce Square', 'woocommerce-admin' ),
};

View File

@ -317,7 +317,7 @@ class OnboardingPlugins extends \WC_REST_Data_Controller {
$redirect_url = apply_filters( 'woocommerce_admin_onboarding_jetpack_connect_redirect_url', esc_url_raw( $request['redirect_url'] ) );
$connect_url = \Jetpack::init()->build_connect_url( true, $redirect_url, 'woocommerce-onboarding' );
$calypso_env = defined( 'WOOCOMMERCE_CALYPSO_ENVIRONMENT' ) && in_array( WOOCOMMERCE_CALYPSO_ENVIRONMENT, array( 'development', 'wpcalypso', 'horizon', 'stage' ) ) ? WOOCOMMERCE_CALYPSO_ENVIRONMENT : 'production';
$calypso_env = defined( 'WOOCOMMERCE_CALYPSO_ENVIRONMENT' ) && in_array( WOOCOMMERCE_CALYPSO_ENVIRONMENT, array( 'development', 'wpcalypso', 'horizon', 'stage' ), true ) ? WOOCOMMERCE_CALYPSO_ENVIRONMENT : 'production';
$connect_url = add_query_arg( array( 'calypso_env' => $calypso_env ), $connect_url );
return( array(
@ -372,7 +372,7 @@ class OnboardingPlugins extends \WC_REST_Data_Controller {
\WC_Helper_API::url( 'oauth/authorize' )
);
if ( defined( 'WOOCOMMERCE_CALYPSO_ENVIRONMENT' ) && in_array( WOOCOMMERCE_CALYPSO_ENVIRONMENT, array( 'development', 'wpcalypso', 'horizon', 'stage' ) ) ) {
if ( defined( 'WOOCOMMERCE_CALYPSO_ENVIRONMENT' ) && in_array( WOOCOMMERCE_CALYPSO_ENVIRONMENT, array( 'development', 'wpcalypso', 'horizon', 'stage' ), true ) ) {
$connect_url = add_query_arg(
array(
'calypso_env' => WOOCOMMERCE_CALYPSO_ENVIRONMENT,
@ -463,12 +463,12 @@ class OnboardingPlugins extends \WC_REST_Data_Controller {
'env' => 'live',
'wc_ppec_ips_admin_nonce' => wp_create_nonce( 'wc_ppec_ips' ),
),
wc_admin_url( '&task=payments&paypal-connect-finish=1' )
wc_admin_url( '&task=payments&method=paypal&paypal-connect-finish=1' )
);
// https://github.com/woocommerce/woocommerce-gateway-paypal-express-checkout/blob/b6df13ba035038aac5024d501e8099a37e13d6cf/includes/class-wc-gateway-ppec-ips-handler.php#L79-L93.
$query_args = array(
'redirect' => urlencode( $redirect_url ),
'redirect' => rawurlencode( $redirect_url ),
'countryCode' => WC()->countries->get_base_country(),
'merchantId' => md5( site_url( '/' ) . time() ),
);
@ -491,9 +491,9 @@ class OnboardingPlugins extends \WC_REST_Data_Controller {
$url = \WooCommerce\Square\Handlers\Connection::CONNECT_URL_PRODUCTION;
$redirect_url = wp_nonce_url( wc_admin_url( '&task=payments&square-connect-finish=1' ), 'wc_square_connected' );
$redirect_url = wp_nonce_url( wc_admin_url( '&task=payments&method=square&square-connect-finish=1' ), 'wc_square_connected' );
$args = array(
'redirect' => urlencode( urlencode( $redirect_url ) ),
'redirect' => rawurlencode( rawurlencode( $redirect_url ) ),
'scopes' => implode(
',',
array(

View File

@ -655,7 +655,7 @@ class Onboarding {
*/
public function is_loading( $is_loading ) {
$show_profiler = self::should_show_profiler();
$is_dashboard = ! isset( $_GET['path'] ); // WPCS: csrf ok.
$is_dashboard = ! isset( $_GET['path'] ); // phpcs:ignore csrf ok.
if ( ! $show_profiler || ! $is_dashboard ) {
return $is_loading;
@ -675,7 +675,7 @@ class Onboarding {
if ( substr( $location, -strlen( $settings_page ) ) === $settings_page ) {
$settings_array = (array) get_option( 'woocommerce_ppec_paypal_settings', array() );
$connected = isset( $settings_array['api_username'] ) && isset( $settings_array['api_password'] ) ? true : false;
return wc_admin_url( '&task=payments&paypal-connect=' . $connected );
return wc_admin_url( '&task=payments&method=paypal&paypal-connect=' . $connected );
}
return $location;
}
@ -686,7 +686,7 @@ class Onboarding {
public function finish_paypal_connect() {
if (
! Loader::is_admin_page() ||
! isset( $_GET['paypal-connect-finish'] ) // WPCS: CSRF ok.
! isset( $_GET['paypal-connect-finish'] ) // phpcs:ignore CSRF ok.
) {
return;
}
@ -711,7 +711,7 @@ class Onboarding {
public function overwrite_square_redirect( $location, $status ) {
$settings_page = 'page=wc-settings&tab=square';
if ( substr( $location, -strlen( $settings_page ) ) === $settings_page ) {
return wc_admin_url( '&task=payments&square-connect=1' );
return wc_admin_url( '&task=payments&method=square&square-connect=1' );
}
return $location;
}
@ -722,7 +722,7 @@ class Onboarding {
public function finish_square_connect() {
if (
! Loader::is_admin_page() ||
! isset( $_GET['square-connect-finish'] ) // WPCS: CSRF ok.
! isset( $_GET['square-connect-finish'] ) // phpcs:ignore CSRF ok.
) {
return;
}
@ -873,9 +873,9 @@ class Onboarding {
* Allows quick access to testing the calypso parts of onboarding.
*/
public static function calypso_tests() {
$calypso_env = defined( 'WOOCOMMERCE_CALYPSO_ENVIRONMENT' ) && in_array( WOOCOMMERCE_CALYPSO_ENVIRONMENT, array( 'development', 'wpcalypso', 'horizon', 'stage' ) ) ? WOOCOMMERCE_CALYPSO_ENVIRONMENT : 'production';
$calypso_env = defined( 'WOOCOMMERCE_CALYPSO_ENVIRONMENT' ) && in_array( WOOCOMMERCE_CALYPSO_ENVIRONMENT, array( 'development', 'wpcalypso', 'horizon', 'stage' ), true ) ? WOOCOMMERCE_CALYPSO_ENVIRONMENT : 'production';
if ( Loader::is_admin_page() && class_exists( 'Jetpack' ) && isset( $_GET['test_wc_jetpack_connect'] ) && 1 === absint( $_GET['test_wc_jetpack_connect'] ) ) { // WPCS: CSRF ok.
if ( Loader::is_admin_page() && class_exists( 'Jetpack' ) && isset( $_GET['test_wc_jetpack_connect'] ) && 1 === absint( $_GET['test_wc_jetpack_connect'] ) ) { // phpcs:ignore CSRF ok.
$redirect_url = esc_url_raw(
add_query_arg(
array(
@ -892,7 +892,7 @@ class Onboarding {
exit;
}
if ( Loader::is_admin_page() && isset( $_GET['test_wc_helper_connect'] ) && 1 === absint( $_GET['test_wc_helper_connect'] ) ) { // WPCS: CSRF ok.
if ( Loader::is_admin_page() && isset( $_GET['test_wc_helper_connect'] ) && 1 === absint( $_GET['test_wc_helper_connect'] ) ) { // phpcs:ignore CSRF ok.
include_once WC_ABSPATH . 'includes/admin/helper/class-wc-helper-api.php';
$redirect_uri = wc_admin_url( '&task=connect&wccom-connected=1' );
@ -942,12 +942,12 @@ class Onboarding {
public static function reset_profiler() {
if (
! Loader::is_admin_page() ||
! isset( $_GET['reset_profiler'] ) // WPCS: CSRF ok.
! isset( $_GET['reset_profiler'] ) // phpcs:ignore CSRF ok.
) {
return;
}
$previous = 1 === absint( $_GET['reset_profiler'] );
$previous = 1 === absint( $_GET['reset_profiler'] ); // phpcs:ignore CSRF ok.
$new_value = ! $previous;
wc_admin_record_tracks_event(
@ -978,12 +978,12 @@ class Onboarding {
public static function reset_task_list() {
if (
! Loader::is_admin_page() ||
! isset( $_GET['reset_task_list'] ) // WPCS: CSRF ok.
! isset( $_GET['reset_task_list'] ) // phpcs:ignore CSRF ok.
) {
return;
}
$new_value = 1 === absint( $_GET['reset_task_list'] ) ? 'no' : 'yes'; // WPCS: CSRF ok.
$new_value = 1 === absint( $_GET['reset_task_list'] ) ? 'no' : 'yes'; // phpcs:ignore CSRF ok.
update_option( 'woocommerce_task_list_hidden', $new_value );
wp_safe_redirect( wc_admin_url() );
exit;