diff --git a/changelog/add-7245-transaction-details-inquiry-steps-to-resolve b/changelog/add-7245-transaction-details-inquiry-steps-to-resolve new file mode 100644 index 00000000000..c6f31448275 --- /dev/null +++ b/changelog/add-7245-transaction-details-inquiry-steps-to-resolve @@ -0,0 +1,5 @@ +Significance: patch +Type: add +Comment: Behind feature flag: add steps to resolve section to the transaction dispute details + + diff --git a/client/payment-details/dispute-details/dispute-awaiting-response-details.tsx b/client/payment-details/dispute-details/dispute-awaiting-response-details.tsx index 394d014df33..b7efafd7e51 100644 --- a/client/payment-details/dispute-details/dispute-awaiting-response-details.tsx +++ b/client/payment-details/dispute-details/dispute-awaiting-response-details.tsx @@ -32,7 +32,7 @@ import { getAdminUrl } from 'wcpay/utils'; import DisputeNotice from './dispute-notice'; import IssuerEvidenceList from './evidence-list'; import DisputeSummaryRow from './dispute-summary-row'; -import DisputeSteps from './dispute-steps'; +import { DisputeSteps, InquirySteps } from './dispute-steps'; import InlineNotice from 'components/inline-notice'; import './style.scss'; @@ -164,8 +164,6 @@ const DisputeAwaitingResponseDetails: React.FC< Props > = ( { const countdownDays = Math.floor( dueBy.diff( now, 'days', true ) ); const hasStagedEvidence = dispute.evidence_details?.has_evidence; const { createErrorNotice } = useDispatch( 'core/notices' ); - // This is a temporary restriction and can be removed once steps and actions for inquiries are implemented. - const showDisputeSteps = ! isInquiry( dispute ); const onModalClose = () => { setModalOpen( false ); @@ -207,18 +205,23 @@ const DisputeAwaitingResponseDetails: React.FC< Props > = ( { ) } ) } - - { showDisputeSteps && ( + + + + { isInquiry( dispute ) ? ( + + ) : ( ) } + diff --git a/client/payment-details/dispute-details/dispute-due-by-date.tsx b/client/payment-details/dispute-details/dispute-due-by-date.tsx new file mode 100644 index 00000000000..00c74ee2745 --- /dev/null +++ b/client/payment-details/dispute-details/dispute-due-by-date.tsx @@ -0,0 +1,52 @@ +/** + * External dependencies + */ +import React from 'react'; +import { dateI18n } from '@wordpress/date'; +import { __, _n, sprintf } from '@wordpress/i18n'; +import classNames from 'classnames'; +import moment from 'moment'; + +const DisputeDueByDate: React.FC< { + dueBy: number; +} > = ( { dueBy } ) => { + const daysRemaining = Math.floor( + moment.unix( dueBy ).diff( moment(), 'days', true ) + ); + const respondByDate = dateI18n( + 'M j, Y, g:ia', + moment( dueBy * 1000 ).toISOString() + ); + return ( + + { respondByDate } + 2, + } ) } + > + { daysRemaining > 0 && + sprintf( + // Translators: %d is the number of days left to respond to the dispute. + _n( + '(%d day left to respond)', + '(%d days left to respond)', + daysRemaining, + 'woocommerce-payments' + ), + daysRemaining + ) } + + { daysRemaining === 0 && + __( '(Last day today)', 'woocommerce-payments' ) } + { daysRemaining < 0 && + __( '(Past due)', 'woocommerce-payments' ) } + + + ); +}; + +export default DisputeDueByDate; diff --git a/client/payment-details/dispute-details/dispute-steps.tsx b/client/payment-details/dispute-details/dispute-steps.tsx index dc6ec5f10f8..e3695c381d3 100644 --- a/client/payment-details/dispute-details/dispute-steps.tsx +++ b/client/payment-details/dispute-details/dispute-steps.tsx @@ -4,13 +4,12 @@ * External dependencies */ import React from 'react'; -import { __, _n, sprintf } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { createInterpolateElement } from '@wordpress/element'; import { ExternalLink } from '@wordpress/components'; import { dateI18n } from '@wordpress/date'; import moment from 'moment'; import HelpOutlineIcon from 'gridicons/dist/help-outline'; -import classNames from 'classnames'; /** * Internal dependencies @@ -20,19 +19,18 @@ import { ChargeBillingDetails } from 'wcpay/types/charges'; import { formatExplicitCurrency } from 'utils/currency'; import { ClickTooltip } from 'wcpay/components/tooltip'; import { getDisputeFeeFormatted } from 'wcpay/disputes/utils'; +import DisputeDueByDate from './dispute-due-by-date'; interface Props { dispute: Dispute; customer: ChargeBillingDetails | null; chargeCreated: number; - daysRemaining: number; } -const DisputeSteps: React.FC< Props > = ( { +export const DisputeSteps: React.FC< Props > = ( { dispute, customer, chargeCreated, - daysRemaining, } ) => { let emailLink; if ( customer?.email ) { @@ -57,7 +55,7 @@ const DisputeSteps: React.FC< Props > = ( { const emailBody = sprintf( // Translators: %1$s is the customer name, %2$s is the dispute date, %3$s is the dispute amount with currency-code e.g. $15 USD, %4$s is the charge date. __( - `Hello %1$s\n\n` + + `Hello %1$s,\n\n` + `We noticed that on %2$s, you disputed a %3$s charge on %4$s. We wanted to contact you to make sure everything was all right with your purchase and see if there's anything else we can do to resolve any problems you might have had.\n\n` + `Alternatively, if the dispute was a mistake, you can easily withdraw it by calling the number on the back of your card. Thank you so much - we appreciate your business and look forward to working with you.`, 'woocommerce-payments' @@ -72,13 +70,6 @@ const DisputeSteps: React.FC< Props > = ( { ) }&body=${ encodeURIComponent( emailBody ) }`; } - const respondByDate = dispute.evidence_details?.due_by - ? dateI18n( - 'M j, Y, g:ia', - moment( dispute.evidence_details?.due_by * 1000 ).toISOString() - ) - : '–'; - return (
@@ -124,15 +115,15 @@ const DisputeSteps: React.FC< Props > = ( {
  • { createInterpolateElement( __( - 'Challenge or accept the dispute by .', + 'Challenge or accept the dispute by .', 'woocommerce-payments' ), { - challengeicon: ( + challengeIcon: ( } buttonLabel={ __( - 'Challenge the dispute', + 'Challenge the dispute tooltip', 'woocommerce-payments' ) } content={ __( @@ -141,11 +132,11 @@ const DisputeSteps: React.FC< Props > = ( { ) } /> ), - accepticon: ( + acceptIcon: ( } buttonLabel={ __( - 'Accept the dispute', + 'Accept the dispute tooltip', 'woocommerce-payments' ) } content={ sprintf( @@ -161,35 +152,10 @@ const DisputeSteps: React.FC< Props > = ( { ) } /> ), - disputeduedate: ( - - { respondByDate } - 2, - } ) } - > - { daysRemaining === 0 - ? __( - '(Last day today)', - 'woocommerce-payments' - ) - : sprintf( - // Translators: %s is the number of days left to respond to the dispute. - _n( - '(%s day left to respond)', - '(%s days left to respond)', - daysRemaining, - 'woocommerce-payments' - ), - daysRemaining - ) } - - + dueByDate: ( + ), } ) } @@ -199,4 +165,114 @@ const DisputeSteps: React.FC< Props > = ( { ); }; -export default DisputeSteps; +export const InquirySteps: React.FC< Props > = ( { + dispute, + customer, + chargeCreated, +} ) => { + let emailLink; + if ( customer?.email ) { + const chargeDate = dateI18n( + 'Y-m-d', + moment( chargeCreated * 1000 ).toISOString() + ); + const disputeDate = dateI18n( + 'Y-m-d', + moment( dispute.created * 1000 ).toISOString() + ); + const emailSubject = sprintf( + // Translators: %1$s is the store name, %2$s is the charge date. + __( + `Problem with your purchase from %1$s on %2$s?`, + 'woocommerce-payments' + ), + wcpaySettings.storeName, + chargeDate + ); + const customerName = customer?.name || ''; + const emailBody = sprintf( + // Translators: %1$s is the customer name, %2$s is the dispute date, %3$s is the dispute amount with currency-code e.g. $15 USD, %4$s is the charge date. + __( + `Hello %1$s,\n\n` + + `We noticed that on %2$s, you disputed a %3$s charge on %4$s. We wanted to contact you to make sure everything was all right with your purchase and see if there's anything else we can do to resolve any problems you might have had.\n\n` + + `Alternatively, if the dispute was a mistake, you can easily withdraw it by calling the number on the back of your card. Thank you so much - we appreciate your business and look forward to working with you.`, + 'woocommerce-payments' + ), + customerName, + disputeDate, + formatExplicitCurrency( dispute.amount, dispute.currency ), + chargeDate + ); + emailLink = `mailto:${ customer.email }?subject=${ encodeURIComponent( + emailSubject + ) }&body=${ encodeURIComponent( emailBody ) }`; + } + + return ( +
    +
    + { __( 'Steps to resolve:', 'woocommerce-payments' ) } +
    +
      +
    1. + { customer?.email + ? createInterpolateElement( + __( + 'Email the customer to identify the issue and work towards a resolution where possible.', + 'woocommerce-payments' + ), + { + a: ( + // eslint-disable-next-line jsx-a11y/anchor-has-content + + ), + } + ) + : __( + 'Email the customer to identify the issue and work towards a resolution where possible.', + 'woocommerce-payments' + ) } +
    2. +
    3. + { createInterpolateElement( + __( + 'Submit evidence or issue a refund by .', + 'woocommerce-payments' + ), + { + submitEvidenceIcon: ( + } + buttonLabel={ __( + 'Submit evidence tooltip', + 'woocommerce-payments' + ) } + content={ createInterpolateElement( + __( + "To submit evidence, provide documentation that supports your case. Keep in mind that submitting evidence doesn't ensure a favorable outcome. If the cardholder agrees to withdraw the inquiry, you'll still need to officially submit your evidence to prevent bank escalation. Learn more", + 'woocommerce-payments' + ), + { + learnMoreLink: ( + + ), + } + ) } + /> + ), + dueByDate: ( + + ), + } + ) } +
    4. +
    +
    + ); +}; diff --git a/client/payment-details/dispute-details/dispute-summary-row.tsx b/client/payment-details/dispute-details/dispute-summary-row.tsx index d288c863c86..dcd2bca4235 100644 --- a/client/payment-details/dispute-details/dispute-summary-row.tsx +++ b/client/payment-details/dispute-details/dispute-summary-row.tsx @@ -6,9 +6,8 @@ import React from 'react'; import moment from 'moment'; import HelpOutlineIcon from 'gridicons/dist/help-outline'; -import { __, _n, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; import { dateI18n } from '@wordpress/date'; -import classNames from 'classnames'; /** * Internal dependencies @@ -21,20 +20,13 @@ import { formatStringValue } from 'wcpay/utils'; import { ClickTooltip } from 'wcpay/components/tooltip'; import Paragraphs from 'wcpay/components/paragraphs'; import { getDisputeDeductedBalanceTransaction } from 'wcpay/disputes/utils'; +import DisputeDueByDate from './dispute-due-by-date'; interface Props { dispute: Dispute; - daysRemaining: number; } -const DisputeSummaryRow: React.FC< Props > = ( { dispute, daysRemaining } ) => { - const respondByDate = dispute.evidence_details?.due_by - ? dateI18n( - 'M j, Y, g:ia', - moment( dispute.evidence_details?.due_by * 1000 ).toISOString() - ) - : '–'; - +const DisputeSummaryRow: React.FC< Props > = ( { dispute } ) => { const disputeReason = formatStringValue( reasons[ dispute.reason ]?.display || dispute.reason ); @@ -105,34 +97,7 @@ const DisputeSummaryRow: React.FC< Props > = ( { dispute, daysRemaining } ) => { { title: __( 'Respond By', 'woocommerce-payments' ), content: ( - - { respondByDate } - 2, - } ) } - > - { daysRemaining > 0 && - sprintf( - // Translators: %s is the number of days left to respond to the dispute. - _n( - '(%s day left to respond)', - '(%s days left to respond)', - daysRemaining, - 'woocommerce-payments' - ), - daysRemaining - ) } - - { daysRemaining === 0 && - __( '(Last day today)', 'woocommerce-payments' ) } - { daysRemaining < 0 && - __( '(Past due)', 'woocommerce-payments' ) } - - + ), }, ]; diff --git a/client/payment-details/summary/test/index.test.tsx b/client/payment-details/summary/test/index.test.tsx index 399f73ee1d8..50d6ec136ba 100755 --- a/client/payment-details/summary/test/index.test.tsx +++ b/client/payment-details/summary/test/index.test.tsx @@ -822,9 +822,16 @@ describe( 'PaymentDetailsSummary', () => { { ignore: '.a11y-speak-region' } ); + // Steps to resolve + screen.getByText( /Steps to resolve/i ); + screen.getByRole( 'link', { + name: /Email the customer/i, + } ); + screen.getByText( /Submit evidence /i ); + // Actions screen.getByRole( 'button', { - name: /Submit evidence/i, + name: /Submit evidence$/i, } ); screen.getByRole( 'button', { name: /Issue refund/i, diff --git a/client/types/disputes.d.ts b/client/types/disputes.d.ts index 5ffd72d0506..8c42cf7315f 100644 --- a/client/types/disputes.d.ts +++ b/client/types/disputes.d.ts @@ -76,7 +76,7 @@ export type DisputeStatus = export interface Dispute { status: DisputeStatus; id: string; - evidence_details?: EvidenceDetails; + evidence_details: EvidenceDetails; metadata: { /* eslint-disable @typescript-eslint/naming-convention -- required to allow underscores in keys */ /**