woocommerce/plugins/woocommerce-admin/client/task-list/tasks/payments/paypal.js

531 lines
12 KiB
JavaScript
Raw Normal View History

/* global ppcp_onboarding */
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import apiFetch from '@wordpress/api-fetch';
import { Button } from '@wordpress/components';
import { Component, Fragment, useEffect } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import interpolateComponents from 'interpolate-components';
import { withDispatch, withSelect } from '@wordpress/data';
import { isEmail } from '@wordpress/url';
import { Form, Link, TextControl, Stepper } from '@woocommerce/components';
import { getQuery } from '@woocommerce/navigation';
import { PLUGINS_STORE_NAME, OPTIONS_STORE_NAME } from '@woocommerce/data';
const PAYPAL_PLUGIN = 'woocommerce-paypal-payments';
const WC_PAYPAL_NAMESPACE = '/wc-paypal/v1';
/**
* Loads the onboarding script file into the dom on the fly.
*
* @param {string} url of the onboarding js file.
* @param {Object} data required for the onboarding script, labeled as PayPalCommerceGatewayOnboarding
* @param {Function} onLoad callback for when the script is loaded.
*/
function loadOnboardingScript( url, data, onLoad ) {
try {
// eslint-disable-next-line camelcase
if ( ppcp_onboarding ) {
onLoad();
}
} catch ( e ) {
const script = document.createElement( 'script' );
script.src = url;
document.body.append( script );
// Callback after scripts have loaded.
script.onload = function () {
onLoad();
};
window.PayPalCommerceGatewayOnboarding = data;
}
}
function PaypalConnectButton( { connectUrl } ) {
useEffect( () => {
// eslint-disable-next-line camelcase
if ( ppcp_onboarding ) {
// Makes sure the onboarding is hooked up to the Connect button rendered.
ppcp_onboarding.reload();
}
}, [] );
return (
<a
className="button-primary"
target="_blank"
rel="noreferrer"
href={ connectUrl }
data-paypal-onboard-button="true"
data-paypal-button="true"
data-paypal-onboard-complete="ppcp_onboarding_productionCallback"
>
{ __( 'Connect', 'woocommerce-admin' ) }
</a>
);
}
class PayPal extends Component {
constructor( props ) {
super( props );
this.state = {
autoConnectFailed: false,
connectURL: '',
};
this.enablePaypalPlugin = this.enablePaypalPlugin.bind( this );
this.setCredentials = this.setCredentials.bind( this );
this.validate = this.validate.bind( this );
}
componentDidMount() {
const { createNotice } = this.props;
const query = getQuery();
// Handle redirect back from PayPal
if ( query.onboarding ) {
if (
query.onboarding === 'complete' &&
! query[ 'ppcp-onboarding-error' ]
) {
this.enablePaypalPlugin();
return;
}
if ( query[ 'ppcp-onboarding-error' ] ) {
/* eslint-disable react/no-did-mount-set-state */
this.setState( {
autoConnectFailed: true,
} );
createNotice(
'error',
__(
'There was a problem saving your payment settings through the onboarding, please fill the fields in manually.',
'woocommerce-admin'
)
);
}
return;
}
this.fetchOAuthConnectURLAndOnboardingSetup();
}
componentDidUpdate( prevProps ) {
const { activePlugins } = this.props;
if (
! prevProps.activePlugins.includes( PAYPAL_PLUGIN ) &&
activePlugins.includes( PAYPAL_PLUGIN )
) {
this.fetchOAuthConnectURLAndOnboardingSetup();
}
}
async fetchOAuthConnectURLAndOnboardingSetup() {
const { activePlugins, createNotice } = this.props;
if ( ! activePlugins.includes( PAYPAL_PLUGIN ) ) {
return;
}
this.setState( { isPending: true } );
try {
const result = await apiFetch( {
path: WC_PAYPAL_NAMESPACE + '/onboarding/get-params',
method: 'POST',
data: {
environment: 'production',
returnUrlArgs: {
ppcpobw: '1',
},
},
} );
if ( ! result || ! result.signupLink ) {
this.setState( {
autoConnectFailed: true,
isPending: false,
} );
return;
}
loadOnboardingScript( result.scriptURL, result.scriptData, () => {
this.setState( {
connectURL: result.signupLink,
isPending: false,
} );
} );
} catch ( error ) {
if ( error && error.data && error.data.status === 500 ) {
createNotice(
'error',
__(
'There was a problem with the Paypal onboarding setup, please fill the fields in manually.',
'woocommerce-admin'
)
);
}
this.setState( {
autoConnectFailed: true,
isPending: false,
} );
}
}
async enablePaypalPlugin( skipPpcpSettingsUpdate ) {
const {
createNotice,
updateOptions,
markConfigured,
options,
} = this.props;
const updatedOptions = {
'woocommerce_ppcp-gateway_settings': {
enabled: 'yes',
},
};
if ( ! skipPpcpSettingsUpdate ) {
updatedOptions[ 'woocommerce-ppcp-settings' ] = {
...options,
enabled: true,
};
}
const update = await updateOptions( updatedOptions );
if ( update.success ) {
createNotice(
'success',
__( 'PayPal connected successfully.', 'woocommerce-admin' )
);
markConfigured( 'paypal' );
} else {
createNotice(
'error',
__(
'There was a problem saving your payment settings.',
'woocommerce-admin'
)
);
}
}
async setCredentials( values ) {
const { createNotice } = this.props;
try {
const result = await apiFetch( {
path: WC_PAYPAL_NAMESPACE + '/onboarding/set-credentials',
method: 'POST',
data: {
environment: 'production',
...values,
},
} );
if ( result && result.data ) {
createNotice(
'error',
__(
'There was a problem updating the credentials.',
'woocommerce-admin'
)
);
} else {
await this.enablePaypalPlugin( true );
}
} catch ( error ) {
if ( error && error.data && error.data.status === 404 ) {
await this.updateManualSettings( values );
}
}
}
async updateManualSettings( values ) {
const {
createNotice,
options,
updateOptions,
markConfigured,
} = this.props;
const productionValues = Object.keys( values ).reduce(
( vals, key ) => {
const prodKey = key + '_production';
return {
...vals,
[ prodKey ]: values[ key ],
};
},
{}
);
/**
* merchant data can be the same across sandbox and production, that's why we set it as
* standalone as well.
*/
const optionValues = {
...options,
enabled: true,
sandbox_on: false,
merchant_email: values.merchant_email,
merchant_id: values.merchant_id,
...productionValues,
};
const update = await updateOptions( {
'woocommerce-ppcp-settings': optionValues,
'woocommerce_ppcp-gateway_settings': {
enabled: 'yes',
},
} );
if ( update.success ) {
createNotice(
'success',
__( 'PayPal connected successfully.', 'woocommerce-admin' )
);
markConfigured( 'paypal' );
} else {
createNotice(
'error',
__(
'There was a problem saving your payment settings.',
'woocommerce-admin'
)
);
}
}
getInitialConfigValues() {
const { options } = this.props;
return [
'merchant_email',
'merchant_id',
'client_id',
'client_secret',
].reduce( ( initialVals, key ) => {
return {
...initialVals,
[ key ]:
options && options[ key + '_production' ]
? options[ key + '_production' ]
: '',
};
}, {} );
}
validate( values ) {
const errors = {};
if ( ! values.merchant_email ) {
errors.merchant_email = __(
'Please enter your Merchant email',
'woocommerce-admin'
);
}
if ( ! isEmail( values.merchant_email ) ) {
errors.merchant_email = __(
'Please enter a valid email address',
'woocommerce-admin'
);
}
if ( ! values.merchant_id ) {
errors.merchant_id = __(
'Please enter your Merchant Id',
'woocommerce-admin'
);
}
if ( ! values.client_id ) {
errors.client_id = __(
'Please enter your Client Id',
'woocommerce-admin'
);
}
if ( ! values.client_secret ) {
errors.client_secret = __(
'Please enter your Client Secret',
'woocommerce-admin'
);
}
return errors;
}
renderManualConfig() {
const { isOptionsUpdating } = this.props;
const stripeHelp = interpolateComponents( {
mixedString: __(
'Your API details can be obtained from your {{docsLink}}Paypal developer account{{/docsLink}}, and your Merchant Id from your {{merchantLink}}Paypal Business account{{/merchantLink}}. Dont have a Paypal account? {{registerLink}}Create one.{{/registerLink}}',
'woocommerce-admin'
),
components: {
docsLink: (
<Link
href="https://developer.paypal.com/docs/api-basics/manage-apps/#create-or-edit-sandbox-and-live-apps"
target="_blank"
type="external"
/>
),
merchantLink: (
<Link
href="https://www.paypal.com/ca/smarthelp/article/FAQ3850"
target="_blank"
type="external"
/>
),
registerLink: (
<Link
href="https://www.paypal.com/us/business"
target="_blank"
type="external"
/>
),
},
} );
return (
<Form
initialValues={ this.getInitialConfigValues() }
onSubmitCallback={ this.setCredentials }
validate={ this.validate }
>
{ ( { getInputProps, handleSubmit } ) => {
return (
<Fragment>
<TextControl
label={ __(
'Email address',
'woocommerce-admin'
) }
required
{ ...getInputProps( 'merchant_email' ) }
/>
<TextControl
label={ __(
'Merchant Id',
'woocommerce-admin'
) }
required
{ ...getInputProps( 'merchant_id' ) }
/>
<TextControl
label={ __( 'Client Id', 'woocommerce-admin' ) }
required
{ ...getInputProps( 'client_id' ) }
/>
<TextControl
label={ __(
'Secret Key',
'woocommerce-admin'
) }
required
{ ...getInputProps( 'client_secret' ) }
/>
<Button
isPrimary
isBusy={ isOptionsUpdating }
onClick={ handleSubmit }
>
{ __( 'Proceed', 'woocommerce-admin' ) }
</Button>
<p>{ stripeHelp }</p>
</Fragment>
);
} }
</Form>
);
}
renderConnectFields() {
const { autoConnectFailed, connectURL } = this.state;
if ( ! autoConnectFailed && connectURL ) {
return (
<>
<PaypalConnectButton connectUrl={ connectURL } />
<p>
{ __(
'You will be redirected to the PayPal website to create the connection.',
'woocommerce-admin'
) }
</p>
</>
);
}
if ( autoConnectFailed ) {
return this.renderManualConfig();
}
}
getConnectStep() {
const { isRequestingOptions } = this.props;
return {
key: 'connect',
label: __( 'Connect your PayPal account', 'woocommerce-admin' ),
description: __(
'A PayPal account is required to process payments. Connect your store to your PayPal account.',
'woocommerce-admin'
),
content: isRequestingOptions ? null : this.renderConnectFields(),
};
}
render() {
const {
installStep,
isRequestingOptions,
isOptionsUpdating,
} = this.props;
const { isPending } = this.state;
return (
<Stepper
isVertical
isPending={
! installStep.isComplete ||
isPending ||
isRequestingOptions ||
isOptionsUpdating
}
currentStep={ installStep.isComplete ? 'connect' : 'install' }
steps={ [ installStep, this.getConnectStep() ] }
/>
);
}
}
PayPal.defaultProps = {
manualConfig: false, // WCS is not required for the PayPal OAuth flow, so we can default to smooth connection.
};
export default compose(
withSelect( ( select ) => {
const { getOption, isOptionsUpdating, hasFinishedResolution } = select(
OPTIONS_STORE_NAME
);
const { getActivePlugins } = select( PLUGINS_STORE_NAME );
const paypalOptions = getOption( 'woocommerce-ppcp-settings' );
const isRequestingOptions = ! hasFinishedResolution( 'getOption', [
'woocommerce-ppcp-settings',
] );
const activePlugins = getActivePlugins();
return {
activePlugins,
isOptionsUpdating: isOptionsUpdating(),
options: paypalOptions,
isRequestingOptions,
};
} ),
withDispatch( ( dispatch ) => {
const { createNotice } = dispatch( 'core/notices' );
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
return {
createNotice,
updateOptions,
};
} )
)( PayPal );
export { PayPal, PAYPAL_PLUGIN };