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 {{link}}pending period{{/link}} in your country',
- 'woocommerce-payments'
- ),
- components: {
- link: (
- // eslint-disable-next-line jsx-a11y/anchor-has-content
-
- ),
- },
- } ) }
-
- -
- { interpolateComponents( {
- mixedString: __(
- "Your account's {{link}}available funds{{/link}}",
- 'woocommerce-payments'
- ),
- components: {
- link: (
- // eslint-disable-next-line jsx-a11y/anchor-has-content
-
- ),
- },
- } ) }
-
- -
- { interpolateComponents( {
- mixedString: __(
- 'Your {{link}}payout schedule{{/link}} settings',
- 'woocommerce-payments'
- ),
- components: {
- link: (
- // eslint-disable-next-line jsx-a11y/anchor-has-content
-
- ),
- },
- } ) }
-
-
+ { 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/'
)
);