diff --git a/.editorconfig b/.editorconfig index bda37923161..da11c9e8092 100644 --- a/.editorconfig +++ b/.editorconfig @@ -20,7 +20,7 @@ trim_trailing_whitespace = true [*.txt] trim_trailing_whitespace = false -[*.{md,json,yml}] +[*.{md,json,yml,snap}] trim_trailing_whitespace = false indent_style = space indent_size = 2 diff --git a/.eslintrc b/.eslintrc index 3e82ae64c1e..f69a69be7e9 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,9 +1,13 @@ /** @format */ - { "root": true, "parser": "babel-eslint", - "extends": [ "wpcalypso/react", "plugin:jsx-a11y/recommended", "prettier", "plugin:@woocommerce/eslint-plugin/recommended" ], + "extends": [ + "wpcalypso/react", + "plugin:jsx-a11y/recommended", + "prettier", + "plugin:@woocommerce/eslint-plugin/recommended" + ], "plugins": [ "jsx-a11y", "jest", "jsdoc-alignment" ], "env": { "browser": true, @@ -29,10 +33,21 @@ } }, "rules": { - "camelcase": [ "error", { "properties": "always" } ], + "camelcase": [ + "error", + { + "properties": "never", + "ignoreGlobals": true + } + ], "import/no-extraneous-dependencies": "off", "indent": "off", - "max-len": [ "error", { "code": 140 } ], + "max-len": [ + "error", + { + "code": 140 + } + ], "no-console": "warn", "react/no-danger": "off", "react/react-in-jsx-scope": "off", @@ -41,7 +56,12 @@ "wpcalypso/redux-no-bound-selectors": "warn", "react/jsx-curly-spacing": [ 2, - { "when": "always", "children": { "when": "always" } } + { + "when": "always", + "children": { + "when": "always" + } + } ], "jsx-a11y/label-has-for": [ "error", @@ -52,37 +72,32 @@ "jsdoc/check-tag-names": [ "error", { - "definedTags": [ - "format" - ] + "definedTags": [ "format" ] } ], - "yoda": [ - "error", - "always" - ], + "yoda": [ "error", "always" ], /* partially disable rules to get @woocommerce/eslint-plugin integration done */ - "jsdoc/no-undefined-types": "off", - "jsdoc/require-param": "off", - "jsdoc/check-param-names": "off", - "jsdoc/require-property": "off", - "@wordpress/no-global-event-listener": "off", + "jsdoc/no-undefined-types": "off", + "jsdoc/require-param": "off", + "jsdoc/check-param-names": "off", + "jsdoc/require-property": "off", + "@wordpress/no-global-event-listener": "off", "@wordpress/no-unused-vars-before-return": "off", - "@wordpress/i18n-translator-comments": "off", - "@wordpress/valid-sprintf": "off", - "@woocommerce/dependency-group": "off", - "react-hooks/exhaustive-deps": "error", - "react-hooks/rules-of-hooks": "error", - "jest/no-conditional-expect": "off", - "jest/valid-title": "off", - "jest/expect-expect": "off", - "jest/no-disabled-tests": "off", - "jest/no-standalone-expect": "off", - "jest/no-identical-title": "off", - "jest/no-deprecated-functions": "off", - "no-alert": "off", - "object-shorthand": "off", - "no-multi-str": "off" + "@wordpress/i18n-translator-comments": "off", + "@wordpress/valid-sprintf": "off", + "@woocommerce/dependency-group": "off", + "react-hooks/exhaustive-deps": "error", + "react-hooks/rules-of-hooks": "error", + "jest/no-conditional-expect": "off", + "jest/valid-title": "off", + "jest/expect-expect": "off", + "jest/no-disabled-tests": "off", + "jest/no-standalone-expect": "off", + "jest/no-identical-title": "off", + "jest/no-deprecated-functions": "off", + "no-alert": "off", + "object-shorthand": "off", + "no-multi-str": "off" }, "overrides": [ { @@ -97,6 +112,18 @@ "prettier/@typescript-eslint" ], "rules": { + "camelcase": "off", + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": [ "method", "variableLike" ], + "format": [ "camelCase", "PascalCase" ] + }, + { + "selector": "typeProperty", + "format": [ "camelCase", "snake_case" ] + } + ], "@typescript-eslint/no-explicit-any": "off", "no-use-before-define": "off", "@typescript-eslint/no-use-before-define": [ "error" ], diff --git a/.github/workflows/php-lint-test.yml b/.github/workflows/php-lint-test.yml index fedf13dacb6..74bfcd5595d 100644 --- a/.github/workflows/php-lint-test.yml +++ b/.github/workflows/php-lint-test.yml @@ -26,7 +26,7 @@ jobs: tools: composer coverage: none # install dependencies and run linter - - run: composer self-update 2.0.6 && composer install --no-progress && ./vendor/bin/phpcs --standard=phpcs.xml.dist $(git ls-files | grep .php$) + - run: composer self-update 2.0.6 && composer install --no-progress && ./vendor/bin/phpcs --standard=phpcs.xml.dist $(git ls-files | grep .php$) && ./vendor/bin/psalm test: name: PHP testing diff --git a/.husky/post-checkout b/.husky/post-checkout index 62ecd220e85..369c5dae8a1 100755 --- a/.husky/post-checkout +++ b/.husky/post-checkout @@ -3,3 +3,6 @@ # using `--no-install` just in case it's the first time a person is checking out the repo and doesn't have yarnhook installed npx --no-install yarnhook + +# make sure the autoload files are regenerated +composer dumpautoload diff --git a/.husky/post-merge b/.husky/post-merge index 62ecd220e85..369c5dae8a1 100755 --- a/.husky/post-merge +++ b/.husky/post-merge @@ -3,3 +3,6 @@ # using `--no-install` just in case it's the first time a person is checking out the repo and doesn't have yarnhook installed npx --no-install yarnhook + +# make sure the autoload files are regenerated +composer dumpautoload diff --git a/.husky/post-rewrite b/.husky/post-rewrite index 62ecd220e85..369c5dae8a1 100755 --- a/.husky/post-rewrite +++ b/.husky/post-rewrite @@ -3,3 +3,6 @@ # using `--no-install` just in case it's the first time a person is checking out the repo and doesn't have yarnhook installed npx --no-install yarnhook + +# make sure the autoload files are regenerated +composer dumpautoload diff --git a/README.md b/README.md index fce51898f0e..fe88ab6dedc 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,15 @@ This is a feature plugin for accepting payments via a WooCommerce-branded paymen ## Dependencies -- WooCommerce +- WooCommerce ## Development ### Install dependencies & build -- `npm install` -- `composer install` -- `npm run build:client` +- `npm install` +- `composer install` +- `npm run build:client`, or if you're developing the client you can have it auto-update when changes are made: `npm start` ## Setup @@ -20,14 +20,16 @@ If you're using the Docker environment see setup instructions here: https://github.com/Automattic/woocommerce-payments/blob/trunk/docker/README.md Install the following plugins: -- WooCommerce + +- WooCommerce + ## Test account setup For setting up a test account follow [these instructions](https://docs.woocommerce.com/document/payments/testing/dev-mode/). You will need a externally accessible URL to set up the plugin. You can use ngrok for this. -```ngrok http 8082``` +`ngrok http 8082` See: https://github.com/Automattic/woocommerce-payments/blob/trunk/CONTRIBUTING.md (possibly move contents here for visibility sake) diff --git a/bin/run-psalm.sh b/bin/run-psalm.sh new file mode 100755 index 00000000000..5df07b3d234 --- /dev/null +++ b/bin/run-psalm.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -e + +./vendor/bin/psalm $* diff --git a/changelog.txt b/changelog.txt index cc901771a1f..33d93422e71 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,12 @@ *** WooCommerce Payments Changelog *** += 2.7.0 - 2021-xx-xx = +* Add - Add a link to the snackbar notice that appears after submitting or saving evidence for a dispute challenge. +* Add - Support saving new cards and paying with previously saved cards in the WooCommerce Checkout Block. +* Fix - WooCommerce Payments admin pages redirect to the onboarding page when the WooCommerce Payments account is disconnected. +* Fix - Do not overwrite admin pages when account is disconnected. +* Update - Set a description when creating payment intents. + = 2.6.1 - 2021-07-01 = * Fix - Updates the notes query filters to prevent breaking the WooCommerce > Home inbox. diff --git a/client/additional-methods-setup/index.js b/client/additional-methods-setup/index.js index a3358046ff6..42a624c77b1 100644 --- a/client/additional-methods-setup/index.js +++ b/client/additional-methods-setup/index.js @@ -10,6 +10,8 @@ import { addFilter } from '@wordpress/hooks'; * Internal dependencies */ import MethodSelector from './methods-selector'; +import UpePreviewMethodSelector from './upe-preview-methods-selector'; +import WcPayUpeContextProvider from '../settings/wcpay-upe-toggle/provider'; addFilter( 'woocommerce_admin_onboarding_task_list', @@ -37,6 +39,40 @@ addFilter( 'woocommerce-payments' ), isDismissable: true, + + // overwriting the default values, while the test is running + // using object spread to override the attributes makes things a bit easier when it'll be time to clean up + ...( window.wcpayAdditionalMethodsSetup + .isUpeSettingsPreviewEnabled + ? { + additionalInfo: __( + 'Get early access to additional payment methods and an improved checkout experience', + 'woocommerce-payments' + ), + title: __( + 'Boost your sales by accepting new payment methods', + 'woocommerce-payments' + ), + container: ( + + + + ), + completed: + 'yes' === + window.wcpayAdditionalMethodsSetup + .isSetupCompleted || + '1' === + window.wcpayAdditionalMethodsSetup + .isUpeEnabled, + } + : {} ), }, ]; } diff --git a/client/additional-methods-setup/methods-selector/add-payment-methods-task.js b/client/additional-methods-setup/methods-selector/add-payment-methods-task.js index 4a6aaa9d5fe..fbddfa10d44 100644 --- a/client/additional-methods-setup/methods-selector/add-payment-methods-task.js +++ b/client/additional-methods-setup/methods-selector/add-payment-methods-task.js @@ -25,7 +25,7 @@ import { useEnabledPaymentMethodIds, useGetAvailablePaymentMethodIds, useSettings, - useDigitalWalletsEnabledSettings, + usePaymentRequestEnabledSettings, } from '../../data'; import './add-payment-methods-task.scss'; @@ -73,9 +73,9 @@ const AddPaymentMethodsTask = () => { ] = useEnabledPaymentMethodIds(); const [ - initialIsDigitalWalletsEnabled, - setIsDigitalWalletsEnabled, - ] = useDigitalWalletsEnabledSettings(); + initialIsPaymentRequestEnabled, + setIsPaymentRequestEnabled, + ] = usePaymentRequestEnabledSettings(); const { saveSettings, isSaving } = useSettings(); @@ -93,8 +93,8 @@ const AddPaymentMethodsTask = () => { ) ); - const [ isWalletsChecked, setWalletsChecked ] = useState( - initialIsDigitalWalletsEnabled + const [ isPaymentRequestChecked, setPaymentRequestChecked ] = useState( + initialIsPaymentRequestEnabled ); const { setCompleted } = useContext( WizardTaskContext ); @@ -116,13 +116,13 @@ const AddPaymentMethodsTask = () => { return; } - setIsDigitalWalletsEnabled( isWalletsChecked ); + setIsPaymentRequestEnabled( isPaymentRequestChecked ); updateEnabledPaymentMethodIds( checkedPaymentMethods ); const isSuccess = await saveSettings(); if ( ! isSuccess ) { // restoring the state, in case of soft route - setIsDigitalWalletsEnabled( initialIsDigitalWalletsEnabled ); + setIsPaymentRequestEnabled( initialIsPaymentRequestEnabled ); updateEnabledPaymentMethodIds( initialEnabledPaymentMethodIds ); return; } @@ -137,9 +137,9 @@ const AddPaymentMethodsTask = () => { saveSettings, setCompleted, initialEnabledPaymentMethodIds, - initialIsDigitalWalletsEnabled, - isWalletsChecked, - setIsDigitalWalletsEnabled, + initialIsPaymentRequestEnabled, + isPaymentRequestChecked, + setIsPaymentRequestEnabled, ] ); const countryName = useGetCountryName(); @@ -234,8 +234,8 @@ const AddPaymentMethodsTask = () => {
{ } updateOptions( { - // eslint-disable-next-line camelcase wcpay_additional_methods_setup_completed: 'yes', } ); diff --git a/client/additional-methods-setup/methods-selector/test/add-payment-methods-task.test.js b/client/additional-methods-setup/methods-selector/test/add-payment-methods-task.test.js index 056549db04d..bc109ac788d 100644 --- a/client/additional-methods-setup/methods-selector/test/add-payment-methods-task.test.js +++ b/client/additional-methods-setup/methods-selector/test/add-payment-methods-task.test.js @@ -13,14 +13,14 @@ import WizardTaskContext from '../../wizard/task/context'; import AddPaymentMethodsTask from '../add-payment-methods-task'; import { useEnabledPaymentMethodIds, - useDigitalWalletsEnabledSettings, + usePaymentRequestEnabledSettings, useGetAvailablePaymentMethodIds, useSettings, } from '../../../data'; jest.mock( '../../../data', () => ( { useEnabledPaymentMethodIds: jest.fn(), - useDigitalWalletsEnabledSettings: jest.fn(), + usePaymentRequestEnabledSettings: jest.fn(), useGetAvailablePaymentMethodIds: jest.fn(), useSettings: jest.fn(), } ) ); @@ -31,7 +31,7 @@ jest.mock( '@wordpress/data', () => ( { describe( 'AddPaymentMethodsTask', () => { beforeEach( () => { useSelect.mockReturnValue( {} ); - useDigitalWalletsEnabledSettings.mockReturnValue( [ + usePaymentRequestEnabledSettings.mockReturnValue( [ false, jest.fn(), ] ); @@ -110,10 +110,10 @@ describe( 'AddPaymentMethodsTask', () => { } ); it( 'should save the checkboxes state on "continue" click', async () => { - const updateDigitalWalletsEnabledMock = jest.fn(); - useDigitalWalletsEnabledSettings.mockReturnValue( [ + const updatePaymentRequestEnabledMock = jest.fn(); + usePaymentRequestEnabledSettings.mockReturnValue( [ false, - updateDigitalWalletsEnabledMock, + updatePaymentRequestEnabledMock, ] ); const updateEnabledPaymentMethodIdsMock = jest.fn(); useEnabledPaymentMethodIds.mockReturnValue( [ @@ -151,7 +151,7 @@ describe( 'AddPaymentMethodsTask', () => { expect( setCompletedMock ).not.toHaveBeenCalled(); expect( updateEnabledPaymentMethodIdsMock ).not.toHaveBeenCalled(); - expect( updateDigitalWalletsEnabledMock ).not.toHaveBeenCalled(); + expect( updatePaymentRequestEnabledMock ).not.toHaveBeenCalled(); userEvent.click( screen.getByText( 'Continue' ) ); @@ -164,6 +164,6 @@ describe( 'AddPaymentMethodsTask', () => { expect( updateEnabledPaymentMethodIdsMock ).toHaveBeenCalledWith( [ 'giropay', ] ); - expect( updateDigitalWalletsEnabledMock ).toHaveBeenCalledWith( true ); + expect( updatePaymentRequestEnabledMock ).toHaveBeenCalledWith( true ); } ); } ); diff --git a/client/additional-methods-setup/upe-preview-methods-selector/enable-upe-preview-task.js b/client/additional-methods-setup/upe-preview-methods-selector/enable-upe-preview-task.js new file mode 100644 index 00000000000..5bd51ec8db4 --- /dev/null +++ b/client/additional-methods-setup/upe-preview-methods-selector/enable-upe-preview-task.js @@ -0,0 +1,56 @@ +/** + * External dependencies + */ +import React, { useCallback, useContext } from 'react'; +import { __ } from '@wordpress/i18n'; +import { Button } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import WizardTaskContext from '../wizard/task/context'; +import CollapsibleBody from '../wizard/collapsible-body'; +import WizardTaskItem from '../wizard/task-item'; +import WcPayUpeContext from '../../settings/wcpay-upe-toggle/context'; + +const EnableUpePreviewTask = () => { + const { setIsUpeEnabled, status } = useContext( WcPayUpeContext ); + + const { setCompleted } = useContext( WizardTaskContext ); + + const handleContinueClick = useCallback( () => { + setIsUpeEnabled( true ).then( () => { + setCompleted( true, 'setup-complete' ); + } ); + }, [ setIsUpeEnabled, setCompleted ] ); + + return ( + + +

+ { __( + 'Get early access to additional payment methods and an improved checkout experience, ' + + 'coming soon to WooCommerce payments.', + 'woocommerce-payments' + ) } +

+ +
+
+ ); +}; + +export default EnableUpePreviewTask; diff --git a/client/additional-methods-setup/upe-preview-methods-selector/index.js b/client/additional-methods-setup/upe-preview-methods-selector/index.js new file mode 100644 index 00000000000..60da3f5e0c0 --- /dev/null +++ b/client/additional-methods-setup/upe-preview-methods-selector/index.js @@ -0,0 +1,48 @@ +/** + * External dependencies + */ +import React from 'react'; +import { Card, CardBody } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import Wizard from '../wizard/wrapper'; +import WizardTask from '../wizard/task'; +import WizardTaskList from '../wizard/task-list'; +import EnableUpePreviewTask from './enable-upe-preview-task'; +import SetupCompleteTask from './setup-complete-task'; +import './index.scss'; +import useIsUpeEnabled from '../../settings/wcpay-upe-toggle/hook'; +import WcpaySettingsFetcher from './wcpay-settings-fetcher'; + +const UpePreviewMethodsSelector = () => { + const [ isUpeEnabled ] = useIsUpeEnabled(); + + return ( + + + + + + + + + + + + + + + + ); +}; + +export default UpePreviewMethodsSelector; diff --git a/client/additional-methods-setup/upe-preview-methods-selector/index.scss b/client/additional-methods-setup/upe-preview-methods-selector/index.scss new file mode 100644 index 00000000000..24cb875c885 --- /dev/null +++ b/client/additional-methods-setup/upe-preview-methods-selector/index.scss @@ -0,0 +1,4 @@ +.upe-preview-methods-selector { + max-width: 680px; + margin: 0 auto; +} diff --git a/client/additional-methods-setup/upe-preview-methods-selector/setup-complete-task.js b/client/additional-methods-setup/upe-preview-methods-selector/setup-complete-task.js new file mode 100644 index 00000000000..b19642200ba --- /dev/null +++ b/client/additional-methods-setup/upe-preview-methods-selector/setup-complete-task.js @@ -0,0 +1,69 @@ +/** + * External dependencies + */ +import React from 'react'; +import { useEffect, useContext } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { Button } from '@wordpress/components'; +import { useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import CollapsibleBody from '../wizard/collapsible-body'; +import WizardTaskItem from '../wizard/task-item'; +import WizardTaskContext from '../wizard/task/context'; + +const SetupComplete = () => { + const { isActive } = useContext( WizardTaskContext ); + + const { updateOptions } = useDispatch( 'wc/admin/options' ); + + useEffect( () => { + if ( ! isActive ) { + return; + } + + updateOptions( { + // eslint-disable-next-line camelcase + wcpay_additional_methods_setup_completed: 'yes', + } ); + + // Set the local `isSetupCompleted` to `yes` so that task appears completed on the list. + // Please note that marking an item as "completed" is different from "dismissing" it. + window.wcpayAdditionalMethodsSetup.isSetupCompleted = 'yes'; + window.wcpayAdditionalMethodsSetup.isUpeEnabled = true; + }, [ isActive, updateOptions ] ); + + return ( + + +

+ { __( 'Setup complete!', 'woocommerce-payments' ) } +

+
+ + +
+
+
+ ); +}; + +export default SetupComplete; diff --git a/client/additional-methods-setup/upe-preview-methods-selector/test/enable-upe-preview-task.test.js b/client/additional-methods-setup/upe-preview-methods-selector/test/enable-upe-preview-task.test.js new file mode 100644 index 00000000000..d2ba053262e --- /dev/null +++ b/client/additional-methods-setup/upe-preview-methods-selector/test/enable-upe-preview-task.test.js @@ -0,0 +1,50 @@ +/** + * External dependencies + */ +import React from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +/** + * Internal dependencies + */ +import WizardTaskContext from '../../wizard/task/context'; +import WcPayUpeContext from '../../../settings/wcpay-upe-toggle/context'; + +import EnableUpePreviewTask from '../enable-upe-preview-task'; + +describe( 'EnableUpePreviewTask', () => { + it( 'should enable the UPE flag when clicking the "Enable" button', async () => { + const setCompletedMock = jest.fn(); + const setIsUpeEnabledMock = jest.fn().mockResolvedValue( true ); + + render( + + + + + + ); + + expect( setCompletedMock ).not.toHaveBeenCalled(); + expect( setIsUpeEnabledMock ).not.toHaveBeenCalled(); + + userEvent.click( screen.getByText( 'Enable' ) ); + + expect( setIsUpeEnabledMock ).toHaveBeenCalledWith( true ); + await waitFor( () => expect( setIsUpeEnabledMock ).toHaveReturned() ); + expect( setCompletedMock ).toHaveBeenCalledWith( + true, + // change this to the second step's ID, once implemented + 'setup-complete' + ); + } ); +} ); diff --git a/client/additional-methods-setup/upe-preview-methods-selector/test/wcpay-settings-fetcher.test.js b/client/additional-methods-setup/upe-preview-methods-selector/test/wcpay-settings-fetcher.test.js new file mode 100644 index 00000000000..1f9489bbec0 --- /dev/null +++ b/client/additional-methods-setup/upe-preview-methods-selector/test/wcpay-settings-fetcher.test.js @@ -0,0 +1,39 @@ +/** + * External dependencies + */ +import React from 'react'; +import { render } from '@testing-library/react'; + +/** + * Internal dependencies + */ +import WizardTaskContext from '../../wizard/task/context'; +import { useSettings } from '../../../data'; + +import WcpaySettingsFetcher from '../wcpay-settings-fetcher'; + +jest.mock( '../../../data', () => ( { + useSettings: jest.fn().mockReturnValue( {} ), +} ) ); + +describe( 'WcpaySettingsFetcher', () => { + it( 'should not fetch the settings when the task to enable UPE is not completed', () => { + render( + + + + ); + + expect( useSettings ).not.toHaveBeenCalled(); + } ); + + it( 'should fetch the settings when the task to enable UPE is completed', () => { + render( + + + + ); + + expect( useSettings ).toHaveBeenCalled(); + } ); +} ); diff --git a/client/additional-methods-setup/upe-preview-methods-selector/wcpay-settings-fetcher.js b/client/additional-methods-setup/upe-preview-methods-selector/wcpay-settings-fetcher.js new file mode 100644 index 00000000000..590fc73476b --- /dev/null +++ b/client/additional-methods-setup/upe-preview-methods-selector/wcpay-settings-fetcher.js @@ -0,0 +1,25 @@ +/** + * External dependencies + */ +import React, { useContext } from 'react'; + +/** + * Internal dependencies + */ +import WizardTaskContext from '../wizard/task/context'; +import { useSettings } from '../../data'; + +const WcpaySettingsFetcher = () => { + useSettings(); + + return null; +}; + +const WcpaySettingsFetcherWrapper = () => { + const { isCompleted } = useContext( WizardTaskContext ); + + // fetch the settings only _after_ UPE is enabled, to ensure that the payment methods are correctly fetched + return isCompleted ? : null; +}; + +export default WcpaySettingsFetcherWrapper; diff --git a/client/checkout/api/index.js b/client/checkout/api/index.js index 83932799108..155edf1c72a 100644 --- a/client/checkout/api/index.js +++ b/client/checkout/api/index.js @@ -96,7 +96,6 @@ export default class WCPayAPI { constructor() { this.args = { ...elements, - // eslint-disable-next-line camelcase billing_details: { address: {}, }, @@ -233,15 +232,11 @@ export default class WCPayAPI { const ajaxCall = this.request( ajaxUrl, { action: 'update_order_status', - // eslint-disable-next-line camelcase order_id: orderId, // Update the current order status nonce with the new one to ensure that the update // order status call works when a guest user creates an account during checkout. - // eslint-disable-next-line camelcase _ajax_nonce: nonce, - // eslint-disable-next-line camelcase intent_id: intentId, - // eslint-disable-next-line camelcase payment_method_id: paymentMethodToSave || null, } ); @@ -280,7 +275,6 @@ export default class WCPayAPI { initSetupIntent() { return this.request( getConfig( 'ajaxUrl' ), { action: 'init_setup_intent', - // eslint-disable-next-line camelcase _ajax_nonce: getConfig( 'createSetupIntentNonce' ), } ).then( ( response ) => { if ( ! response.success ) { @@ -300,7 +294,6 @@ export default class WCPayAPI { return this.request( getConfig( 'ajaxUrl' ), { action: 'create_setup_intent', 'wcpay-payment-method': paymentMethodId, - // eslint-disable-next-line camelcase _ajax_nonce: getConfig( 'createSetupIntentNonce' ), } ).then( ( response ) => { if ( ! response.success ) { @@ -312,31 +305,30 @@ export default class WCPayAPI { return response.data; } - return ( - this.getStripe() - // eslint-disable-next-line camelcase - .confirmCardSetup( response.data.client_secret ) - .then( ( confirmedSetupIntent ) => { - const { setupIntent, error } = confirmedSetupIntent; - if ( error ) { - throw error; - } + return this.getStripe() + .confirmCardSetup( response.data.client_secret ) + .then( ( confirmedSetupIntent ) => { + const { setupIntent, error } = confirmedSetupIntent; + if ( error ) { + throw error; + } - return setupIntent; - } ) - ); + return setupIntent; + } ); } ); } /** * Creates an intent based on a payment method. * + * @param {int} orderId The id of the order if creating the intent on Order Pay page. + * * @return {Promise} The final promise for the request to the server. */ - createIntent() { + createIntent( orderId ) { return this.request( getConfig( 'ajaxUrl' ), { action: 'create_payment_intent', - // eslint-disable-next-line camelcase + wcpay_order_id: orderId, _ajax_nonce: getConfig( 'createPaymentIntentNonce' ), } ) .then( ( response ) => { @@ -355,6 +347,42 @@ export default class WCPayAPI { } ); } + /** + * Updates a payment intent with data from order: customer, level3 data and and maybe sets the payment for future use. + * + * @param {string} paymentIntentId The id of the payment intent. + * @param {int} orderId The id of the order. + * @param {string} savePaymentMethod 'yes' if saving. + * + * @return {Promise} The final promise for the request to the server. + */ + updateIntent( paymentIntentId, orderId, savePaymentMethod ) { + return this.request( getConfig( 'ajaxUrl' ), { + // eslint-disable-next-line camelcase + wcpay_order_id: orderId, + // eslint-disable-next-line camelcase + wc_payment_intent_id: paymentIntentId, + // eslint-disable-next-line camelcase + save_payment_method: savePaymentMethod, + action: 'update_payment_intent', + // eslint-disable-next-line camelcase + _ajax_nonce: getConfig( 'updatePaymentIntentNonce' ), + } ) + .then( ( response ) => { + if ( 'failure' === response.result ) { + throw new Error( response.messages ); + } + return response; + } ) + .catch( ( error ) => { + if ( error.message ) { + throw error; + } else { + // Covers the case of error on the Ajaxrequest. + throw new Error( error.statusText ); + } + } ); + } /** * Process checkout and update payment intent via AJAX. * @@ -365,7 +393,6 @@ export default class WCPayAPI { processCheckout( paymentIntentId, fields ) { return this.request( getConfig( 'ajaxUrl' ), { ...fields, - // eslint-disable-next-line camelcase wc_payment_intent_id: paymentIntentId, action: 'woocommerce_checkout', } ) @@ -397,7 +424,6 @@ export default class WCPayAPI { getPaymentRequestAjaxURL( 'get_shipping_options' ), { security: getPaymentRequestData( 'nonce' )?.shipping, - // eslint-disable-next-line camelcase is_product_page: getPaymentRequestData( 'is_product_page' ), ...shippingAddress, } @@ -415,10 +441,8 @@ export default class WCPayAPI { getPaymentRequestAjaxURL( 'update_shipping_method' ), { security: getPaymentRequestData( 'nonce' )?.update_shipping, - /* eslint-disable camelcase */ shipping_method: [ shippingOption.id ], is_product_page: getPaymentRequestData( 'is_product_page' ), - /* eslint-enable camelcase */ } ); } diff --git a/client/checkout/blocks/confirm-card-payment.js b/client/checkout/blocks/confirm-card-payment.js index 231194713c9..4558ef9351f 100644 --- a/client/checkout/blocks/confirm-card-payment.js +++ b/client/checkout/blocks/confirm-card-payment.js @@ -1,20 +1,25 @@ /** * Handles the confirmation of card payments (3DSv2 modals/SCA challenge). * - * @param {WCPayAPI} api The API used for connection both with the server and Stripe. - * @param {Object} paymentDetails Details about the payment, received from the server. - * @param {Object} emitResponse Various helpers for usage with observer response objects. + * @param {WCPayAPI} api The API used for connection both with the server and Stripe. + * @param {Object} paymentDetails Details about the payment, received from the server. + * @param {Object} emitResponse Various helpers for usage with observer response objects. + * @param {boolean} shouldSavePayment Indicates whether the payment method should be saved or not. * @return {Object} An object, which contains the result from the action. */ export default async function confirmCardPayment( api, paymentDetails, - emitResponse + emitResponse, + shouldSavePayment ) { - const { redirect } = paymentDetails; + const { redirect, payment_method: paymentMethod } = paymentDetails; try { - const confirmation = api.confirmIntent( redirect ); + const confirmation = api.confirmIntent( + redirect, + shouldSavePayment ? paymentMethod : null + ); // `true` means there is no intent to confirm. if ( true === confirmation ) { diff --git a/client/checkout/blocks/fields.js b/client/checkout/blocks/fields.js index 03484a4831f..4137adb6a47 100644 --- a/client/checkout/blocks/fields.js +++ b/client/checkout/blocks/fields.js @@ -12,8 +12,8 @@ import { useEffect, useState } from '@wordpress/element'; * Internal dependencies */ import generatePaymentMethod from './generate-payment-method.js'; -import confirmCardPayment from './confirm-card-payment.js'; import { PAYMENT_METHOD_NAME_CARD } from '../constants.js'; +import { usePaymentCompleteHandler } from './hooks'; const WCPayFields = ( { api, @@ -26,6 +26,7 @@ const WCPayFields = ( { onCheckoutAfterProcessingWithSuccess, }, emitResponse, + shouldSavePayment, } ) => { const [ errorMessage, setErrorMessage ] = useState( null ); @@ -62,15 +63,13 @@ const WCPayFields = ( { ); // Once the server has completed payment processing, confirm the intent of necessary. - useEffect( - () => - onCheckoutAfterProcessingWithSuccess( - ( { processingResponse: { paymentDetails } } ) => - confirmCardPayment( api, paymentDetails, emitResponse ) - ), - // not sure if we need to disable this, but kept it as-is to ensure nothing breaks. Please consider passing all the deps. - // eslint-disable-next-line react-hooks/exhaustive-deps - [ elements, stripe, api ] + usePaymentCompleteHandler( + api, + stripe, + elements, + onCheckoutAfterProcessingWithSuccess, + emitResponse, + shouldSavePayment ); // Checks whether there are errors within a field, and saves them for later reporting. @@ -86,7 +85,12 @@ const WCPayFields = ( { }; return ( - +
+ +
); }; diff --git a/client/checkout/blocks/generate-payment-method.js b/client/checkout/blocks/generate-payment-method.js index ae0e230b6f5..728ef3bc75f 100644 --- a/client/checkout/blocks/generate-payment-method.js +++ b/client/checkout/blocks/generate-payment-method.js @@ -37,7 +37,6 @@ const generatePaymentMethod = async ( api, elements, billingData ) => { meta: { paymentMethodData: { paymentMethod: PAYMENT_METHOD_NAME_CARD, - // eslint-disable-next-line camelcase 'wcpay-payment-method': id, }, }, diff --git a/client/checkout/blocks/hooks.js b/client/checkout/blocks/hooks.js new file mode 100644 index 00000000000..c7486f89bc0 --- /dev/null +++ b/client/checkout/blocks/hooks.js @@ -0,0 +1,35 @@ +/** + * External dependencies + */ +import { useEffect } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import confirmCardPayment from './confirm-card-payment.js'; + +export const usePaymentCompleteHandler = ( + api, + stripe, + elements, + onCheckoutAfterProcessingWithSuccess, + emitResponse, + shouldSavePayment +) => { + // Once the server has completed payment processing, confirm the intent of necessary. + useEffect( + () => + onCheckoutAfterProcessingWithSuccess( + ( { processingResponse: { paymentDetails } } ) => + confirmCardPayment( + api, + paymentDetails, + emitResponse, + shouldSavePayment + ) + ), + // not sure if we need to disable this, but kept it as-is to ensure nothing breaks. Please consider passing all the deps. + // eslint-disable-next-line react-hooks/exhaustive-deps + [ elements, stripe, api, shouldSavePayment ] + ); +}; diff --git a/client/checkout/blocks/index.js b/client/checkout/blocks/index.js index 2d745be3222..343af169eb8 100644 --- a/client/checkout/blocks/index.js +++ b/client/checkout/blocks/index.js @@ -17,6 +17,7 @@ import { PAYMENT_METHOD_NAME_CARD } from '../constants.js'; import { getConfig } from 'utils/checkout'; import WCPayAPI from './../api'; import WCPayFields from './fields.js'; +import { SavedTokenHandler } from './saved-token-handler'; import request from './request.js'; import enqueueFraudScripts from 'fraud-scripts'; import paymentRequestPaymentMethod from '../../payment-request/blocks'; @@ -36,11 +37,14 @@ registerPaymentMethod( { name: PAYMENT_METHOD_NAME_CARD, content: , edit: , + savedTokenComponent: , canMakePayment: () => !! api.getStripe(), paymentMethodId: PAYMENT_METHOD_NAME_CARD, label: __( 'Credit Card', 'woocommerce-payments' ), ariaLabel: __( 'Credit Card', 'woocommerce-payments' ), supports: { + showSavedCards: getConfig( 'isSavedCardsEnabled' ) ?? false, + showSaveOption: getConfig( 'isSavedCardsEnabled' ) ?? false, features: getConfig( 'features' ), }, } ); diff --git a/client/checkout/blocks/saved-token-handler.js b/client/checkout/blocks/saved-token-handler.js new file mode 100644 index 00000000000..a2e2b0ea339 --- /dev/null +++ b/client/checkout/blocks/saved-token-handler.js @@ -0,0 +1,24 @@ +/** + * Internal dependencies + */ +import { usePaymentCompleteHandler } from './hooks'; + +export const SavedTokenHandler = ( { + api, + stripe, + elements, + eventRegistration: { onCheckoutAfterProcessingWithSuccess }, + emitResponse, +} ) => { + // Once the server has completed payment processing, confirm the intent of necessary. + usePaymentCompleteHandler( + api, + stripe, + elements, + onCheckoutAfterProcessingWithSuccess, + emitResponse, + false // No need to save a payment that has already been saved. + ); + + return <>; +}; diff --git a/client/checkout/classic/index.js b/client/checkout/classic/index.js index a07eeb13e12..0046f03251d 100644 --- a/client/checkout/classic/index.js +++ b/client/checkout/classic/index.js @@ -60,7 +60,7 @@ jQuery( function ( $ ) { // Giropay payment method details const giropayPayment = { - type: 'giropay' /* eslint-disable camelcase */, + type: 'giropay', }; // Create a SEPA element @@ -71,13 +71,13 @@ jQuery( function ( $ ) { } ); const sepaPayment = { - type: 'sepa_debit' /* eslint-disable camelcase */, + type: 'sepa_debit', sepa_debit: sepaElement, }; // Sofort payment method details const sofortPayment = { - type: 'sofort' /* eslint-disable camelcase */, + type: 'sofort', }; /** diff --git a/client/checkout/classic/upe.js b/client/checkout/classic/upe.js index 0eb6ec0a39b..bc34d32cf72 100644 --- a/client/checkout/classic/upe.js +++ b/client/checkout/classic/upe.js @@ -18,6 +18,7 @@ jQuery( function ( $ ) { const publishableKey = getConfig( 'publishableKey' ); const isUPEEnabled = getConfig( 'isUPEEnabled' ); + const paymentMethodsConfig = getConfig( 'paymentMethodsConfig' ); if ( ! publishableKey ) { // If no configuration is present, probably this is not the checkout page. @@ -169,6 +170,19 @@ jQuery( function ( $ ) { $( document.body ).trigger( 'checkout_error' ); }; + // Show or hide save payment information checkbox + const showNewPaymentMethodCheckbox = ( show = true ) => { + if ( show ) { + $( '.woocommerce-SavedPaymentMethods-saveNew' ).show(); + } else { + $( '.woocommerce-SavedPaymentMethods-saveNew' ).hide(); + $( 'input#wc-woocommerce_payments-new-payment-method' ).prop( + 'checked', + false + ); + } + }; + /** * Mounts Stripe UPE element if feature is enabled. * @@ -179,9 +193,17 @@ jQuery( function ( $ ) { if ( upeElement || paymentIntentId ) { return; } + + // If paying from order, we need to create Payment Intent from order not cart. + const isOrderPay = getConfig( 'isOrderPay' ); + let orderId; + if ( isOrderPay ) { + orderId = getConfig( 'orderId' ); + } + const intentAction = isSetupIntent ? api.initSetupIntent() - : api.createIntent(); + : api.createIntent( orderId ); intentAction .then( ( response ) => { @@ -205,6 +227,9 @@ jQuery( function ( $ ) { } ); upeElement.mount( '#wcpay-upe-element' ); upeElement.on( 'change', ( event ) => { + const isPaymentMethodReusable = + paymentMethodsConfig[ event.value.type ].isReusable; + showNewPaymentMethodCheckbox( isPaymentMethodReusable ); isUPEComplete = event.complete; } ); } ) @@ -250,40 +275,67 @@ jQuery( function ( $ ) { } /** - * Submits the confirmation of the setup intent to Stripe on Add Payment Method page. - * Stripe redirects to Payment Methods page on sucess. + * Checks if UPE form is filled out. Displays errors if not. * * @param {Object} $form The jQuery object for the form. - * @return {boolean} A flag for the event handler. + * @return {boolean} false if incomplete. */ - const handleUPEAddPayment = async ( $form ) => { + const checkUPEForm = async ( $form ) => { if ( ! upeElement ) { showError( 'Your payment information is incomplete.' ); - return; + return false; } - const returnUrl = getConfig( 'paymentMethodsURL' ); if ( ! isUPEComplete ) { - // If UPE fields are not filled, confirm setup to trigger validation errors - const { error } = await api.getStripe().confirmSetup( { + // If UPE fields are not filled, confirm payment to trigger validation errors + const { error } = await api.getStripe().confirmPayment( { element: upeElement, confirmParams: { - // eslint-disable-next-line camelcase - return_url: returnUrl, + return_url: '', }, } ); $form.removeClass( 'processing' ).unblock(); showError( error.message ); + return false; + } + return true; + }; + /** + * Submits the confirmation of the intent to Stripe on Pay for Order page. + * Stripe redirects to Order Thank you page on sucess. + * + * @param {Object} $form The jQuery object for the form. + * @return {boolean} A flag for the event handler. + */ + const handleUPEOrderPay = async ( $form ) => { + const isUPEFormValid = await checkUPEForm( $( '#order_review' ) ); + if ( ! isUPEFormValid ) { return; } - blockUI( $form ); try { - const { error } = await api.getStripe().confirmSetup( { + const isSavingPaymentMethod = $( + '#wc-woocommerce_payments-new-payment-method' + ).is( ':checked' ); + const savePaymentMethod = isSavingPaymentMethod ? 'yes' : 'no'; + + const returnUrl = + getConfig( 'orderReturnURL' ) + + `&save_payment_method=${ savePaymentMethod }`; + + const orderId = getConfig( 'orderId' ); + + // Update payment intent with level3 data, customer and maybe setup for future use. + await api.updateIntent( + paymentIntentId, + orderId, + savePaymentMethod + ); + + const { error } = await api.getStripe().confirmPayment( { element: upeElement, confirmParams: { - // eslint-disable-next-line camelcase return_url: returnUrl, }, } ); @@ -297,28 +349,48 @@ jQuery( function ( $ ) { }; /** - * Submits checkout form via AJAX to create order and uses custom - * redirect URL in AJAX response to request payment confirmation from UPE + * Submits the confirmation of the setup intent to Stripe on Add Payment Method page. + * Stripe redirects to Payment Methods page on sucess. * * @param {Object} $form The jQuery object for the form. * @return {boolean} A flag for the event handler. */ - const handleUPECheckout = async ( $form ) => { - if ( ! upeElement ) { - showError( 'Your payment information is incomplete.' ); + const handleUPEAddPayment = async ( $form ) => { + const isUPEFormValid = await checkUPEForm( $form ); + if ( ! isUPEFormValid ) { return; } - if ( ! isUPEComplete ) { - // If UPE fields are not filled, confirm payment to trigger validation errors - const { error } = await api.getStripe().confirmPayment( { + blockUI( $form ); + + try { + const returnUrl = getConfig( 'paymentMethodsURL' ); + + const { error } = await api.getStripe().confirmSetup( { element: upeElement, confirmParams: { - // eslint-disable-next-line camelcase - return_url: '', + return_url: returnUrl, }, } ); + if ( error ) { + throw error; + } + } catch ( error ) { + $form.removeClass( 'processing' ).unblock(); showError( error.message ); + } + }; + + /** + * Submits checkout form via AJAX to create order and uses custom + * redirect URL in AJAX response to request payment confirmation from UPE + * + * @param {Object} $form The jQuery object for the form. + * @return {boolean} A flag for the event handler. + */ + const handleUPECheckout = async ( $form ) => { + const isUPEFormValid = await checkUPEForm( $form ); + if ( ! isUPEFormValid ) { return; } @@ -338,7 +410,6 @@ jQuery( function ( $ ) { const { error } = await api.getStripe().confirmPayment( { element: upeElement, confirmParams: { - // eslint-disable-next-line camelcase return_url: redirectUrl, }, } ); @@ -445,6 +516,14 @@ jQuery( function ( $ ) { } } ); + // Handle the Pay for Order form if WooCommerce Payments is chosen. + $( '#order_review' ).on( 'submit', () => { + if ( ! isUsingSavedPaymentMethod() ) { + handleUPEOrderPay( $( '#order_review' ) ); + return false; + } + } ); + // On every page load, check to see whether we should display the authentication // modal and display it if it should be displayed. maybeShowAuthenticationModal(); diff --git a/client/components/account-status/account-fees/test/index.js b/client/components/account-status/account-fees/test/index.js index 426a54a8121..4aef5f81cab 100644 --- a/client/components/account-status/account-fees/test/index.js +++ b/client/components/account-status/account-fees/test/index.js @@ -1,4 +1,3 @@ -/* eslint-disable camelcase */ /** @format */ /** * External dependencies diff --git a/client/components/account-status/test/index.js b/client/components/account-status/test/index.js index 393f47f645c..86749884287 100644 --- a/client/components/account-status/test/index.js +++ b/client/components/account-status/test/index.js @@ -1,4 +1,3 @@ -/* eslint-disable camelcase */ /** @format */ /** * External dependencies diff --git a/client/components/deposits-information/block.tsx b/client/components/deposits-information/block.tsx index 66285d04ea9..eb50d281e01 100644 --- a/client/components/deposits-information/block.tsx +++ b/client/components/deposits-information/block.tsx @@ -4,11 +4,11 @@ import * as React from 'react'; import { FlexBlock } from '@wordpress/components'; -type DepositsInformationBlockProps = { +interface DepositsInformationBlockProps { title: string; value: string; children?: any; -}; +} const DepositsInformationBlock: React.FunctionComponent< DepositsInformationBlockProps > = ( { title, diff --git a/client/components/deposits-information/declarations.d.ts b/client/components/deposits-information/declarations.d.ts index fa007178685..fcb076b607b 100644 --- a/client/components/deposits-information/declarations.d.ts +++ b/client/components/deposits-information/declarations.d.ts @@ -1,4 +1,3 @@ -/* eslint-disable camelcase */ declare namespace AccountOverview { interface Account { default_currency: string; @@ -64,10 +63,18 @@ declare module 'data' { declare module 'gridicons' { type GridiconParams = { icon: string; - size: number; + size?: number; className?: string; }; const Gridicon: ( props: GridiconParams ) => JSX.Element; export = Gridicon; } + +declare module '@woocommerce/components' { + type LinkParams = { + href: string; + children: React.ReactNode; + }; + const Link: ( props: LinkParams ) => JSX.Element; +} diff --git a/client/components/deposits-information/index.tsx b/client/components/deposits-information/index.tsx index 51b26121c93..104150ed981 100644 --- a/client/components/deposits-information/index.tsx +++ b/client/components/deposits-information/index.tsx @@ -3,6 +3,7 @@ */ import * as React from 'react'; import { Card, CardHeader, Flex } from '@wordpress/components'; +import { Link } from '@woocommerce/components'; import Gridicon from 'gridicons'; import { __, sprintf } from '@wordpress/i18n'; @@ -24,10 +25,10 @@ import { useAllDeposistsOverviews } from 'data'; import './style.scss'; -type OverviewProps = { +interface OverviewProps { overview: AccountOverview.Overview; account: AccountOverview.Account; -}; +} /** * Renders a deposits overview @@ -50,32 +51,31 @@ const DepositsInformationOverview: React.FunctionComponent< OverviewProps > = ( const pendingAmount = pending ? pending.amount : 0; const pendingDepositsLink = pending?.deposits_count ? ( - + { getBalanceDepositCount( pending ) } - + ) : ( '' ); const nextScheduledAmount = nextScheduled ? nextScheduled.amount : 0; const nextScheduledLink = nextScheduled && ( - + { getNextDepositLabelFormatted( nextScheduled ) } - + ); const lastPaidAmount = lastPaid ? lastPaid.amount : 0; const lastPaidLink = lastPaid && ( - + { getDepositDate( lastPaid ) } - + ); const availableAmount = available ? available.amount : 0; const scheduleDescriptor = getDepositScheduleDescriptor( { account, - // eslint-disable-next-line camelcase last_deposit: lastPaid, } ); diff --git a/client/components/deposits-information/test/__snapshots__/index.js.snap b/client/components/deposits-information/test/__snapshots__/index.js.snap index b114beee226..2046a194ac3 100644 --- a/client/components/deposits-information/test/__snapshots__/index.js.snap +++ b/client/components/deposits-information/test/__snapshots__/index.js.snap @@ -114,6 +114,7 @@ exports[`Deposits information renders correctly with multiple currencies 1`] = ` data-testid="extra" > 2 deposits @@ -138,6 +139,7 @@ exports[`Deposits information renders correctly with multiple currencies 1`] = ` data-testid="extra" > Est. Jun 3, 2021 @@ -166,6 +168,7 @@ exports[`Deposits information renders correctly with multiple currencies 1`] = ` data-testid="extra" > April 26, 2021 @@ -249,6 +252,7 @@ exports[`Deposits information renders correctly with multiple currencies 1`] = ` data-testid="extra" > 2 deposits @@ -273,6 +277,7 @@ exports[`Deposits information renders correctly with multiple currencies 1`] = ` data-testid="extra" > Est. Jun 3, 2021 @@ -301,6 +306,7 @@ exports[`Deposits information renders correctly with multiple currencies 1`] = ` data-testid="extra" > April 26, 2021 @@ -395,6 +401,7 @@ exports[`Deposits information renders instant deposit button only where applicab data-testid="extra" > 2 deposits @@ -419,6 +426,7 @@ exports[`Deposits information renders instant deposit button only where applicab data-testid="extra" > Est. Jun 3, 2021 @@ -447,6 +455,7 @@ exports[`Deposits information renders instant deposit button only where applicab data-testid="extra" > April 26, 2021 @@ -530,6 +539,7 @@ exports[`Deposits information renders instant deposit button only where applicab data-testid="extra" > 2 deposits @@ -554,6 +564,7 @@ exports[`Deposits information renders instant deposit button only where applicab data-testid="extra" > Est. Jun 3, 2021 @@ -582,6 +593,7 @@ exports[`Deposits information renders instant deposit button only where applicab data-testid="extra" > April 26, 2021 diff --git a/client/components/deposits-information/test/index.js b/client/components/deposits-information/test/index.js index c0eff738ffe..80c2c45d9ad 100644 --- a/client/components/deposits-information/test/index.js +++ b/client/components/deposits-information/test/index.js @@ -16,7 +16,6 @@ jest.mock( 'data', () => ( { useInstantDeposit: jest.fn(), } ) ); -/* eslint-disable camelcase */ const createMockAccount = ( account = {} ) => merge( { @@ -55,7 +54,6 @@ const createMockCurrency = ( currencyCode, extra = {} ) => }, extra ); -/* eslint-enable camelcase */ const mockOverviews = ( currencies = null, account = null ) => { return useAllDeposistsOverviews.mockReturnValue( { diff --git a/client/components/dispute-status-chip/mappings.js b/client/components/dispute-status-chip/mappings.js index 8a28b3e4525..73e2ffb9bb3 100644 --- a/client/components/dispute-status-chip/mappings.js +++ b/client/components/dispute-status-chip/mappings.js @@ -1,5 +1,4 @@ /** @format **/ -/* eslint-disable camelcase */ /** * External dependencies diff --git a/client/components/page/index.js b/client/components/page/index.tsx similarity index 59% rename from client/components/page/index.js rename to client/components/page/index.tsx index 2f1dc1f68be..bd944c2998a 100644 --- a/client/components/page/index.js +++ b/client/components/page/index.tsx @@ -2,6 +2,7 @@ * External dependencies */ import { useEffect } from '@wordpress/element'; +import * as React from 'react'; /** * Internal dependencies @@ -9,8 +10,21 @@ import { useEffect } from '@wordpress/element'; import enqueueFraudScripts from 'fraud-scripts'; import './style.scss'; -const Page = ( { children, maxWidth, isNarrow, className = '' } ) => { - const customStyle = maxWidth ? { maxWidth } : null; +interface PageProps { + isNarrow?: boolean; + maxWidth?: string; + className?: string; +} + +// The React.FunctionComponent is helpful here to make the type declaration of the props a bit +// more concise; we get the `children` prop for free. +const Page: React.FC< PageProps > = ( { + children, + maxWidth, + isNarrow, + className = '', +} ) => { + const customStyle = maxWidth ? { maxWidth } : undefined; const classNames = [ className, 'woocommerce-payments-page' ]; if ( isNarrow ) { classNames.push( 'is-narrow' ); diff --git a/client/components/payment-status-chip/mappings.js b/client/components/payment-status-chip/mappings.js index ff8ec233ed9..4fd237cadbb 100644 --- a/client/components/payment-status-chip/mappings.js +++ b/client/components/payment-status-chip/mappings.js @@ -1,5 +1,4 @@ /** @format **/ -/* eslint-disable camelcase */ /** * External dependencies diff --git a/client/components/test-mode-notice/index.js b/client/components/test-mode-notice/index.js index 225ff84a5c5..989e3de150d 100644 --- a/client/components/test-mode-notice/index.js +++ b/client/components/test-mode-notice/index.js @@ -103,8 +103,8 @@ export const getNoticeMessage = ( topic ) => { }; export const TestModeNotice = ( { topic } ) => { - return ( - isInTestMode() && ( + if ( isInTestMode() ) { + return ( { > { getNoticeMessage( topic ) } - ) - ); + ); + } + return <>; }; diff --git a/client/connect-account-page/index.js b/client/connect-account-page/index.js index a72b605e201..dc939d5ac32 100644 --- a/client/connect-account-page/index.js +++ b/client/connect-account-page/index.js @@ -3,8 +3,7 @@ * External dependencies */ import React from 'react'; -import { Card } from '@woocommerce/components'; -import { Button, Notice } from '@wordpress/components'; +import { Button, Card, CardBody, Notice } from '@wordpress/components'; import { render, useState, useEffect } from '@wordpress/element'; /** @@ -142,7 +141,6 @@ const ConnectPageOnboarding = () => { setSubmitted( true ); wcpayTracks.recordEvent( wcpayTracks.events.CONNECT_ACCOUNT_CLICKED, { - // eslint-disable-next-line camelcase wpcom_connection: wcpaySettings.isJetpackConnected ? 'Yes' : 'No', } ); }; @@ -215,18 +213,22 @@ const ConnectAccountPage = () => { - -
- { wcpaySettings.onBoardingDisabled ? ( - - ) : ( - - ) } -
+ + +
+ { wcpaySettings.onBoardingDisabled ? ( + + ) : ( + + ) } +
+
{ ! wcpaySettings.onBoardingDisabled && ( - + + + ) }
diff --git a/client/connect-account-page/modal/test/__snapshots__/index.js.snap b/client/connect-account-page/modal/test/__snapshots__/index.js.snap index 63fc4ff506a..28394dc5878 100644 --- a/client/connect-account-page/modal/test/__snapshots__/index.js.snap +++ b/client/connect-account-page/modal/test/__snapshots__/index.js.snap @@ -34,10 +34,9 @@ exports[`Onboarding: location check dialog renders correctly when continue butto - -
  • -
    +
  • - - - - + + 🇦🇪 + + + + United Arab Emirates dirham + + + + ( + د.إ + AED + ) + + +
  • - - -
  • -
    +
  • - - - - + + 🇨🇩 + + + + Congolese franc + + + + ( + Fr + CDF + ) + + +
    - -
  • -
  • -
    +
  • - - - - + + 🇳🇿 + + + + New Zealand dollar + + + + ( + $ + NZD + ) + + +
    - -
  • -
  • -
    +
  • - - - - + + 🇩🇰 + + + + Danish krone + + + + ( + DKK + ) + + +
    - -
  • -
  • -
    +
  • - - - - + + 🇧🇮 + + + + Burundian franc + + + + ( + Fr + BIF + ) + + +
    - -
  • -
  • -
    +
  • - - - - + + 🇨🇱 + + + + Chilean peso + + + + ( + $ + CLP + ) + + +
    - -
  • - + + +