+
diff --git a/client/deposits/list/index.tsx b/client/deposits/list/index.tsx
index 1ac643d31dc..dc3e20ac02a 100644
--- a/client/deposits/list/index.tsx
+++ b/client/deposits/list/index.tsx
@@ -6,9 +6,7 @@
import React, { useState } from 'react';
import { recordEvent } from 'tracks';
import { useMemo } from '@wordpress/element';
-import { dateI18n } from '@wordpress/date';
import { __, _n, sprintf } from '@wordpress/i18n';
-import moment from 'moment';
import { TableCard, Link } from '@woocommerce/components';
import { onQueryChange, getQuery } from '@woocommerce/navigation';
import {
@@ -48,6 +46,7 @@ import CSVExportModal from 'components/csv-export-modal';
import { ReportingExportLanguageHook } from 'wcpay/settings/reporting-settings/interfaces';
import './style.scss';
+import { formatDateTimeFromString } from 'wcpay/utils/date-time';
const getColumns = ( sortByDate?: boolean ): DepositsTableHeader[] => [
{
@@ -140,11 +139,7 @@ export const DepositsList = (): JSX.Element => {
href={ getDetailsURL( deposit.id, 'payouts' ) }
onClick={ () => recordEvent( 'wcpay_deposits_row_click' ) }
>
- { dateI18n(
- 'M j, Y',
- moment.utc( deposit.date ).toISOString(),
- true // TODO Change call to gmdateI18n and remove this deprecated param once WP 5.4 support ends.
- ) }
+ { formatDateTimeFromString( deposit.date ) }
);
@@ -335,11 +330,7 @@ export const DepositsList = (): JSX.Element => {
row[ 0 ],
{
...row[ 1 ],
- value: dateI18n(
- 'Y-m-d',
- moment.utc( row[ 1 ].value ).toISOString(),
- true
- ),
+ value: formatDateTimeFromString( row[ 1 ].value as string ),
},
...row.slice( 2 ),
] );
diff --git a/client/deposits/list/test/__snapshots__/index.tsx.snap b/client/deposits/list/test/__snapshots__/index.tsx.snap
index 07945cd0c64..c26a364fd04 100644
--- a/client/deposits/list/test/__snapshots__/index.tsx.snap
+++ b/client/deposits/list/test/__snapshots__/index.tsx.snap
@@ -361,7 +361,7 @@ exports[`Deposits list renders correctly a single deposit 1`] = `
data-link-type="wc-admin"
href="admin.php?page=wc-admin&path=%2Fpayments%2Fpayouts%2Fdetails&id=po_mock1"
>
- Jan 2, 2020
+ Jan 2 2020
- Jan 3, 2020
+ Jan 3 2020
|
- Jan 4, 2020
+ Jan 4 2020
|
- Jan 2, 2020
+ Jan 2 2020
|
- Jan 3, 2020
+ Jan 3 2020
|
- Jan 4, 2020
+ Jan 4 2020
|
{
reporting: {
exportModalDismissed: true,
},
+ dateFormat: 'M j Y',
};
} );
@@ -321,7 +323,7 @@ describe( 'Deposits list', () => {
// 2. The indexOf check in amount's expect is because the amount in CSV may not contain
// trailing zeros as in the display amount.
//
- expect( formatDate( csvFirstDeposit[ 1 ], 'M j, Y' ) ).toBe(
+ expect( csvFirstDeposit[ 1 ].replace( /^"|"$/g, '' ) ).toBe(
displayFirstDeposit[ 0 ]
); // date
expect( csvFirstDeposit[ 2 ] ).toBe( displayFirstDeposit[ 1 ] ); // type
diff --git a/client/deposits/utils/index.ts b/client/deposits/utils/index.ts
index 3d8fd6276e1..05f65c46bc3 100644
--- a/client/deposits/utils/index.ts
+++ b/client/deposits/utils/index.ts
@@ -2,21 +2,15 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
-import { dateI18n } from '@wordpress/date';
import moment from 'moment';
-
-const formatDate = ( format: string, date: number | string ) =>
- dateI18n(
- format,
- moment.utc( date ).toISOString(),
- true // TODO Change call to gmdateI18n and remove this deprecated param once WP 5.4 support ends.
- );
+import { formatDateTimeFromString } from 'wcpay/utils/date-time';
interface DepositObject {
date: number | string;
}
+
export const getDepositDate = ( deposit?: DepositObject | null ): string =>
- deposit ? formatDate( 'F j, Y', deposit?.date ) : '—';
+ deposit ? formatDateTimeFromString( deposit?.date as string ) : '—';
interface GetDepositMonthlyAnchorLabelProps {
monthlyAnchor: number;
diff --git a/client/deposits/utils/test/index.ts b/client/deposits/utils/test/index.ts
index d0361137104..e1957ce4564 100644
--- a/client/deposits/utils/test/index.ts
+++ b/client/deposits/utils/test/index.ts
@@ -8,7 +8,29 @@ import momentLib from 'moment';
*/
import { getDepositDate, getDepositMonthlyAnchorLabel } from '../';
+declare const global: {
+ wcpaySettings: {
+ dateFormat: string;
+ };
+};
+
+// Mock dateI18n
+jest.mock( '@wordpress/date', () => ( {
+ dateI18n: jest.fn( ( format, date ) => {
+ return jest
+ .requireActual( '@wordpress/date' )
+ .dateI18n( format, date, 'UTC' ); // Ensure UTC is used
+ } ),
+} ) );
+
describe( 'Deposits Overview Utils / getDepositDate', () => {
+ beforeEach( () => {
+ jest.clearAllMocks();
+ global.wcpaySettings = {
+ dateFormat: 'F j, Y',
+ };
+ } );
+
test( 'returns a display value without a deposit', () => {
expect( getDepositDate() ).toEqual( '—' );
} );
diff --git a/client/disputes/evidence/test/index.js b/client/disputes/evidence/test/index.js
index e9ccdb826cb..142ee738ab8 100644
--- a/client/disputes/evidence/test/index.js
+++ b/client/disputes/evidence/test/index.js
@@ -96,6 +96,7 @@ describe( 'Dispute evidence form', () => {
global.wcpaySettings = {
restUrl: 'http://example.com/wp-json/',
+ dateFormat: 'M j, Y',
};
} );
afterEach( () => {
@@ -190,6 +191,8 @@ describe( 'Dispute evidence page', () => {
precision: 2,
},
},
+ dateFormat: 'M j, Y',
+ timeFormat: 'g:iA',
};
} );
diff --git a/client/disputes/index.tsx b/client/disputes/index.tsx
index cdb85131f5d..6e8c4d2d61b 100644
--- a/client/disputes/index.tsx
+++ b/client/disputes/index.tsx
@@ -5,7 +5,6 @@
*/
import React, { useState } from 'react';
import { recordEvent } from 'tracks';
-import { dateI18n } from '@wordpress/date';
import { _n, __, sprintf } from '@wordpress/i18n';
import moment from 'moment';
import { Button } from '@wordpress/components';
@@ -56,8 +55,9 @@ import { useSettings } from 'wcpay/data';
import { isAwaitingResponse } from 'wcpay/disputes/utils';
import CSVExportModal from 'components/csv-export-modal';
import { ReportingExportLanguageHook } from 'wcpay/settings/reporting-settings/interfaces';
-
+import DateFormatNotice from 'wcpay/components/date-format-notice';
import './style.scss';
+import { formatDateTimeFromString } from 'wcpay/utils/date-time';
const getHeaders = ( sortColumn?: string ): DisputesTableHeader[] => [
{
@@ -201,10 +201,9 @@ const smartDueDate = ( dispute: CachedDispute ) => {
);
}
- return dateI18n(
- 'M j, Y / g:iA',
- moment.utc( dispute.due_by ).local().toISOString()
- );
+ return formatDateTimeFromString( dispute.due_by, {
+ includeTime: true,
+ } );
};
export const DisputesList = (): JSX.Element => {
@@ -301,10 +300,9 @@ export const DisputesList = (): JSX.Element => {
created: {
value: dispute.created,
display: clickable(
- dateI18n(
- 'M j, Y',
- moment( dispute.created ).toISOString()
- )
+ formatDateTimeFromString( dispute.created, {
+ includeTime: true,
+ } )
),
},
dueBy: {
@@ -485,17 +483,18 @@ export const DisputesList = (): JSX.Element => {
{
// Disputed On.
...row[ 10 ],
- value: dateI18n(
- 'Y-m-d',
- moment( row[ 10 ].value ).toISOString()
+ value: formatDateTimeFromString(
+ row[ 10 ].value as string
),
},
{
// Respond by.
...row[ 11 ],
- value: dateI18n(
- 'Y-m-d / g:iA',
- moment( row[ 11 ].value ).toISOString()
+ value: formatDateTimeFromString(
+ row[ 11 ].value as string,
+ {
+ includeTime: true,
+ }
),
},
];
@@ -553,6 +552,7 @@ export const DisputesList = (): JSX.Element => {
return (
+
{
precision: 2,
},
},
+ dateFormat: 'M j, Y',
+ timeFormat: 'g:iA',
};
} );
diff --git a/client/disputes/test/__snapshots__/index.tsx.snap b/client/disputes/test/__snapshots__/index.tsx.snap
index 06459d236e4..b3fae6b5d47 100644
--- a/client/disputes/test/__snapshots__/index.tsx.snap
+++ b/client/disputes/test/__snapshots__/index.tsx.snap
@@ -5,6 +5,54 @@ exports[`Disputes list renders correctly 1`] = `
+
diff --git a/client/disputes/test/index.tsx b/client/disputes/test/index.tsx
index 1409bfc852d..37bbd2e93af 100644
--- a/client/disputes/test/index.tsx
+++ b/client/disputes/test/index.tsx
@@ -17,13 +17,14 @@ import {
useReportingExportLanguage,
useSettings,
} from 'data/index';
-import { formatDate, getUnformattedAmount } from 'wcpay/utils/test-utils';
+import { getUnformattedAmount } from 'wcpay/utils/test-utils';
import React from 'react';
import {
CachedDispute,
DisputeReason,
DisputeStatus,
} from 'wcpay/types/disputes';
+import { formatDateTimeFromString } from 'wcpay/utils/date-time';
jest.mock( '@woocommerce/csv-export', () => {
const actualModule = jest.requireActual( '@woocommerce/csv-export' );
@@ -100,6 +101,8 @@ declare const global: {
reporting?: {
exportModalDismissed: boolean;
};
+ dateFormat?: string;
+ timeFormat?: string;
};
};
@@ -198,6 +201,8 @@ describe( 'Disputes list', () => {
reporting: {
exportModalDismissed: true,
},
+ dateFormat: 'Y-m-d',
+ timeFormat: 'g:iA',
};
} );
@@ -363,8 +368,10 @@ describe( 'Disputes list', () => {
`"${ displayFirstDispute[ 5 ] }"`
); // customer
- expect( formatDate( csvFirstDispute[ 11 ], 'Y-m-d / g:iA' ) ).toBe(
- formatDate( displayFirstDispute[ 6 ], 'Y-m-d / g:iA' )
+ expect( csvFirstDispute[ 11 ].replace( /^"|"$/g, '' ) ).toBe(
+ formatDateTimeFromString( mockDisputes[ 0 ].due_by, {
+ includeTime: true,
+ } )
); // date respond by
} );
} );
diff --git a/client/documents/index.tsx b/client/documents/index.tsx
index 07f75e99ddf..c95c9d3a6ba 100644
--- a/client/documents/index.tsx
+++ b/client/documents/index.tsx
@@ -9,10 +9,11 @@ import React from 'react';
import Page from 'components/page';
import DocumentsList from './list';
import { TestModeNotice } from 'components/test-mode-notice';
-
+import DateFormatNotice from 'wcpay/components/date-format-notice';
export const DocumentsPage = (): JSX.Element => {
return (
+
diff --git a/client/documents/list/index.tsx b/client/documents/list/index.tsx
index b69e2df54af..3223d45dcec 100644
--- a/client/documents/list/index.tsx
+++ b/client/documents/list/index.tsx
@@ -4,9 +4,7 @@
* External dependencies
*/
import React, { useCallback, useEffect, useState } from 'react';
-import { dateI18n } from '@wordpress/date';
import { __, _n, sprintf } from '@wordpress/i18n';
-import moment from 'moment';
import { TableCard, TableCardColumn } from '@woocommerce/components';
import { onQueryChange, getQuery } from '@woocommerce/navigation';
import { Button } from '@wordpress/components';
@@ -21,6 +19,7 @@ import DocumentsFilters from '../filters';
import Page from '../../components/page';
import { getDocumentUrl } from 'wcpay/utils';
import VatFormModal from 'wcpay/vat/form-modal';
+import { formatDateTimeFromString } from 'wcpay/utils/date-time';
interface Column extends TableCardColumn {
key: 'date' | 'type' | 'description' | 'download';
@@ -68,16 +67,8 @@ const getDocumentDescription = ( document: Document ) => {
if ( document.period_from && document.period_to ) {
return sprintf(
__( 'VAT invoice for %s to %s', 'woocommerce-payments' ),
- dateI18n(
- 'M j, Y',
- moment.utc( document.period_from ).toISOString(),
- 'utc'
- ),
- dateI18n(
- 'M j, Y',
- moment.utc( document.period_to ).toISOString(),
- 'utc'
- )
+ formatDateTimeFromString( document.period_from ),
+ formatDateTimeFromString( document.period_to )
);
}
return __(
@@ -180,10 +171,7 @@ export const DocumentsList = (): JSX.Element => {
const data = {
date: {
value: document.date,
- display: dateI18n(
- 'M j, Y',
- moment.utc( document.date ).local().toISOString()
- ),
+ display: formatDateTimeFromString( document.date ),
},
type: {
value: documentType,
diff --git a/client/documents/list/test/index.tsx b/client/documents/list/test/index.tsx
index c54cf7a02c8..0eac7e0bf61 100644
--- a/client/documents/list/test/index.tsx
+++ b/client/documents/list/test/index.tsx
@@ -36,6 +36,7 @@ declare const global: {
accountStatus: {
hasSubmittedVatData: boolean;
};
+ dateFormat: string;
};
};
@@ -60,6 +61,11 @@ describe( 'Documents list', () => {
let container: Element;
let rerender: ( ui: React.ReactElement ) => void;
beforeEach( () => {
+ global.wcpaySettings = {
+ accountStatus: { hasSubmittedVatData: true },
+ dateFormat: 'M j, Y',
+ };
+
mockUseDocuments.mockReturnValue( {
documents: getMockDocuments(),
isLoading: false,
@@ -200,6 +206,7 @@ describe( 'Document download button', () => {
beforeEach( () => {
global.wcpaySettings = {
accountStatus: { hasSubmittedVatData: true },
+ dateFormat: 'M j, Y',
};
render( );
@@ -223,6 +230,7 @@ describe( 'Document download button', () => {
beforeEach( () => {
global.wcpaySettings = {
accountStatus: { hasSubmittedVatData: false },
+ dateFormat: 'M j, Y',
};
render( );
@@ -293,6 +301,7 @@ describe( 'Direct document download', () => {
global.wcpaySettings = {
accountStatus: { hasSubmittedVatData: true },
+ dateFormat: 'M j, Y',
};
} );
diff --git a/client/globals.d.ts b/client/globals.d.ts
index 0d10d7de86b..8b91ee4b05f 100644
--- a/client/globals.d.ts
+++ b/client/globals.d.ts
@@ -123,6 +123,7 @@ declare global {
storeName: string;
isNextDepositNoticeDismissed: boolean;
isInstantDepositNoticeDismissed: boolean;
+ isDateFormatNoticeDismissed: boolean;
reporting: {
exportModalDismissed?: boolean;
};
@@ -137,6 +138,8 @@ declare global {
isOverviewSurveySubmitted: boolean;
lifetimeTPV: number;
defaultExpressCheckoutBorderRadius: string;
+ dateFormat: string;
+ timeFormat: string;
};
const wc: {
diff --git a/client/overview/index.js b/client/overview/index.js
index edb215993c7..5d6d06a52c2 100644
--- a/client/overview/index.js
+++ b/client/overview/index.js
@@ -33,6 +33,7 @@ import SandboxModeSwitchToLiveNotice from 'wcpay/components/sandbox-mode-switch-
import './style.scss';
import BannerNotice from 'wcpay/components/banner-notice';
import { PayoutsRenameNotice } from 'wcpay/deposits/rename-notice';
+import DateFormatNotice from 'wcpay/components/date-format-notice';
const OverviewPageError = () => {
const queryParams = getQuery();
@@ -152,6 +153,7 @@ const OverviewPage = () => {
+
{ showLoanOfferError && (
{ __(
diff --git a/client/overview/modal/update-business-details/index.tsx b/client/overview/modal/update-business-details/index.tsx
index 2c654a57f9b..29a649ad561 100644
--- a/client/overview/modal/update-business-details/index.tsx
+++ b/client/overview/modal/update-business-details/index.tsx
@@ -3,9 +3,7 @@
*/
import React, { useState } from 'react';
import { Button, Modal, Notice } from '@wordpress/components';
-import { dateI18n } from '@wordpress/date';
import { sprintf } from '@wordpress/i18n';
-import moment from 'moment';
/**
* Internal dependencies
@@ -13,6 +11,7 @@ import moment from 'moment';
import strings from './strings';
import './index.scss';
import { recordEvent } from 'wcpay/tracks';
+import { formatDateTimeFromTimestamp } from 'wcpay/utils/date-time';
interface Props {
errorMessages: Array< string >;
@@ -57,11 +56,11 @@ const UpdateBusinessDetailsModal = ( {
currentDeadline
? sprintf(
strings.restrictedSoonDescription,
- dateI18n(
- 'ga M j, Y',
- moment(
- currentDeadline * 1000
- ).toISOString()
+ formatDateTimeFromTimestamp(
+ currentDeadline,
+ {
+ customFormat: 'ga M j, Y',
+ }
)
)
: strings.restrictedDescription }
diff --git a/client/overview/task-list/tasks/dispute-task.tsx b/client/overview/task-list/tasks/dispute-task.tsx
index 235b92696b9..333c15d6709 100644
--- a/client/overview/task-list/tasks/dispute-task.tsx
+++ b/client/overview/task-list/tasks/dispute-task.tsx
@@ -2,7 +2,6 @@
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
-import { dateI18n } from '@wordpress/date';
import moment from 'moment';
import { getHistory } from '@woocommerce/navigation';
@@ -15,6 +14,7 @@ import { formatCurrency } from 'multi-currency/interface/functions';
import { getAdminUrl } from 'wcpay/utils';
import { recordEvent } from 'tracks';
import { isDueWithin } from 'wcpay/disputes/utils';
+import { formatDateTimeFromString } from 'wcpay/utils/date-time';
/**
* Returns an array of disputes that are due within the specified number of days.
@@ -142,10 +142,9 @@ export const getDisputeResolutionTask = (
? sprintf(
__( 'Respond today by %s', 'woocommerce-payments' ),
// Show due_by time in local timezone: e.g. "11:59 PM".
- dateI18n(
- 'g:i A',
- moment.utc( dispute.due_by ).local().toISOString()
- )
+ formatDateTimeFromString( dispute.due_by, {
+ customFormat: 'g:i A',
+ } )
)
: sprintf(
__(
@@ -153,11 +152,8 @@ export const getDisputeResolutionTask = (
'woocommerce-payments'
),
// Show due_by date in local timezone: e.g. "Jan 1, 2021".
- dateI18n(
- 'M j, Y',
- moment.utc( dispute.due_by ).local().toISOString()
- ),
- moment( dispute.due_by ).fromNow( true ) // E.g. "2 days".
+ formatDateTimeFromString( dispute.due_by ),
+ moment.utc( dispute.due_by ).fromNow( true ) // E.g. "2 days".
);
return disputeTask;
diff --git a/client/overview/task-list/tasks/update-business-details-task.tsx b/client/overview/task-list/tasks/update-business-details-task.tsx
index a18e06e9c09..61255a0e413 100644
--- a/client/overview/task-list/tasks/update-business-details-task.tsx
+++ b/client/overview/task-list/tasks/update-business-details-task.tsx
@@ -11,9 +11,8 @@ import { addQueryArgs } from '@wordpress/url';
*/
import type { TaskItemProps } from '../types';
import UpdateBusinessDetailsModal from 'wcpay/overview/modal/update-business-details';
-import { dateI18n } from '@wordpress/date';
-import moment from 'moment';
import { recordEvent } from 'wcpay/tracks';
+import { formatDateTimeFromTimestamp } from 'wcpay/utils/date-time';
export const getUpdateBusinessDetailsTask = (
errorMessages: string[],
@@ -46,10 +45,9 @@ export const getUpdateBusinessDetailsTask = (
'Update by %s to avoid a disruption in payouts.',
'woocommerce-payments'
),
- dateI18n(
- 'ga M j, Y',
- moment( currentDeadline * 1000 ).toISOString()
- )
+ formatDateTimeFromTimestamp( currentDeadline, {
+ customFormat: 'ga M j, Y',
+ } )
);
if ( hasSingleError ) {
diff --git a/client/overview/task-list/test/tasks.js b/client/overview/task-list/test/tasks.js
index 733d2208b08..9134a1c6842 100644
--- a/client/overview/task-list/test/tasks.js
+++ b/client/overview/task-list/test/tasks.js
@@ -139,6 +139,7 @@ describe( 'getTasks()', () => {
precision: 2,
},
},
+ dateFormat: 'M j, Y',
};
} );
afterEach( () => {
diff --git a/client/payment-details/dispute-details/dispute-due-by-date.tsx b/client/payment-details/dispute-details/dispute-due-by-date.tsx
index 18993ef2387..91255a4d786 100644
--- a/client/payment-details/dispute-details/dispute-due-by-date.tsx
+++ b/client/payment-details/dispute-details/dispute-due-by-date.tsx
@@ -2,22 +2,22 @@
* 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';
+import { formatDateTimeFromTimestamp } from 'wcpay/utils/date-time';
const DisputeDueByDate: React.FC< {
dueBy: number;
showRemainingDays?: boolean;
} > = ( { dueBy, showRemainingDays = true } ) => {
const daysRemaining = Math.floor(
- moment.unix( dueBy ).diff( moment(), 'days', true )
- );
- const respondByDate = dateI18n(
- 'M j, Y, g:ia',
- moment( dueBy * 1000 ).toISOString()
+ moment.unix( dueBy ).utc().diff( moment().utc(), 'days', true )
);
+ const respondByDate = formatDateTimeFromTimestamp( dueBy, {
+ separator: ', ',
+ includeTime: true,
+ } );
return (
{ respondByDate }
diff --git a/client/payment-details/dispute-details/dispute-resolution-footer.tsx b/client/payment-details/dispute-details/dispute-resolution-footer.tsx
index 15fec759244..bc4b1e94dbd 100644
--- a/client/payment-details/dispute-details/dispute-resolution-footer.tsx
+++ b/client/payment-details/dispute-details/dispute-resolution-footer.tsx
@@ -2,8 +2,6 @@
* External dependencies
*/
import React from 'react';
-import moment from 'moment';
-import { dateI18n } from '@wordpress/date';
import { __, sprintf } from '@wordpress/i18n';
import { Link } from '@woocommerce/components';
import { createInterpolateElement } from '@wordpress/element';
@@ -17,18 +15,15 @@ import { recordEvent } from 'tracks';
import { getAdminUrl } from 'wcpay/utils';
import { getDisputeFeeFormatted } from 'wcpay/disputes/utils';
import './style.scss';
+import { formatDateTimeFromTimestamp } from 'wcpay/utils/date-time';
const DisputeUnderReviewFooter: React.FC< {
dispute: Pick< Dispute, 'id' | 'metadata' | 'status' >;
} > = ( { dispute } ) => {
const submissionDateFormatted = dispute.metadata.__evidence_submitted_at
- ? dateI18n(
- 'M j, Y',
- moment
- .unix(
- parseInt( dispute.metadata.__evidence_submitted_at, 10 )
- )
- .toISOString()
+ ? formatDateTimeFromTimestamp(
+ parseInt( dispute.metadata.__evidence_submitted_at, 10 ),
+ { includeTime: true }
)
: '-';
@@ -93,13 +88,9 @@ const DisputeWonFooter: React.FC< {
dispute: Pick< Dispute, 'id' | 'metadata' | 'status' >;
} > = ( { dispute } ) => {
const closedDateFormatted = dispute.metadata.__dispute_closed_at
- ? dateI18n(
- 'M j, Y',
- moment
- .unix(
- parseInt( dispute.metadata.__dispute_closed_at, 10 )
- )
- .toISOString()
+ ? formatDateTimeFromTimestamp(
+ parseInt( dispute.metadata.__dispute_closed_at, 10 ),
+ { includeTime: true }
)
: '-';
@@ -171,13 +162,8 @@ const DisputeLostFooter: React.FC< {
const disputeFeeFormatted = getDisputeFeeFormatted( dispute, true ) ?? '-';
const closedDateFormatted = dispute.metadata.__dispute_closed_at
- ? dateI18n(
- 'M j, Y',
- moment
- .unix(
- parseInt( dispute.metadata.__dispute_closed_at, 10 )
- )
- .toISOString()
+ ? formatDateTimeFromTimestamp(
+ parseInt( dispute.metadata.__dispute_closed_at, 10 )
)
: '-';
@@ -274,13 +260,8 @@ const InquiryUnderReviewFooter: React.FC< {
dispute: Pick< Dispute, 'id' | 'metadata' | 'status' >;
} > = ( { dispute } ) => {
const submissionDateFormatted = dispute.metadata.__evidence_submitted_at
- ? dateI18n(
- 'M j, Y',
- moment
- .unix(
- parseInt( dispute.metadata.__evidence_submitted_at, 10 )
- )
- .toISOString()
+ ? formatDateTimeFromTimestamp(
+ parseInt( dispute.metadata.__evidence_submitted_at, 10 )
)
: '-';
@@ -346,13 +327,8 @@ const InquiryClosedFooter: React.FC< {
} > = ( { dispute } ) => {
const isSubmitted = !! dispute.metadata.__evidence_submitted_at;
const closedDateFormatted = dispute.metadata.__dispute_closed_at
- ? dateI18n(
- 'M j, Y',
- moment
- .unix(
- parseInt( dispute.metadata.__dispute_closed_at, 10 )
- )
- .toISOString()
+ ? formatDateTimeFromTimestamp(
+ parseInt( dispute.metadata.__dispute_closed_at, 10 )
)
: '-';
diff --git a/client/payment-details/dispute-details/dispute-steps.tsx b/client/payment-details/dispute-details/dispute-steps.tsx
index 0f90723a8f1..28707dfc6c4 100644
--- a/client/payment-details/dispute-details/dispute-steps.tsx
+++ b/client/payment-details/dispute-details/dispute-steps.tsx
@@ -7,8 +7,6 @@ import React from 'react';
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';
/**
@@ -20,6 +18,7 @@ import { formatExplicitCurrency } from 'multi-currency/interface/functions';
import { ClickTooltip } from 'wcpay/components/tooltip';
import { getDisputeFeeFormatted } from 'wcpay/disputes/utils';
import DisputeDueByDate from './dispute-due-by-date';
+import { formatDateTimeFromTimestamp } from 'wcpay/utils/date-time';
interface Props {
dispute: Dispute;
@@ -35,14 +34,8 @@ export const DisputeSteps: React.FC< Props > = ( {
} ) => {
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 chargeDate = formatDateTimeFromTimestamp( chargeCreated );
+ const disputeDate = formatDateTimeFromTimestamp( dispute.created );
const emailSubject = sprintf(
// Translators: %1$s is the store name, %2$s is the charge date.
__(
@@ -175,14 +168,12 @@ export const InquirySteps: React.FC< Props > = ( {
} ) => {
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 chargeDate = formatDateTimeFromTimestamp( chargeCreated, {
+ includeTime: true,
+ } );
+ const disputeDate = formatDateTimeFromTimestamp( dispute.created, {
+ includeTime: true,
+ } );
const emailSubject = sprintf(
// Translators: %1$s is the store name, %2$s is the charge date.
__(
diff --git a/client/payment-details/dispute-details/dispute-summary-row.tsx b/client/payment-details/dispute-details/dispute-summary-row.tsx
index 0a43cb223e0..95119d01f82 100644
--- a/client/payment-details/dispute-details/dispute-summary-row.tsx
+++ b/client/payment-details/dispute-details/dispute-summary-row.tsx
@@ -4,10 +4,8 @@
* External dependencies
*/
import React from 'react';
-import moment from 'moment';
import HelpOutlineIcon from 'gridicons/dist/help-outline';
import { __ } from '@wordpress/i18n';
-import { dateI18n } from '@wordpress/date';
/**
* Internal dependencies
@@ -20,6 +18,7 @@ import { formatStringValue } from 'wcpay/utils';
import { ClickTooltip } from 'wcpay/components/tooltip';
import Paragraphs from 'wcpay/components/paragraphs';
import DisputeDueByDate from './dispute-due-by-date';
+import { formatDateTimeFromTimestamp } from 'wcpay/utils/date-time';
interface Props {
dispute: Dispute;
@@ -39,10 +38,10 @@ const DisputeSummaryRow: React.FC< Props > = ( { dispute } ) => {
{
title: __( 'Disputed On', 'woocommerce-payments' ),
content: dispute.created
- ? dateI18n(
- 'M j, Y, g:ia',
- moment( dispute.created * 1000 ).toISOString()
- )
+ ? formatDateTimeFromTimestamp( dispute.created, {
+ separator: ', ',
+ includeTime: true,
+ } )
: '–',
},
{
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 7b4a7e3650f..a1af47f03c4 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
@@ -6,6 +6,54 @@ exports[`Order details page should match the snapshot - Charge without payment i
class="wcpay-payment-details woocommerce-payments-page"
style="max-width: 1032px;"
>
+
{
featureFlags: { paymentTimeline: true },
zeroDecimalCurrencies: [],
connect: { country: 'US' },
+ timeFormat: 'g:ia',
+ dateFormat: 'M j, Y',
};
const selectMock = jest.fn( ( storeName ) =>
diff --git a/client/payment-details/payment-details/index.tsx b/client/payment-details/payment-details/index.tsx
index cdf568520f2..a32d0c94193 100644
--- a/client/payment-details/payment-details/index.tsx
+++ b/client/payment-details/payment-details/index.tsx
@@ -17,7 +17,7 @@ import PaymentDetailsPaymentMethod from '../payment-method';
import { ApiError } from '../../types/errors';
import { Charge } from '../../types/charges';
import { PaymentIntent } from '../../types/payment-intents';
-
+import DateFormatNotice from 'wcpay/components/date-format-notice';
interface PaymentDetailsProps {
id: string;
isLoading: boolean;
@@ -56,6 +56,7 @@ const PaymentDetails: React.FC< PaymentDetailsProps > = ( {
return (
+
{
const { readers, chargeError, isLoading } = useCardReaderStats(
props.chargeId,
@@ -34,6 +34,7 @@ const PaymentCardReaderChargeDetails = ( props ) => {
if ( ! isLoading && chargeError instanceof Error ) {
return (
+
diff --git a/client/payment-details/summary/index.tsx b/client/payment-details/summary/index.tsx
index 1e5b6bfcac9..59ae556c785 100644
--- a/client/payment-details/summary/index.tsx
+++ b/client/payment-details/summary/index.tsx
@@ -4,7 +4,6 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
-import { dateI18n } from '@wordpress/date';
import {
Card,
CardBody,
@@ -64,6 +63,10 @@ import DisputeResolutionFooter from '../dispute-details/dispute-resolution-foote
import ErrorBoundary from 'components/error-boundary';
import RefundModal from 'wcpay/payment-details/summary/refund-modal';
import CardNotice from 'wcpay/components/card-notice';
+import {
+ formatDateTimeFromString,
+ formatDateTimeFromTimestamp,
+} from 'wcpay/utils/date-time';
declare const window: any;
@@ -110,10 +113,10 @@ const composePaymentSummaryItems = ( {
{
title: __( 'Date', 'woocommerce-payments' ),
content: charge.created
- ? dateI18n(
- 'M j, Y, g:ia',
- moment( charge.created * 1000 ).toISOString()
- )
+ ? formatDateTimeFromTimestamp( charge.created, {
+ separator: ', ',
+ includeTime: true,
+ } )
: '–',
},
{
@@ -714,12 +717,13 @@ const PaymentDetailsSummary: React.FC< PaymentDetailsSummaryProps > = ( {
}
) }{ ' ' }
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 083da902f05..6dacc01df87 100644
--- a/client/payment-details/summary/test/__snapshots__/index.test.tsx.snap
+++ b/client/payment-details/summary/test/__snapshots__/index.test.tsx.snap
@@ -304,7 +304,7 @@ exports[`PaymentDetailsSummary capture notification and fraud buttons renders ca
this charge within the next
7 days
@@ -660,7 +660,7 @@ exports[`PaymentDetailsSummary capture notification and fraud buttons renders th
this charge within the next
7 days
diff --git a/client/payment-details/summary/test/index.test.tsx b/client/payment-details/summary/test/index.test.tsx
index 9055d481dda..8c47e9b9b6a 100755
--- a/client/payment-details/summary/test/index.test.tsx
+++ b/client/payment-details/summary/test/index.test.tsx
@@ -17,6 +17,15 @@ import PaymentDetailsSummary from '../';
import { useAuthorization } from 'wcpay/data';
import { paymentIntentMock } from 'wcpay/data/payment-intents/test/hooks';
+// Mock dateI18n
+jest.mock( '@wordpress/date', () => ( {
+ dateI18n: jest.fn( ( format, date ) => {
+ return jest
+ .requireActual( '@wordpress/date' )
+ .dateI18n( format, date, 'UTC' ); // Ensure UTC is used
+ } ),
+} ) );
+
declare const global: {
wcSettings: {
locale: {
@@ -34,6 +43,8 @@ declare const global: {
featureFlags: {
isAuthAndCaptureEnabled: boolean;
};
+ dateFormat: string;
+ timeFormat: string;
};
};
@@ -74,7 +85,7 @@ const getBaseCharge = (): Charge =>
id: 'ch_38jdHA39KKA',
payment_intent: 'pi_abc',
/* Stripe data comes in seconds, instead of the default Date milliseconds */
- created: Date.parse( 'Sep 19, 2019, 5:24 pm' ) / 1000,
+ created: 1568913840,
amount: 2000,
amount_refunded: 0,
application_fee_amount: 70,
@@ -203,6 +214,8 @@ describe( 'PaymentDetailsSummary', () => {
precision: 0,
},
},
+ dateFormat: 'M j, Y',
+ timeFormat: 'g:ia',
};
// mock Date.now that moment library uses to get current date for testing purposes
@@ -408,7 +421,7 @@ describe( 'PaymentDetailsSummary', () => {
).toHaveTextContent( /\$20.00/ );
expect(
screen.getByText( /Disputed On/i ).nextSibling
- ).toHaveTextContent( /Aug 30, 2023/ );
+ ).toHaveTextContent( /Aug 31, 2023/ );
expect( screen.getByText( /Reason/i ).nextSibling ).toHaveTextContent(
/Transaction unauthorized/
);
diff --git a/client/payment-details/test/__snapshots__/index.test.tsx.snap b/client/payment-details/test/__snapshots__/index.test.tsx.snap
index aa8a34effd0..19af9ff7235 100644
--- a/client/payment-details/test/__snapshots__/index.test.tsx.snap
+++ b/client/payment-details/test/__snapshots__/index.test.tsx.snap
@@ -6,6 +6,54 @@ exports[`Payment details page should match the snapshot - Charge query param 1`]
class="wcpay-payment-details woocommerce-payments-page"
style="max-width: 1032px;"
>
+
+
{
diff --git a/client/payment-details/timeline/map-events.js b/client/payment-details/timeline/map-events.js
index 7e5570ce8ce..6e4f593e1f0 100644
--- a/client/payment-details/timeline/map-events.js
+++ b/client/payment-details/timeline/map-events.js
@@ -5,9 +5,7 @@
*/
import { flatMap } from 'lodash';
import { __, sprintf } from '@wordpress/i18n';
-import { dateI18n } from '@wordpress/date';
import { addQueryArgs } from '@wordpress/url';
-import moment from 'moment';
import { createInterpolateElement } from '@wordpress/element';
import { Link } from '@woocommerce/components';
import SyncIcon from 'gridicons/dist/sync';
@@ -31,6 +29,7 @@ import { formatFee } from 'utils/fees';
import { getAdminUrl } from 'wcpay/utils';
import { ShieldIcon } from 'wcpay/icons';
import { fraudOutcomeRulesetMapping, paymentFailureMapping } from './mappings';
+import { formatDateTimeFromTimestamp } from 'wcpay/utils/date-time';
/**
* Creates a timeline item about a payment status change
@@ -84,10 +83,7 @@ const getDepositTimelineItem = (
'woocommerce-payments'
),
formattedAmount,
- dateI18n(
- 'M j, Y',
- moment( event.deposit.arrival_date * 1000 ).toISOString()
- )
+ formatDateTimeFromTimestamp( event.deposit.arrival_date )
);
const depositUrl = getAdminUrl( {
page: 'wc-admin',
@@ -143,10 +139,7 @@ const getFinancingPaydownTimelineItem = ( event, formattedAmount, body ) => {
'woocommerce-payments'
),
formattedAmount,
- dateI18n(
- 'M j, Y',
- moment( event.deposit.arrival_date * 1000 ).toISOString()
- )
+ formatDateTimeFromTimestamp( event.deposit.arrival_date )
);
const depositUrl = getAdminUrl( {
diff --git a/client/payment-details/timeline/test/index.js b/client/payment-details/timeline/test/index.js
index 2529f3d673e..616c780dd69 100644
--- a/client/payment-details/timeline/test/index.js
+++ b/client/payment-details/timeline/test/index.js
@@ -34,6 +34,7 @@ describe( 'PaymentDetailsTimeline', () => {
precision: 2,
},
},
+ dateFormat: 'M j, Y',
};
} );
diff --git a/client/payment-details/timeline/test/map-events.js b/client/payment-details/timeline/test/map-events.js
index f1c0588d659..beee5dc959b 100644
--- a/client/payment-details/timeline/test/map-events.js
+++ b/client/payment-details/timeline/test/map-events.js
@@ -47,6 +47,7 @@ describe( 'mapTimelineEvents', () => {
precision: 2,
},
},
+ dateFormat: 'M j, Y',
};
} );
diff --git a/client/transactions/blocked/columns.tsx b/client/transactions/blocked/columns.tsx
index 1d75e407cea..8e4f410ff32 100644
--- a/client/transactions/blocked/columns.tsx
+++ b/client/transactions/blocked/columns.tsx
@@ -2,9 +2,7 @@
* External dependencies
*/
import React from 'react';
-import { dateI18n } from '@wordpress/date';
import { __ } from '@wordpress/i18n';
-import moment from 'moment';
import { TableCardColumn, TableCardBodyColumn } from '@woocommerce/components';
/**
@@ -15,6 +13,7 @@ import TransactionStatusPill from 'wcpay/components/transaction-status-pill';
import { FraudOutcomeTransaction } from '../../data';
import { getDetailsURL } from '../../components/details-link';
import ClickableCell from '../../components/clickable-cell';
+import { formatDateTimeFromString } from 'wcpay/utils/date-time';
interface Column extends TableCardColumn {
key: 'created' | 'amount' | 'customer' | 'status';
@@ -70,10 +69,9 @@ export const getBlockedListRowContent = (
data.payment_intent.id || data.order_id.toString(),
'transactions'
);
- const formattedCreatedDate = dateI18n(
- 'M j, Y / g:iA',
- moment.utc( data.created ).local().toISOString()
- );
+ const formattedCreatedDate = formatDateTimeFromString( data.created, {
+ includeTime: true,
+ } );
const clickable = ( children: JSX.Element | string ) => (
{ children }
diff --git a/client/transactions/blocked/test/columns.test.tsx b/client/transactions/blocked/test/columns.test.tsx
index 7ca2c3d4895..b65e2d10c12 100644
--- a/client/transactions/blocked/test/columns.test.tsx
+++ b/client/transactions/blocked/test/columns.test.tsx
@@ -15,6 +15,8 @@ declare const global: {
connect: {
country: string;
};
+ dateFormat: string;
+ timeFormat: string;
};
};
const mockWcPaySettings = {
@@ -23,6 +25,8 @@ const mockWcPaySettings = {
connect: {
country: 'US',
},
+ dateFormat: 'M j, Y',
+ timeFormat: 'g:iA',
};
describe( 'Blocked fraud outcome transactions columns', () => {
diff --git a/client/transactions/index.tsx b/client/transactions/index.tsx
index 507a972cd5a..f8139f16797 100644
--- a/client/transactions/index.tsx
+++ b/client/transactions/index.tsx
@@ -23,6 +23,7 @@ import {
} from 'wcpay/data';
import WCPaySettingsContext from '../settings/wcpay-settings-context';
import BlockedList from './blocked';
+import DateFormatNotice from 'components/date-format-notice';
declare const window: any;
@@ -106,6 +107,7 @@ export const TransactionsPage: React.FC = () => {
return (
+
= ( { depositId, dateAvailable } ) => {
id: depositId,
} );
- const formattedDateAvailable = dateI18n(
- 'M j, Y',
- moment.utc( dateAvailable ).toISOString(),
- true // TODO Change call to gmdateI18n and remove this deprecated param once WP 5.4 support ends.
+ const formattedDateAvailable = formatDateTimeFromString(
+ dateAvailable
);
-
return { formattedDateAvailable };
}
diff --git a/client/transactions/list/index.tsx b/client/transactions/list/index.tsx
index a5206ae0e5e..a4d9abc852a 100644
--- a/client/transactions/list/index.tsx
+++ b/client/transactions/list/index.tsx
@@ -7,9 +7,7 @@ import React, { Fragment, useState } from 'react';
import { uniq } from 'lodash';
import { useDispatch } from '@wordpress/data';
import { useMemo } from '@wordpress/element';
-import { dateI18n } from '@wordpress/date';
import { __, _n, sprintf } from '@wordpress/i18n';
-import moment from 'moment';
import {
TableCard,
Search,
@@ -70,6 +68,7 @@ import p24BankList from '../../payment-details/payment-method/p24/bank-list';
import { HoverTooltip } from 'components/tooltip';
import { PAYMENT_METHOD_TITLES } from 'wcpay/constants/payment-method';
import { ReportingExportLanguageHook } from 'wcpay/settings/reporting-settings/interfaces';
+import { formatDateTimeFromString } from 'wcpay/utils/date-time';
interface TransactionsListProps {
depositId?: string;
@@ -466,10 +465,9 @@ export const TransactionsList = (
date: {
value: txn.date,
display: clickable(
- dateI18n(
- 'M j, Y / g:iA',
- moment.utc( txn.date ).local().toISOString()
- )
+ formatDateTimeFromString( txn.date, {
+ includeTime: true,
+ } )
),
},
channel: {
diff --git a/client/transactions/list/test/__snapshots__/index.tsx.snap b/client/transactions/list/test/__snapshots__/index.tsx.snap
index 7a4b1eb3392..c432444b3ea 100644
--- a/client/transactions/list/test/__snapshots__/index.tsx.snap
+++ b/client/transactions/list/test/__snapshots__/index.tsx.snap
@@ -493,7 +493,7 @@ exports[`Transactions list renders correctly when can filter by several currenci
href="admin.php?page=wc-admin&path=%2Fpayments%2Ftransactions%2Fdetails&id=pi_mock&transaction_id=txn_j23jda9JJa&transaction_type=refund"
tabindex="-1"
>
- Jan 2, 2020 / 12:46PM
+ Jan 2, 2020 / 5:46PM
- Jan 4, 2020 / 11:22PM
+ Jan 5, 2020 / 4:22AM
|
- Jan 2, 2020 / 2:55PM
+ Jan 2, 2020 / 7:55PM
|
- Jan 2, 2020 / 12:46PM
+ Jan 2, 2020 / 5:46PM
|
- Jan 4, 2020 / 11:22PM
+ Jan 5, 2020 / 4:22AM
|
- Jan 2, 2020 / 2:55PM
+ Jan 2, 2020 / 7:55PM
|
- Jan 4, 2020 / 11:22PM
+ Jan 5, 2020 / 4:22AM
|
- Jan 2, 2020 / 12:46PM
+ Jan 2, 2020 / 5:46PM
|
- Jan 4, 2020 / 11:22PM
+ Jan 5, 2020 / 4:22AM
|
- Jan 2, 2020 / 2:55PM
+ Jan 2, 2020 / 7:55PM
|
- Jan 2, 2020 / 12:46PM
+ Jan 2, 2020 / 5:46PM
|
- Jan 4, 2020 / 11:22PM
+ Jan 5, 2020 / 4:22AM
|
- Jan 2, 2020 / 2:55PM
+ Jan 2, 2020 / 7:55PM
|
- Jan 2, 2020 / 12:46PM
+ Jan 2, 2020 / 5:46PM
|
- Jan 4, 2020 / 11:22PM
+ Jan 5, 2020 / 4:22AM
|
- Jan 2, 2020 / 2:55PM
+ Jan 2, 2020 / 7:55PM
| ( {
+ dateI18n: jest.fn( ( format, date ) => {
+ return jest
+ .requireActual( '@wordpress/date' )
+ .dateI18n( format, date, 'UTC' ); // Ensure UTC is used
+ } ),
+} ) );
+
describe( 'Deposit', () => {
+ beforeEach( () => {
+ // Mock the window.wcpaySettings property
+ window.wcpaySettings.dateFormat = 'M j, Y';
+ window.wcpaySettings.timeFormat = 'g:i a';
+ } );
+
+ afterEach( () => {
+ // Reset the mock
+ jest.clearAllMocks();
+ } );
+
test( 'renders with date and payout available', () => {
const { container: link } = render(
diff --git a/client/transactions/list/test/index.tsx b/client/transactions/list/test/index.tsx
index b233b4d5477..66db0268a34 100644
--- a/client/transactions/list/test/index.tsx
+++ b/client/transactions/list/test/index.tsx
@@ -58,6 +58,15 @@ jest.mock( 'data/index', () => ( {
useReportingExportLanguage: jest.fn( () => [ 'en', jest.fn() ] ),
} ) );
+// Mock dateI18n
+jest.mock( '@wordpress/date', () => ( {
+ dateI18n: jest.fn( ( format, date ) => {
+ return jest
+ .requireActual( '@wordpress/date' )
+ .dateI18n( format, date, 'UTC' ); // Ensure UTC is used
+ } ),
+} ) );
+
const mockDownloadCSVFile = downloadCSVFile as jest.MockedFunction<
typeof downloadCSVFile
>;
@@ -244,6 +253,8 @@ describe( 'Transactions list', () => {
exportModalDismissed: true,
},
};
+ window.wcpaySettings.dateFormat = 'M j, Y';
+ window.wcpaySettings.timeFormat = 'g:iA';
} );
test( 'renders correctly when filtered by payout', () => {
diff --git a/client/transactions/risk-review/columns.tsx b/client/transactions/risk-review/columns.tsx
index d7f5de95111..c1e24c4d428 100644
--- a/client/transactions/risk-review/columns.tsx
+++ b/client/transactions/risk-review/columns.tsx
@@ -2,9 +2,7 @@
* External dependencies
*/
import React from 'react';
-import { dateI18n } from '@wordpress/date';
import { __ } from '@wordpress/i18n';
-import moment from 'moment';
import { TableCardColumn, TableCardBodyColumn } from '@woocommerce/components';
import { Button } from '@wordpress/components';
@@ -17,6 +15,7 @@ import { formatExplicitCurrency } from 'multi-currency/interface/functions';
import { recordEvent } from 'tracks';
import TransactionStatusPill from 'wcpay/components/transaction-status-pill';
import { FraudOutcomeTransaction } from '../../data';
+import { formatDateTimeFromString } from 'wcpay/utils/date-time';
interface Column extends TableCardColumn {
key: 'created' | 'amount' | 'customer' | 'status';
@@ -76,10 +75,9 @@ export const getRiskReviewListRowContent = (
data: FraudOutcomeTransaction
): Record< string, TableCardBodyColumn > => {
const detailsURL = getDetailsURL( data.payment_intent.id, 'transactions' );
- const formattedCreatedDate = dateI18n(
- 'M j, Y / g:iA',
- moment.utc( data.created ).local().toISOString()
- );
+ const formattedCreatedDate = formatDateTimeFromString( data.created, {
+ includeTime: true,
+ } );
const clickable = ( children: JSX.Element | string ) => (
{ children }
diff --git a/client/transactions/risk-review/test/columns.test.tsx b/client/transactions/risk-review/test/columns.test.tsx
index 033f9eb7aa2..54de107e0e4 100644
--- a/client/transactions/risk-review/test/columns.test.tsx
+++ b/client/transactions/risk-review/test/columns.test.tsx
@@ -15,6 +15,8 @@ declare const global: {
connect: {
country: string;
};
+ dateFormat: string;
+ timeFormat: string;
};
};
const mockWcPaySettings = {
@@ -23,6 +25,8 @@ const mockWcPaySettings = {
connect: {
country: 'US',
},
+ dateFormat: 'M j, Y',
+ timeFormat: 'g:iA',
};
describe( 'Review fraud outcome transactions columns', () => {
diff --git a/client/transactions/uncaptured/index.tsx b/client/transactions/uncaptured/index.tsx
index 17058760c19..42d6d69542b 100644
--- a/client/transactions/uncaptured/index.tsx
+++ b/client/transactions/uncaptured/index.tsx
@@ -7,7 +7,6 @@ import React, { useEffect } from 'react';
import { __ } from '@wordpress/i18n';
import { TableCard, TableCardColumn } from '@woocommerce/components';
import { onQueryChange, getQuery } from '@woocommerce/navigation';
-import { dateI18n } from '@wordpress/date';
import moment from 'moment';
/**
@@ -21,6 +20,7 @@ import { formatExplicitCurrency } from 'multi-currency/interface/functions';
import RiskLevel, { calculateRiskMapping } from 'components/risk-level';
import { recordEvent } from 'tracks';
import CaptureAuthorizationButton from 'wcpay/components/capture-authorization-button';
+import { formatDateTimeFromString } from 'wcpay/utils/date-time';
interface Column extends TableCardColumn {
key:
@@ -130,35 +130,25 @@ export const AuthorizationsList = (): JSX.Element => {
display: auth.payment_intent_id,
},
created: {
- value: dateI18n(
- 'M j, Y / g:iA',
- moment.utc( auth.created ).local().toISOString()
- ),
+ value: formatDateTimeFromString( auth.created, {
+ includeTime: true,
+ } ),
display: clickable(
- dateI18n(
- 'M j, Y / g:iA',
- moment.utc( auth.created ).local().toISOString()
- )
+ formatDateTimeFromString( auth.created, {
+ includeTime: true,
+ } )
),
},
// Payments are authorized for a maximum of 7 days
capture_by: {
- value: dateI18n(
- 'M j, Y / g:iA',
- moment
- .utc( auth.created )
- .add( 7, 'd' )
- .local()
- .toISOString()
+ value: formatDateTimeFromString(
+ moment.utc( auth.created ).add( 7, 'd' ).toISOString(),
+ { includeTime: true }
),
display: clickable(
- dateI18n(
- 'M j, Y / g:iA',
- moment
- .utc( auth.created )
- .add( 7, 'd' )
- .local()
- .toISOString()
+ formatDateTimeFromString(
+ moment.utc( auth.created ).add( 7, 'd' ).toISOString(),
+ { includeTime: true }
)
),
},
diff --git a/client/transactions/uncaptured/test/index.test.tsx b/client/transactions/uncaptured/test/index.test.tsx
index 52f76dcc882..fc21a532b67 100644
--- a/client/transactions/uncaptured/test/index.test.tsx
+++ b/client/transactions/uncaptured/test/index.test.tsx
@@ -67,6 +67,8 @@ declare const global: {
precision: number;
};
};
+ dateFormat: string;
+ timeFormat: string;
};
};
@@ -126,6 +128,8 @@ describe( 'Authorizations list', () => {
precision: 2,
},
},
+ dateFormat: 'M j, Y',
+ timeFormat: 'g:iA',
};
} );
diff --git a/client/utils/date-time.ts b/client/utils/date-time.ts
new file mode 100644
index 00000000000..83e4c2c2257
--- /dev/null
+++ b/client/utils/date-time.ts
@@ -0,0 +1,82 @@
+/**
+ * External dependencies
+ */
+import { dateI18n } from '@wordpress/date';
+import moment from 'moment';
+
+type DateTimeFormat = string | null;
+
+interface FormatDateTimeOptions {
+ /** Whether to include time in the formatted string (defaults to true) */
+ includeTime?: boolean;
+ /** Separator between date and time (defaults to ' / ') */
+ separator?: string;
+ /** Custom format to use instead of WordPress settings */
+ customFormat?: DateTimeFormat;
+ /** Timezone string (e.g., 'UTC', 'America/New_York'). If undefined, uses site default */
+ timezone?: string;
+}
+
+/**
+ * Formats a date/time string in YYYY-MM-DD HH:MM:SS format according to WordPress settings.
+ * The input date string is converted to UTC for consistent handling across timezones.
+ *
+ * @param dateTimeStr - Date time string in YYYY-MM-DD HH:MM:SS format
+ * @param options - Formatting options
+ */
+export function formatDateTimeFromString(
+ dateTimeStr: string,
+ options: FormatDateTimeOptions = {}
+): string {
+ const {
+ customFormat = null,
+ includeTime = false,
+ separator = ' / ',
+ timezone = undefined,
+ } = options;
+
+ // Convert to UTC ISO string for consistent handling
+ const utcDateTime = moment.utc( dateTimeStr ).toISOString();
+
+ const format =
+ customFormat ||
+ `${ window.wcpaySettings.dateFormat }${
+ includeTime
+ ? `${ separator }${ window.wcpaySettings.timeFormat }`
+ : ''
+ }`;
+
+ return dateI18n( format, utcDateTime, timezone );
+}
+
+/**
+ * Formats a Unix timestamp according to WordPress settings.
+ * The input timestamp is converted to UTC for consistent handling across timezones.
+ *
+ * @param timestamp - Unix timestamp (seconds since epoch)
+ * @param options - Formatting options
+ */
+export function formatDateTimeFromTimestamp(
+ timestamp: number,
+ options: FormatDateTimeOptions = {}
+): string {
+ const {
+ customFormat = null,
+ includeTime = false,
+ separator = ' / ',
+ timezone = undefined,
+ } = options;
+
+ // Convert to UTC ISO string for consistent handling
+ const utcDateTime = moment.unix( timestamp ).utc().toISOString();
+
+ const format =
+ customFormat ||
+ `${ window.wcpaySettings.dateFormat }${
+ includeTime
+ ? `${ separator }${ window.wcpaySettings.timeFormat }`
+ : ''
+ }`;
+
+ return dateI18n( format, utcDateTime, timezone );
+}
diff --git a/client/utils/test/date-time.test.ts b/client/utils/test/date-time.test.ts
new file mode 100644
index 00000000000..798c95d7755
--- /dev/null
+++ b/client/utils/test/date-time.test.ts
@@ -0,0 +1,181 @@
+/**
+ * Internal dependencies
+ */
+import {
+ formatDateTimeFromString,
+ formatDateTimeFromTimestamp,
+} from 'wcpay/utils/date-time';
+
+// Mock dateI18n
+jest.mock( '@wordpress/date', () => ( {
+ dateI18n: jest.fn( ( format, date, timezone ) => {
+ return jest
+ .requireActual( '@wordpress/date' )
+ .dateI18n( format, date, timezone || 'UTC' ); // Use provided timezone or fallback to UTC
+ } ),
+} ) );
+
+describe( 'Date/Time Formatting', () => {
+ const originalWcpaySettings = window.wcpaySettings;
+ const mockWcpaySettings = {
+ dateFormat: 'Y-m-d',
+ timeFormat: 'H:i',
+ };
+
+ beforeEach( () => {
+ jest.clearAllMocks();
+ window.wcpaySettings = mockWcpaySettings as typeof wcpaySettings;
+ } );
+
+ afterEach( () => {
+ window.wcpaySettings = originalWcpaySettings;
+ } );
+
+ describe( 'formatDateTimeFromString', () => {
+ it( 'should format using default WordPress settings', () => {
+ const dateTime = '2024-10-23 15:28:26';
+ const formatted = formatDateTimeFromString( dateTime, {
+ includeTime: true,
+ } );
+
+ expect( formatted ).toBe( '2024-10-23 / 15:28' );
+ } );
+
+ it( 'should use custom format if provided', () => {
+ const dateTime = '2024-10-23 15:28:26';
+ const options = { customFormat: 'd-m-Y H:i:s' };
+ const formatted = formatDateTimeFromString( dateTime, options );
+
+ expect( formatted ).toBe( '23-10-2024 15:28:26' );
+ } );
+
+ it( 'should exclude time if includeTime is set to false', () => {
+ const dateTime = '2024-10-23 15:28:26';
+ const formatted = formatDateTimeFromString( dateTime );
+
+ expect( formatted ).toBe( '2024-10-23' );
+ } );
+
+ it( 'should use custom separator when provided', () => {
+ const dateTime = '2024-10-23 15:28:26';
+ const options = { separator: ' - ', includeTime: true };
+ const formatted = formatDateTimeFromString( dateTime, options );
+
+ expect( formatted ).toBe( '2024-10-23 - 15:28' );
+ } );
+
+ it( 'should handle different timezones correctly', () => {
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ const dateI18n = require( '@wordpress/date' ).dateI18n;
+ // Temporarily modify the mock to use a different timezone: America/New_York
+ dateI18n.mockImplementationOnce(
+ ( format: string, date: string | number ) => {
+ return jest
+ .requireActual( '@wordpress/date' )
+ .dateI18n( format, date, 'America/New_York' );
+ }
+ );
+
+ const dateTime = '2024-10-23 15:28:26';
+ const formatted = formatDateTimeFromString( dateTime, {
+ includeTime: true,
+ } );
+
+ expect( formatted ).toBe( '2024-10-23 / 11:28' );
+ } );
+
+ it( 'should respect explicitly provided timezone', () => {
+ const dateTime = '2024-10-23 15:28:26';
+
+ // Test with UTC timezone
+ const formattedUTC = formatDateTimeFromString( dateTime, {
+ includeTime: true,
+ timezone: 'UTC',
+ } );
+ expect( formattedUTC ).toBe( '2024-10-23 / 15:28' );
+
+ // Test with New York timezone
+ const formattedNY = formatDateTimeFromString( dateTime, {
+ includeTime: true,
+ timezone: 'America/New_York',
+ } );
+ expect( formattedNY ).toBe( '2024-10-23 / 11:28' );
+ } );
+ } );
+
+ describe( 'formatDateTimeFromTimestamp', () => {
+ it( 'should format using default WordPress settings', () => {
+ const timestamp = 1729766906; // 2024-10-23 10:48:26 UTC
+ const formatted = formatDateTimeFromTimestamp( timestamp, {
+ includeTime: true,
+ } );
+
+ expect( formatted ).toBe( '2024-10-24 / 10:48' );
+ } );
+
+ it( 'should use custom format if provided', () => {
+ const timestamp = 1729766906; // 2024-10-23 10:48:26 UTC
+ const options = { customFormat: 'd-m-Y H:i:s' };
+ const formatted = formatDateTimeFromTimestamp( timestamp, options );
+
+ expect( formatted ).toBe( '24-10-2024 10:48:26' );
+ } );
+
+ it( 'should exclude time if includeTime is set to false', () => {
+ const timestamp = 1729766906; // 2024-10-23 10:48:26 UTC
+ const formatted = formatDateTimeFromTimestamp( timestamp );
+
+ expect( formatted ).toBe( '2024-10-24' );
+ } );
+
+ it( 'should use custom separator when provided', () => {
+ const timestamp = 1729766906; // 2024-10-23 10:48:26 UTC
+ const options = {
+ separator: ' - ',
+ includeTime: true,
+ };
+ const formatted = formatDateTimeFromTimestamp( timestamp, options );
+
+ expect( formatted ).toBe( '2024-10-24 - 10:48' );
+ } );
+
+ it( 'should handle different timezones correctly', () => {
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ const dateI18n = require( '@wordpress/date' ).dateI18n;
+ // Temporarily modify the mock to use a different timezone: America/New_York
+ dateI18n.mockImplementationOnce(
+ ( format: string, date: string | number ) => {
+ return jest
+ .requireActual( '@wordpress/date' )
+ .dateI18n( format, date, 'America/New_York' );
+ }
+ );
+
+ const timestamp = 1729766906; // 2024-10-24 10:48:26 UTC
+ const formatted = formatDateTimeFromTimestamp( timestamp, {
+ includeTime: true,
+ } );
+
+ // In New York (EDT), this should be 4 hours behind UTC
+ expect( formatted ).toBe( '2024-10-24 / 06:48' );
+ } );
+
+ it( 'should respect explicitly provided timezone', () => {
+ const timestamp = 1729766906; // 2024-10-24 10:48:26 UTC
+
+ // Test with UTC timezone
+ const formattedUTC = formatDateTimeFromTimestamp( timestamp, {
+ includeTime: true,
+ timezone: 'UTC',
+ } );
+ expect( formattedUTC ).toBe( '2024-10-24 / 10:48' );
+
+ // Test with New York timezone
+ const formattedNY = formatDateTimeFromTimestamp( timestamp, {
+ includeTime: true,
+ timezone: 'America/New_York',
+ } );
+ expect( formattedNY ).toBe( '2024-10-24 / 06:48' );
+ } );
+ } );
+} );
diff --git a/includes/admin/class-wc-payments-admin.php b/includes/admin/class-wc-payments-admin.php
index d78671d1298..392dec5c611 100644
--- a/includes/admin/class-wc-payments-admin.php
+++ b/includes/admin/class-wc-payments-admin.php
@@ -973,6 +973,7 @@ private function get_js_settings(): array {
'storeName' => get_bloginfo( 'name' ),
'isNextDepositNoticeDismissed' => WC_Payments_Features::is_next_deposit_notice_dismissed(),
'isInstantDepositNoticeDismissed' => get_option( 'wcpay_instant_deposit_notice_dismissed', false ),
+ 'isDateFormatNoticeDismissed' => get_option( 'wcpay_date_format_notice_dismissed', false ),
'reporting' => [
'exportModalDismissed' => get_option( 'wcpay_reporting_export_modal_dismissed', false ),
],
@@ -983,6 +984,8 @@ private function get_js_settings(): array {
'lifetimeTPV' => $this->account->get_lifetime_total_payment_volume(),
'defaultExpressCheckoutBorderRadius' => WC_Payments_Express_Checkout_Button_Handler::DEFAULT_BORDER_RADIUS_IN_PX,
'isWooPayGlobalThemeSupportEligible' => WC_Payments_Features::is_woopay_global_theme_support_eligible(),
+ 'dateFormat' => wc_date_format(),
+ 'timeFormat' => get_option( 'time_format' ),
];
return apply_filters( 'wcpay_js_settings', $this->wcpay_js_settings );
diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php
index 4ad2d32625e..7b4ae0750c2 100644
--- a/includes/class-wc-payments.php
+++ b/includes/class-wc-payments.php
@@ -1930,6 +1930,7 @@ public static function add_wcpay_options_to_woocommerce_permissions_list( $permi
'wcpay_duplicate_payment_method_notices_dismissed',
'wcpay_exit_survey_dismissed',
'wcpay_instant_deposit_notice_dismissed',
+ 'wcpay_date_format_notice_dismissed',
],
true
);
From f6010a425ef2d3619c83441552a4fff58c6fcf6e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?C=C3=A9sar=20Costa?=
<10233985+cesarcosta99@users.noreply.github.com>
Date: Fri, 20 Dec 2024 10:38:33 -0300
Subject: [PATCH 32/38] Show "postal code check" label based on store country
(#9952)
---
changelog/fix-9896-postal-code-label | 4 ++++
.../order-details/test/__snapshots__/index.test.tsx.snap | 2 +-
client/payment-details/payment-method/card/index.js | 5 ++++-
.../payment-details/test/__snapshots__/index.test.tsx.snap | 4 ++--
client/style.scss | 4 ++++
5 files changed, 15 insertions(+), 4 deletions(-)
create mode 100644 changelog/fix-9896-postal-code-label
diff --git a/changelog/fix-9896-postal-code-label b/changelog/fix-9896-postal-code-label
new file mode 100644
index 00000000000..dad971912a8
--- /dev/null
+++ b/changelog/fix-9896-postal-code-label
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Localize postal code check label based on country.
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 a1af47f03c4..ed6c9656178 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
@@ -613,7 +613,7 @@ exports[`Order details page should match the snapshot - Charge without payment i
- Zip check
+ Postal code check
{
diff --git a/client/payment-details/test/__snapshots__/index.test.tsx.snap b/client/payment-details/test/__snapshots__/index.test.tsx.snap
index 19af9ff7235..feea948e950 100644
--- a/client/payment-details/test/__snapshots__/index.test.tsx.snap
+++ b/client/payment-details/test/__snapshots__/index.test.tsx.snap
@@ -485,7 +485,7 @@ exports[`Payment details page should match the snapshot - Charge query param 1`]
aria-busy="true"
class="is-loadable-placeholder is-block"
>
- Zip check
+ Postal code check
- Zip check
+ Postal code check
Date: Fri, 20 Dec 2024 16:35:13 +0200
Subject: [PATCH 33/38] Fix blank Overview page when WC onboarding is disabled
(#10020)
Co-authored-by: Cvetan Cvetanov
---
changelog/fix-9742-blank-overview-page-without-wc-features | 4 ++++
client/onboarding/utils.ts | 2 +-
2 files changed, 5 insertions(+), 1 deletion(-)
create mode 100644 changelog/fix-9742-blank-overview-page-without-wc-features
diff --git a/changelog/fix-9742-blank-overview-page-without-wc-features b/changelog/fix-9742-blank-overview-page-without-wc-features
new file mode 100644
index 00000000000..8d473bd8ab4
--- /dev/null
+++ b/changelog/fix-9742-blank-overview-page-without-wc-features
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix blank Payments > Overview page when WC onboarding is disabled.
diff --git a/client/onboarding/utils.ts b/client/onboarding/utils.ts
index 306328f64f7..809c87a9927 100644
--- a/client/onboarding/utils.ts
+++ b/client/onboarding/utils.ts
@@ -140,7 +140,7 @@ export const isPoEligible = async (
* @return {string | undefined} The MCC code for the selected industry. Will return undefined if no industry is selected.
*/
export const getMccFromIndustry = (): string | undefined => {
- const industry = wcSettings.admin.onboarding.profile.industry?.[ 0 ];
+ const industry = wcSettings.admin?.onboarding?.profile?.industry?.[ 0 ];
if ( ! industry ) {
return undefined;
}
From cd650bd4444cfb6487f05aa4238ae8b7d8044757 Mon Sep 17 00:00:00 2001
From: Malith Senaweera <6216000+malithsen@users.noreply.github.com>
Date: Fri, 20 Dec 2024 09:01:43 -0600
Subject: [PATCH 34/38] Show a beta badge next to WooPay theme checkbox
(#10000)
---
changelog/add-beta-badge-woopay-theme | 5 +++++
.../settings/express-checkout-settings/index.scss | 12 ++++++++++++
.../express-checkout-settings/woopay-settings.js | 15 +++++++++++----
3 files changed, 28 insertions(+), 4 deletions(-)
create mode 100644 changelog/add-beta-badge-woopay-theme
diff --git a/changelog/add-beta-badge-woopay-theme b/changelog/add-beta-badge-woopay-theme
new file mode 100644
index 00000000000..8d379077613
--- /dev/null
+++ b/changelog/add-beta-badge-woopay-theme
@@ -0,0 +1,5 @@
+Significance: patch
+Type: add
+Comment: Adds a label to a gated feature.
+
+
diff --git a/client/settings/express-checkout-settings/index.scss b/client/settings/express-checkout-settings/index.scss
index f57e1e973e7..7b6bd1ca1fe 100644
--- a/client/settings/express-checkout-settings/index.scss
+++ b/client/settings/express-checkout-settings/index.scss
@@ -58,6 +58,18 @@
}
.woopay-settings {
+ &__badge {
+ background-color: $studio-gray-5;
+ padding: 0 8px;
+ border-radius: 2px;
+ font-size: 12px;
+ line-height: 20px;
+ margin-left: 4px;
+ }
+ &__global-theme-label {
+ display: inline-flex;
+ align-items: center;
+ }
&__custom-message-wrapper {
position: relative;
diff --git a/client/settings/express-checkout-settings/woopay-settings.js b/client/settings/express-checkout-settings/woopay-settings.js
index f2a4931f9f6..6f5b16baece 100644
--- a/client/settings/express-checkout-settings/woopay-settings.js
+++ b/client/settings/express-checkout-settings/woopay-settings.js
@@ -226,10 +226,17 @@ const WooPaySettings = ( { section } ) => {
onChange={
updateIsWooPayGlobalThemeSupportEnabled
}
- label={ __(
- 'Enable global theme support',
- 'woocommerce-payments'
- ) }
+ label={
+
+ { __(
+ 'Enable global theme support',
+ 'woocommerce-payments'
+ ) }
+
+ Beta
+
+
+ }
help={ interpolateComponents( {
mixedString: __(
'When enabled, WooPay checkout will be themed with your store’s brand colors and fonts. ' +
From 34e3731ed0424de26dc0cab68751c4c163d10247 Mon Sep 17 00:00:00 2001
From: Cvetan Cvetanov
Date: Fri, 20 Dec 2024 18:03:58 +0200
Subject: [PATCH 35/38] Enable Payment Methods preselected by NOX after
onboarding accounts (#10018)
---
.../add-9973-enable-pms-from-capabilities | 4 +
includes/class-wc-payments-account.php | 30 ++++++
.../class-wc-payments-onboarding-service.php | 92 +++++++++++++++++++
3 files changed, 126 insertions(+)
create mode 100644 changelog/add-9973-enable-pms-from-capabilities
diff --git a/changelog/add-9973-enable-pms-from-capabilities b/changelog/add-9973-enable-pms-from-capabilities
new file mode 100644
index 00000000000..5ecfd2ac5cd
--- /dev/null
+++ b/changelog/add-9973-enable-pms-from-capabilities
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Enable Payment Methods preselected by NOX after onboarding accounts
diff --git a/includes/class-wc-payments-account.php b/includes/class-wc-payments-account.php
index dc5430d6f0f..ebe3888afc5 100644
--- a/includes/class-wc-payments-account.php
+++ b/includes/class-wc-payments-account.php
@@ -2049,6 +2049,21 @@ private function init_stripe_onboarding( string $setup_mode, string $wcpay_conne
$gateway->update_option( 'enabled', 'yes' );
$gateway->update_option( 'test_mode', empty( $onboarding_data['is_live'] ) ? 'yes' : 'no' );
+ /**
+ * ==================
+ * Enforces the update of payment methods to 'enabled' based on the capabilities
+ * provided during the NOX onboarding process.
+ *
+ * @see WC_Payments_Onboarding_Service::update_enabled_payment_methods_ids
+ * ==================
+ */
+ $capabilities = $this->onboarding_service->get_capabilities_from_request();
+
+ // Activate enabled Payment Methods IDs.
+ if ( ! empty( $capabilities ) ) {
+ $this->onboarding_service->update_enabled_payment_methods_ids( $gateway, $capabilities );
+ }
+
// Store a state after completing KYC for tracks. This is stored temporarily in option because
// user might not have agreed to TOS yet.
update_option( '_wcpay_onboarding_stripe_connected', [ 'is_existing_stripe_account' => true ] );
@@ -2161,6 +2176,20 @@ private function finalize_connection( string $state, string $mode, array $additi
$gateway->update_option( 'enabled', 'yes' );
$gateway->update_option( 'test_mode', 'live' !== $mode ? 'yes' : 'no' );
+ /**
+ * ==================
+ * Enforces the update of payment methods to 'enabled' based on the capabilities
+ * provided during the NOX onboarding process.
+ *
+ * @see WC_Payments_Onboarding_Service::update_enabled_payment_methods_ids
+ * ==================
+ */
+ $capabilities = $this->onboarding_service->get_capabilities_from_request();
+ // Activate enabled Payment Methods IDs.
+ if ( ! empty( $capabilities ) ) {
+ $this->onboarding_service->update_enabled_payment_methods_ids( $gateway, $capabilities );
+ }
+
// Store a state after completing KYC for tracks. This is stored temporarily in option because
// user might not have agreed to TOS yet.
update_option( '_wcpay_onboarding_stripe_connected', [ 'is_existing_stripe_account' => false ] );
@@ -2570,6 +2599,7 @@ function (): array {
);
}
+
/**
* Send a Tracks event.
*
diff --git a/includes/class-wc-payments-onboarding-service.php b/includes/class-wc-payments-onboarding-service.php
index 504ac3bd5e4..cd5fe83d348 100644
--- a/includes/class-wc-payments-onboarding-service.php
+++ b/includes/class-wc-payments-onboarding-service.php
@@ -277,6 +277,22 @@ public function create_embedded_kyc_session( array $self_assessment_data, bool $
);
$actioned_notes = self::get_actioned_notes();
+ /**
+ * ==================
+ * Enforces the update of payment methods to 'enabled' based on the capabilities
+ * provided during the NOX onboarding process.
+ *
+ * @see self::update_enabled_payment_methods_ids
+ * ==================
+ */
+ $capabilities = $this->get_capabilities_from_request();
+ $gateway = WC_Payments::get_gateway();
+
+ // Activate enabled Payment Methods IDs.
+ if ( ! empty( $capabilities ) ) {
+ $this->update_enabled_payment_methods_ids( $gateway, $capabilities );
+ }
+
try {
$account_session = $this->payments_api_client->initialize_onboarding_embedded_kyc(
'live' === $setup_mode,
@@ -1024,4 +1040,80 @@ public function maybe_add_test_drive_settings_to_new_account_request( array $arg
return $args;
}
+
+ /**
+ * Update payment methods to 'enabled' based on the capabilities
+ * provided during the NOX onboarding process. Merchants can preselect their preferred
+ * payment methods as part of this flow.
+ *
+ * The capabilities are provided in the following format:
+ *
+ * [
+ * 'card' => true,
+ * 'affirm' => true,
+ * ...
+ * ]
+ *
+ * @param WC_Payment_Gateway_WCPay $gateway Payment gateway instance.
+ * @param array $capabilities Provided capabilities.
+ */
+ public function update_enabled_payment_methods_ids( $gateway, $capabilities = [] ): void {
+ $enabled_gateways = $gateway->get_upe_enabled_payment_method_ids();
+
+ $enabled_payment_methods = array_unique(
+ array_merge(
+ $enabled_gateways,
+ $this->exclude_placeholder_payment_methods( $capabilities )
+ )
+ );
+
+ // Update the gateway option.
+ $gateway->update_option( 'upe_enabled_payment_method_ids', $enabled_payment_methods );
+
+ /**
+ * Keeps the list of enabled payment method IDs synchronized between the default
+ * `woocommerce_woocommerce_payments_settings` and duplicates in individual gateway settings.
+ */
+ foreach ( $enabled_payment_methods as $payment_method_id ) {
+ $payment_gateway = WC_Payments::get_payment_gateway_by_id( $payment_method_id );
+ if ( $payment_gateway ) {
+ $payment_gateway->enable();
+ $payment_gateway->update_option( 'upe_enabled_payment_method_ids', $enabled_payment_methods );
+ }
+ }
+
+ // If WooPay is enabled, update the gateway option.
+ if ( ! empty( $capabilities['woopay'] ) ) {
+ $gateway->update_is_woopay_enabled( true );
+ }
+
+ // If Apple Pay and Google Pay are disabled update the gateway option,
+ // otherwise they are enabled by default.
+ if ( empty( $capabilities['apple_google'] ) ) {
+ $gateway->update_option( 'payment_request', 'no' );
+ }
+ }
+
+ /**
+ * Excludes placeholder payment methods and removes duplicates.
+ *
+ * WooPay and Apple Pay & Google Pay are considered placeholder payment methods and are excluded.
+ *
+ * @param array $payment_methods Array of payment methods to process.
+ *
+ * @return array Filtered array of unique payment methods.
+ */
+ private function exclude_placeholder_payment_methods( array $payment_methods ): array {
+ // Placeholder payment methods.
+ $excluded_methods = [ 'woopay', 'apple_google' ];
+
+ return array_filter(
+ array_unique(
+ array_keys( array_filter( $payment_methods ) )
+ ),
+ function ( $payment_method ) use ( $excluded_methods ) {
+ return ! in_array( $payment_method, $excluded_methods, true );
+ }
+ );
+ }
}
From 23516e938e9e7e0ddc879fa0543de28992c69160 Mon Sep 17 00:00:00 2001
From: Francesco
Date: Sun, 22 Dec 2024 08:00:15 +0100
Subject: [PATCH 36/38] fix: tokenized ECE do not show button when missing
billing email (#10019)
---
...ed-ece-pay-for-order-without-billing-email | 5 +++
...ayments-express-checkout-button-helper.php | 34 ++++++++++++++++++-
...ayments-express-checkout-button-helper.php | 26 +++++++++++++-
3 files changed, 63 insertions(+), 2 deletions(-)
create mode 100644 changelog/fix-tokenized-ece-pay-for-order-without-billing-email
diff --git a/changelog/fix-tokenized-ece-pay-for-order-without-billing-email b/changelog/fix-tokenized-ece-pay-for-order-without-billing-email
new file mode 100644
index 00000000000..6e34989b976
--- /dev/null
+++ b/changelog/fix-tokenized-ece-pay-for-order-without-billing-email
@@ -0,0 +1,5 @@
+Significance: patch
+Type: fix
+Comment: fix: tokenized ECE do not show button when missing billing email
+
+
diff --git a/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php b/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php
index 86d1a82c54d..0613e3a4557 100644
--- a/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php
+++ b/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php
@@ -412,7 +412,7 @@ public function should_show_express_checkout_button() {
// Order total doesn't matter for Pay for Order page. Thus, this page should always display payment buttons.
if ( $this->is_pay_for_order_page() ) {
- return true;
+ return $this->is_pay_for_order_supported();
}
// Non-shipping product and tax is calculated based on shopper billing address. Excludes Pay for Order page.
@@ -749,6 +749,38 @@ public function get_product_data() {
return apply_filters( 'wcpay_payment_request_product_data', $data, $product );
}
+ /**
+ * The Store API doesn't allow checkout without the billing email address present on the order data.
+ * https://github.com/woocommerce/woocommerce/issues/48540
+ *
+ * @return bool
+ */
+ private function is_pay_for_order_supported() {
+ if ( ! WC_Payments_Features::is_tokenized_cart_ece_enabled() ) {
+ return true;
+ }
+
+ $order_id = absint( get_query_var( 'order-pay' ) );
+ if ( 0 === $order_id ) {
+ return false;
+ }
+
+ $order = wc_get_order( $order_id );
+ if ( ! is_a( $order, 'WC_Order' ) ) {
+ return false;
+ }
+
+ // we don't need to check its validity or value, we just need to ensure a billing email is present.
+ $billing_email = $order->get_billing_email();
+ if ( ! empty( $billing_email ) ) {
+ return true;
+ }
+
+ Logger::log( 'Billing email not present ( Express Checkout Element button disabled )' );
+
+ return false;
+ }
+
/**
* Whether product page has a supported product.
*
diff --git a/tests/unit/express-checkout/test-class-wc-payments-express-checkout-button-helper.php b/tests/unit/express-checkout/test-class-wc-payments-express-checkout-button-helper.php
index 8006faac78f..9c7ebbef971 100644
--- a/tests/unit/express-checkout/test-class-wc-payments-express-checkout-button-helper.php
+++ b/tests/unit/express-checkout/test-class-wc-payments-express-checkout-button-helper.php
@@ -104,6 +104,7 @@ public function tear_down() {
remove_filter( 'wc_tax_enabled', '__return_false' );
remove_filter( 'pre_option_woocommerce_tax_display_cart', [ $this, '__return_excl' ] );
remove_filter( 'pre_option_woocommerce_tax_display_cart', [ $this, '__return_incl' ] );
+ delete_option( '_wcpay_feature_tokenized_cart_ece' );
parent::tear_down();
}
@@ -208,6 +209,30 @@ function () {
remove_all_filters( 'wcpay_payment_request_total_label_suffix' );
}
+ public function test_should_show_express_checkout_button_for_tokenized_ece_with_billing_email() {
+ global $wp;
+ global $wp_query;
+
+ $this->mock_wcpay_account
+ ->method( 'is_stripe_connected' )
+ ->willReturn( true );
+ WC_Payments::mode()->dev();
+ $_GET['pay_for_order'] = true;
+
+ // Total is 100 USD, which is above both payment methods (Affirm and AfterPay) minimums.
+ $order = WC_Helper_Order::create_order( 1, 100 );
+ $order_id = $order->get_id();
+ $wp->query_vars = [ 'order-pay' => strval( $order_id ) ];
+ $wp_query->query_vars = [ 'order-pay' => strval( $order_id ) ];
+
+ update_option( '_wcpay_feature_tokenized_cart_ece', '1' );
+ add_filter( 'woocommerce_is_checkout', '__return_true' );
+
+ $this->assertTrue( $this->system_under_test->should_show_express_checkout_button() );
+
+ remove_filter( 'woocommerce_is_checkout', '__return_true' );
+ }
+
public function test_should_show_express_checkout_button_for_non_shipping_but_price_includes_tax() {
$this->mock_wcpay_account
->method( 'is_stripe_connected' )
@@ -229,7 +254,6 @@ public function test_should_show_express_checkout_button_for_non_shipping_but_pr
remove_filter( 'pre_option_woocommerce_tax_display_cart', [ $this, '__return_incl' ] );
}
-
public function test_should_not_show_express_checkout_button_for_non_shipping_but_price_does_not_include_tax() {
$this->mock_wcpay_account
->method( 'is_stripe_connected' )
From 49b6961fd123f3156115dbbbdff51f6307d5c2db Mon Sep 17 00:00:00 2001
From: Eric Jinks <3147296+Jinksi@users.noreply.github.com>
Date: Mon, 23 Dec 2024 14:10:32 +1000
Subject: [PATCH 37/38] Add TS type assertion for disputes CSV export API
response to fix type safety (#10008)
Co-authored-by: Shendy <73803630+shendy-a8c@users.noreply.github.com>
---
...isputes-csv-export-response-type-assertion | 4 +++
client/disputes/index.tsx | 35 ++++++++++---------
2 files changed, 23 insertions(+), 16 deletions(-)
create mode 100644 changelog/fix-10007-disputes-csv-export-response-type-assertion
diff --git a/changelog/fix-10007-disputes-csv-export-response-type-assertion b/changelog/fix-10007-disputes-csv-export-response-type-assertion
new file mode 100644
index 00000000000..b578456ece2
--- /dev/null
+++ b/changelog/fix-10007-disputes-csv-export-response-type-assertion
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Add type assertion for disputes CSV export response to ensure type safety and fix TypeScript error
diff --git a/client/disputes/index.tsx b/client/disputes/index.tsx
index 6e8c4d2d61b..ad5e8258d7f 100644
--- a/client/disputes/index.tsx
+++ b/client/disputes/index.tsx
@@ -397,22 +397,25 @@ export const DisputesList = (): JSX.Element => {
window.confirm( confirmMessage )
) {
try {
- const { exported_disputes: exportedDisputes } = await apiFetch(
- {
- path: getDisputesCSV( {
- userEmail,
- locale,
- dateAfter,
- dateBefore,
- dateBetween,
- match,
- filter,
- statusIs,
- statusIsNot,
- } ),
- method: 'POST',
- }
- );
+ const {
+ exported_disputes: exportedDisputes,
+ } = await apiFetch< {
+ /** The total number of disputes that will be exported in the CSV. */
+ exported_disputes: number;
+ } >( {
+ path: getDisputesCSV( {
+ userEmail,
+ locale,
+ dateAfter,
+ dateBefore,
+ dateBetween,
+ match,
+ filter,
+ statusIs,
+ statusIsNot,
+ } ),
+ method: 'POST',
+ } );
createNotice(
'success',
From 96372477c893eeb6892c268b3893e944ec0aac9e Mon Sep 17 00:00:00 2001
From: Eric Jinks <3147296+Jinksi@users.noreply.github.com>
Date: Mon, 23 Dec 2024 14:15:00 +1000
Subject: [PATCH 38/38] =?UTF-8?q?Upgrade=20`@woocommerce/csv-export`=20to?=
=?UTF-8?q?=20v1.10.0=20=E2=80=93=20fixes=20unnecessary=20escaping=20of=20?=
=?UTF-8?q?negative=20values=20in=20CSV=20exports=20(#10028)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...303-upgrade-woocommerce-csv-export-package | 4 +
package-lock.json | 83 ++++++++++---------
package.json | 4 +-
3 files changed, 49 insertions(+), 42 deletions(-)
create mode 100644 changelog/fix-9303-upgrade-woocommerce-csv-export-package
diff --git a/changelog/fix-9303-upgrade-woocommerce-csv-export-package b/changelog/fix-9303-upgrade-woocommerce-csv-export-package
new file mode 100644
index 00000000000..db23bab80b2
--- /dev/null
+++ b/changelog/fix-9303-upgrade-woocommerce-csv-export-package
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Upgrade `@woocommerce/csv-export` package to v1.10.0 – fixes unnecessary escaping of negative values in CSV exports that was preventing numerical analysis in spreadsheet applications
diff --git a/package-lock.json b/package-lock.json
index 083ed1adf12..1ddaf7a4ded 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -48,7 +48,7 @@
"@typescript-eslint/parser": "4.15.2",
"@woocommerce/api": "0.2.0",
"@woocommerce/components": "12.3.0",
- "@woocommerce/csv-export": "1.9.0",
+ "@woocommerce/csv-export": "1.10.0",
"@woocommerce/currency": "4.3.0",
"@woocommerce/date": "4.2.0",
"@woocommerce/dependency-extraction-webpack-plugin": "2.2.0",
@@ -10969,6 +10969,11 @@
"react-dom": ">=16.8.0"
}
},
+ "node_modules/@woocommerce/components/node_modules/@types/node": {
+ "version": "16.18.122",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.122.tgz",
+ "integrity": "sha512-rF6rUBS80n4oK16EW8nE75U+9fw0SSUgoPtWSvHhPXdT7itbvmS7UjB/jyM8i3AkvI6yeSM5qCwo+xN0npGDHg=="
+ },
"node_modules/@woocommerce/components/node_modules/@types/react": {
"version": "17.0.80",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.80.tgz",
@@ -10989,6 +10994,19 @@
"@types/wordpress__rich-text": "*"
}
},
+ "node_modules/@woocommerce/components/node_modules/@woocommerce/csv-export": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@woocommerce/csv-export/-/csv-export-1.9.0.tgz",
+ "integrity": "sha512-fo1byPYTljic8ml5HxvxafdhrYc8pMYXqjlGZtZ9xNxWL2yffXJvskbcb7Kj6JSQhHT3BCh/5PlczJci9vQuDQ==",
+ "dependencies": {
+ "@types/node": "^16.18.68",
+ "browser-filesaver": "^1.1.1"
+ },
+ "engines": {
+ "node": "^20.11.1",
+ "pnpm": "^9.1.0"
+ }
+ },
"node_modules/@woocommerce/components/node_modules/@wordpress/api-fetch": {
"version": "6.55.0",
"resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.55.0.tgz",
@@ -11652,23 +11670,19 @@
}
},
"node_modules/@woocommerce/csv-export": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/@woocommerce/csv-export/-/csv-export-1.9.0.tgz",
- "integrity": "sha512-fo1byPYTljic8ml5HxvxafdhrYc8pMYXqjlGZtZ9xNxWL2yffXJvskbcb7Kj6JSQhHT3BCh/5PlczJci9vQuDQ==",
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@woocommerce/csv-export/-/csv-export-1.10.0.tgz",
+ "integrity": "sha512-OckWCp52fGFcyK2lwBJyiheBbvUFl0dO3Ai6tSfX+jApJa8SUlx9txhwKVSz4Xnpo+nH0B0SPHjoPtQeQeXa2w==",
+ "dev": true,
"dependencies": {
- "@types/node": "^16.18.68",
+ "@types/node": "20.x.x",
"browser-filesaver": "^1.1.1"
},
"engines": {
"node": "^20.11.1",
- "pnpm": "^9.1.0"
+ "pnpm": "9.1.3"
}
},
- "node_modules/@woocommerce/csv-export/node_modules/@types/node": {
- "version": "16.18.102",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.102.tgz",
- "integrity": "sha512-eSe2YwGCcRjqPidxfm20IAq02krERWcIIJW4FNPkU0zQLbc4L9pvhsmB0p6UJecjEf0j/E2ERHsKq7madvthKw=="
- },
"node_modules/@woocommerce/currency": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@woocommerce/currency/-/currency-4.3.0.tgz",
@@ -13775,6 +13789,24 @@
"pnpm": "^8.12.1"
}
},
+ "node_modules/@woocommerce/onboarding/node_modules/@types/node": {
+ "version": "16.18.122",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.122.tgz",
+ "integrity": "sha512-rF6rUBS80n4oK16EW8nE75U+9fw0SSUgoPtWSvHhPXdT7itbvmS7UjB/jyM8i3AkvI6yeSM5qCwo+xN0npGDHg=="
+ },
+ "node_modules/@woocommerce/onboarding/node_modules/@woocommerce/csv-export": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@woocommerce/csv-export/-/csv-export-1.9.0.tgz",
+ "integrity": "sha512-fo1byPYTljic8ml5HxvxafdhrYc8pMYXqjlGZtZ9xNxWL2yffXJvskbcb7Kj6JSQhHT3BCh/5PlczJci9vQuDQ==",
+ "dependencies": {
+ "@types/node": "^16.18.68",
+ "browser-filesaver": "^1.1.1"
+ },
+ "engines": {
+ "node": "^20.11.1",
+ "pnpm": "^9.1.0"
+ }
+ },
"node_modules/@woocommerce/onboarding/node_modules/@woocommerce/experimental": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@woocommerce/experimental/-/experimental-3.2.0.tgz",
@@ -20443,23 +20475,6 @@
"node": ">= 10.13.0"
}
},
- "node_modules/@wordpress/scripts/node_modules/rechoir/node_modules/resolve": {
- "version": "1.22.8",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
- "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
- "dev": true,
- "dependencies": {
- "is-core-module": "^2.13.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/@wordpress/scripts/node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -20655,18 +20670,6 @@
"node": ">=8"
}
},
- "node_modules/@wordpress/scripts/node_modules/supports-hyperlinks/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/@wordpress/scripts/node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
diff --git a/package.json b/package.json
index f634378e064..cf6c7f7b843 100644
--- a/package.json
+++ b/package.json
@@ -115,7 +115,7 @@
"@typescript-eslint/parser": "4.15.2",
"@woocommerce/api": "0.2.0",
"@woocommerce/components": "12.3.0",
- "@woocommerce/csv-export": "1.9.0",
+ "@woocommerce/csv-export": "1.10.0",
"@woocommerce/currency": "4.3.0",
"@woocommerce/date": "4.2.0",
"@woocommerce/dependency-extraction-webpack-plugin": "2.2.0",
@@ -215,7 +215,7 @@
"overrides": {
"@automattic/puppeteer-utils": "github:Automattic/puppeteer-utils#update/babel-deps",
"@woocommerce/components": {
- "@woocommerce/csv-export": "1.9.0",
+ "@woocommerce/csv-export": "1.10.0",
"@woocommerce/currency": "4.3.0"
},
"@wordpress/scripts": {
| |