diff --git a/changelog/fix-7230-payments-details-mobile-view b/changelog/fix-7230-payments-details-mobile-view new file mode 100644 index 00000000000..93e179a44ca --- /dev/null +++ b/changelog/fix-7230-payments-details-mobile-view @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix styling of transaction details page in mobile view. diff --git a/changelog/fix-9612-inquiry-order-note b/changelog/fix-9612-inquiry-order-note new file mode 100644 index 00000000000..3fce0a23430 --- /dev/null +++ b/changelog/fix-9612-inquiry-order-note @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Order notes for inquiries have clearer content. diff --git a/changelog/fix-9830-browser-error-on-dispute-submission b/changelog/fix-9830-browser-error-on-dispute-submission new file mode 100644 index 00000000000..918ad744351 --- /dev/null +++ b/changelog/fix-9830-browser-error-on-dispute-submission @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Browser error no longer shows after dispute evidence submission diff --git a/changelog/fix-stripe-link-button b/changelog/fix-stripe-link-button new file mode 100644 index 00000000000..d8acf0626f1 --- /dev/null +++ b/changelog/fix-stripe-link-button @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Restrict Stripe Link to credit card payment method and improve cleanup. diff --git a/changelog/update-7900-payout-notice b/changelog/update-7900-payout-notice new file mode 100644 index 00000000000..4a49df73e41 --- /dev/null +++ b/changelog/update-7900-payout-notice @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Remove payout timing notice and update the help tooltil on Payments Overview page. diff --git a/client/checkout/blocks/payment-processor.js b/client/checkout/blocks/payment-processor.js index ee7ef6af861..952470aa46b 100644 --- a/client/checkout/blocks/payment-processor.js +++ b/client/checkout/blocks/payment-processor.js @@ -28,7 +28,10 @@ import { useCustomerData } from './utils'; import enableStripeLinkPaymentMethod from 'wcpay/checkout/stripe-link'; import { getUPEConfig } from 'wcpay/utils/checkout'; import { validateElements } from 'wcpay/checkout/classic/payment-processing'; -import { PAYMENT_METHOD_ERROR } from 'wcpay/checkout/constants'; +import { + PAYMENT_METHOD_ERROR, + PAYMENT_METHOD_NAME_CARD, +} from 'wcpay/checkout/constants'; const getBillingDetails = ( billingData ) => { return { @@ -70,6 +73,7 @@ const PaymentProcessor = ( { const stripe = useStripe(); const elements = useElements(); const hasLoadErrorRef = useRef( false ); + const linkCleanupRef = useRef( null ); const paymentMethodsConfig = getUPEConfig( 'paymentMethodsConfig' ); const isTestMode = getUPEConfig( 'testMode' ); @@ -81,7 +85,10 @@ const PaymentProcessor = ( { } = useCustomerData(); useEffect( () => { - if ( isLinkEnabled( paymentMethodsConfig ) ) { + if ( + activePaymentMethod === PAYMENT_METHOD_NAME_CARD && + isLinkEnabled( paymentMethodsConfig ) + ) { enableStripeLinkPaymentMethod( { api: api, elements: elements, @@ -123,11 +130,22 @@ const PaymentProcessor = ( { } ); }, onButtonShow: blocksShowLinkButtonHandler, + } ).then( ( cleanup ) => { + linkCleanupRef.current = cleanup; } ); + + // Cleanup the Link button when the component unmounts + return () => { + if ( linkCleanupRef.current ) { + linkCleanupRef.current(); + linkCleanupRef.current = null; + } + }; } }, [ api, elements, + activePaymentMethod, paymentMethodsConfig, setBillingAddress, setShippingAddress, diff --git a/client/checkout/stripe-link/index.js b/client/checkout/stripe-link/index.js index 45f4feabf36..e44cf0d8236 100644 --- a/client/checkout/stripe-link/index.js +++ b/client/checkout/stripe-link/index.js @@ -4,6 +4,13 @@ import { dispatchChangeEventFor } from '../utils/upe'; export const switchToNewPaymentTokenElement = () => { + // Switch to card payment method before enabling new payment token element + document + .querySelector( + 'input[name="payment_method"][value="woocommerce_payments"]' + ) + ?.click(); + const newPaymentTokenElement = document.getElementById( 'wc-woocommerce_payments-payment-token-new' ); @@ -44,16 +51,17 @@ const enableStripeLinkPaymentMethod = async ( options ) => { const emailField = document.getElementById( options.emailId ); if ( ! emailField ) { - return; + return Promise.resolve( () => null ); } const stripe = await options.api.getStripe(); // https://stripe.com/docs/payments/link/autofill-modal const linkAutofill = stripe.linkAutofillModal( options.elements ); - emailField.addEventListener( 'keyup', ( event ) => { + const handleKeyup = ( event ) => { linkAutofill.launch( { email: event.target.value } ); - } ); + }; + emailField.addEventListener( 'keyup', handleKeyup ); options.onButtonShow( linkAutofill ); @@ -65,6 +73,11 @@ const enableStripeLinkPaymentMethod = async ( options ) => { ); switchToNewPaymentTokenElement(); } ); + + return () => { + emailField.removeEventListener( 'keyup', handleKeyup ); + removeLinkButton(); + }; }; export default enableStripeLinkPaymentMethod; diff --git a/client/checkout/stripe-link/test/index.test.js b/client/checkout/stripe-link/test/index.test.js index b8d907c3508..392a170b179 100644 --- a/client/checkout/stripe-link/test/index.test.js +++ b/client/checkout/stripe-link/test/index.test.js @@ -85,6 +85,35 @@ describe( 'Stripe Link elements behavior', () => { expect( handleButtonShow ).toHaveBeenCalled(); } ); + test( 'Should properly clean up when cleanup function is called', async () => { + createStripeLinkElements(); + const billingEmailInput = document.getElementById( 'billing_email' ); + const removeEventListenerSpy = jest.spyOn( + billingEmailInput, + 'removeEventListener' + ); + const removeLinkButtonSpy = jest.spyOn( + document.querySelector( '.wcpay-stripelink-modal-trigger' ), + 'remove' + ); + + const cleanup = await enableStripeLinkPaymentMethod( { + api: WCPayAPI(), + emailId: 'billing_email', + onAutofill: () => null, + onButtonShow: () => null, + } ); + + // Call the cleanup function + cleanup(); + + expect( removeEventListenerSpy ).toHaveBeenCalledWith( + 'keyup', + expect.any( Function ) + ); + expect( removeLinkButtonSpy ).toHaveBeenCalled(); + } ); + function createStripeLinkElements() { // Create the input field const billingEmailInput = document.createElement( 'input' ); diff --git a/client/components/deposits-overview/deposit-notices.tsx b/client/components/deposits-overview/deposit-notices.tsx index 281785fc60a..7a65a9a8e6f 100644 --- a/client/components/deposits-overview/deposit-notices.tsx +++ b/client/components/deposits-overview/deposit-notices.tsx @@ -5,7 +5,6 @@ import React from 'react'; import { __, sprintf } from '@wordpress/i18n'; import interpolateComponents from '@automattic/interpolate-components'; import { Link } from '@woocommerce/components'; -import { tip } from '@wordpress/icons'; import { ExternalLink } from '@wordpress/components'; import { addQueryArgs } from '@wordpress/url'; @@ -104,22 +103,6 @@ export const NewAccountWaitingPeriodNotice: React.FC = () => ( ); -/** - * Renders a notice informing the user of the number of days it may take for deposits to appear in their bank account. - */ -export const DepositTransitDaysNotice: React.FC = () => ( - - { __( - 'It may take 1-3 business days for payouts to reach your bank account.', - 'woocommerce-payments' - ) } - -); - /** * Renders a notice informing the user that their deposits may be paused due to a negative balance. */ diff --git a/client/components/deposits-overview/deposit-schedule.tsx b/client/components/deposits-overview/deposit-schedule.tsx index c463dfa335f..11221606544 100644 --- a/client/components/deposits-overview/deposit-schedule.tsx +++ b/client/components/deposits-overview/deposit-schedule.tsx @@ -118,72 +118,24 @@ const DepositSchedule: React.FC< DepositScheduleProps > = ( { const nextDepositHelpContent = ( <> - { __( - 'Payouts are initiated based on the following criteria:', - 'woocommerce-payments' - ) } - + { interpolateComponents( { + mixedString: __( + 'The timing and amount of your payouts may vary due to several factors. Check out our {{link}}payout schedule guide{{/link}} for details.', + 'woocommerce-payments' + ), + components: { + link: ( + // eslint-disable-next-line jsx-a11y/anchor-has-content + + ), + }, + } ) } ); diff --git a/client/components/deposits-overview/index.tsx b/client/components/deposits-overview/index.tsx index 11c0b3aa023..c00eca0f953 100644 --- a/client/components/deposits-overview/index.tsx +++ b/client/components/deposits-overview/index.tsx @@ -24,7 +24,6 @@ import RecentDepositsList from './recent-deposits-list'; import DepositSchedule from './deposit-schedule'; import { DepositMinimumBalanceNotice, - DepositTransitDaysNotice, NegativeBalanceDepositsPausedNotice, NewAccountWaitingPeriodNotice, NoFundsAvailableForDepositNotice, @@ -149,11 +148,6 @@ const DepositsOverview: React.FC = () => { ) : ( <> - { isDepositsUnrestricted && - ! isDepositAwaitingPendingFunds && - ! hasErroredExternalAccount && ( - - ) } { ! hasCompletedWaitingPeriod && ( ) } diff --git a/client/components/deposits-overview/test/__snapshots__/index.tsx.snap b/client/components/deposits-overview/test/__snapshots__/index.tsx.snap index ac1ac4c69ca..f80d3f68edf 100644 --- a/client/components/deposits-overview/test/__snapshots__/index.tsx.snap +++ b/client/components/deposits-overview/test/__snapshots__/index.tsx.snap @@ -138,50 +138,7 @@ exports[`Deposits Overview information Component Renders 1`] = ` class="components-card__body components-card-body wcpay-deposits-overview__notices__container css-1nwhnu3-View-Body-borderRadius-medium em57xhy0" data-wp-c16t="true" data-wp-component="CardBody" - > -
-
-
-
- -
-
- It may take 1-3 business days for payouts to reach your bank account. -
-
-
-
-
-
+ />
= ( { status, dueBy, prefixDisputeType, + className, } ) => { const mapping = displayStatus[ status ] || {}; let message = mapping.message || formatStringValue( status ); @@ -50,7 +52,7 @@ const DisputeStatusChip: React.FC< Props > = ( { type = 'alert'; } - return ; + return ; }; export default DisputeStatusChip; diff --git a/client/components/payment-status-chip/index.js b/client/components/payment-status-chip/index.js index b26da2ad4ed..fb57843849b 100755 --- a/client/components/payment-status-chip/index.js +++ b/client/components/payment-status-chip/index.js @@ -11,11 +11,11 @@ import displayStatus from './mappings'; import Chip from '../chip'; import { formatStringValue } from 'utils'; -const PaymentStatusChip = ( { status } ) => { +const PaymentStatusChip = ( { status, className } ) => { const mapping = displayStatus[ status ] || {}; const message = mapping.message || formatStringValue( status ); const type = mapping.type || 'light'; - return ; + return ; }; export default PaymentStatusChip; diff --git a/client/disputes/evidence/index.js b/client/disputes/evidence/index.js index 17501b3c3b0..940c78c865e 100644 --- a/client/disputes/evidence/index.js +++ b/client/disputes/evidence/index.js @@ -455,6 +455,8 @@ export default ( { query } ) => { const [ dispute, setDispute ] = useState(); const [ loading, setLoading ] = useState( false ); const [ evidence, setEvidence ] = useState( {} ); // Evidence to update. + const [ redirectAfterSave, setRedirectAfterSave ] = useState( false ); + const { createSuccessNotice, createErrorNotice, @@ -475,7 +477,7 @@ export default ( { query } ) => { ); const confirmationNavigationCallback = useConfirmNavigation( () => { - if ( pristine ) { + if ( pristine || redirectAfterSave ) { return; } @@ -488,6 +490,7 @@ export default ( { query } ) => { useEffect( confirmationNavigationCallback, [ pristine, confirmationNavigationCallback, + redirectAfterSave, ] ); useEffect( () => { @@ -603,11 +606,6 @@ export default ( { query } ) => { const message = submit ? __( 'Evidence submitted!', 'woocommerce-payments' ) : __( 'Evidence saved!', 'woocommerce-payments' ); - const href = getAdminUrl( { - page: 'wc-admin', - path: '/payments/disputes', - filter: 'awaiting_response', - } ); recordEvent( submit @@ -639,9 +637,20 @@ export default ( { query } ) => { ], } ); - window.location.replace( href ); + setRedirectAfterSave( true ); }; + useEffect( () => { + if ( redirectAfterSave && pristine ) { + const href = getAdminUrl( { + page: 'wc-admin', + path: '/payments/disputes', + filter: 'awaiting_response', + } ); + window.location.replace( href ); + } + }, [ redirectAfterSave, pristine ] ); + const handleSaveError = ( err, submit ) => { recordEvent( submit @@ -690,8 +699,8 @@ export default ( { query } ) => { }, } ); setDispute( updatedDispute ); - handleSaveSuccess( submit ); setEvidence( {} ); + handleSaveSuccess( submit ); updateDisputeInStore( updatedDispute ); } catch ( err ) { handleSaveError( err, submit ); diff --git a/client/payment-details/dispute-details/dispute-notice.tsx b/client/payment-details/dispute-details/dispute-notice.tsx index 9d9edd8a9f7..d2c8b00fbf2 100644 --- a/client/payment-details/dispute-details/dispute-notice.tsx +++ b/client/payment-details/dispute-details/dispute-notice.tsx @@ -64,7 +64,12 @@ const DisputeNotice: React.FC< DisputeNoticeProps > = ( { { createInterpolateElement( sprintf( noticeText, shopperDisputeReason ), { - a: , + a: ( + + ), strong: , } ) } diff --git a/client/payment-details/dispute-details/style.scss b/client/payment-details/dispute-details/style.scss index 60773396b54..b68be2d0a20 100644 --- a/client/payment-details/dispute-details/style.scss +++ b/client/payment-details/dispute-details/style.scss @@ -182,3 +182,17 @@ margin-bottom: 0; } } + +.dispute-notice { + .dispute-notice__link { + display: block; + } +} + +@media screen and ( max-width: $break-small ) { + .wcpay-inline-notice.components-notice + .components-notice__content + a.dispute-notice__link { + white-space: normal; + } +} 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 9fa0098691b..7b4a7e3650f 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 @@ -30,21 +30,25 @@ exports[`Order details page should match the snapshot - Charge without payment i
-

- $15.00 - - USD - + $15.00 + + USD + +

Pending -

+
diff --git a/client/payment-details/summary/index.tsx b/client/payment-details/summary/index.tsx index b7c4ad0b059..1e5b6bfcac9 100644 --- a/client/payment-details/summary/index.tsx +++ b/client/payment-details/summary/index.tsx @@ -258,37 +258,41 @@ const PaymentDetailsSummary: React.FC< PaymentDetailsSummaryProps > = ( {
-

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

+
+

+ + { formattedAmount } + + { charge.currency || 'USD' } + + +

+ { charge.dispute ? ( + + ) : ( + + ) } +
{ renderStorePrice ? (

diff --git a/client/payment-details/summary/style.scss b/client/payment-details/summary/style.scss index ef67a756663..0c6c7775733 100755 --- a/client/payment-details/summary/style.scss +++ b/client/payment-details/summary/style.scss @@ -11,7 +11,7 @@ .payment-details-summary { display: flex; flex: 1; - @include breakpoint( '<660px' ) { + @media screen and ( max-width: $break-medium ) { flex-direction: column; } @@ -19,13 +19,29 @@ flex-grow: 1; } + .payment-details-summary__amount-wrapper { + display: flex; + align-items: center; + } + + @media screen and ( max-width: $break-small ) { + .payment-details-summary__amount-wrapper { + flex-direction: column; + align-items: flex-start; + + .payment-details-summary__status { + order: -1; + } + } + } + .payment-details-summary__amount { @include font-size( 32 ); font-weight: 300; padding: 0; margin: 0; display: flex; - flex-wrap: wrap; + flex-wrap: nowrap; align-items: center; .payment-details-summary__amount-currency { @@ -93,7 +109,7 @@ justify-content: initial; flex-direction: column; flex-wrap: nowrap; - @include breakpoint( '>660px' ) { + @media screen and ( min-width: $break-medium ) { justify-content: flex-start; align-items: flex-end; } diff --git a/client/payment-details/summary/test/__snapshots__/index.test.tsx.snap b/client/payment-details/summary/test/__snapshots__/index.test.tsx.snap index 8286a7941bb..083da902f05 100644 --- a/client/payment-details/summary/test/__snapshots__/index.test.tsx.snap +++ b/client/payment-details/summary/test/__snapshots__/index.test.tsx.snap @@ -26,21 +26,25 @@ exports[`PaymentDetailsSummary capture notification and fraud buttons renders ca

-

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

Payment authorized -

+
@@ -362,21 +366,25 @@ exports[`PaymentDetailsSummary capture notification and fraud buttons renders th
-

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

Needs review -

+
@@ -708,21 +716,25 @@ exports[`PaymentDetailsSummary correctly renders a charge 1`] = `
-

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

Paid -

+
@@ -1029,21 +1041,25 @@ exports[`PaymentDetailsSummary correctly renders when payment intent is missing
-

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

Paid -

+
@@ -1336,21 +1352,25 @@ exports[`PaymentDetailsSummary order missing notice does not render notice if or
-

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

Paid -

+
@@ -1657,21 +1677,25 @@ exports[`PaymentDetailsSummary order missing notice renders notice if order miss
-

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

Paid -

+
@@ -2001,21 +2025,25 @@ exports[`PaymentDetailsSummary renders a charge with subscriptions 1`] = `
-

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

Paid -

+
@@ -2349,21 +2377,25 @@ exports[`PaymentDetailsSummary renders fully refunded information for a charge 1
-

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

Refunded -

+
@@ -2647,19 +2679,23 @@ exports[`PaymentDetailsSummary renders loading state 1`] = `
-

- - - USD - + + + USD + +

-

+
@@ -2887,21 +2923,25 @@ exports[`PaymentDetailsSummary renders partially refunded information for a char
-

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

Partial refund -

+
@@ -3211,21 +3251,25 @@ exports[`PaymentDetailsSummary renders the Tap to Pay channel from metadata 1`]
-

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

Paid -

+
@@ -3532,21 +3576,25 @@ exports[`PaymentDetailsSummary renders the information of a dispute-reversal cha
-

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

Disputed: Won -

+
diff --git a/client/payment-details/test/__snapshots__/index.test.tsx.snap b/client/payment-details/test/__snapshots__/index.test.tsx.snap index 0798e4b5d52..aa8a34effd0 100644 --- a/client/payment-details/test/__snapshots__/index.test.tsx.snap +++ b/client/payment-details/test/__snapshots__/index.test.tsx.snap @@ -30,16 +30,25 @@ exports[`Payment details page should match the snapshot - Charge query param 1`]
-

+

+ + Amount placeholder + +

- Amount placeholder + Paid -

+
@@ -493,21 +502,25 @@ exports[`Payment details page should match the snapshot - Payment Intent query p
-

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

Paid -

+
diff --git a/includes/class-wc-payments-order-service.php b/includes/class-wc-payments-order-service.php index 195dbe115a7..c563877e830 100644 --- a/includes/class-wc-payments-order-service.php +++ b/includes/class-wc-payments-order-service.php @@ -314,15 +314,17 @@ public function mark_order_blocked_for_fraud( $order, $intent_id, $intent_status * @param string $amount The disputed amount – formatted currency value. * @param string $reason The reason for the dispute – human-readable text. * @param string $due_by The deadline for responding to the dispute - formatted date string. + * @param string $status The status of the dispute. * * @return void */ - public function mark_payment_dispute_created( $order, $charge_id, $amount, $reason, $due_by ) { + public function mark_payment_dispute_created( $order, $charge_id, $amount, $reason, $due_by, $status = '' ) { if ( ! is_a( $order, 'WC_Order' ) ) { return; } - $note = $this->generate_dispute_created_note( $charge_id, $amount, $reason, $due_by ); + $is_inquiry = strpos( $status, 'warning_' ) === 0; + $note = $this->generate_dispute_created_note( $charge_id, $amount, $reason, $due_by, $is_inquiry ); if ( $this->order_note_exists( $order, $note ) ) { return; } @@ -346,7 +348,8 @@ public function mark_payment_dispute_closed( $order, $charge_id, $status ) { return; } - $note = $this->generate_dispute_closed_note( $charge_id, $status ); + $is_inquiry = strpos( $status, 'warning_' ) === 0; + $note = $this->generate_dispute_closed_note( $charge_id, $status, $is_inquiry ); if ( $this->order_note_exists( $order, $note ) ) { return; @@ -1643,15 +1646,32 @@ private function generate_fraud_blocked_note( $order ): string { * @param string $amount The disputed amount – formatted currency value. * @param string $reason The reason for the dispute – human-readable text. * @param string $due_by The deadline for responding to the dispute - formatted date string. + * @param bool $is_inquiry Whether the dispute is an inquiry or not. * * @return string Note content. */ - private function generate_dispute_created_note( $charge_id, $amount, $reason, $due_by ) { + private function generate_dispute_created_note( $charge_id, $amount, $reason, $due_by, $is_inquiry = false ) { $dispute_url = $this->compose_dispute_url( $charge_id ); // Get merchant-friendly dispute reason description. $reason = WC_Payments_Utils::get_dispute_reason_description( $reason ); + if ( $is_inquiry ) { + return sprintf( + WC_Payments_Utils::esc_interpolated_html( + /* translators: %1: the disputed amount and currency; %2: the dispute reason; %3 the deadline date for responding to the inquiry */ + __( 'A payment inquiry has been raised for %1$s with reason "%2$s". Response due by %3$s.', 'woocommerce-payments' ), + [ + 'a' => '', + ] + ), + $amount, + $reason, + $due_by, + $dispute_url + ); + } + return sprintf( WC_Payments_Utils::esc_interpolated_html( /* translators: %1: the disputed amount and currency; %2: the dispute reason; %3 the deadline date for responding to dispute */ @@ -1672,15 +1692,31 @@ private function generate_dispute_created_note( $charge_id, $amount, $reason, $d * * @param string $charge_id The ID of the disputed charge associated with this order. * @param string $status The status of the dispute. + * @param bool $is_inquiry Whether the dispute is an inquiry or not. * * @return string Note content. */ - private function generate_dispute_closed_note( $charge_id, $status ) { + private function generate_dispute_closed_note( $charge_id, $status, $is_inquiry = false ) { $dispute_url = $this->compose_dispute_url( $charge_id ); + + if ( $is_inquiry ) { + return sprintf( + WC_Payments_Utils::esc_interpolated_html( + /* translators: %1: the dispute status */ + __( 'Payment inquiry has been closed with status %1$s. See payment status for more details.', 'woocommerce-payments' ), + [ + 'a' => '', + ] + ), + $status, + $dispute_url + ); + } + return sprintf( WC_Payments_Utils::esc_interpolated_html( /* translators: %1: the dispute status */ - __( 'Payment dispute has been closed with status %1$s. See dispute overview for more details.', 'woocommerce-payments' ), + __( 'Dispute has been closed with status %1$s. See dispute overview for more details.', 'woocommerce-payments' ), [ 'a' => '', ] diff --git a/includes/class-wc-payments-webhook-processing-service.php b/includes/class-wc-payments-webhook-processing-service.php index 0cad2ffe950..d9b0333c765 100644 --- a/includes/class-wc-payments-webhook-processing-service.php +++ b/includes/class-wc-payments-webhook-processing-service.php @@ -535,6 +535,7 @@ private function process_webhook_dispute_created( $event_body ) { $reason = $this->read_webhook_property( $event_object, 'reason' ); $amount_raw = $this->read_webhook_property( $event_object, 'amount' ); $evidence = $this->read_webhook_property( $event_object, 'evidence_details' ); + $status = $this->read_webhook_property( $event_object, 'status' ); $due_by = $this->read_webhook_property( $evidence, 'due_by' ); $order = $this->wcpay_db->order_from_charge_id( $charge_id ); @@ -558,7 +559,7 @@ private function process_webhook_dispute_created( $event_body ) { ); } - $this->order_service->mark_payment_dispute_created( $order, $charge_id, $amount, $reason, $due_by ); + $this->order_service->mark_payment_dispute_created( $order, $charge_id, $amount, $reason, $due_by, $status ); // Clear dispute caches to trigger a fetch of new data. $this->database_cache->delete( DATABASE_CACHE::DISPUTE_STATUS_COUNTS_KEY ); diff --git a/tests/unit/test-class-wc-payments-order-service.php b/tests/unit/test-class-wc-payments-order-service.php index d7c6ca45c0d..2eeaa50864e 100644 --- a/tests/unit/test-class-wc-payments-order-service.php +++ b/tests/unit/test-class-wc-payments-order-service.php @@ -867,6 +867,44 @@ public function test_mark_payment_dispute_created() { $this->assertCount( 2, $notes_2 ); } + + /** + * Tests if the payment was updated to show inquiry created. + */ + public function test_mark_payment_dispute_created_for_inquiry() { + // Arrange: Set the charge_id and reason, and the order status. + $charge_id = 'ch_123'; + $amount = '$123.45'; + $reason = 'product_not_received'; + $deadline = 'June 7, 2023'; + $order_status = Order_Status::ON_HOLD; + $dispute_status = 'warning_needs_response'; + + // Act: Attempt to mark payment dispute created. + $this->order_service->mark_payment_dispute_created( $this->order, $charge_id, $amount, $reason, $deadline, $dispute_status ); + + // Assert: Check that the order status was updated to on-hold status. + $this->assertTrue( $this->order->has_status( [ $order_status ] ) ); + + $notes = wc_get_order_notes( [ 'order_id' => $this->order->get_id() ] ); + + // Assert: Check that dispute order note was added with relevant info and link to dispute detail. + $this->assertStringNotContainsString( 'Payment has been disputed', $notes[0]->content ); + $this->assertStringContainsString( 'inquiry', $notes[0]->content ); + $this->assertStringContainsString( $amount, $notes[0]->content ); + $this->assertStringContainsString( 'Product not received', $notes[0]->content ); + $this->assertStringContainsString( $deadline, $notes[0]->content ); + $this->assertStringContainsString( '%2Fpayments%2Ftransactions%2Fdetails&id=ch_123" target="_blank" rel="noopener noreferrer">Response due by', $notes[0]->content ); + + // Assert: Check that order status change note was added. + $this->assertStringContainsString( 'Pending payment to On hold', $notes[1]->content ); + + // Assert: Applying the same data multiple times does not cause duplicate actions. + $this->order_service->mark_payment_dispute_created( $this->order, $charge_id, $amount, $reason, $deadline, $dispute_status ); + $notes_2 = wc_get_order_notes( [ 'order_id' => $this->order->get_id() ] ); + $this->assertCount( 2, $notes_2 ); + } + /** * Tests to make sure mark_payment_dispute_created exits if the order is invalid. */ @@ -909,7 +947,7 @@ public function test_mark_payment_dispute_closed_with_status_won() { // Assert: Check that the notes were updated. $notes = wc_get_order_notes( [ 'order_id' => $this->order->get_id() ] ); $this->assertStringContainsString( 'Pending payment to Completed', $notes[1]->content ); - $this->assertStringContainsString( 'Payment dispute has been closed with status won', $notes[0]->content ); + $this->assertStringContainsString( 'Dispute has been closed with status won', $notes[0]->content ); $this->assertStringContainsString( '%2Fpayments%2Ftransactions%2Fdetails&id=ch_123" target="_blank" rel="noopener noreferrer">dispute overview', $notes[0]->content ); // Assert: Applying the same data multiple times does not cause duplicate actions. @@ -937,7 +975,7 @@ public function test_mark_payment_dispute_closed_with_status_lost() { // Assert: Check that the notes were updated. $notes = wc_get_order_notes( [ 'order_id' => $this->order->get_id() ] ); $this->assertStringContainsString( 'On hold to Refunded', $notes[1]->content ); - $this->assertStringContainsString( 'Payment dispute has been closed with status lost', $notes[0]->content ); + $this->assertStringContainsString( 'Dispute has been closed with status lost', $notes[0]->content ); $this->assertStringContainsString( '%2Fpayments%2Ftransactions%2Fdetails&id=ch_123" target="_blank" rel="noopener noreferrer">dispute overview', $notes[0]->content ); // Assert: Check for created refund, and the amount is correct. @@ -951,6 +989,35 @@ public function test_mark_payment_dispute_closed_with_status_lost() { $this->assertCount( 3, $notes_2 ); } + + /** + * Tests if the order note was added to show inquiry closed. + */ + public function test_mark_payment_dispute_closed_with_status_warning_closed() { + // Arrange: Set the charge_id, dispute status, the order status, and update the order status. + $charge_id = 'ch_123'; + $status = 'warning_closed'; + $order_status = Order_Status::COMPLETED; + $this->order->update_status( Order_Status::ON_HOLD ); // When a dispute is created, the order status is changed to On Hold. + + // Act: Attempt to mark payment dispute created. + $this->order_service->mark_payment_dispute_closed( $this->order, $charge_id, $status ); + + // Assert: Check that the order status was left in on-hold status. + $this->assertTrue( $this->order->has_status( [ $order_status ] ) ); + + // Assert: Check that the notes were updated. + $notes = wc_get_order_notes( [ 'order_id' => $this->order->get_id() ] ); + $this->assertStringNotContainsString( 'Dispute has been closed with status won', $notes[0]->content ); + $this->assertStringContainsString( 'inquiry', $notes[0]->content ); + $this->assertStringContainsString( '%2Fpayments%2Ftransactions%2Fdetails&id=ch_123" target="_blank" rel="noopener noreferrer">payment status', $notes[0]->content ); + + // Assert: Applying the same data multiple times does not cause duplicate actions. + $this->order_service->mark_payment_dispute_closed( $this->order, $charge_id, $status ); + $notes_2 = wc_get_order_notes( [ 'order_id' => $this->order->get_id() ] ); + $this->assertCount( 3, $notes_2 ); + } + /** * Tests to make sure mark_payment_dispute_closed exits if the order is invalid. */ diff --git a/tests/unit/test-class-wc-payments-webhook-processing-service.php b/tests/unit/test-class-wc-payments-webhook-processing-service.php index d376b1491fd..2acb5c318ad 100644 --- a/tests/unit/test-class-wc-payments-webhook-processing-service.php +++ b/tests/unit/test-class-wc-payments-webhook-processing-service.php @@ -1240,6 +1240,7 @@ public function test_dispute_created_order_note() { 'charge' => 'test_charge_id', 'reason' => 'test_reason', 'amount' => 9900, + 'status' => 'test_status', 'evidence_details' => [ 'due_by' => 'test_due_by', ], @@ -1289,7 +1290,7 @@ public function test_dispute_closed_order_note() { ->method( 'add_order_note' ) ->with( $this->matchesRegularExpression( - '/Payment dispute has been closed with status test_status/' + '/Dispute has been closed with status test_status/' ) );