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

531 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 };