Skip to content

Commit

Permalink
Merge branch 'develop' into add/7245-transaction-details-inquiry-step…
Browse files Browse the repository at this point in the history
…s-to-resolve
  • Loading branch information
Jinksi committed Oct 4, 2023
2 parents 68d36bb + e1b3b62 commit 5a4cf65
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 61 deletions.
5 changes: 5 additions & 0 deletions changelog/add-7193-dispute-cta-for-inquries
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: dev
Comment: Add CTA for Inquiries, behind a feature flag.


Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
import React, { useState } from 'react';
import moment from 'moment';
import { __, sprintf } from '@wordpress/i18n';
import { backup, edit, lock } from '@wordpress/icons';
import { backup, edit, lock, arrowRight } from '@wordpress/icons';
import { useDispatch } from '@wordpress/data';
import { createInterpolateElement } from '@wordpress/element';
import { Link } from '@woocommerce/components';
import {
Expand Down Expand Up @@ -39,12 +40,121 @@ interface Props {
dispute: Dispute;
customer: ChargeBillingDetails | null;
chargeCreated: number;
orderUrl: string | undefined;
}

/**
* The lines of text to display in the modal to confirm acceptance / refunding of the dispute / inquiry.
*/
interface ModalLineItem {
icon: JSX.Element;
description: string | JSX.Element;
}

interface AcceptDisputeProps {
/**
* The label for the button that opens the modal.
*/
acceptButtonLabel: string;
/**
* The event to track when the button that opens the modal is clicked.
*/
acceptButtonTracksEvent: string;
/**
* The title of the modal.
*/
modalTitle: string;
/**
* The lines of text to display in the modal.
*/
modalLines: ModalLineItem[];
/**
* The label for the primary button in the modal to Accept / Refund the dispute / inquiry.
*/
modalButtonLabel: string;
/**
* The event to track when the primary button in the modal is clicked.
*/
modalButtonTracksEvent: string;
}

/**
* Disputes and Inquiries have different text for buttons and the modal.
* They also have different icons and tracks events. This function returns the correct props.
*
* @param dispute
*/
function getAcceptDisputeProps( dispute: Dispute ): AcceptDisputeProps {
if ( isInquiry( dispute ) ) {
return {
acceptButtonLabel: __( 'Issue refund', 'woocommerce-payments' ),
acceptButtonTracksEvent:
wcpayTracks.events.DISPUTE_INQUIRY_REFUND_MODAL_VIEW,
modalTitle: __( 'Issue a refund?', 'woocommerce-payments' ),
modalLines: [
{
icon: <Icon icon={ backup } size={ 24 } />,
description: __(
'Issuing a refund will close the inquiry, returning the amount in question back to the cardholder. No additional fees apply.',
'woocommerce-payments'
),
},
{
icon: <Icon icon={ arrowRight } size={ 24 } />,
description: __(
'You will be taken to the order, where you must complete the refund process manually.',
'woocommerce-payments'
),
},
],
modalButtonLabel: __(
'View order to issue refund',
'woocommerce-payments'
),
modalButtonTracksEvent:
wcpayTracks.events.DISPUTE_INQUIRY_REFUND_CLICK,
};
}

return {
acceptButtonLabel: __( 'Accept dispute', 'woocommerce-payments' ),
acceptButtonTracksEvent: wcpayTracks.events.DISPUTE_ACCEPT_MODAL_VIEW,
modalTitle: __( 'Accept the dispute?', 'woocommerce-payments' ),
modalLines: [
{
icon: <Icon icon={ backup } size={ 24 } />,
description: createInterpolateElement(
sprintf(
/* translators: %s: dispute fee, <em>: emphasis HTML element. */
__(
'Accepting the dispute marks it as <em>Lost</em>. The disputed amount will be returned to the cardholder, with a %s dispute fee deducted from your account.',
'woocommerce-payments'
),
getDisputeFeeFormatted( dispute, true ) ?? '-'
),
{
em: <em />,
}
),
},
{
icon: <Icon icon={ lock } size={ 24 } />,
description: __(
'This action is final and cannot be undone.',
'woocommerce-payments'
),
},
],
modalButtonLabel: __( 'Accept dispute', 'woocommerce-payments' ),
modalButtonTracksEvent: wcpayTracks.events.DISPUTE_ACCEPT_CLICK,
};
}

const DisputeAwaitingResponseDetails: React.FC< Props > = ( {
dispute,
customer,
chargeCreated,
orderUrl,
} ) => {
const { doAccept, isLoading } = useDisputeAccept( dispute );
const [ isModalOpen, setModalOpen ] = useState( false );
Expand All @@ -53,13 +163,32 @@ const DisputeAwaitingResponseDetails: React.FC< Props > = ( {
const dueBy = moment.unix( dispute.evidence_details?.due_by ?? 0 );
const countdownDays = Math.floor( dueBy.diff( now, 'days', true ) );
const hasStagedEvidence = dispute.evidence_details?.has_evidence;
// This is a temporary restriction and can be removed once actions for inquiries are implemented.
const showDisputeActions = ! isInquiry( dispute );
const { createErrorNotice } = useDispatch( 'core/notices' );

const onModalClose = () => {
setModalOpen( false );
};

const viewOrder = () => {
if ( orderUrl ) {
window.location.href = orderUrl;
return;
}

createErrorNotice(
__(
'Unable to view order. Order not found.',
'woocommerce-payments'
)
);
};

const disputeAcceptAction = getAcceptDisputeProps( dispute );

const challengeButtonDefaultText = isInquiry( dispute )
? __( 'Submit evidence', 'woocommerce-payments' )
: __( 'Challenge dispute', 'woocommerce-payments' );

return (
<div className="transaction-details-dispute-details-wrapper">
<Card>
Expand All @@ -76,6 +205,7 @@ const DisputeAwaitingResponseDetails: React.FC< Props > = ( {
) }
</InlineNotice>
) }

<DisputeSummaryRow dispute={ dispute } />

{ isInquiry( dispute ) ? (
Expand All @@ -97,7 +227,7 @@ const DisputeAwaitingResponseDetails: React.FC< Props > = ( {
/>

{ /* Dispute Actions */ }
{ showDisputeActions && (
{
<div className="transaction-details-dispute-details-body__actions">
<Link
href={
Expand Down Expand Up @@ -130,10 +260,7 @@ const DisputeAwaitingResponseDetails: React.FC< Props > = ( {
'Continue with challenge',
'woocommerce-payments'
)
: __(
'Challenge dispute',
'woocommerce-payments'
) }
: challengeButtonDefaultText }
</Button>
</Link>

Expand All @@ -142,24 +269,21 @@ const DisputeAwaitingResponseDetails: React.FC< Props > = ( {
disabled={ isLoading }
onClick={ () => {
wcpayTracks.recordEvent(
wcpayTracks.events
.DISPUTE_ACCEPT_MODAL_VIEW,
disputeAcceptAction.acceptButtonTracksEvent,
{
dispute_status: dispute.status,
}
);
setModalOpen( true );
} }
>
{ __(
'Accept dispute',
'woocommerce-payments'
) }
{ disputeAcceptAction.acceptButtonLabel }
</Button>

{ /** Accept dispute modal */ }
{ isModalOpen && (
<Modal
title="Accept the dispute?"
title={ disputeAcceptAction.modalTitle }
onRequestClose={ onModalClose }
className="transaction-details-dispute-accept-modal"
>
Expand All @@ -171,40 +295,19 @@ const DisputeAwaitingResponseDetails: React.FC< Props > = ( {
) }
</strong>
</p>
<Flex justify="start">
<FlexItem className="transaction-details-dispute-accept-modal__icon">
<Icon icon={ backup } size={ 24 } />
</FlexItem>
<FlexItem>
{ createInterpolateElement(
sprintf(
/* translators: %s: dispute fee, <em>: emphasis HTML element. */
__(
'Accepting the dispute marks it as <em>Lost</em>. The disputed amount will be returned to the cardholder, with a %s dispute fee deducted from your account.',
'woocommerce-payments'
),
getDisputeFeeFormatted(
dispute,
true
) ?? '-'
),
{
em: <em />,
}
) }
</FlexItem>
</Flex>
<Flex justify="start">
<FlexItem className="transaction-details-dispute-accept-modal__icon">
<Icon icon={ lock } size={ 24 } />
</FlexItem>
<FlexItem>
{ __(
'Accepting the dispute is final and cannot be undone.',
'woocommerce-payments'
) }
</FlexItem>
</Flex>

{ disputeAcceptAction.modalLines.map(
( line, key ) => (
<Flex justify="start" key={ key }>
<FlexItem className="transaction-details-dispute-accept-modal__icon">
{ line.icon }
</FlexItem>
<FlexItem>
{ line.description }
</FlexItem>
</Flex>
)
) }

<Flex
className="transaction-details-dispute-accept-modal__actions"
Expand All @@ -223,27 +326,33 @@ const DisputeAwaitingResponseDetails: React.FC< Props > = ( {
variant="primary"
onClick={ () => {
wcpayTracks.recordEvent(
wcpayTracks.events
.DISPUTE_ACCEPT_CLICK,
disputeAcceptAction.modalButtonTracksEvent,
{
dispute_status:
dispute.status,
}
);
setModalOpen( false );
doAccept();
/**
* Handle the primary modal action.
* If it's an inquiry, redirect to the order page; otherwise, continue with the default dispute acceptance.
*/
if ( isInquiry( dispute ) ) {
viewOrder();
} else {
doAccept();
}
} }
>
{ __(
'Accept dispute',
'woocommerce-payments'
) }
{
disputeAcceptAction.modalButtonLabel
}
</Button>
</Flex>
</Modal>
) }
</div>
) }
}
</CardBody>
</Card>
</div>
Expand Down
6 changes: 3 additions & 3 deletions client/payment-details/dispute-details/dispute-steps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export const DisputeSteps: React.FC< Props > = ( {
<ClickTooltip
buttonIcon={ <HelpOutlineIcon /> }
buttonLabel={ __(
'Challenge the dispute',
'Challenge the dispute tooltip',
'woocommerce-payments'
) }
content={ __(
Expand All @@ -136,7 +136,7 @@ export const DisputeSteps: React.FC< Props > = ( {
<ClickTooltip
buttonIcon={ <HelpOutlineIcon /> }
buttonLabel={ __(
'Accept the dispute',
'Accept the dispute tooltip',
'woocommerce-payments'
) }
content={ sprintf(
Expand Down Expand Up @@ -248,7 +248,7 @@ export const InquirySteps: React.FC< Props > = ( {
<ClickTooltip
buttonIcon={ <HelpOutlineIcon /> }
buttonLabel={ __(
'Submit evidence',
'Submit evidence tooltip',
'woocommerce-payments'
) }
content={ createInterpolateElement(
Expand Down
1 change: 1 addition & 0 deletions client/payment-details/summary/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,7 @@ const PaymentDetailsSummary: React.FC< PaymentDetailsSummaryProps > = ( {
dispute={ charge.dispute }
customer={ charge.billing_details }
chargeCreated={ charge.created }
orderUrl={ charge.order?.url }
/>
) : (
<DisputeResolutionFooter dispute={ charge.dispute } />
Expand Down
Loading

0 comments on commit 5a4cf65

Please sign in to comment.