diff --git a/changelog/7588-express-checkout-utilities b/changelog/7588-express-checkout-utilities new file mode 100644 index 00000000000..143b670d60c --- /dev/null +++ b/changelog/7588-express-checkout-utilities @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Introduce WC_Payments_Express_Checkout_Button_Utils class. diff --git a/changelog/add-334-test-mode-notice-order-details b/changelog/add-334-test-mode-notice-order-details new file mode 100644 index 00000000000..de22a34c323 --- /dev/null +++ b/changelog/add-334-test-mode-notice-order-details @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add test mode notice in page order detail. diff --git a/changelog/add-7248-refund-transaction-from-details-page b/changelog/add-7248-refund-transaction-from-details-page new file mode 100644 index 00000000000..bfd753860e8 --- /dev/null +++ b/changelog/add-7248-refund-transaction-from-details-page @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add refund controls to transaction details view diff --git a/changelog/add-7846-test-mode-confirm-modal b/changelog/add-7846-test-mode-confirm-modal new file mode 100644 index 00000000000..0725c55b1aa --- /dev/null +++ b/changelog/add-7846-test-mode-confirm-modal @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Display a Confirmaton Modal on enabling Test Mode diff --git a/changelog/add-prb-load-tracks b/changelog/add-prb-load-tracks new file mode 100644 index 00000000000..5109e5ce0ca --- /dev/null +++ b/changelog/add-prb-load-tracks @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Track payment-request-button loads diff --git a/changelog/cleanup-upe-checkout-class b/changelog/cleanup-upe-checkout-class new file mode 100644 index 00000000000..95cfa040bf5 --- /dev/null +++ b/changelog/cleanup-upe-checkout-class @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Cleanup the deprecated payment gateway processing - part IV diff --git a/changelog/cleanup-upe-gateways-II b/changelog/cleanup-upe-gateways-II new file mode 100644 index 00000000000..16f3e3cd17f --- /dev/null +++ b/changelog/cleanup-upe-gateways-II @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Cleanup the deprecated payment gateway processing - part VI diff --git a/changelog/cleanup-upe-gateways-part-1 b/changelog/cleanup-upe-gateways-part-1 new file mode 100644 index 00000000000..d449f2047df --- /dev/null +++ b/changelog/cleanup-upe-gateways-part-1 @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Cleanup the deprecated payment gateway processing - part V diff --git a/changelog/dev-3468-allow-reset-account-management b/changelog/dev-3468-allow-reset-account-management new file mode 100644 index 00000000000..ed030674f95 --- /dev/null +++ b/changelog/dev-3468-allow-reset-account-management @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add Account Management tools with reset account functionality for partially onboarded accounts. diff --git a/changelog/dev-bump-wc-version-8-4-0 b/changelog/dev-bump-wc-version-8-4-0 new file mode 100644 index 00000000000..5c00289cc5b --- /dev/null +++ b/changelog/dev-bump-wc-version-8-4-0 @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Bump WC tested up to version to 8.4.0. \ No newline at end of file diff --git a/changelog/fix-6700-remove-currency-sign-modification-code b/changelog/fix-6700-remove-currency-sign-modification-code new file mode 100644 index 00000000000..74634338357 --- /dev/null +++ b/changelog/fix-6700-remove-currency-sign-modification-code @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix currency negative sign position on JS rendered amounts diff --git a/changelog/fix-7913-woopay-automatewoo-referrals-integration b/changelog/fix-7913-woopay-automatewoo-referrals-integration new file mode 100644 index 00000000000..bace9a2cc9b --- /dev/null +++ b/changelog/fix-7913-woopay-automatewoo-referrals-integration @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix WooPay integration with AutomateWoo - Refer a Friend extension. diff --git a/changelog/fix-7958-transaction-details-dispute-details-broken-ui b/changelog/fix-7958-transaction-details-dispute-details-broken-ui new file mode 100644 index 00000000000..b196bc9910c --- /dev/null +++ b/changelog/fix-7958-transaction-details-dispute-details-broken-ui @@ -0,0 +1,5 @@ +Significance: patch +Type: fix +Comment: Not user-facing: fixes styling bug introduced in develop branch + + diff --git a/changelog/fix-jsx-account-status-error-messages b/changelog/fix-jsx-account-status-error-messages new file mode 100644 index 00000000000..a45adc8b5ab --- /dev/null +++ b/changelog/fix-jsx-account-status-error-messages @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix account status error messages with links. diff --git a/changelog/fix-test-support-phone b/changelog/fix-test-support-phone new file mode 100644 index 00000000000..97abae072f7 --- /dev/null +++ b/changelog/fix-test-support-phone @@ -0,0 +1,5 @@ +Significance: patch +Type: fix +Comment: Modified the test phone numbers supported by Stripe. + + diff --git a/changelog/fix-trim-woopay-source-url b/changelog/fix-trim-woopay-source-url new file mode 100644 index 00000000000..d0542cc8201 --- /dev/null +++ b/changelog/fix-trim-woopay-source-url @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Checkout error when page URL is too long diff --git a/changelog/revert-file-needed-for-plugin-update b/changelog/revert-file-needed-for-plugin-update new file mode 100644 index 00000000000..f4e5d1e3224 --- /dev/null +++ b/changelog/revert-file-needed-for-plugin-update @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: This is the file revert to avoid failures on plugin update. This is a temporary solution. Both removal & revert happen on develop meaning there is no change to the outside world. + + diff --git a/changelog/subscriptions-6.6.0-1 b/changelog/subscriptions-6.6.0-1 new file mode 100644 index 00000000000..9c70ea3d4ce --- /dev/null +++ b/changelog/subscriptions-6.6.0-1 @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Deprecate the WC_Subscriptions_Synchroniser::add_to_recurring_cart_key(). Use WC_Subscriptions_Synchroniser::add_to_recurring_product_grouping_key() instead. diff --git a/changelog/subscriptions-6.6.0-2 b/changelog/subscriptions-6.6.0-2 new file mode 100644 index 00000000000..98a24e2a8d8 --- /dev/null +++ b/changelog/subscriptions-6.6.0-2 @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Introduce a new wcs_get_subscription_grouping_key() function to generate a unique key for a subscription based on its billing schedule. This function uses the existing recurring cart key concept. diff --git a/changelog/subscriptions-core-6.6.0 b/changelog/subscriptions-core-6.6.0 new file mode 100644 index 00000000000..192de7697f3 --- /dev/null +++ b/changelog/subscriptions-core-6.6.0 @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Updated subscriptions-core to version 6.6.0 diff --git a/changelog/subscriptions-core-6.6.0-3 b/changelog/subscriptions-core-6.6.0-3 new file mode 100644 index 00000000000..39e3728713e --- /dev/null +++ b/changelog/subscriptions-core-6.6.0-3 @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Fetch and update the `_cancelled_email_sent` meta in a HPOS compatibile way. diff --git a/changelog/subscriptions-core-6.6.0-4 b/changelog/subscriptions-core-6.6.0-4 new file mode 100644 index 00000000000..adf5488ac54 --- /dev/null +++ b/changelog/subscriptions-core-6.6.0-4 @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Ensure proper backfilling of subscription metadata (i.e. dates and cache) to the postmeta table when HPOS is enabled and compatibility mode (data syncing) is turned on. diff --git a/changelog/subscriptions-core-6.6.0-5 b/changelog/subscriptions-core-6.6.0-5 new file mode 100644 index 00000000000..fc15acdb576 --- /dev/null +++ b/changelog/subscriptions-core-6.6.0-5 @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Resolved an issue that would cause undefined $current_page, $max_num_pages, and $paginate variable errors when viewing a page with the subscriptions-shortcode. diff --git a/changelog/subscriptions-core-6.6.0-6 b/changelog/subscriptions-core-6.6.0-6 new file mode 100644 index 00000000000..df965094736 --- /dev/null +++ b/changelog/subscriptions-core-6.6.0-6 @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +When HPOS is enabled and data compatibility mode is turned on, make sure subscription date changes made to postmeta are synced to orders_meta table. diff --git a/changelog/subscriptions-core-6.6.0-7 b/changelog/subscriptions-core-6.6.0-7 new file mode 100644 index 00000000000..96c2cae1f2c --- /dev/null +++ b/changelog/subscriptions-core-6.6.0-7 @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Prevents a PHP fatal error that occurs when the cart contains a renewal order item that no longer exists. diff --git a/changelog/subscriptions-core-6.6.0-8 b/changelog/subscriptions-core-6.6.0-8 new file mode 100644 index 00000000000..a2ffa0feb0c --- /dev/null +++ b/changelog/subscriptions-core-6.6.0-8 @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +When using the checkout block to pay for renewal orders, ensure the order's cart hash is updated to make sure the existing order can be used. diff --git a/client/checkout/api/index.js b/client/checkout/api/index.js index 3e2dcf1f7af..9b05ea40a26 100644 --- a/client/checkout/api/index.js +++ b/client/checkout/api/index.js @@ -335,24 +335,6 @@ export default class WCPayAPI { }; } - /** - * Creates a setup intent without confirming it. - * - * @return {Promise} The final promise for the request to the server. - */ - initSetupIntent() { - const path = 'init_setup_intent'; - - return this.request( buildAjaxURL( getConfig( 'wcAjaxUrl' ), path ), { - _ajax_nonce: getConfig( 'createSetupIntentNonce' ), - } ).then( ( response ) => { - if ( ! response.success ) { - throw response.data.error; - } - return response.data; - } ); - } - /** * Sets up an intent based on a payment method. * @@ -389,92 +371,6 @@ export default class WCPayAPI { } ); } - /** - * Creates an intent based on a payment method. - * - * @param {Object} options Object containing intent optional parameters (fingerprint, paymentMethodType, orderId) - * - * @return {Promise} The final promise for the request to the server. - */ - createIntent( options ) { - const { fingerprint, orderId } = options; - const path = 'create_payment_intent'; - const params = { - _ajax_nonce: getConfig( 'createPaymentIntentNonce' ), - 'wcpay-fingerprint': fingerprint, - }; - - if ( orderId ) { - params.wcpay_order_id = orderId; - } - - return this.request( - buildAjaxURL( getConfig( 'wcAjaxUrl' ), path ), - params - ) - .then( ( response ) => { - if ( ! response.success ) { - throw response.data.error; - } - return response.data; - } ) - .catch( ( error ) => { - if ( error.message ) { - throw error; - } else { - // Covers the case of error on the Ajax request. - throw new Error( error.statusText ); - } - } ); - } - - /** - * Updates a payment intent with data from order: customer, level3 data 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. - * @param {string} selectedUPEPaymentType The name of the selected UPE payment type or empty string. - * @param {string?} paymentCountry The payment two-letter iso country code or null. - * @param {string?} fingerprint User fingerprint. - * - * @return {Promise} The final promise for the request to the server. - */ - updateIntent( - paymentIntentId, - orderId, - savePaymentMethod, - selectedUPEPaymentType, - paymentCountry, - fingerprint - ) { - const path = 'update_payment_intent'; - - return this.request( buildAjaxURL( getConfig( 'wcAjaxUrl' ), path ), { - wcpay_order_id: orderId, - wc_payment_intent_id: paymentIntentId, - save_payment_method: savePaymentMethod, - wcpay_selected_upe_payment_type: selectedUPEPaymentType, - wcpay_payment_country: paymentCountry, - _ajax_nonce: getConfig( 'updatePaymentIntentNonce' ), - 'wcpay-fingerprint': fingerprint, - } ) - .then( ( response ) => { - if ( response.result === 'failure' ) { - 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 ); - } - } ); - } - /** * Confirm Stripe payment with fallback for rate limit error. * @@ -544,39 +440,6 @@ export default class WCPayAPI { } ); } - /** - * Process checkout and update payment intent via AJAX. - * - * @param {string} paymentIntentId ID of payment intent to be updated. - * @param {Object} fields Checkout fields. - * @param {string} fingerprint User fingerprint. - * @return {Promise} Promise containing redirect URL for UPE element. - */ - processCheckout( paymentIntentId, fields, fingerprint ) { - return this.request( - buildAjaxURL( getConfig( 'wcAjaxUrl' ), 'checkout', '' ), - { - ...fields, - wc_payment_intent_id: paymentIntentId, - 'wcpay-fingerprint': fingerprint, - } - ) - .then( ( response ) => { - if ( response.result === 'failure' ) { - 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 ); - } - } ); - } - /** * Submits shipping address to get available shipping options * from Payment Request button. diff --git a/client/checkout/utils/test/upe.test.js b/client/checkout/utils/test/upe.test.js index c6bbd8e726e..c785baa427b 100644 --- a/client/checkout/utils/test/upe.test.js +++ b/client/checkout/utils/test/upe.test.js @@ -5,7 +5,6 @@ import { getTerms, getCookieValue, isWCPayChosen, - getPaymentIntentFromSession, generateCheckoutEventNames, getUpeSettings, getStripeElementOptions, @@ -168,71 +167,6 @@ describe( 'UPE checkout utils', () => { } ); } ); - describe( 'getPaymentIntentFromSession', () => { - const paymentMethodsConfig = { - card: { - upePaymentIntentData: - 'abcd1234-pi_abc123-pi_abc123_secret_5678xyz', - }, - eps: { - upePaymentIntentData: null, - }, - }; - - const cardData = { - clientSecret: 'pi_abc123_secret_5678xyz', - intentId: 'pi_abc123', - }; - - it( 'should return the correct client secret and intent ID', () => { - Object.defineProperty( document, 'cookie', { - get: () => { - return 'woocommerce_cart_hash=abcd1234;'; - }, - configurable: true, - } ); - expect( - getPaymentIntentFromSession( paymentMethodsConfig, 'card' ) - ).toEqual( cardData ); - } ); - - it( 'should return an empty object if no payment intent exists', () => { - Object.defineProperty( document, 'cookie', { - get: () => { - return 'woocommerce_cart_hash=abcd1234;'; - }, - configurable: true, - } ); - expect( - getPaymentIntentFromSession( paymentMethodsConfig, 'eps' ) - ).toEqual( {} ); - } ); - - it( 'should return an empty object if no cart hash exists', () => { - Object.defineProperty( document, 'cookie', { - get: () => { - return 'woocommerce_cart_items=1;'; - }, - configurable: true, - } ); - expect( - getPaymentIntentFromSession( paymentMethodsConfig, 'card' ) - ).toEqual( {} ); - } ); - - it( 'should return an empty object if the payment intent data does not start with the cart hash', () => { - Object.defineProperty( document, 'cookie', { - get: () => { - return 'woocommerce_cart_hash=xyz9876;'; - }, - configurable: true, - } ); - expect( - getPaymentIntentFromSession( paymentMethodsConfig, 'card' ) - ).toEqual( {} ); - } ); - } ); - describe( 'getUPESettings', () => { afterEach( () => { const checkboxElement = document.getElementById( diff --git a/client/checkout/utils/upe.js b/client/checkout/utils/upe.js index 71a673d3a2a..f7b49138690 100644 --- a/client/checkout/utils/upe.js +++ b/client/checkout/utils/upe.js @@ -44,34 +44,6 @@ export const isWCPayChosen = function () { .checked; }; -/** - * Returns the cached payment intent for the current cart state. - * - * @param {Object} paymentMethodsConfig Array of configs for payment methods. - * @param {string} paymentMethodType Type of the payment method. - * @return {Object} The intent id and client secret required for mounting the UPE element. - */ -export const getPaymentIntentFromSession = ( - paymentMethodsConfig, - paymentMethodType -) => { - const cartHash = getCookieValue( 'woocommerce_cart_hash' ); - const upePaymentIntentData = - paymentMethodsConfig[ paymentMethodType ].upePaymentIntentData; - - if ( - cartHash && - upePaymentIntentData && - upePaymentIntentData.startsWith( cartHash ) - ) { - const intentId = upePaymentIntentData.split( '-' )[ 1 ]; - const clientSecret = upePaymentIntentData.split( '-' )[ 2 ]; - return { intentId, clientSecret }; - } - - return {}; -}; - /** * Finds selected payment gateway and returns matching Stripe payment method for gateway. * diff --git a/client/checkout/woopay/email-input-iframe.js b/client/checkout/woopay/email-input-iframe.js index f57c3b4e90f..4a2efe861e5 100644 --- a/client/checkout/woopay/email-input-iframe.js +++ b/client/checkout/woopay/email-input-iframe.js @@ -263,7 +263,10 @@ export const handleWooPayEmailInput = async ( ); urlParams.append( 'wcpayVersion', getConfig( 'wcpayVersionNumber' ) ); urlParams.append( 'is_blocks', isBlocksCheckout ? 'true' : 'false' ); - urlParams.append( 'source_url', window.location.href ); + urlParams.append( + 'source_url', + wcSettings?.storePages?.checkout?.permalink + ); urlParams.append( 'viewport', `${ viewportWidth }x${ viewportHeight }` diff --git a/client/components/account-status/account-fees/index.js b/client/components/account-status/account-fees/index.js index 066a4faf1b3..93a93293c11 100644 --- a/client/components/account-status/account-fees/index.js +++ b/client/components/account-status/account-fees/index.js @@ -16,6 +16,8 @@ import { getCurrentBaseFee, getTransactionsPaymentMethodName, } from 'utils/account-fees'; +import { CardDivider } from '@wordpress/components'; +import './styles.scss'; const AccountFee = ( props ) => { const { accountFee, paymentMethod } = props; @@ -59,7 +61,12 @@ const AccountFees = ( props ) => { return ( <> { haveDiscounts && ( -

{ __( 'Active discounts', 'woocommerce-payments' ) }

+
+ +

+ { __( 'Active discounts', 'woocommerce-payments' ) } +

+
) } { activeDiscounts } diff --git a/client/components/account-status/account-fees/styles.scss b/client/components/account-status/account-fees/styles.scss new file mode 100644 index 00000000000..9938ef462b1 --- /dev/null +++ b/client/components/account-status/account-fees/styles.scss @@ -0,0 +1,3 @@ +.account-fees { + padding-top: 16px; +} diff --git a/client/components/account-status/account-fees/test/__snapshots__/index.js.snap b/client/components/account-status/account-fees/test/__snapshots__/index.js.snap index 6a2576c6862..653e680afac 100644 --- a/client/components/account-status/account-fees/test/__snapshots__/index.js.snap +++ b/client/components/account-status/account-fees/test/__snapshots__/index.js.snap @@ -2,9 +2,20 @@ exports[`AccountFees renders discounted base fee 1`] = `
-

- Active discounts -

+
+ +

+ Active discounts +

+

Card transactions : @@ -47,9 +58,20 @@ exports[`AccountFees renders discounted base fee 1`] = ` exports[`AccountFees renders discounted fee with USD volume currency and non-USD base fee 1`] = `

-

- Active discounts -

+

Card transactions : @@ -92,9 +114,20 @@ exports[`AccountFees renders discounted fee with USD volume currency and non-USD exports[`AccountFees renders discounted fee with end date 1`] = `

-

- Active discounts -

+

Card transactions : @@ -115,9 +148,20 @@ exports[`AccountFees renders discounted fee with end date 1`] = ` exports[`AccountFees renders discounted fee with volume limit 1`] = `

-

- Active discounts -

+

Card transactions : @@ -160,9 +204,20 @@ exports[`AccountFees renders discounted fee with volume limit 1`] = ` exports[`AccountFees renders discounted fee with volume limit and end date 1`] = `

-

- Active discounts -

+

Card transactions : @@ -205,9 +260,20 @@ exports[`AccountFees renders discounted fee with volume limit and end date 1`] = exports[`AccountFees renders discounted fee without volume limit 1`] = `

-

- Active discounts -

+

Card transactions : @@ -223,9 +289,20 @@ exports[`AccountFees renders discounted fee without volume limit 1`] = ` exports[`AccountFees renders discounted non-USD base fee 1`] = `

-

- Active discounts -

+

Card transactions : @@ -268,9 +345,20 @@ exports[`AccountFees renders discounted non-USD base fee 1`] = ` exports[`AccountFees renders discounts multiple payment methods 1`] = `

-

- Active discounts -

+

Card transactions : @@ -306,9 +394,20 @@ exports[`AccountFees renders discounts multiple payment methods 1`] = ` exports[`AccountFees renders first discounted fee ignoring the rest 1`] = `

-

- Active discounts -

+

Card transactions : @@ -324,9 +423,20 @@ exports[`AccountFees renders first discounted fee ignoring the rest 1`] = ` exports[`AccountFees renders non-USD base fee 1`] = `

-

- Active discounts -

+

Card transactions : @@ -342,9 +452,20 @@ exports[`AccountFees renders non-USD base fee 1`] = ` exports[`AccountFees renders normal base fee 1`] = `

-

- Active discounts -

+

Card transactions : diff --git a/client/components/account-status/account-tools/index.tsx b/client/components/account-status/account-tools/index.tsx new file mode 100644 index 00000000000..1a7d8039829 --- /dev/null +++ b/client/components/account-status/account-tools/index.tsx @@ -0,0 +1,67 @@ +/** + * External dependencies + */ +import React, { useState } from 'react'; +import { Button, CardDivider } from '@wordpress/components'; +import { addQueryArgs } from '@wordpress/url'; + +/** + * Internal dependencies + */ +import strings from './strings'; +import { isInDevMode } from 'utils'; +import './styles.scss'; +import ResetAccountModal from 'wcpay/overview/modal/reset-account'; +import { trackAccountReset } from 'wcpay/onboarding/tracking'; + +interface Props { + accountLink: string; + openModal: () => void; +} + +const handleReset = () => { + trackAccountReset(); + + window.location.href = addQueryArgs( wcpaySettings.connectUrl, { + 'wcpay-reset-account': true, + } ); +}; + +export const AccountTools: React.FC< Props > = ( props: Props ) => { + const accountLink = props.accountLink; + const [ modalVisible, setModalVisible ] = useState( false ); + + if ( isInDevMode() ) return null; + + return ( + <> +

+ +

{ strings.title }

+

{ strings.description }

+ { /* Use wrapping div to keep buttons grouped together. */ } +
+ + +
+
+ + setModalVisible( false ) } + onSubmit={ handleReset } + /> + + ); +}; diff --git a/client/components/account-status/account-tools/strings.tsx b/client/components/account-status/account-tools/strings.tsx new file mode 100644 index 00000000000..47c668d0a88 --- /dev/null +++ b/client/components/account-status/account-tools/strings.tsx @@ -0,0 +1,15 @@ +/* eslint-disable max-len */ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; + +export default { + title: __( 'Account Tools', 'woocommerce-payments' ), + description: __( + 'Payments and deposits are disabled until account setup is completed. If you are experiencing problems completing account setup, or need to change the email/country associated with your account, you can reset your account and start from the beginning.', + 'woocommerce-payments' + ), + finish: __( 'Finish setup', 'woocommerce-payments' ), + reset: __( 'Reset account', 'woocommerce-payments' ), +}; diff --git a/client/components/account-status/account-tools/styles.scss b/client/components/account-status/account-tools/styles.scss new file mode 100644 index 00000000000..aa2a29348cd --- /dev/null +++ b/client/components/account-status/account-tools/styles.scss @@ -0,0 +1,11 @@ +.account-tools { + padding-top: $gap; + + &__actions { + display: grid; + grid-auto-flow: column; + grid-auto-columns: max-content; + column-gap: $gap-small; + margin-top: $gap-small; + } +} diff --git a/client/components/account-status/account-tools/test/__snapshots__/index.test.tsx.snap b/client/components/account-status/account-tools/test/__snapshots__/index.test.tsx.snap new file mode 100644 index 00000000000..cfeba86037a --- /dev/null +++ b/client/components/account-status/account-tools/test/__snapshots__/index.test.tsx.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AccountTools should render in live mode 1`] = ` +
+ +
+`; diff --git a/client/components/account-status/account-tools/test/index.test.tsx b/client/components/account-status/account-tools/test/index.test.tsx new file mode 100644 index 00000000000..7c5827b115a --- /dev/null +++ b/client/components/account-status/account-tools/test/index.test.tsx @@ -0,0 +1,49 @@ +/** @format */ +/** + * External dependencies + */ +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +/** + * Internal dependencies + */ +import { AccountTools } from '..'; + +const accountLink = '/onboarding'; +const openModal = jest.fn(); + +declare const global: { + wcpaySettings: { + devMode: boolean; + }; +}; + +describe( 'AccountTools', () => { + it( 'should render in live mode', () => { + global.wcpaySettings = { + devMode: false, + }; + + const { container } = render( + + ); + expect( container ).toMatchSnapshot(); + } ); + + it( 'should not render in dev mode', () => { + global.wcpaySettings = { + devMode: true, + }; + + render( + + ); + + expect( + screen.queryByText( + 'If you are experiencing problems completing account setup, or need to change the email/country associated with your account, you can reset your account and start from the beginning.' + ) + ).not.toBeInTheDocument(); + } ); +} ); diff --git a/client/components/account-status/index.js b/client/components/account-status/index.js index 6a35084ad18..19fc715b1ff 100755 --- a/client/components/account-status/index.js +++ b/client/components/account-status/index.js @@ -23,6 +23,7 @@ import PaymentsStatus from 'components/payments-status'; import StatusChip from './status-chip'; import './style.scss'; import './shared.scss'; +import { AccountTools } from './account-tools'; const AccountStatusCard = ( props ) => { const { title, children, value } = props; @@ -102,6 +103,9 @@ const AccountStatusDetails = ( props ) => { } /> + { ! accountStatus.detailsSubmitted && ( + + ) } { accountFees.length > 0 && ( ) } diff --git a/client/components/account-status/test/__snapshots__/index.js.snap b/client/components/account-status/test/__snapshots__/index.js.snap index edead6f2fbd..712f139c0ca 100644 --- a/client/components/account-status/test/__snapshots__/index.js.snap +++ b/client/components/account-status/test/__snapshots__/index.js.snap @@ -166,9 +166,53 @@ exports[`AccountStatus renders normal status 1`] = `
-

- Active discounts -

+ +

Card transactions : diff --git a/client/components/woopay/save-user/checkout-page-save-user.js b/client/components/woopay/save-user/checkout-page-save-user.js index 8edd7c583d4..c38bcae2929 100644 --- a/client/components/woopay/save-user/checkout-page-save-user.js +++ b/client/components/woopay/save-user/checkout-page-save-user.js @@ -73,7 +73,8 @@ const CheckoutPageSaveUser = ( { isBlocksCheckout } ) => { ? {} : { save_user_in_woopay: isSaveDetailsChecked, - woopay_source_url: window.location.href, + woopay_source_url: + wcSettings?.storePages?.checkout?.permalink, woopay_is_blocks: true, woopay_viewport: `${ viewportWidth }x${ viewportHeight }`, woopay_user_phone_field: { @@ -291,7 +292,9 @@ const CheckoutPageSaveUser = ( { isBlocksCheckout } ) => { -1 !== id.indexOf( 'ch_' ) || -1 !== id.indexOf( 'py_' ); export const usePaymentIntentWithChargeFallback = ( id: string -): PaymentChargeDetailsResponse => - useSelect( +): PaymentChargeDetailsResponse => { + const { data, error, isLoading } = useSelect( ( select ) => { const selectors = select( STORE_NAME ); const isChargeId = getIsChargeId( id ); @@ -52,3 +53,16 @@ export const usePaymentIntentWithChargeFallback = ( }, [ id ] ); + + const { refundCharge } = useDispatch( STORE_NAME ); + + const doRefund = ( charge: Charge, reason: string | null ) => + refundCharge( charge, reason ); + + return { + data, + error, + isLoading, + doRefund, + }; +}; diff --git a/client/data/payment-intents/test/hooks.ts b/client/data/payment-intents/test/hooks.ts index 370816ac23a..e2631e291f2 100644 --- a/client/data/payment-intents/test/hooks.ts +++ b/client/data/payment-intents/test/hooks.ts @@ -110,6 +110,16 @@ describe( 'Payment Intent hooks', () => { ( useSelect as jest.Mock ).mockImplementation( ( cb: ( callback: any ) => jest.Mock ) => cb( selectMock ) ); + + jest.spyOn( + // eslint-disable-next-line @typescript-eslint/no-var-requires + require( '@wordpress/data' ), + 'useDispatch' + ).mockReturnValue( () => { + return { + refundCharge: jest.fn(), // Mock the refundCharge function + }; + } ); } ); describe( 'usePaymentIntentWithChargeFallback', () => { @@ -133,6 +143,7 @@ describe( 'Payment Intent hooks', () => { expect( result ).toEqual( { data: paymentIntentMock.charge, + doRefund: expect.any( Function ), error: {}, isLoading: false, } ); @@ -158,6 +169,7 @@ describe( 'Payment Intent hooks', () => { expect( result ).toEqual( { data: paymentIntentMock, + doRefund: expect.any( Function ), error: {}, isLoading: false, } ); @@ -181,6 +193,7 @@ describe( 'Payment Intent hooks', () => { expect( result ).toEqual( { data: {}, + doRefund: expect.any( Function ), error: {}, isLoading: true, } ); diff --git a/client/onboarding/tracking.ts b/client/onboarding/tracking.ts index 3a083eeb64b..66ea41ebf60 100644 --- a/client/onboarding/tracking.ts +++ b/client/onboarding/tracking.ts @@ -57,6 +57,9 @@ export const trackRedirected = ( isEligible: boolean ): void => { } ); }; +export const trackAccountReset = (): void => + wcpayTracks.recordEvent( wcpayTracks.events.ONBOARDING_FLOW_RESET, {} ); + export const trackEligibilityModalClosed = ( action: 'dismiss' | 'setup_deposits' | 'enable_payments_only' ): void => diff --git a/client/order/index.js b/client/order/index.js index 4b2606caff0..1bfdfeee7ad 100644 --- a/client/order/index.js +++ b/client/order/index.js @@ -13,6 +13,7 @@ import { isAwaitingResponse, isUnderReview } from 'wcpay/disputes/utils'; import RefundConfirmationModal from './refund-confirm-modal'; import CancelConfirmationModal from './cancel-confirm-modal'; import CancelAuthorizationConfirmationModal from './cancel-authorization-confirm-modal'; +import TestModeNotice from './test-mode-notice'; import DisputedOrderNoticeHandler from 'wcpay/components/disputed-order-notice'; function disableWooOrderRefundButton( disputeStatus ) { @@ -59,8 +60,9 @@ jQuery( function ( $ ) { const disableManualRefunds = getConfig( 'disableManualRefunds' ) ?? false; const manualRefundsTip = getConfig( 'manualRefundsTip' ) ?? ''; const chargeId = getConfig( 'chargeId' ); + const testMode = getConfig( 'testMode' ); - maybeShowDisputeNotice(); + maybeShowOrderNotices(); $( '#woocommerce-order-items' ).on( 'click', @@ -168,21 +170,27 @@ jQuery( function ( $ ) { ReactDOM.render( modalToRender, container ); } - function maybeShowDisputeNotice() { + function maybeShowOrderNotices() { const container = document.querySelector( '#wcpay-order-payment-details-container' ); // If the container doesn't exist (WC < 7.9), or the charge ID isn't present, don't render the notice. - if ( ! container || ! chargeId ) { + if ( ! container ) { return; } ReactDOM.render( - , + <> + { testMode && } + + { chargeId && ( + + ) } + , container ); } diff --git a/client/order/test-mode-notice/index.tsx b/client/order/test-mode-notice/index.tsx new file mode 100644 index 00000000000..7778f38a65a --- /dev/null +++ b/client/order/test-mode-notice/index.tsx @@ -0,0 +1,40 @@ +/** + * External dependencies + */ +import React from 'react'; +import { __ } from '@wordpress/i18n'; +import interpolateComponents from '@automattic/interpolate-components'; + +/** + * Internal dependencies + */ +import InlineNotice from 'wcpay/components/inline-notice'; + +const TestModeNotice = (): JSX.Element => { + return ( + + { interpolateComponents( { + mixedString: __( + 'WooPayments was in test mode when this order was placed. {{learnMoreLink/}}', + 'woocommerce-payments' + ), + components: { + learnMoreLink: ( + + { __( + 'Learn more about test mode', + 'woocommerce-payments' + ) } + + ), + }, + } ) } + + ); +}; + +export default TestModeNotice; diff --git a/client/overview/modal/reset-account/index.tsx b/client/overview/modal/reset-account/index.tsx new file mode 100644 index 00000000000..1146645566c --- /dev/null +++ b/client/overview/modal/reset-account/index.tsx @@ -0,0 +1,64 @@ +/** + * External dependencies + */ +import React from 'react'; +import { Button, CardDivider, Modal } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import './style.scss'; +import strings from './strings'; + +interface Props { + isVisible: boolean; + onSubmit: () => void; + onDismiss: () => void; +} + +const ResetAccountModal: React.FC< Props > = ( props: Props ) => { + const { isVisible, onDismiss, onSubmit } = props; + if ( ! isVisible ) return null; + + return ( + +

+

{ strings.description }

+

+ { strings.beforeContinue } +

+
    +
  1. { strings.step1 }
  2. +
+ +
    +
  1. { strings.step2 }
  2. +
+ +
    +
  1. { strings.step3 }
  2. +
+ +

{ strings.confirmation }

+
+
+ + +
+ + ); +}; + +export default ResetAccountModal; diff --git a/client/overview/modal/reset-account/strings.tsx b/client/overview/modal/reset-account/strings.tsx new file mode 100644 index 00000000000..1a1c7616ff0 --- /dev/null +++ b/client/overview/modal/reset-account/strings.tsx @@ -0,0 +1,36 @@ +/* eslint-disable max-len */ +/** + * External dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; + +export default { + title: __( 'Reset account', 'woocommerce-payments' ), + description: __( + 'If you are experiencing problems completing account setup, or need to change the email/country associated with your account, you can reset your account and start from the beginning.', + 'woocommerce-payments' + ), + beforeContinue: __( 'Before you continue', 'woocommerce-payments' ), + step1: sprintf( + /* translators: %s: WooPayments. */ + __( + 'Your %s account will be reset, and all data will be lost.', + 'woocommerce-payments' + ), + 'WooPayments' + ), + step2: __( + 'You will have to re-confirm your business and banking details.', + 'woocommerce-payments' + ), + step3: __( + 'Once confirmed, this cannot be undone.', + 'woocommerce-payments' + ), + confirmation: __( + 'Are you sure you want to continue?', + 'woocommerce-payments' + ), + cancel: __( 'Cancel', 'woocommerce-payments' ), + reset: __( 'Yes, reset account', 'woocommerce-payments' ), +}; diff --git a/client/overview/modal/reset-account/style.scss b/client/overview/modal/reset-account/style.scss new file mode 100644 index 00000000000..1c0f9fc0f36 --- /dev/null +++ b/client/overview/modal/reset-account/style.scss @@ -0,0 +1,25 @@ +.wcpay-reset-account-modal { + // fix for the modal being too short on smaller screens + @media ( max-height: 880px ) { + max-height: 100% !important; + } + + .components-modal__content { + box-sizing: border-box; + max-width: 700px; + } + + &__footer { + text-align: right; + margin-top: $gap-large; + + & :first-child { + margin-right: $gap-smaller; + } + + button { + margin-top: $gap; + padding: $gap-smaller $gap; + } + } +} diff --git a/client/overview/modal/reset-account/test/index.test.tsx b/client/overview/modal/reset-account/test/index.test.tsx new file mode 100644 index 00000000000..50f55e78bbe --- /dev/null +++ b/client/overview/modal/reset-account/test/index.test.tsx @@ -0,0 +1,35 @@ +/** + * External dependencies + */ +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +/** + * Internal dependencies + */ +import ResetAccountModal from '..'; + +jest.mock( '@wordpress/data', () => ( { + useDispatch: jest.fn().mockReturnValue( { updateOptions: jest.fn() } ), +} ) ); + +const onSubmit = jest.fn(); +const onDismiss = jest.fn(); + +describe( 'Reset Account Modal', () => { + it( 'modal is open when is visible is true', () => { + render( + + ); + + expect( + screen.queryByText( + 'If you are experiencing problems completing account setup, or need to change the email/country associated with your account, you can reset your account and start from the beginning.' + ) + ).toBeInTheDocument(); + } ); +} ); diff --git a/client/overview/task-list/tasks/update-business-details-task.tsx b/client/overview/task-list/tasks/update-business-details-task.tsx index 5f6542c67ff..adb8e5b430f 100644 --- a/client/overview/task-list/tasks/update-business-details-task.tsx +++ b/client/overview/task-list/tasks/update-business-details-task.tsx @@ -26,7 +26,7 @@ export const getUpdateBusinessDetailsTask = ( const hasMultipleErrors = 1 < errorMessages.length; const hasSingleError = 1 === errorMessages.length; - let accountDetailsTaskDescription = '', + let accountDetailsTaskDescription: React.ReactElement | string = '', errorMessageDescription, accountDetailsUpdateByDescription; @@ -45,9 +45,11 @@ export const getUpdateBusinessDetailsTask = ( if ( hasSingleError ) { errorMessageDescription = errorMessages[ 0 ]; - accountDetailsTaskDescription = errorMessageDescription.concat( - ' ', - accountDetailsUpdateByDescription + accountDetailsTaskDescription = ( + <> + { errorMessageDescription }{ ' ' } + { accountDetailsUpdateByDescription } + ); } else { accountDetailsTaskDescription = accountDetailsUpdateByDescription; diff --git a/client/overview/task-list/types.d.ts b/client/overview/task-list/types.d.ts index 2556617dcd7..d572fc0cbb3 100644 --- a/client/overview/task-list/types.d.ts +++ b/client/overview/task-list/types.d.ts @@ -17,4 +17,6 @@ export interface TaskItemProps extends React.ComponentProps< typeof TaskItem > { * Whether the task is dismissable. */ isDismissable?: boolean; + + content: string | React.ReactElement; } diff --git a/client/payment-details/order-details/test/__snapshots__/index.test.tsx.snap b/client/payment-details/order-details/test/__snapshots__/index.test.tsx.snap index d02d14d3476..ea41fb13ccb 100644 --- a/client/payment-details/order-details/test/__snapshots__/index.test.tsx.snap +++ b/client/payment-details/order-details/test/__snapshots__/index.test.tsx.snap @@ -20,51 +20,60 @@ exports[`Order details page should match the snapshot - Charge without payment i data-wp-component="CardBody" >
-

- $15.00 - - USD - - - Pending - -

- -

- Fees: - -$0.00 -

- -

- Net: +

$15.00 + + USD + + + Pending +

+
+ +

+ Fees: + -$0.00 +

+ +

+ Net: + $15.00 +

+
-
-
- Payment ID: - 776 +
+ Payment ID: + 776 +
+

= ( { charge.currency && balance.currency !== charge.currency; const { - featureFlags: { isAuthAndCaptureEnabled, isRefundControlsEnabled }, + featureFlags: { isAuthAndCaptureEnabled }, } = useContext( WCPaySettingsContext ); // We should only fetch the authorization data if the payment is marked for manual capture and it is not already captured. @@ -225,245 +239,318 @@ const PaymentDetailsSummary: React.FC< PaymentDetailsSummaryProps > = ( { balance.currency ); + const [ isRefundModalOpen, setIsRefundModalOpen ] = useState( false ); return ( -
-
-

- - { formattedAmount } - - { charge.currency || 'USD' } - - { charge.dispute ? ( - - ) : ( - - ) } - -

-
- { renderStorePrice ? ( -

- { formatExplicitCurrency( - balance.amount, - balance.currency - ) } -

- ) : null } - { balance.refunded ? ( -

- { `${ - disputeFee - ? __( - 'Deducted', - 'woocommerce-payments' - ) - : __( - 'Refunded', - 'woocommerce-payments' - ) - }: ` } - { formatExplicitCurrency( - -balance.refunded, - balance.currency - ) } -

- ) : ( - '' - ) } -

+ +

+
+

- { `${ __( - 'Fees', - 'woocommerce-payments' - ) }: ` } - { formatCurrency( - -balance.fee, - balance.currency - ) } - { disputeFee && ( - } - buttonLabel={ __( - 'Fee breakdown', - 'woocommerce-payments' - ) } - content={ - <> - - - - { formatCurrency( - transactionFee.fee, - transactionFee.currency - ) } - - - - - - { disputeFee } - - - - - - { formatCurrency( - balance.fee, - balance.currency - ) } - - - + { formattedAmount } + + { charge.currency || 'USD' } + + { charge.dispute ? ( + + ) : ( + ) }

- { charge.paydown ? ( +
+ { renderStorePrice ? ( +

+ { formatExplicitCurrency( + balance.amount, + balance.currency + ) } +

+ ) : null } + { balance.refunded ? ( +

+ { `${ + disputeFee + ? __( + 'Deducted', + 'woocommerce-payments' + ) + : __( + 'Refunded', + 'woocommerce-payments' + ) + }: ` } + { formatExplicitCurrency( + -balance.refunded, + balance.currency + ) } +

+ ) : ( + '' + ) }

- { `${ __( - 'Loan repayment', - 'woocommerce-payments' - ) }: ` } - { formatExplicitCurrency( - charge.paydown.amount, - balance.currency - ) } + + { `${ __( + 'Fees', + 'woocommerce-payments' + ) }: ` } + { formatCurrency( + -balance.fee, + balance.currency + ) } + { disputeFee && ( + + } + buttonLabel={ __( + 'Fee breakdown', + 'woocommerce-payments' + ) } + content={ + <> + + + + { formatCurrency( + transactionFee.fee, + transactionFee.currency + ) } + + + + + + { disputeFee } + + + + + + { formatCurrency( + balance.fee, + balance.currency + ) } + + + + } + /> + ) } + +

+ { charge.paydown ? ( +

+ { `${ __( + 'Loan repayment', + 'woocommerce-payments' + ) }: ` } + { formatExplicitCurrency( + charge.paydown.amount, + balance.currency + ) } +

+ ) : ( + '' + ) } +

+ + { `${ __( + 'Net', + 'woocommerce-payments' + ) }: ` } + { formatExplicitCurrency( + charge.paydown + ? balance.net - + Math.abs( + charge.paydown + .amount + ) + : balance.net, + balance.currency + ) } +

- ) : ( - '' +
+
+
+ { ! isLoading && isFraudOutcomeReview && ( +
+ { + wcpayTracks.recordEvent( + 'wcpay_fraud_protection_transaction_reviewed_merchant_blocked', + { + payment_intent_id: + charge.payment_intent, + } + ); + wcpayTracks.recordEvent( + 'payments_transactions_details_cancel_charge_button_click', + { + payment_intent_id: + charge.payment_intent, + } + ); + } } + > + { __( 'Block transaction' ) } + + + { + wcpayTracks.recordEvent( + 'wcpay_fraud_protection_transaction_reviewed_merchant_approved', + { + payment_intent_id: + charge.payment_intent, + } + ); + wcpayTracks.recordEvent( + 'payments_transactions_details_capture_charge_button_click', + { + payment_intent_id: + charge.payment_intent, + } + ); + } } + > + { __( 'Approve Transaction' ) } + +
) } -

+

{ `${ __( - 'Net', + 'Payment ID', 'woocommerce-payments' ) }: ` } - { formatExplicitCurrency( - charge.paydown - ? balance.net - - Math.abs( - charge.paydown.amount - ) - : balance.net, - balance.currency - ) } + { charge.payment_intent + ? charge.payment_intent + : charge.id } -

+
-
- { ! isLoading && isFraudOutcomeReview && ( -
- { - wcpayTracks.recordEvent( - 'wcpay_fraud_protection_transaction_reviewed_merchant_blocked', - { - payment_intent_id: - charge.payment_intent, - } - ); - wcpayTracks.recordEvent( - 'payments_transactions_details_cancel_charge_button_click', - { - payment_intent_id: - charge.payment_intent, - } - ); - } } - > - { __( 'Block transaction' ) } - - - { - wcpayTracks.recordEvent( - 'wcpay_fraud_protection_transaction_reviewed_merchant_approved', - { - payment_intent_id: - charge.payment_intent, - } - ); - wcpayTracks.recordEvent( - 'payments_transactions_details_capture_charge_button_click', - { - payment_intent_id: - charge.payment_intent, - } - ); - } } - > - { __( 'Approve Transaction' ) } - -
- ) } -
+
+ { ! charge?.refunded && charge?.captured && ( - { `${ __( - 'Payment ID', - 'woocommerce-payments' - ) }: ` } - { charge.payment_intent - ? charge.payment_intent - : charge.id } + + { ( { onClose } ) => ( + + { + setIsRefundModalOpen( + true + ); + wcpayTracks.recordEvent( + 'payments_transactions_details_refund_modal_open', + { + payment_intent_id: + charge.payment_intent, + } + ); + onClose(); + } } + > + { __( + 'Refund in full', + 'woocommerce-payments' + ) } + + { charge.order && ( + { + wcpayTracks.recordEvent( + 'payments_transactions_details_partial_refund', + { + payment_intent_id: + charge.payment_intent, + order_id: + charge.order + ?.number, + } + ); + window.location = + charge.order?.url; + } } + > + { __( + 'Partial refund', + 'woocommerce-payments' + ) } + + ) } + + ) } + -
+ ) }
-
+ @@ -491,14 +578,28 @@ const PaymentDetailsSummary: React.FC< PaymentDetailsSummaryProps > = ( { ) } ) } - { isRefundControlsEnabled && - ! _.isEmpty( charge ) && - ! charge.order && ( - - ) } + { isRefundModalOpen && ( + { + setIsRefundModalOpen( false ); + wcpayTracks.recordEvent( + 'payments_transactions_details_refund_modal_close', + { + payment_intent_id: charge.payment_intent, + } + ); + } } + /> + ) } + { ! _.isEmpty( charge ) && ! charge.order && ! isLoading && ( + setIsRefundModalOpen( true ) } + /> + ) } { isAuthAndCaptureEnabled && authorization && ! authorization.captured && ( diff --git a/client/payment-details/summary/missing-order-notice/index.tsx b/client/payment-details/summary/missing-order-notice/index.tsx index 69d7f008760..7b05331ee4c 100644 --- a/client/payment-details/summary/missing-order-notice/index.tsx +++ b/client/payment-details/summary/missing-order-notice/index.tsx @@ -5,10 +5,8 @@ */ import React from 'react'; -import { Button, RadioControl } from '@wordpress/components'; -import { __, sprintf } from '@wordpress/i18n'; -import { useState } from '@wordpress/element'; -import interpolateComponents from '@automattic/interpolate-components'; +import { Button } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies. @@ -16,131 +14,53 @@ import interpolateComponents from '@automattic/interpolate-components'; import './style.scss'; import CardNotice from 'wcpay/components/card-notice'; -import ConfirmationModal from 'wcpay/components/confirmation-modal'; import Loadable from 'wcpay/components/loadable'; +import { Charge } from 'wcpay/types/charges'; interface MissingOrderNoticeProps { + charge: Charge; isLoading: boolean; - formattedAmount: string; + onButtonClick: () => void; } const MissingOrderNotice: React.FC< MissingOrderNoticeProps > = ( { + charge, isLoading, - formattedAmount, + onButtonClick, } ) => { - const [ isModalOpen, setIsModalOpen ] = useState( false ); - - const [ reason, setReason ] = useState< string | null >( null ); - - const handleOnButtonClick = () => { - setIsModalOpen( true ); - }; - - const handleModalCancel = () => { - setIsModalOpen( false ); - }; - - const handleModalConfirmation = () => { - // TODO: Handle the refund. - }; - return ( <> - { __( 'Refund', 'woocommerce-payments' ) } - + ! charge.refunded ? ( + + ) : ( + <> + ) } > { __( - 'This transaction is not connected to order. Investigate this purchase and refund the transaction as needed.', + 'This transaction is not connected to order. ', 'woocommerce-payments' ) } + { charge.refunded + ? __( + 'It has been refunded and is not a subject for disputes.', + 'woocommerce-payments' + ) + : __( + 'Investigate this purchase and refund the transaction as needed.', + 'woocommerce-payments' + ) } - { isModalOpen && ( - - - - - } - onRequestClose={ handleModalCancel } - > -

- { interpolateComponents( { - mixedString: sprintf( - __( - 'This will issue a full refund of {{strong}}%s{{/strong}} to the customer.', - 'woocommerce-payments' - ), - formattedAmount - ), - components: { - strong: , - }, - } ) } -

- setReason( value ) } - /> -
- ) } ); }; diff --git a/client/payment-details/summary/missing-order-notice/test/__snapshots__/index.test.tsx.snap b/client/payment-details/summary/missing-order-notice/test/__snapshots__/index.test.tsx.snap new file mode 100644 index 00000000000..64cd813ebc2 --- /dev/null +++ b/client/payment-details/summary/missing-order-notice/test/__snapshots__/index.test.tsx.snap @@ -0,0 +1,34 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MissingOrderNotice it renders correctly 1`] = ` +
+ +`; + +exports[`MissingOrderNotice renders loading state 1`] = ` +
+ +
+`; diff --git a/client/payment-details/summary/missing-order-notice/test/index.test.tsx b/client/payment-details/summary/missing-order-notice/test/index.test.tsx new file mode 100644 index 00000000000..97758906fdc --- /dev/null +++ b/client/payment-details/summary/missing-order-notice/test/index.test.tsx @@ -0,0 +1,39 @@ +/** @format */ + +/** + * External dependencies + */ +import React from 'react'; +import { render } from '@testing-library/react'; +import { chargeMock } from 'wcpay/data/payment-intents/test/hooks'; + +/** + * Internal dependencies + */ +import MissingOrderNotice from '..'; +import { Charge } from 'wcpay/types/charges'; + +describe( 'MissingOrderNotice', () => { + test( 'it renders correctly', () => { + const { container: notice } = render( + + ); + + expect( notice ).toMatchSnapshot(); + } ); + + test( 'renders loading state', () => { + const { container: notice } = render( + + ); + expect( notice ).toMatchSnapshot(); + } ); +} ); diff --git a/client/payment-details/summary/refund-modal/index.tsx b/client/payment-details/summary/refund-modal/index.tsx new file mode 100644 index 00000000000..0127e379210 --- /dev/null +++ b/client/payment-details/summary/refund-modal/index.tsx @@ -0,0 +1,127 @@ +/** @format **/ + +/** + * External dependencies + */ + +import React from 'react'; +import { Button, RadioControl } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { useState } from '@wordpress/element'; +import interpolateComponents from '@automattic/interpolate-components'; + +/** + * Internal dependencies. + */ + +import ConfirmationModal from 'wcpay/components/confirmation-modal'; +import { Charge } from 'wcpay/types/charges'; +import { usePaymentIntentWithChargeFallback } from 'wcpay/data'; +import { PaymentChargeDetailsResponse } from 'wcpay/payment-details/types'; +import wcpayTracks from 'tracks'; + +interface RefundModalProps { + charge: Charge; + formattedAmount: string; + onModalClose: () => void; +} + +const RefundModal: React.FC< RefundModalProps > = ( { + charge, + formattedAmount, + onModalClose, +} ) => { + const [ reason, setReason ] = useState< string | null >( null ); + + const [ isRefundInProgress, setIsRefundInProgress ] = useState< boolean >( + false + ); + + const { doRefund } = usePaymentIntentWithChargeFallback( + charge.payment_intent as string + ) as PaymentChargeDetailsResponse; + + const handleModalCancel = () => { + onModalClose(); + }; + + const handleRefund = async () => { + wcpayTracks.recordEvent( 'payments_transactions_details_refund_full', { + payment_intent_id: charge.payment_intent, + } ); + setIsRefundInProgress( true ); + await doRefund( charge, reason === 'other' ? null : reason ); + setIsRefundInProgress( false ); + handleModalCancel(); + }; + + return ( + + + + + } + onRequestClose={ handleModalCancel } + > +

+ { interpolateComponents( { + mixedString: sprintf( + __( + 'This will issue a full refund of {{strong}}%s{{/strong}} to the customer.', + 'woocommerce-payments' + ), + formattedAmount + ), + components: { + strong: , + }, + } ) } +

+ setReason( value ) } + /> +
+ ); +}; + +export default RefundModal; diff --git a/client/payment-details/summary/refund-modal/test/__snapshots__/index.test.tsx.snap b/client/payment-details/summary/refund-modal/test/__snapshots__/index.test.tsx.snap new file mode 100644 index 00000000000..251b5f8438f --- /dev/null +++ b/client/payment-details/summary/refund-modal/test/__snapshots__/index.test.tsx.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RefundModal it renders correctly 1`] = ` +
-

- $20.00 - - usd - - + usd + + + Paid + +

+
- Paid - -

+ +

+ Fees: + -$0.70 +

+ +

+ Net: + $19.30 +

+
+
- -

- Fees: - $-0.70 -

- -

- Net: - $19.30 -

+
+ Payment ID: + ch_38jdHA39KKA +
- Payment ID: - ch_38jdHA39KKA +
@@ -888,49 +941,84 @@ exports[`PaymentDetailsSummary order missing notice does not render notice if or data-wp-component="CardBody" >
-

- $20.00 - - usd - - + usd + + + Paid + +

+
- Paid - -

+ +

+ Fees: + -$0.70 +

+ +

+ Net: + $19.30 +

+
+
- -

- Fees: - $-0.70 -

- -

- Net: - $19.30 -

+
+ Payment ID: + ch_38jdHA39KKA +
- Payment ID: - ch_38jdHA39KKA +
@@ -1148,49 +1236,84 @@ exports[`PaymentDetailsSummary order missing notice renders notice if order miss data-wp-component="CardBody" >
-

- $20.00 - - usd - - + usd + + + Paid + +

+
- Paid - -

+ +

+ Fees: + -$0.70 +

+ +

+ Net: + $19.30 +

+
+
- -

- Fees: - $-0.70 -

- -

- Net: - $19.30 -

+
+ Payment ID: + ch_38jdHA39KKA +
- Payment ID: - ch_38jdHA39KKA +
@@ -1383,7 +1506,8 @@ exports[`PaymentDetailsSummary order missing notice renders notice if order miss
- This transaction is not connected to order. Investigate this purchase and refund the transaction as needed. + This transaction is not connected to order. + Investigate this purchase and refund the transaction as needed.
-

- $20.00 - - usd - - + usd + + + Paid + +

+
- Paid - -

+ +

+ Fees: + -$0.70 +

+ +

+ Net: + $19.30 +

+
+
- -

- Fees: - $-0.70 -

- -

- Net: - $19.30 -

+
+ Payment ID: + ch_38jdHA39KKA +
- Payment ID: - ch_38jdHA39KKA +
@@ -1717,54 +1876,63 @@ exports[`PaymentDetailsSummary renders fully refunded information for a charge 1 data-wp-component="CardBody" >
-

- $20.00 - - usd - - - Refunded - -

-

- Refunded: - $-20.00 -

-

- Fees: - $-0.70 -

- -

- Net: - $-0.70 +

+ $20.00 + + usd + + + Refunded +

+
+

+ Refunded: + -$20.00 +

+

+ Fees: + -$0.70 +

+ +

+ Net: + -$0.70 +

+
-
-
- Payment ID: - ch_38jdHA39KKA +
+ Payment ID: + ch_38jdHA39KKA +
+

-

- - - USD - - -

- -

- Fees: - $0.00 -

- -

- Net: - $0.00 +

+ + + USD + +

+
+ +

+ Fees: + $0.00 +

+ +

+ Net: + $0.00 +

+
-
-
- Payment ID: +
+ Payment ID: +
+

-

- $20.00 - - usd - - + usd + + + Partial refund + +

+
- Partial refund - -

+

+ Refunded: + -$12.00 +

+

+ Fees: + -$0.70 +

+ +

+ Net: + $7.30 +

+
+
-

- Refunded: - $-12.00 -

-

- Fees: - $-0.70 -

- -

- Net: - $7.30 -

+
+ Payment ID: + ch_38jdHA39KKA +
- Payment ID: - ch_38jdHA39KKA +
@@ -2476,49 +2688,84 @@ exports[`PaymentDetailsSummary renders the Tap to Pay channel from metadata 1`] data-wp-component="CardBody" >
-

- $20.00 - - usd - - + usd + + + Paid + +

+
- Paid - -

+ +

+ Fees: + -$0.70 +

+ +

+ Net: + $19.30 +

+
+
- -

- Fees: - $-0.70 -

- -

- Net: - $19.30 -

+
+ Payment ID: + ch_38jdHA39KKA +
- Payment ID: - ch_38jdHA39KKA +
@@ -2736,49 +2983,84 @@ exports[`PaymentDetailsSummary renders the information of a dispute-reversal cha data-wp-component="CardBody" >
-

- $20.00 - - usd - - + usd + + + Disputed: Won + +

+
- Disputed: Won - -

+ +

+ Fees: + -$0.70 +

+ +

+ Net: + $19.30 +

+
+
- -

- Fees: - $-0.70 -

- -

- Net: - $19.30 -

+
+ Payment ID: + ch_38jdHA39KKA +
- Payment ID: - ch_38jdHA39KKA +
diff --git a/client/payment-details/summary/test/index.test.tsx b/client/payment-details/summary/test/index.test.tsx index 58de0645616..522c613d5ff 100755 --- a/client/payment-details/summary/test/index.test.tsx +++ b/client/payment-details/summary/test/index.test.tsx @@ -251,7 +251,7 @@ describe( 'PaymentDetailsSummary', () => { } ); const container = renderCharge( charge ); - screen.getByText( /Refunded: \$-20.00/i ); + screen.getByText( /Refunded: -\$20.00/i ); expect( container ).toMatchSnapshot(); } ); diff --git a/client/payment-details/test/__snapshots__/index.test.tsx.snap b/client/payment-details/test/__snapshots__/index.test.tsx.snap index b2ed253f99d..ebe9d418a92 100644 --- a/client/payment-details/test/__snapshots__/index.test.tsx.snap +++ b/client/payment-details/test/__snapshots__/index.test.tsx.snap @@ -20,57 +20,82 @@ exports[`Payment details page should match the snapshot - Charge query param 1`] data-wp-component="CardBody" >
-

- - Amount placeholder - -

- -

+

- Fee amount + Amount placeholder

- -

+

+ +

+ + Fee amount + +

+ +

+ + Net amount + +

+
+
+
+
- Net amount + Payment ID: pi_xxxxxxxxxxxxxxxxxxxxxxxx -

+
-
- -
+ + +
@@ -458,49 +483,84 @@ exports[`Payment details page should match the snapshot - Payment Intent query p data-wp-component="CardBody" >
-

- $1,500.00 - - usd - - + usd + + + Paid + +

+
- Paid - -

+ +

+ Fees: + -$74.00 +

+ +

+ Net: + $1,426.00 +

+
+
- -

- Fees: - -$74.00 -

- -

- Net: - $1,426.00 -

+
+ Payment ID: + pi_mock +
- Payment ID: - pi_mock +
@@ -682,6 +742,32 @@ exports[`Payment details page should match the snapshot - Payment Intent query p
+