Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dispute steps to resolve to the transaction details page. #7206

Merged
merged 33 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
bb6ba35
Pass store name in wcpaySettings because it will be needed as part of…
shendy-a8c Sep 13, 2023
3088de5
Steps to resolve section in the dispute details section on transactio…
shendy-a8c Sep 13, 2023
284243f
Challenge and accept icons for the 4th step from the Steps to resolve…
shendy-a8c Sep 13, 2023
18f2fa5
Add comment to translator explaining that %s is formatted currency am…
shendy-a8c Sep 13, 2023
5a3cbe5
Show dispute response due by date at the 4th step of the Steps to res…
shendy-a8c Sep 13, 2023
53c6679
Changelog.
shendy-a8c Sep 13, 2023
f9c94f9
Positioning the click tool icons more correctly.
shendy-a8c Sep 13, 2023
45e25fa
Add TODO to add link to issuer's evidence files link.
shendy-a8c Sep 13, 2023
63bd71f
Merge branch 'develop' into add/6925-steps-to-resolve
shendy-a8c Sep 13, 2023
6fe270b
Add TODO to link 'guidance on dispute withdrawal' to the docs when it…
shendy-a8c Sep 13, 2023
d28219e
Remove test CSS class.
shendy-a8c Sep 13, 2023
04ee3f6
Show dispute fee instead of dispute amount in the tooltip that explai…
shendy-a8c Sep 13, 2023
e94f356
Merge branch 'develop' into add/6925-steps-to-resolve
shendy-a8c Sep 17, 2023
a7ddf94
Add link to dispute withdrawal docs.
shendy-a8c Sep 17, 2023
116a8db
Handle when customer's name is null.
shendy-a8c Sep 17, 2023
ad54e79
Merge branch 'develop' into add/6925-steps-to-resolve
brucealdridge Sep 20, 2023
6e2b7ff
Merge branch 'develop' into add/6925-steps-to-resolve
Jinksi Sep 20, 2023
d9d006e
Update list style to match design
Jinksi Sep 20, 2023
e8a83fe
Remove "Review the claim" step, out of PR scope
Jinksi Sep 21, 2023
f9126c5
Don't show steps for inquiries
Jinksi Sep 21, 2023
d105ead
Fix card child padding when steps not rendered (inquiries)
Jinksi Sep 21, 2023
19545bd
Add "external link" icon to guidance link to match design
Jinksi Sep 21, 2023
973a63c
Add/update tests for steps to resolve
Jinksi Sep 21, 2023
64556a1
Minor code formatting
Jinksi Sep 21, 2023
0b768b3
Fix tooltip shifting neighboring elements bug
Jinksi Sep 21, 2023
6a55603
Add tooltip component vertical-align as a sensible default
Jinksi Sep 21, 2023
3d35ecc
Add dispute fee to accept tooltip
Jinksi Sep 21, 2023
787b8d9
Make email subject and body translatable
Jinksi Sep 21, 2023
341ef80
Fix typo in translatable string domain
Jinksi Sep 21, 2023
0498f7a
Merge branch 'develop' into add/6925-steps-to-resolve
shendy-a8c Sep 21, 2023
4a10c2a
Add comment for temp inquiry restriction
Jinksi Sep 21, 2023
9275269
Improve CSS comment grammar
Jinksi Sep 21, 2023
71a3349
Merge branch 'develop' into add/6925-steps-to-resolve
Jinksi Sep 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelog/add-6925-steps-to-resolve
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: dev
Comment: This work is part of a UI improvements to increase disputes response that is behind a feature flag. A changelog entry will be added to represent the work as a whole.


1 change: 1 addition & 0 deletions client/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ declare global {
isWooPayStoreCountryAvailable: boolean;
isSubscriptionsPluginActive: boolean;
isStripeBillingEligible: boolean;
storeName: string;
};

const wcTracks: any;
Expand Down
189 changes: 189 additions & 0 deletions client/payment-details/dispute-details/dispute-steps.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/** @format **/

/**
* External dependencies
*/
import React from 'react';
import { __, _n, sprintf } from '@wordpress/i18n';
import { createInterpolateElement } from '@wordpress/element';
import { dateI18n } from '@wordpress/date';
import moment from 'moment';
import HelpOutlineIcon from 'gridicons/dist/help-outline';
import classNames from 'classnames';

/**
* Internal dependencies
*/
import type { Dispute } from 'wcpay/types/disputes';
import { ChargeBillingDetails } from 'wcpay/types/charges';
import { formatExplicitCurrency } from 'utils/currency';
import { ClickTooltip } from 'wcpay/components/tooltip';

interface Props {
dispute: Dispute;
customer: ChargeBillingDetails | null;
chargeCreated: number;
daysRemaining: number;
}

const DisputeSteps: React.FC< Props > = ( {
dispute,
customer,
chargeCreated,
daysRemaining,
} ) => {
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 = `Problem with your purchase from ${ wcpaySettings.storeName } on ${ chargeDate }?`;
Copy link
Contributor Author

@shendy-a8c shendy-a8c Sep 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intentionally do not translate the email subject and body because it is a bit weird to translate a content that is for the customer who might not speak the same language as merchant.

What do you think @souravdebnath1986 @nikkivias? Should we send this email as merchant's language or in English? Or maybe both as I've seen that practice (sending in 2 languages in one email) a lot, but if so, what about the subject?

Copy link
Contributor

@haszari haszari Sep 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep it simple? This is site content, so it should be translatable. Just like content on pages, buttons, or in site emails.

The merchant has control of all of this. The default experience should not have mixed languages IMO.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note – this will not send an email, just generate a draft email in merchant's email client.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, if a multilingual merchant wants to send a localised "is everything ok with your purchase" email, they can edit in their mail client of choice or use another mailing solution such as MailPoet etc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made the email subject and body translatable in 787b8d9 to let the merchant decide what language to use.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think to default to the merchant's language is better than to suddenly see English if they have localized configuration.

const emailBody =
`Hello ${ customer?.name }\n\n` +
shendy-a8c marked this conversation as resolved.
Show resolved Hide resolved
`We noticed that on ${ disputeDate }, you disputed a ${ formatExplicitCurrency(
dispute.amount,
dispute.currency
) } from ${ chargeDate }. 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 could 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.`;

emailLink = `mailto:${ customer.email }?subject=${ encodeURIComponent(
Copy link
Contributor Author

@shendy-a8c shendy-a8c Sep 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wanted to use querystring.strinfigy() but I don't see it's used at all in the codebase, so I just use encodeURIComponent() to escape each param's value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has anyone tested clicking the link correctly works with at least one email client? If there's any issue with escaping or protocol then that would catch it! I have this behaviour disabled on my mac (because I use GMail), so wasn't able to test.

Noting so we can test in our test swarm today e.g. on mobile or if someone has mailto configured right :)

emailSubject
) }&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 (
<div className="dispute-steps">
<div className="dispute-steps__header">
{ __( 'Steps to resolve:', 'woocommercts' ) }
</div>
<ol className="dispute-steps__steps">
<li>
{
// TODO: add link to the issuer evidence files link. See https://github.com/Automattic/woocommerce-payments/pull/7192.
Jinksi marked this conversation as resolved.
Show resolved Hide resolved
__(
"Review the claim issued by the cardholder's bank.",
'woocommerce-payments'
)
}
</li>
<li>
{ customer?.email
? createInterpolateElement(
__(
'<a>Email the customer</a> to address their concerns.',
'woocommerce-payments'
),
{
a: (
// eslint-disable-next-line jsx-a11y/anchor-has-content
<a
target="_blank"
rel="noopener noreferrer"
href={ emailLink }
/>
),
}
)
: __(
'Email the customer to address their concerns.',
'woocommerce-payments'
) }
</li>
<li>
{
// TODO: link 'guidance on dispute withdrawal' to the docs when it's ready.
shendy-a8c marked this conversation as resolved.
Show resolved Hide resolved
__(
'Provide guidance on dispute withdrawal if the customer agrees.',
'woocommerce-payments'
)
}
</li>
<li>
{ createInterpolateElement(
__(
'Challenge <challengeicon/> or accept <accepticon/> the dispute by <disputeduedate/>.',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found these inline icon tooltips a bit weird, they break the flow of the sentence. It's functioning well though and super clear and usable so not a blocker.

Maybe that info could be in a single help popup, nearer the action buttons. Thinking out loud – @nikkivias is our UX expert here.

'woocommerce-payments'
),
{
challengeicon: (
<ClickTooltip
buttonIcon={ <HelpOutlineIcon /> }
buttonLabel={ __(
'Challenge the dispute',
'woocommerce-payments'
) }
content={ __(
"Challenge the dispute if you consider the claim invalid. You'll need to provide evidence to back your claim. Keep in mind that challenging doesn't ensure a resolution in your favor.",
'woocommerce-payments'
) }
/>
),
accepticon: (
<ClickTooltip
buttonIcon={ <HelpOutlineIcon /> }
buttonLabel={ __(
'Accept the dispute',
'woocommerce-payments'
) }
content={ sprintf(
// Translators: %s is a formatted currency amount, eg $10.00.
__(
`Accepting this dispute will automatically close it. Your account will be charged a %s fee, and the disputed amount will be refunded to the cardholder.`,
'woocommerce-payments'
),
// TODO: use getDisputeFee() from https://github.com/Automattic/woocommerce-payments/pull/7118.
Jinksi marked this conversation as resolved.
Show resolved Hide resolved
''
) }
/>
),
disputeduedate: (
<span className="dispute-steps__steps__response-date">
Copy link
Contributor Author

@shendy-a8c shendy-a8c Sep 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied this from DisputeSummaryRow's so it looks consistent.

One doubt I have is that in the design paJDYF-9Ip-p2, when due date is still more than 7-day away, it does not show the ({n} days left to respond) part.

Screenshot 2023-09-14 at 07 16 13

but I see in DisputeSummaryRow, it shows that text, so for consistency sake, I follow DisputeSummaryRow.

{ respondByDate }
<span
className={ classNames( {
'dispute-steps__steps__response-date--urgent':
daysRemaining < 3,
'dispute-steps__steps__response-date--warning':
daysRemaining < 7 &&
daysRemaining > 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
) }
</span>
</span>
),
}
) }
</li>
</ol>
</div>
);
};

export default DisputeSteps;
16 changes: 15 additions & 1 deletion client/payment-details/dispute-details/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,22 @@ import type { Dispute } from 'wcpay/types/disputes';
import { isAwaitingResponse } from 'wcpay/disputes/utils';
import DisputeNotice from './dispute-notice';
import DisputeSummaryRow from './dispute-summary-row';
import DisputeSteps from './dispute-steps';
import InlineNotice from 'components/inline-notice';
import './style.scss';
import { ChargeBillingDetails } from 'wcpay/types/charges';

interface DisputeDetailsProps {
dispute: Dispute;
customer: ChargeBillingDetails | null;
chargeCreated: number;
}

const DisputeDetails: React.FC< DisputeDetailsProps > = ( { dispute } ) => {
const DisputeDetails: React.FC< DisputeDetailsProps > = ( {
dispute,
customer,
chargeCreated,
} ) => {
const now = moment();
const dueBy = moment.unix( dispute.evidence_details?.due_by ?? 0 );
const countdownDays = Math.floor( dueBy.diff( now, 'days', true ) );
Expand Down Expand Up @@ -55,6 +63,12 @@ const DisputeDetails: React.FC< DisputeDetailsProps > = ( { dispute } ) => {
dispute={ dispute }
daysRemaining={ countdownDays }
/>
<DisputeSteps
dispute={ dispute }
customer={ customer }
chargeCreated={ chargeCreated }
daysRemaining={ countdownDays }
/>
</>
) }
</CardBody>
Expand Down
38 changes: 38 additions & 0 deletions client/payment-details/dispute-details/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,41 @@
margin-bottom: 8px;
}
}
.dispute-steps {
&__header {
font-weight: 600;
}
&__steps {
list-style-position: inside;
margin-left: 0;

.wcpay-tooltip__content-wrapper.wcpay-tooltip--click__content-wrapper
> .wcpay-tooltip__content-wrapper
> [role='button'] {
margin-left: 0;
margin-right: 0;
vertical-align: text-bottom;
Jinksi marked this conversation as resolved.
Show resolved Hide resolved
}

.wcpay-tooltip__content-wrapper.wcpay-tooltip--click__content-wrapper
> .wcpay-tooltip__content-wrapper
+ div {
display: inline;
Jinksi marked this conversation as resolved.
Show resolved Hide resolved
}

&__response-date {
display: inline-flex;
align-items: center;
gap: var( --grid-unit-05, 4px );
flex-wrap: wrap;
&--warning {
color: $wp-yellow-30;
font-weight: 700;
}
&--urgent {
font-weight: 700;
color: $alert-red;
}
}
}
}
16 changes: 14 additions & 2 deletions client/payment-details/dispute-details/test/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,13 @@ describe( 'DisputeDetails', () => {
} );
test( 'correctly renders dispute details', () => {
const charge = getBaseCharge();
render( <DisputeDetails dispute={ charge.dispute } /> );
render(
<DisputeDetails
dispute={ charge.dispute }
customer={ charge.billing_details }
chargeCreated={ charge.created }
/>
);

// Expect this warning to be logged to the console
expect( console ).toHaveWarnedWith(
Expand Down Expand Up @@ -187,7 +193,13 @@ describe( 'DisputeDetails', () => {
submission_count: 0,
};

render( <DisputeDetails dispute={ charge.dispute } /> );
render(
<DisputeDetails
dispute={ charge.dispute }
customer={ charge.billing_details }
chargeCreated={ charge.created }
/>
);

screen.getByText(
/The cardholder claims this is an unauthorized transaction/,
Expand Down
6 changes: 5 additions & 1 deletion client/payment-details/summary/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,11 @@ const PaymentDetailsSummary: React.FC< PaymentDetailsSummaryProps > = ( {
</LoadableBlock>
</CardBody>
{ isDisputeOnTransactionPageEnabled && charge.dispute && (
<DisputeDetails dispute={ charge.dispute } />
<DisputeDetails
dispute={ charge.dispute }
customer={ charge.billing_details }
chargeCreated={ charge.created }
/>
) }
{ isAuthAndCaptureEnabled &&
authorization &&
Expand Down
1 change: 1 addition & 0 deletions includes/admin/class-wc-payments-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,7 @@ private function get_js_settings(): array {
'isWooPayStoreCountryAvailable' => WooPay_Utilities::is_store_country_available(),
'isStripeBillingEnabled' => WC_Payments_Features::is_stripe_billing_enabled(),
'isStripeBillingEligible' => WC_Payments_Features::is_stripe_billing_eligible(),
'storeName' => get_bloginfo( 'name' ),
];

return apply_filters( 'wcpay_js_settings', $this->wcpay_js_settings );
Expand Down
Loading