diff --git a/changelog.txt b/changelog.txt index 18c98319d74..73e649d188e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,11 @@ *** WooPayments Changelog *** += 6.9.2 - 2023-12-14 = +* Add - Notice is added when merchant has funds that are not yet available for deposit. +* Add - Show a deposit schedule notice on the deposits list page to indicate that future deposits can be expected. +* Fix - Show deposit schedule message when deposits are unrestricted +* Fix - Transactions List - indicate when a transaction is expected to be included in a future deposit + = 6.9.1 - 2023-12-07 = * Fix - Display Klarna & Afterpay on the checkout for UK based stores diff --git a/changelog/add-7591-missing-columns-export-csv b/changelog/add-7591-missing-columns-export-csv new file mode 100644 index 00000000000..668644d2cfc --- /dev/null +++ b/changelog/add-7591-missing-columns-export-csv @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Introduce Customer currency, Deposit currency, Amount in Customer Currency and Deposit ID columns to the Transaction list UI and CSV export diff --git a/changelog/cleanup-redundant-script-enqueueing b/changelog/cleanup-redundant-script-enqueueing new file mode 100644 index 00000000000..20c952d459e --- /dev/null +++ b/changelog/cleanup-redundant-script-enqueueing @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Cleanup enqueueing of the scripts which were removed diff --git a/changelog/update-4163-compatibility-service b/changelog/update-4163-compatibility-service new file mode 100644 index 00000000000..3a524da05ae --- /dev/null +++ b/changelog/update-4163-compatibility-service @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Adding Compatibility Service to assist with flagging possible compatibility issues in the future. diff --git a/client/components/deposits-overview/deposit-notices.tsx b/client/components/deposits-overview/deposit-notices.tsx index 7a38783a33b..86f0de918a0 100644 --- a/client/components/deposits-overview/deposit-notices.tsx +++ b/client/components/deposits-overview/deposit-notices.tsx @@ -150,3 +150,28 @@ export const NegativeBalanceDepositsPausedNotice: React.FC = () => ( } ) } ); + +/** + * Renders a notice informing the user that deposits only occur when there are funds available. + */ +export const NoFundsAvailableForDepositNotice: React.FC = () => ( + + { interpolateComponents( { + mixedString: __( + 'You have no funds available to deposit. {{whyLink}}Why?{{/whyLink}}', + 'woocommerce-payments' + ), + components: { + whyLink: ( + // Link content is in the format string above. Consider disabling jsx-a11y/anchor-has-content. + // eslint-disable-next-line jsx-a11y/anchor-has-content + + ), + }, + } ) } + +); diff --git a/client/components/deposits-overview/deposit-schedule.tsx b/client/components/deposits-overview/deposit-schedule.tsx index c7eae7bca78..8ae6546655c 100644 --- a/client/components/deposits-overview/deposit-schedule.tsx +++ b/client/components/deposits-overview/deposit-schedule.tsx @@ -17,6 +17,7 @@ import type * as AccountOverview from 'wcpay/types/account-overview'; interface DepositScheduleProps { depositsSchedule: AccountOverview.Account[ 'deposits_schedule' ]; + showNextDepositDate?: boolean; } /** * Renders the Deposit Schedule details component. @@ -25,19 +26,30 @@ interface DepositScheduleProps { */ const DepositSchedule: React.FC< DepositScheduleProps > = ( { depositsSchedule, + showNextDepositDate, } ) => { const nextDepositDate = getNextDepositDate( depositsSchedule ); + const nextDepositDateString = showNextDepositDate + ? sprintf( + /** translators: %s: is the date of the next deposit, e.g. "January 1st, 2023". */ + __( + ' – your next deposit is scheduled for {{strong}}%s{{/strong}}', + 'woocommerce-payments' + ), + nextDepositDate + ) + : ''; switch ( depositsSchedule.interval ) { case 'daily': return interpolateComponents( { mixedString: sprintf( - /** translators: {{strong}}: placeholders are opening and closing strong tags. %s: is the date of the next deposit, e.g. "January 1st, 2023". */ + /** translators: {{strong}}: placeholders are opening and closing strong tags. %s: is an optional next deposit date message. */ __( - 'Available funds are automatically dispatched {{strong}}every day{{/strong}} – your next deposit is scheduled for {{strong}}%s{{/strong}}.', + 'Available funds are automatically dispatched {{strong}}every day{{/strong}}%s.', 'woocommerce-payments' ), - nextDepositDate + nextDepositDateString ), components: { strong: , @@ -52,13 +64,13 @@ const DepositSchedule: React.FC< DepositScheduleProps > = ( { return interpolateComponents( { mixedString: sprintf( - /** translators: %1$s: is the day of the week. eg "Friday". %2$s: is the date of the next deposit, e.g. "January 1st, 2023". {{strong}}: placeholders are opening and closing strong tags. */ + /** translators: %1$s: is the day of the week. eg "Friday". %2$s: is an optional next deposit date message. {{strong}}: placeholders are opening and closing strong tags. */ __( - 'Available funds are automatically dispatched {{strong}}every %1$s{{/strong}} – your next deposit is scheduled for {{strong}}%2$s{{/strong}}.', + 'Available funds are automatically dispatched {{strong}}every %1$s{{/strong}}%2$s.', 'woocommerce-payments' ), dayOfWeek, - nextDepositDate + nextDepositDateString ), components: { strong: , @@ -71,12 +83,12 @@ const DepositSchedule: React.FC< DepositScheduleProps > = ( { if ( monthlyAnchor === 31 ) { return interpolateComponents( { mixedString: sprintf( - /** translators: {{strong}}: placeholders are opening and closing strong tags. %s: is the date of the next deposit, e.g. "January 1st, 2023". */ + /** translators: {{strong}}: placeholders are opening and closing strong tags. %s: is an optional next deposit date message. */ __( - 'Available funds are automatically dispatched {{strong}}on the last day of every month{{/strong}} – your next deposit is scheduled for {{strong}}%s{{/strong}}.', + 'Available funds are automatically dispatched {{strong}}on the last day of every month{{/strong}}%s.', 'woocommerce-payments' ), - nextDepositDate + nextDepositDateString ), components: { strong: , @@ -86,16 +98,16 @@ const DepositSchedule: React.FC< DepositScheduleProps > = ( { return interpolateComponents( { mixedString: sprintf( - /** translators: {{strong}}: placeholders are opening and closing strong tags. %1$s: is the day of the month. eg "31st". %2$s: is the date of the next deposit, e.g. "January 1st, 2023". */ + /** translators: {{strong}}: placeholders are opening and closing strong tags. %1$s: is the day of the month. eg "31st". %2$s: is an optional next deposit date message. */ __( - 'Available funds are automatically dispatched {{strong}}on the %1$s of every month{{/strong}} – your next deposit is scheduled for {{strong}}%2$s{{/strong}}.', + 'Available funds are automatically dispatched {{strong}}on the %1$s of every month{{/strong}}%2$s.', 'woocommerce-payments' ), getDepositMonthlyAnchorLabel( { monthlyAnchor: monthlyAnchor, capitalize: false, } ), - nextDepositDate + nextDepositDateString ), components: { strong: , diff --git a/client/components/deposits-overview/index.tsx b/client/components/deposits-overview/index.tsx index 27c754c0640..8ab797f4977 100644 --- a/client/components/deposits-overview/index.tsx +++ b/client/components/deposits-overview/index.tsx @@ -24,6 +24,7 @@ import { DepositTransitDaysNotice, NegativeBalanceDepositsPausedNotice, NewAccountWaitingPeriodNotice, + NoFundsAvailableForDepositNotice, SuspendedDepositNotice, } from './deposit-notices'; import useRecentDeposits from './hooks'; @@ -35,6 +36,9 @@ const DepositsOverview: React.FC = () => { overview, isLoading: isLoadingOverview, } = useSelectedCurrencyOverview(); + const isDepositsUnrestricted = + wcpaySettings.accountStatus.deposits?.restrictions === + 'deposits_unrestricted'; const selectedCurrency = overview?.currency || wcpaySettings.accountDefaultCurrency; const { isLoading: isLoadingDeposits, deposits } = useRecentDeposits( @@ -44,20 +48,18 @@ const DepositsOverview: React.FC = () => { const isLoading = isLoadingOverview || isLoadingDeposits; const availableFunds = overview?.available?.amount ?? 0; + const pendingFunds = overview?.pending?.amount ?? 0; - // If the account has deposits blocked, there is no available balance or it is negative, there is no future deposit expected. - const isNextDepositExpected = - ! account?.deposits_blocked && availableFunds > 0; // If the available balance is negative, deposits may be paused. const isNegativeBalanceDepositsPaused = availableFunds < 0; + // When there are funds pending but no available funds, deposits are paused. + const isDepositAwaitingPendingFunds = + availableFunds === 0 && pendingFunds > 0; const hasCompletedWaitingPeriod = wcpaySettings.accountStatus.deposits?.completed_waiting_period; // Only show the deposit history section if the page is finished loading and there are deposits. */ } const showRecentDeposits = - ! isLoading && - deposits?.length > 0 && - !! account && - ! account?.deposits_blocked; + ! isLoading && deposits?.length > 0 && !! account; // Show a loading state if the page is still loading. if ( isLoading ) { @@ -87,7 +89,7 @@ const DepositsOverview: React.FC = () => { } // This card isn't shown if there are no deposits, so we can bail early. - if ( ! isLoading && availableFunds === 0 && deposits.length === 0 ) { + if ( ! isLoading && deposits.length === 0 ) { return null; } @@ -98,10 +100,11 @@ const DepositsOverview: React.FC = () => { { /* Deposit schedule message */ } - { isNextDepositExpected && !! account && ( + { isDepositsUnrestricted && !! account && ( 0 } /> ) } @@ -112,12 +115,17 @@ const DepositsOverview: React.FC = () => { ) : ( <> - { isNextDepositExpected && ( - - ) } + { isDepositsUnrestricted && + ! isDepositAwaitingPendingFunds && ( + + ) } { ! hasCompletedWaitingPeriod && ( ) } + { hasCompletedWaitingPeriod && + isDepositAwaitingPendingFunds && ( + + ) } { isNegativeBalanceDepositsPaused && ( ) } diff --git a/client/components/deposits-overview/test/__snapshots__/index.tsx.snap b/client/components/deposits-overview/test/__snapshots__/index.tsx.snap index 9dcdb85548d..6b4db19d73c 100644 --- a/client/components/deposits-overview/test/__snapshots__/index.tsx.snap +++ b/client/components/deposits-overview/test/__snapshots__/index.tsx.snap @@ -17,11 +17,65 @@ exports[`Deposits Overview information Component Renders 1`] = ` > Deposits +
+ Available funds are automatically dispatched + + every Monday + + . +
+ > +
+
+
+
+ +
+
+ It may take 1-3 business days for deposits to reach your bank account. +
+
+
+
+
+
{ global.wcpaySettings = { accountStatus: { deposits: { + restrictions: 'deposits_unrestricted', completed_waiting_period: true, }, }, diff --git a/client/components/tooltip/style.scss b/client/components/tooltip/style.scss index aa0cfe52062..26730944226 100644 --- a/client/components/tooltip/style.scss +++ b/client/components/tooltip/style.scss @@ -37,6 +37,9 @@ z-index: 100010; // Initial left position is set to 0 to fix a positioning bug in mobile Safari. left: 0; + white-space: normal; + font-size: 12px; + font-weight: 400; &.is-hiding { opacity: 0 !important; diff --git a/client/deposits/index.tsx b/client/deposits/index.tsx index ae5ba238679..7f6e5c30f14 100644 --- a/client/deposits/index.tsx +++ b/client/deposits/index.tsx @@ -3,19 +3,80 @@ /** * External dependencies */ -import React from 'react'; +import React, { useState } from 'react'; +import { useDispatch } from '@wordpress/data'; /** * Internal dependencies. */ import Page from 'components/page'; import { TestModeNotice } from 'components/test-mode-notice'; +import BannerNotice from 'components/banner-notice'; +import DepositSchedule from 'components/deposits-overview/deposit-schedule'; +import { useAllDepositsOverviews } from 'data'; import DepositsList from './list'; +const useNextDepositNoticeState = () => { + const { updateOptions } = useDispatch( 'wc/admin/options' ); + const [ isDismissed, setIsDismissed ] = useState( + wcpaySettings.isNextDepositNoticeDismissed + ); + + const setNextDepositNoticeDismissed = () => { + setIsDismissed( true ); + wcpaySettings.isNextDepositNoticeDismissed = true; + updateOptions( { + wcpay_next_deposit_notice_dismissed: true, + } ); + }; + + return { + isNextDepositNoticeDismissed: isDismissed, + handleDismissNextDepositNotice: setNextDepositNoticeDismissed, + }; +}; + +const NextDepositNotice: React.FC = () => { + const { + overviews: { account }, + } = useAllDepositsOverviews(); + const { + isNextDepositNoticeDismissed, + handleDismissNextDepositNotice, + } = useNextDepositNoticeState(); + + const isDepositsUnrestricted = + wcpaySettings.accountStatus.deposits?.restrictions === + 'deposits_unrestricted'; + + const hasCompletedWaitingPeriod = + wcpaySettings.accountStatus.deposits?.completed_waiting_period; + + if ( + ! isDepositsUnrestricted || + ! hasCompletedWaitingPeriod || + ! account || + isNextDepositNoticeDismissed + ) { + return null; + } + + return ( + + + + ); +}; + const DepositsPage: React.FC = () => { return ( + ); diff --git a/client/globals.d.ts b/client/globals.d.ts index eaffe24b8b6..4f791a70795 100644 --- a/client/globals.d.ts +++ b/client/globals.d.ts @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { +import type { MccsDisplayTreeItem, Country, OnboardingFields, @@ -31,6 +31,10 @@ declare global { paymentsEnabled?: boolean; deposits?: { status: string; + restrictions: + | 'deposits_unrestricted' + | 'deposits_blocked' + | 'schedule_restricted'; interval: string; weekly_anchor: string; monthly_anchor: null | number; @@ -116,6 +120,7 @@ declare global { isStripeBillingEligible: boolean; capabilityRequestNotices: Record< string, boolean >; storeName: string; + isNextDepositNoticeDismissed: boolean; }; const wcTracks: any; diff --git a/client/transactions/list/deposit.tsx b/client/transactions/list/deposit.tsx index c0604f0b097..09ceb108ca9 100644 --- a/client/transactions/list/deposit.tsx +++ b/client/transactions/list/deposit.tsx @@ -3,18 +3,27 @@ /** * External dependencies */ -import { dateI18n } from '@wordpress/date'; +import React from 'react'; import moment from 'moment'; +import { dateI18n } from '@wordpress/date'; +import { __ } from '@wordpress/i18n'; +import interpolateComponents from '@automattic/interpolate-components'; +import { ExternalLink } from '@wordpress/components'; import { Link } from '@woocommerce/components'; -import React from 'react'; -import { getAdminUrl } from 'wcpay/utils'; +import InfoOutlineIcon from 'gridicons/dist/info-outline'; + +/** + * Internal dependencies + */ +import { getAdminUrl } from 'utils'; +import { ClickTooltip } from 'components/tooltip'; interface DepositProps { depositId?: string; dateAvailable?: string; } -const Deposit = ( { depositId, dateAvailable }: DepositProps ): JSX.Element => { +const Deposit: React.FC< DepositProps > = ( { depositId, dateAvailable } ) => { if ( depositId && dateAvailable && @@ -35,7 +44,26 @@ const Deposit = ( { depositId, dateAvailable }: DepositProps ): JSX.Element => { return { formattedDateAvailable }; } - return <>; + // Show an icon with a tooltip to communicate that the deposit will be available in the future. + return ( + <> + { __( 'Future deposit', 'woocommerce-payments' ) } + + ), + }, + } ) } + buttonIcon={ } + /> + + ); }; export default Deposit; diff --git a/client/transactions/list/index.tsx b/client/transactions/list/index.tsx index 822047a4afd..931712821a9 100644 --- a/client/transactions/list/index.tsx +++ b/client/transactions/list/index.tsx @@ -144,8 +144,11 @@ const getColumns = ( }, { key: 'date', - label: __( 'Date / Time', 'woocommerce-payments' ), - screenReaderLabel: __( 'Date and time', 'woocommerce-payments' ), + label: __( 'Date / Time (UTC)', 'woocommerce-payments' ), + screenReaderLabel: __( + 'Date and time in UTC', + 'woocommerce-payments' + ), required: true, isLeftAligned: true, defaultOrder: 'desc', @@ -167,10 +170,41 @@ const getColumns = ( required: true, isLeftAligned: true, }, + { + key: 'customer_currency', + label: __( 'Paid Currency', 'woocommerce-payments' ), + screenReaderLabel: __( + 'Customer Currency', + 'woocommerce-payments' + ), + isSortable: true, + visible: false, + }, + { + key: 'customer_amount', + label: __( 'Amount Paid', 'woocommerce-payments' ), + screenReaderLabel: __( + 'Amount in Customer Currency', + 'woocommerce-payments' + ), + isNumeric: true, + isSortable: true, + visible: false, + }, + { + key: 'deposit_currency', + label: __( 'Deposit Currency', 'woocommerce-payments' ), + screenReaderLabel: __( 'Deposit Currency', 'woocommerce-payments' ), + isSortable: true, + visible: false, + }, { key: 'amount', label: __( 'Amount', 'woocommerce-payments' ), - screenReaderLabel: __( 'Amount', 'woocommerce-payments' ), + screenReaderLabel: __( + 'Amount in Deposit Curency', + 'woocommerce-payments' + ), isNumeric: true, isSortable: true, }, @@ -205,8 +239,8 @@ const getColumns = ( }, { key: 'source', - label: __( 'Source', 'woocommerce-payments' ), - screenReaderLabel: __( 'Source', 'woocommerce-payments' ), + label: __( 'Payment Method', 'woocommerce-payments' ), + screenReaderLabel: __( 'Payment Method', 'woocommerce-payments' ), cellClassName: 'is-center-aligned', }, { @@ -236,6 +270,14 @@ const getColumns = ( visible: false, isLeftAligned: true, }, + includeDeposit && { + key: 'deposit_id', + label: __( 'Deposit ID', 'woocommerce-payments' ), + screenReaderLabel: __( 'Deposit ID', 'woocommerce-payments' ), + cellClassName: 'deposit', + isLeftAligned: true, + visible: false, + }, includeDeposit && { key: 'deposit', label: __( 'Deposit date', 'woocommerce-payments' ), @@ -333,12 +375,6 @@ export const TransactionsList = ( txn.customer_email ); - const deposit = ( - - ); const currency = txn.currency.toUpperCase(); const dataType = txn.metadata ? txn.metadata.charge_type : txn.type; @@ -377,15 +413,33 @@ export const TransactionsList = ( ), }; }; - - const depositStatus = txn.deposit_status - ? displayDepositStatus[ txn.deposit_status ] - : ''; + const formatCustomerAmount = () => { + return { + value: formatExportAmount( + txn.customer_amount, + txn.customer_currency + ), + display: clickable( + formatCurrency( txn.customer_amount, txn.customer_currency ) + ), + }; + }; const isFinancingType = -1 !== [ 'financing_payout', 'financing_paydown' ].indexOf( txn.type ); + const deposit = ! isFinancingType && ( + + ); + + const depositStatus = txn.deposit_status + ? displayDepositStatus[ txn.deposit_status ] + : ''; + // Map transaction into table row. const data = { transaction_id: { @@ -463,6 +517,15 @@ export const TransactionsList = ( value: txn.customer_country, display: clickable( txn.customer_country ), }, + customer_currency: { + value: txn.customer_currency.toUpperCase(), + display: clickable( txn.customer_currency.toUpperCase() ), + }, + customer_amount: formatCustomerAmount(), + deposit_currency: { + value: txn.currency.toUpperCase(), + display: clickable( txn.currency.toUpperCase() ), + }, amount: formatAmount(), // fees should display as negative. The format $-9.99 is determined by WC-Admin fees: formatFees(), @@ -476,6 +539,10 @@ export const TransactionsList = ( value: calculateRiskMapping( txn.risk_level ), display: clickable( riskLevel ), }, + deposit_id: { + value: txn.deposit_id, + display: txn.deposit_id, + }, deposit: { value: txn.available_on, display: deposit }, deposit_status: { value: depositStatus, diff --git a/client/transactions/list/test/__snapshots__/deposit.tsx.snap b/client/transactions/list/test/__snapshots__/deposit.tsx.snap index ff5e2f4fb9d..5aae20de2e7 100644 --- a/client/transactions/list/test/__snapshots__/deposit.tsx.snap +++ b/client/transactions/list/test/__snapshots__/deposit.tsx.snap @@ -11,10 +11,101 @@ exports[`Deposit renders with date and deposit available 1`] = `
`; -exports[`Deposit renders with date available but no deposit 1`] = `
`; - -exports[`Deposit renders with deposit but no date available 1`] = `
`; +exports[`Deposit renders with date available but no deposit 1`] = ` +
+ Future deposit + +
+`; -exports[`Deposit renders with estimated date and deposit available 1`] = `
`; +exports[`Deposit renders with deposit but no date available 1`] = ` +
+ Future deposit + +
+`; -exports[`Deposit renders with no date or deposit available 1`] = `
`; +exports[`Deposit renders with no date or deposit available 1`] = ` +
+ Future deposit + +
+`; diff --git a/client/transactions/list/test/__snapshots__/index.tsx.snap b/client/transactions/list/test/__snapshots__/index.tsx.snap index 2f460c0f997..c7bfca77b45 100644 --- a/client/transactions/list/test/__snapshots__/index.tsx.snap +++ b/client/transactions/list/test/__snapshots__/index.tsx.snap @@ -248,19 +248,19 @@ exports[`Transactions list renders correctly when can filter by several currenci - Date and time + Date and time in UTC - Sort by Date and time in ascending order + Sort by Date and time in UTC in ascending order - Amount + Amount in Deposit Curency - Sort by Amount in descending order + Sort by Amount in Deposit Curency in descending order - Source + Payment Method - Source + Payment Method + > + Future deposit + + + > + Future deposit + + @@ -1179,19 +1237,19 @@ exports[`Transactions list renders correctly when filtered by currency 1`] = ` - Date and time + Date and time in UTC - Sort by Date and time in ascending order + Sort by Date and time in UTC in ascending order - Amount + Amount in Deposit Curency - Sort by Amount in descending order + Sort by Amount in Deposit Curency in descending order - Source + Payment Method - Source + Payment Method + > + Future deposit + + + > + Future deposit + + @@ -2107,19 +2223,19 @@ exports[`Transactions list renders correctly when filtered by deposit 1`] = ` - Date and time + Date and time in UTC - Sort by Date and time in ascending order + Sort by Date and time in UTC in ascending order - Amount + Amount in Deposit Curency - Sort by Amount in descending order + Sort by Amount in Deposit Curency in descending order - Source + Payment Method - Source + Payment Method - Date / Time + Date / Time (UTC) - Date and time + Date and time in UTC - Sort by Date and time in ascending order + Sort by Date and time in UTC in ascending order - Amount + Amount in Deposit Curency - Sort by Amount in descending order + Sort by Amount in Deposit Curency in descending order - Source + Payment Method - Source + Payment Method + > + Future deposit + + + > + Future deposit + + @@ -3789,19 +3963,19 @@ exports[`Transactions list when not filtered by deposit renders correctly 1`] = - Date and time + Date and time in UTC - Sort by Date and time in ascending order + Sort by Date and time in UTC in ascending order - Amount + Amount in Deposit Curency - Sort by Amount in descending order + Sort by Amount in Deposit Curency in descending order - Source + Payment Method - Source + Payment Method + > + Future deposit + + + > + Future deposit + + @@ -4762,19 +4994,19 @@ exports[`Transactions list when not filtered by deposit renders table summary on - Date and time + Date and time in UTC - Sort by Date and time in ascending order + Sort by Date and time in UTC in ascending order - Amount + Amount in Deposit Curency - Sort by Amount in descending order + Sort by Amount in Deposit Curency in descending order - Source + Payment Method - Source + Payment Method + > + Future deposit + + + > + Future deposit + + diff --git a/client/transactions/list/test/deposit.tsx b/client/transactions/list/test/deposit.tsx index 478baa171a6..2cb87b0b248 100644 --- a/client/transactions/list/test/deposit.tsx +++ b/client/transactions/list/test/deposit.tsx @@ -19,16 +19,6 @@ describe( 'Deposit', () => { expect( link ).toMatchSnapshot(); } ); - test( 'renders with estimated date and deposit available', () => { - const { container: link } = render( - - ); - expect( link ).toMatchSnapshot(); - } ); - test( 'renders with date available but no deposit', () => { const { container: link } = render( diff --git a/client/transactions/list/test/index.tsx b/client/transactions/list/test/index.tsx index d2706511578..63fbbc01427 100644 --- a/client/transactions/list/test/index.tsx +++ b/client/transactions/list/test/index.tsx @@ -302,18 +302,18 @@ describe( 'Transactions list', () => { } ); test( 'sorts by default field date', () => { - sortBy( 'Date and time' ); + sortBy( 'Date and time in UTC' ); expectSortingToBe( 'date', 'asc' ); - sortBy( 'Date and time' ); + sortBy( 'Date and time in UTC' ); expectSortingToBe( 'date', 'desc' ); } ); test( 'sorts by amount', () => { - sortBy( 'Amount' ); + sortBy( 'Amount in Deposit Curency' ); expectSortingToBe( 'amount', 'desc' ); - sortBy( 'Amount' ); + sortBy( 'Amount in Deposit Curency' ); expectSortingToBe( 'amount', 'asc' ); } ); @@ -609,18 +609,22 @@ describe( 'Transactions list', () => { const expected = [ '"Transaction Id"', - '"Date / Time"', + '"Date / Time (UTC)"', 'Type', 'Channel', + '"Paid Currency"', + '"Amount Paid"', + '"Deposit Currency"', 'Amount', 'Fees', 'Net', '"Order #"', - 'Source', + '"Payment Method"', 'Customer', 'Email', 'Country', '"Risk level"', + '"Deposit ID"', '"Deposit date"', '"Deposit status"', ]; @@ -675,26 +679,26 @@ describe( 'Transactions list', () => { ); // channel expect( getUnformattedAmount( displayFirstTransaction[ 3 ] ).indexOf( - csvFirstTransaction[ 4 ] + csvFirstTransaction[ 7 ] ) ).not.toBe( -1 ); // amount expect( -Number( getUnformattedAmount( displayFirstTransaction[ 4 ] ) ) ).toEqual( Number( - csvFirstTransaction[ 5 ].replace( /['"]+/g, '' ) // strip extra quotes + csvFirstTransaction[ 8 ].replace( /['"]+/g, '' ) // strip extra quotes ) ); // fees expect( getUnformattedAmount( displayFirstTransaction[ 5 ] ).indexOf( - csvFirstTransaction[ 6 ] + csvFirstTransaction[ 9 ] ) ).not.toBe( -1 ); // net expect( displayFirstTransaction[ 6 ] ).toBe( - csvFirstTransaction[ 7 ] + csvFirstTransaction[ 10 ] ); // order number expect( displayFirstTransaction[ 8 ] ).toBe( - csvFirstTransaction[ 9 ].replace( /['"]+/g, '' ) // strip extra quotes + csvFirstTransaction[ 12 ].replace( /['"]+/g, '' ) // strip extra quotes ); // customer } ); } ); diff --git a/includes/admin/class-wc-payments-admin.php b/includes/admin/class-wc-payments-admin.php index 153746bd832..0dc167c4aca 100644 --- a/includes/admin/class-wc-payments-admin.php +++ b/includes/admin/class-wc-payments-admin.php @@ -877,6 +877,7 @@ private function get_js_settings(): array { 'isStripeBillingEligible' => WC_Payments_Features::is_stripe_billing_eligible(), 'capabilityRequestNotices' => get_option( 'wcpay_capability_request_dismissed_notices ', [] ), 'storeName' => get_bloginfo( 'name' ), + 'isNextDepositNoticeDismissed' => WC_Payments_Features::is_next_deposit_notice_dismissed(), ]; return apply_filters( 'wcpay_js_settings', $this->wcpay_js_settings ); diff --git a/includes/admin/class-wc-rest-payments-settings-controller.php b/includes/admin/class-wc-rest-payments-settings-controller.php index 1025c697990..057af86e3f5 100644 --- a/includes/admin/class-wc-rest-payments-settings-controller.php +++ b/includes/admin/class-wc-rest-payments-settings-controller.php @@ -773,6 +773,11 @@ private function update_account( WP_REST_Request $request ) { $updated_fields['deposit_schedule_interval'] = $this->wcpay_gateway->get_option( 'deposit_schedule_interval' ); } + // If we are updating any deposit schedule values, we should invalidate the next deposit notice dismissed notice option. + if ( preg_grep( '/^deposit_schedule_/', array_keys( $updated_fields ) ) ) { + delete_option( 'wcpay_next_deposit_notice_dismissed' ); + } + return $this->wcpay_gateway->update_account_settings( $updated_fields ); } diff --git a/includes/class-compatibility-service.php b/includes/class-compatibility-service.php new file mode 100644 index 00000000000..444b3383cb1 --- /dev/null +++ b/includes/class-compatibility-service.php @@ -0,0 +1,61 @@ +payments_api_client = $payments_api_client; + } + + /** + * Initializes this class's WP hooks. + * + * @return void + */ + public function init_hooks() { + add_action( 'woocommerce_payments_account_refreshed', [ $this, 'update_compatibility_data' ] ); + } + + /** + * Gets the data we need to confirm compatibility and sends it to the server. + * + * @return void + */ + public function update_compatibility_data() { + try { + $this->payments_api_client->update_compatibility_data( + [ + 'woopayments_version' => WCPAY_VERSION_NUMBER, + 'woocommerce_version' => WC_VERSION, + ] + ); + } catch ( API_Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // The exception is already logged if logging is on, nothing else needed. + } + } +} diff --git a/includes/class-wc-payments-features.php b/includes/class-wc-payments-features.php index 416cac38831..904164421e0 100644 --- a/includes/class-wc-payments-features.php +++ b/includes/class-wc-payments-features.php @@ -351,6 +351,15 @@ public static function is_dispute_issuer_evidence_enabled(): bool { return '1' === get_option( self::DISPUTE_ISSUER_EVIDENCE, '0' ); } + /** + * Checks whether the next deposit notice on the deposits list screen has been dismissed. + * + * @return bool + */ + public static function is_next_deposit_notice_dismissed(): bool { + return '1' === get_option( 'wcpay_next_deposit_notice_dismissed', '0' ); + } + /** * Returns feature flags as an array suitable for display on the front-end. * diff --git a/includes/class-wc-payments-upe-split-blocks-payment-method.php b/includes/class-wc-payments-upe-split-blocks-payment-method.php deleted file mode 100644 index 2f1188611ea..00000000000 --- a/includes/class-wc-payments-upe-split-blocks-payment-method.php +++ /dev/null @@ -1,26 +0,0 @@ -init(); @@ -505,6 +515,7 @@ public static function init() { self::$fraud_service->init_hooks(); self::$onboarding_service->init_hooks(); self::$incentives_service->init_hooks(); + self::$compatibility_service->init_hooks(); self::$legacy_card_gateway = new CC_Payment_Gateway( self::$api_client, self::$account, self::$customer_service, self::$token_service, self::$action_scheduler_service, self::$failed_transaction_rate_limiter, self::$order_service, self::$duplicate_payment_prevention_service, self::$localization_service, self::$fraud_service ); @@ -1316,8 +1327,7 @@ public static function get_session_service() { */ public static function register_checkout_gateway( $payment_method_registry ) { require_once __DIR__ . '/class-wc-payments-blocks-payment-method.php'; - require_once __DIR__ . '/class-wc-payments-upe-split-blocks-payment-method.php'; - $payment_method_registry->register( new WC_Payments_UPE_Split_Blocks_Payment_Method() ); + $payment_method_registry->register( new WC_Payments_Blocks_Payment_Method() ); } /** @@ -1645,6 +1655,7 @@ public static function add_wcpay_options_to_woocommerce_permissions_list( $permi 'wcpay_fraud_protection_welcome_tour_dismissed', 'wcpay_capability_request_dismissed_notices', 'wcpay_onboarding_eligibility_modal_dismissed', + 'wcpay_next_deposit_notice_dismissed', ], true ); diff --git a/includes/core/server/class-request.php b/includes/core/server/class-request.php index 80e35dba0de..6264660bb29 100644 --- a/includes/core/server/class-request.php +++ b/includes/core/server/class-request.php @@ -161,6 +161,7 @@ abstract class Request { WC_Payments_API_Client::AUTHORIZATIONS_API => 'authorizations', WC_Payments_API_Client::FRAUD_OUTCOMES_API => 'fraud_outcomes', WC_Payments_API_Client::FRAUD_RULESET_API => 'fraud_ruleset', + WC_Payments_API_Client::COMPATIBILITY_API => 'compatibility', ]; /** diff --git a/includes/wc-payment-api/class-wc-payments-api-client.php b/includes/wc-payment-api/class-wc-payments-api-client.php index f6cd60bfb87..637126b3fa4 100644 --- a/includes/wc-payment-api/class-wc-payments-api-client.php +++ b/includes/wc-payment-api/class-wc-payments-api-client.php @@ -76,6 +76,7 @@ class WC_Payments_API_Client { const FRAUD_SERVICES_API = 'accounts/fraud_services'; const FRAUD_OUTCOMES_API = 'fraud_outcomes'; const FRAUD_RULESET_API = 'fraud_ruleset'; + const COMPATIBILITY_API = 'compatibility'; /** * Common keys in API requests/responses that we might want to redact. @@ -1703,6 +1704,27 @@ public function get_onboarding_po_eligible( array $business_info, array $store_i ); } + /** + * Sends the compatibility data to the server to be saved to the account. + * + * @param array $compatibility_data The array containing the data. + * + * @return array HTTP response on success. + * + * @throws API_Exception - If not connected or request failed. + */ + public function update_compatibility_data( $compatibility_data ) { + $response = $this->request( + [ + 'compatibility_data' => $compatibility_data, + ], + self::COMPATIBILITY_API, + self::POST + ); + + return $response; + } + /** * Sends a request object. * diff --git a/package-lock.json b/package-lock.json index 71e547a4303..7e7375967b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "woocommerce-payments", - "version": "6.9.1", + "version": "6.9.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "woocommerce-payments", - "version": "6.9.1", + "version": "6.9.2", "hasInstallScript": true, "license": "GPL-3.0-or-later", "dependencies": { diff --git a/package.json b/package.json index 905e6bbbe01..d80cfbe4bd0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "woocommerce-payments", - "version": "6.9.1", + "version": "6.9.2", "main": "webpack.config.js", "author": "Automattic", "license": "GPL-3.0-or-later", diff --git a/readme.txt b/readme.txt index e7f21908b84..8a57aead24b 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: woocommerce payments, apple pay, credit card, google pay, payment, payment Requires at least: 6.0 Tested up to: 6.4 Requires PHP: 7.3 -Stable tag: 6.9.1 +Stable tag: 6.9.2 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -94,6 +94,13 @@ Please note that our support for the checkout block is still experimental and th == Changelog == += 6.9.2 - 2023-12-14 = +* Add - Notice is added when merchant has funds that are not yet available for deposit. +* Add - Show a deposit schedule notice on the deposits list page to indicate that future deposits can be expected. +* Fix - Show deposit schedule message when deposits are unrestricted +* Fix - Transactions List - indicate when a transaction is expected to be included in a future deposit + + = 6.9.1 - 2023-12-07 = * Fix - Display Klarna & Afterpay on the checkout for UK based stores diff --git a/tests/unit/test-class-compatibility-service.php b/tests/unit/test-class-compatibility-service.php new file mode 100644 index 00000000000..70e86b5d2dc --- /dev/null +++ b/tests/unit/test-class-compatibility-service.php @@ -0,0 +1,61 @@ +mock_api_client = $this->createMock( WC_Payments_API_Client::class ); + $this->compatibility_service = new Compatibility_Service( $this->mock_api_client ); + $this->compatibility_service->init_hooks(); + } + + public function test_registers_woocommerce_filters_properly() { + $priority = has_filter( 'woocommerce_payments_account_refreshed', [ $this->compatibility_service, 'update_compatibility_data' ] ); + $this->assertEquals( 10, $priority ); + } + + public function test_update_compatibility_data() { + // Arrange: Create the expected value to be passed to update_compatibility_data. + $expected = [ + 'woopayments_version' => WCPAY_VERSION_NUMBER, + 'woocommerce_version' => WC_VERSION, + ]; + + // Arrange/Assert: Set the expectations for update_compatibility_data. + $this->mock_api_client + ->expects( $this->once() ) + ->method( 'update_compatibility_data' ) + ->with( $expected ); + + // Act: Call the method we're testing. + $this->compatibility_service->update_compatibility_data(); + } +} diff --git a/tests/unit/wc-payment-api/test-class-wc-payments-api-client.php b/tests/unit/wc-payment-api/test-class-wc-payments-api-client.php index 4476cce139a..d6320c14bd0 100644 --- a/tests/unit/wc-payment-api/test-class-wc-payments-api-client.php +++ b/tests/unit/wc-payment-api/test-class-wc-payments-api-client.php @@ -907,7 +907,7 @@ public function test_get_disputes_summary_success() { $this->assertSame( 12, $disputes_summary['data']['count'] ); } - public function get_onboarding_po_eligible() { + public function test_get_onboarding_po_eligible() { $this->set_http_mock_response( 200, [ @@ -1203,6 +1203,33 @@ public function test_request_doesnt_retry_get_without_idempotency_header_on_netw [ [], 'intentions', 'GET' ] ); } + + public function test_update_compatibility_data() { + // Arrange: Set expectation and return for remote_request. + $this->mock_http_client + ->expects( $this->once() ) + ->method( 'remote_request' ) + ->willReturn( + [ + 'body' => wp_json_encode( [ 'result' => 'success' ] ), + 'response' => [ + 'code' => 200, + 'message' => 'OK', + ], + ] + ); + + // Act: Get the result of updating the data. + $result = $this->payments_api_client->update_compatibility_data( + [ + 'woocommerce_core_version' => WC_VERSION, + ] + ); + + // Assert: Confirm we get the expected response. + $this->assertSame( 'success', $result['result'] ); + } + /** * Set up http mock response. * diff --git a/woocommerce-payments.php b/woocommerce-payments.php index e135eef1a56..df1a348ec90 100644 --- a/woocommerce-payments.php +++ b/woocommerce-payments.php @@ -12,7 +12,7 @@ * WC tested up to: 8.3.1 * Requires at least: 6.0 * Requires PHP: 7.3 - * Version: 6.9.1 + * Version: 6.9.2 * * @package WooCommerce\Payments */